Skip to content

Instantly share code, notes, and snippets.

@ryanfb
Created April 16, 2024 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanfb/84502d3f9fde8d91fa2339124354b785 to your computer and use it in GitHub Desktop.
Save ryanfb/84502d3f9fde8d91fa2339124354b785 to your computer and use it in GitHub Desktop.
Diff of current Truth Social code release against previous 2022 code release
This file has been truncated, but you can view the full file.
diff -ru truth-old/opensource/.buildpacks truth-new/opensource/.buildpacks
--- truth-old/opensource/.buildpacks 2022-06-08 09:15:38
+++ truth-new/opensource/.buildpacks 2024-04-01 14:59:13
@@ -1,3 +1,3 @@
https://github.com/heroku/heroku-buildpack-apt
https://github.com/Scalingo/ffmpeg-buildpack
-https://github.com/Scalingo/ruby-buildpack
+https://github.com/heroku/heroku-buildpack-ruby
Only in truth-new/opensource: .bundle
Only in truth-new/opensource: .circleci
Only in truth-old/opensource: .env.production.sample
Only in truth-new/opensource: .envrc
diff -ru truth-old/opensource/.eslintrc.js truth-new/opensource/.eslintrc.js
--- truth-old/opensource/.eslintrc.js 2022-06-08 09:15:38
+++ truth-new/opensource/.eslintrc.js 2024-04-01 14:59:13
@@ -1,6 +1,10 @@
module.exports = {
root: true,
+ extends: [
+ 'plugin:import/typescript',
+ ],
+
env: {
browser: true,
node: true,
@@ -36,6 +40,7 @@
},
'import/extensions': [
'.js',
+ '.ts',
],
'import/ignore': [
'node_modules',
@@ -183,6 +188,7 @@
'always',
{
js: 'never',
+ ts: 'never',
},
],
'import/newline-after-import': 'error',
Only in truth-new/opensource: .github
diff -ru truth-old/opensource/.gitignore truth-new/opensource/.gitignore
--- truth-old/opensource/.gitignore 2022-06-08 09:15:38
+++ truth-new/opensource/.gitignore 2024-04-01 14:59:13
@@ -14,23 +14,22 @@
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
+.byebug_history
.eslintcache
/log/*
!/log/.keep
/tmp
/coverage
/public/system
-/public/assets
-/public/packs
-/public/packs-test
.env
.env.production
.env.development
.env.test
+.byebug_history
/node_modules/
/build/
/scripts/
-# Ignore Capistrano customizations
+
/config/deploy/*
# Ignore Vagrant files
@@ -81,4 +80,4 @@
/opensource/
# Ignore generated test data file
-spec/support/examples/lib/csvs/accounts_for_suspension.csv
\ No newline at end of file
+spec/support/examples/lib/csvs/accounts_for_suspension.csv
diff -ru truth-old/opensource/.rubocop.yml truth-new/opensource/.rubocop.yml
--- truth-old/opensource/.rubocop.yml 2022-06-08 09:15:38
+++ truth-new/opensource/.rubocop.yml 2024-04-12 09:09:08
@@ -16,7 +16,10 @@
- 'vendor/**/*'
- 'lib/json_ld/*'
- 'lib/templates/**/*'
+ - 'lib/proto/*'
+ SuggestExtensions: false
+
Bundler/OrderedGems:
Enabled: false
@@ -49,6 +52,12 @@
Lint/DuplicateElsifCondition:
Enabled: true
+Lint/MissingSuper:
+ Exclude:
+ - 'app/services/chat_service.rb'
+ - 'app/services/chat_message_service.rb'
+ - 'app/services/geo_service.rb'
+
Lint/MixedRegexpCaptureTypes:
Enabled: true
@@ -72,6 +81,7 @@
Exclude:
- 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
+ - '**/*_spec.rb'
Metrics/BlockNesting:
Max: 3
@@ -291,6 +301,9 @@
Style/SymbolArray:
Enabled: false
+
+Style/TrailingCommaInArguments:
+ EnforcedStyleForMultiline: 'comma'
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: 'comma'
Only in truth-new/opensource: .vagrant
diff -ru truth-old/opensource/Brewfile truth-new/opensource/Brewfile
--- truth-old/opensource/Brewfile 2022-06-08 09:15:38
+++ truth-new/opensource/Brewfile 2023-05-05 13:42:02
@@ -9,3 +9,5 @@
brew "postgresql"
brew "redis"
brew "direnv"
+brew "imagemagick"
+brew "ffmpeg"
diff -ru truth-old/opensource/Brewfile.lock.json truth-new/opensource/Brewfile.lock.json
--- truth-old/opensource/Brewfile.lock.json 2022-06-08 09:15:38
+++ truth-new/opensource/Brewfile.lock.json 2023-05-05 13:42:02
@@ -2,87 +2,87 @@
"entries": {
"tap": {
"homebrew/bundle": {
- "revision": "4b703e446be1d848521b0bdf3e85f36bb3aae1f9"
+ "revision": "8ec900f210c925f4b4731b0f359b0808ca132285"
},
"homebrew/core": {
- "revision": "18c5a8c7d1ed4d58a80c1b3d5485c26f290eaa01"
+ "revision": "b0fa68871ce4b23a8ea225529e5bc4aea15bac95"
}
},
"brew": {
"protobuf-c": {
- "version": "1.4.0_1",
+ "version": "1.4.1_1",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:4a3986d128583d41b29e369bfddeff1e369267441797b71776b8567b4eac5702",
- "sha256": "4a3986d128583d41b29e369bfddeff1e369267441797b71776b8567b4eac5702"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:201a08aabe9bc83897b908019d7dd8aba6dcddf46224eb15bbccdd5f70f6a21b",
+ "sha256": "201a08aabe9bc83897b908019d7dd8aba6dcddf46224eb15bbccdd5f70f6a21b"
},
"arm64_big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:8e855e301d3e6f20acb9b79f8e86ed46cba43790d03a2a82b2de7024abb721ec",
- "sha256": "8e855e301d3e6f20acb9b79f8e86ed46cba43790d03a2a82b2de7024abb721ec"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:48ea3989f31b6f44c8170479f5115064ed32ccd4ccf6784ea4ad254697d4f53e",
+ "sha256": "48ea3989f31b6f44c8170479f5115064ed32ccd4ccf6784ea4ad254697d4f53e"
},
"monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:1d380b543cfaed179de2a482212975c9bc7219da96aa939148f9c6a6a30e170c",
- "sha256": "1d380b543cfaed179de2a482212975c9bc7219da96aa939148f9c6a6a30e170c"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:eeb51fce7f9a32e9c64ed31ffaa0c9e1fe747b0e047065fcd7e69cc6361b039c",
+ "sha256": "eeb51fce7f9a32e9c64ed31ffaa0c9e1fe747b0e047065fcd7e69cc6361b039c"
},
"big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:c89d06a0c0b555379f137f448cd8d25dd0a476d417ab277c572fd07c6faf0275",
- "sha256": "c89d06a0c0b555379f137f448cd8d25dd0a476d417ab277c572fd07c6faf0275"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:06b3fc06f5fe8b09353ac6aa106373833d897a960bb607a6caf84ba0043634ac",
+ "sha256": "06b3fc06f5fe8b09353ac6aa106373833d897a960bb607a6caf84ba0043634ac"
},
"catalina": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:55732600c0f049e6b40bee2751dfacaadd79d62a72f6f843897e25d129cbd47f",
- "sha256": "55732600c0f049e6b40bee2751dfacaadd79d62a72f6f843897e25d129cbd47f"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:5c3d841771a3527b3c118abb738b2ab04345de884588cf313d8ed14fe8514288",
+ "sha256": "5c3d841771a3527b3c118abb738b2ab04345de884588cf313d8ed14fe8514288"
},
"x86_64_linux": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:a52bfbd47abd15484c307ae0e9d11d93bf9c98606dabaa893b75952e9db80a28",
- "sha256": "a52bfbd47abd15484c307ae0e9d11d93bf9c98606dabaa893b75952e9db80a28"
+ "url": "https://ghcr.io/v2/homebrew/core/protobuf-c/blobs/sha256:4fd6aa2c3972f3b24248fb0a75638c61dc658cd0c2bc3005b088715e68f6a106",
+ "sha256": "4fd6aa2c3972f3b24248fb0a75638c61dc658cd0c2bc3005b088715e68f6a106"
}
}
}
},
"gcc": {
- "version": "11.2.0_3",
+ "version": "11.3.0_2",
"bottle": {
"rebuild": 1,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": "/opt/homebrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:2d179246426328ee69b94a25b8bd4c25caeff0699b5ecb4b3d258fe4efd3673e",
- "sha256": "2d179246426328ee69b94a25b8bd4c25caeff0699b5ecb4b3d258fe4efd3673e"
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:330f9db4ca60cf49809b8bb6ed0307b991330ff0184d8989e1e9fcf31c9b557d",
+ "sha256": "330f9db4ca60cf49809b8bb6ed0307b991330ff0184d8989e1e9fcf31c9b557d"
},
"arm64_big_sur": {
"cellar": "/opt/homebrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:9dbb002aa1aab75071fe1a5432fd3ee61378d711aebe0d35d0ca7226a4225451",
- "sha256": "9dbb002aa1aab75071fe1a5432fd3ee61378d711aebe0d35d0ca7226a4225451"
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:ec9f983bdd7c9d8a9f383c54388d5121a51824710b78b571de892c4d773dfa06",
+ "sha256": "ec9f983bdd7c9d8a9f383c54388d5121a51824710b78b571de892c4d773dfa06"
},
"monterey": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:198f5312ecfe6fc6437b55e2fb3bb380e8c597ae6fa255f8f7d0be90306e7601",
- "sha256": "198f5312ecfe6fc6437b55e2fb3bb380e8c597ae6fa255f8f7d0be90306e7601"
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:5624a8566fb118edbcacd5dba3b2bdd612ea6bc8fa24b3994b62becb0b2429fe",
+ "sha256": "5624a8566fb118edbcacd5dba3b2bdd612ea6bc8fa24b3994b62becb0b2429fe"
},
"big_sur": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:d2d4543675948c7adf3f1d4934dc651b864f66d5dad6fb3c8bdcfc6f5eef42e6",
- "sha256": "d2d4543675948c7adf3f1d4934dc651b864f66d5dad6fb3c8bdcfc6f5eef42e6"
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:b8d94950b02fc77ef7e018dc7b0b69ec00b826a0bd33da32deb4214817a632d3",
+ "sha256": "b8d94950b02fc77ef7e018dc7b0b69ec00b826a0bd33da32deb4214817a632d3"
},
"catalina": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:e721b6a3195d2a1e73e4c12d34d0138bc5ebe6a37fb1a8d63ad733316e944c59",
- "sha256": "e721b6a3195d2a1e73e4c12d34d0138bc5ebe6a37fb1a8d63ad733316e944c59"
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:efd0048f48f5bde84a6b23fbddb29b4d9f05a0c9753799a9d02c65bfddb6c7c6",
+ "sha256": "efd0048f48f5bde84a6b23fbddb29b4d9f05a0c9753799a9d02c65bfddb6c7c6"
},
"x86_64_linux": {
- "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:3717134ab0f56e7eeb167c4f4a993c81329d6c1248dae5ee6e39f59cfdfa0eee",
- "sha256": "3717134ab0f56e7eeb167c4f4a993c81329d6c1248dae5ee6e39f59cfdfa0eee"
+ "cellar": ":any_skip_relocation",
+ "url": "https://ghcr.io/v2/homebrew/core/gcc/blobs/sha256:e826c10b577ca561cdcef55042c426bc7aabb4a937e5e2aab66c0f21d87c79f5",
+ "sha256": "e826c10b577ca561cdcef55042c426bc7aabb4a937e5e2aab66c0f21d87c79f5"
}
}
}
@@ -137,182 +137,250 @@
}
},
"shared-mime-info": {
- "version": "2.1",
+ "version": "2.2",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:49fd4c8b0f7cb6b3d45be48968613f8f26b0bded7f7c55b9e978c11d94efb513",
- "sha256": "49fd4c8b0f7cb6b3d45be48968613f8f26b0bded7f7c55b9e978c11d94efb513"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:6a9e8f01389c00cf8e3d3b7fbd9dc0b95d33744fcdb8bf3dca2c3db87c0d7cd1",
+ "sha256": "6a9e8f01389c00cf8e3d3b7fbd9dc0b95d33744fcdb8bf3dca2c3db87c0d7cd1"
},
"arm64_big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:c2c98a7a02e1b23f5c7f7baafe0e4b04f22a7b1a6df73912a7450ea73c162819",
- "sha256": "c2c98a7a02e1b23f5c7f7baafe0e4b04f22a7b1a6df73912a7450ea73c162819"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:c00d8c439285648cb14490b6f4bbb2111d16bd1cf7bbd386cc16ff9b1825a04a",
+ "sha256": "c00d8c439285648cb14490b6f4bbb2111d16bd1cf7bbd386cc16ff9b1825a04a"
},
"monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:eb8c22370434b81375766139e32b3d8f823a1569a8f3bccc3eaf1fe9f39f250a",
- "sha256": "eb8c22370434b81375766139e32b3d8f823a1569a8f3bccc3eaf1fe9f39f250a"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:d279f9a9dfe8d9eb3aa22388b0ae41bdd284f44b35ef40b654f8d1c04929c488",
+ "sha256": "d279f9a9dfe8d9eb3aa22388b0ae41bdd284f44b35ef40b654f8d1c04929c488"
},
"big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:4857d9f38c0f3cbf23984d60c4ec6280d84b457123d34b9c01e96f3deb8b0bb2",
- "sha256": "4857d9f38c0f3cbf23984d60c4ec6280d84b457123d34b9c01e96f3deb8b0bb2"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:3287f34793705e039a140e2614d3aafad8de654e5829515ffd3b77d024de6551",
+ "sha256": "3287f34793705e039a140e2614d3aafad8de654e5829515ffd3b77d024de6551"
},
"catalina": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:8cb87ae2f3014998ecebab2d8c37ac9ff364f1164417420c4d8778a38ca17d29",
- "sha256": "8cb87ae2f3014998ecebab2d8c37ac9ff364f1164417420c4d8778a38ca17d29"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:406f54a852d1f7ea4e0e2d065495d825cd55a6e32132e0e1572c06010bfc89b6",
+ "sha256": "406f54a852d1f7ea4e0e2d065495d825cd55a6e32132e0e1572c06010bfc89b6"
},
- "mojave": {
- "cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:786d1c053d03676c985de3a7c15d764b69626f5d12e7e36e4048055bdc36413c",
- "sha256": "786d1c053d03676c985de3a7c15d764b69626f5d12e7e36e4048055bdc36413c"
- },
"x86_64_linux": {
"cellar": "/home/linuxbrew/.linuxbrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:6099cf602b42eb8f23022b02c292b0bbdce2e22f4ff5b5e8f4d8a3c4575b298f",
- "sha256": "6099cf602b42eb8f23022b02c292b0bbdce2e22f4ff5b5e8f4d8a3c4575b298f"
+ "url": "https://ghcr.io/v2/homebrew/core/shared-mime-info/blobs/sha256:42873f1d296084b1afe36a085f96b9b4074b1337b290cc0daf9a81859cf6766a",
+ "sha256": "42873f1d296084b1afe36a085f96b9b4074b1337b290cc0daf9a81859cf6766a"
}
}
}
},
"postgresql": {
- "version": "14.1_1",
+ "version": "14.4",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": "/opt/homebrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:6e6f3099ad1e64fbdc9dff2152c33a2f01743d2010330bcb34cefe13052fa228",
- "sha256": "6e6f3099ad1e64fbdc9dff2152c33a2f01743d2010330bcb34cefe13052fa228"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:148b28ec301378520e83e53869452afdb82cce0b29eae4c7966dce23a110d546",
+ "sha256": "148b28ec301378520e83e53869452afdb82cce0b29eae4c7966dce23a110d546"
},
"arm64_big_sur": {
"cellar": "/opt/homebrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:d75aee6c8beaabf4add33f0f77f150b13523ffc21f6b72fd2a3c1ea0b7095362",
- "sha256": "d75aee6c8beaabf4add33f0f77f150b13523ffc21f6b72fd2a3c1ea0b7095362"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:441b6519f16ff4b6d9bdce9c116b804d27d3c0b3537cade961ee28eec2ec89f8",
+ "sha256": "441b6519f16ff4b6d9bdce9c116b804d27d3c0b3537cade961ee28eec2ec89f8"
},
"monterey": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:027c8b48406c3d732241426e0f5d2caf9f48cb4d2d38610b8f5d46f0adf7a89f",
- "sha256": "027c8b48406c3d732241426e0f5d2caf9f48cb4d2d38610b8f5d46f0adf7a89f"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:1e258c37f55737787151ee3a5276e805e0aa4e30cf5d166bdc2208d0d7f812c2",
+ "sha256": "1e258c37f55737787151ee3a5276e805e0aa4e30cf5d166bdc2208d0d7f812c2"
},
"big_sur": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:b207e5d55b0696b3b1dd649b4496d8213c933304b2b85e1913270f0834167b7f",
- "sha256": "b207e5d55b0696b3b1dd649b4496d8213c933304b2b85e1913270f0834167b7f"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:04247388a3fcade374189d6777ff6685f4b3450cf14f90bb6859eb5e2eec4b8c",
+ "sha256": "04247388a3fcade374189d6777ff6685f4b3450cf14f90bb6859eb5e2eec4b8c"
},
"catalina": {
"cellar": "/usr/local/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:7708c5cd803ce6bc1481527ecff0ad387d7489e71a3da47140768995bed3e145",
- "sha256": "7708c5cd803ce6bc1481527ecff0ad387d7489e71a3da47140768995bed3e145"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:3fa8b21ec3952be003c0803a4d7e58d478c219e1a75a0948b93e2ffebd250e7f",
+ "sha256": "3fa8b21ec3952be003c0803a4d7e58d478c219e1a75a0948b93e2ffebd250e7f"
},
"x86_64_linux": {
"cellar": "/home/linuxbrew/.linuxbrew/Cellar",
- "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:37477f8fd9f0fed2956558ad9f7169123200e6761a65936e8732a3a88d95c3ca",
- "sha256": "37477f8fd9f0fed2956558ad9f7169123200e6761a65936e8732a3a88d95c3ca"
+ "url": "https://ghcr.io/v2/homebrew/core/postgresql/blobs/sha256:431a89f854eb55b6eeed149515ea1876d0a425bede3c512cff40e49491223062",
+ "sha256": "431a89f854eb55b6eeed149515ea1876d0a425bede3c512cff40e49491223062"
}
}
}
},
"redis": {
- "version": "6.2.6",
+ "version": "7.0.3",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:a656500c3b5762c7cfe03d587a4fa08c5df4568783d167555962d850e8cab3c3",
- "sha256": "a656500c3b5762c7cfe03d587a4fa08c5df4568783d167555962d850e8cab3c3"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:4cc1d45e351961e95e782e66c00661316f22d580826f1703c2c59907c83e1dba",
+ "sha256": "4cc1d45e351961e95e782e66c00661316f22d580826f1703c2c59907c83e1dba"
},
"arm64_big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:846aada68ca07b36d58fd620ed5d52ae67a759526c5da27042748363bfdb6271",
- "sha256": "846aada68ca07b36d58fd620ed5d52ae67a759526c5da27042748363bfdb6271"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:69d4b633d35eac570aba6c16aacb3e597c16c1f02a5c730c1785ae4dbad3b0a2",
+ "sha256": "69d4b633d35eac570aba6c16aacb3e597c16c1f02a5c730c1785ae4dbad3b0a2"
},
"monterey": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:ac30519a604ff014e3903893ddca6c563c134002fec58df3613632e42c4d117c",
- "sha256": "ac30519a604ff014e3903893ddca6c563c134002fec58df3613632e42c4d117c"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:b0ceaa7592468ee103656390217caa61259a0542c3289d413e442b6237d25fe5",
+ "sha256": "b0ceaa7592468ee103656390217caa61259a0542c3289d413e442b6237d25fe5"
},
"big_sur": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:246f73498993a2a0c6c4326a298d2fcc3da6d61904ad09a631aa9c63a6800f76",
- "sha256": "246f73498993a2a0c6c4326a298d2fcc3da6d61904ad09a631aa9c63a6800f76"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:28bc30760d01dac125aea13c2c3814995728d63025be4df8e92aefe2bdd6fe71",
+ "sha256": "28bc30760d01dac125aea13c2c3814995728d63025be4df8e92aefe2bdd6fe71"
},
"catalina": {
"cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:ff93a763d622cc9130c09fa9ce2ec7236f91562667eaa5c304fcf175c1253746",
- "sha256": "ff93a763d622cc9130c09fa9ce2ec7236f91562667eaa5c304fcf175c1253746"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:3387ab3983d8c93d0f57d8796dcc00db3750184c9bdbb1374e468be172de07f4",
+ "sha256": "3387ab3983d8c93d0f57d8796dcc00db3750184c9bdbb1374e468be172de07f4"
},
- "mojave": {
- "cellar": ":any",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:57842762aad1434f8b511f603364b4528f0545f7d768c9387b362011351cda2b",
- "sha256": "57842762aad1434f8b511f603364b4528f0545f7d768c9387b362011351cda2b"
- },
"x86_64_linux": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:8398fc05ef8eb1ea3d7b26844e3a314a948b3d0d4fb937a00c6c62f0abbe340a",
- "sha256": "8398fc05ef8eb1ea3d7b26844e3a314a948b3d0d4fb937a00c6c62f0abbe340a"
+ "url": "https://ghcr.io/v2/homebrew/core/redis/blobs/sha256:823b2c71e5656342095f1af7bcb4d43eef9014717b3687a2e6248a7516872970",
+ "sha256": "823b2c71e5656342095f1af7bcb4d43eef9014717b3687a2e6248a7516872970"
}
}
}
},
"direnv": {
- "version": "2.30.3",
+ "version": "2.32.1",
"bottle": {
"rebuild": 0,
"root_url": "https://ghcr.io/v2/homebrew/core",
"files": {
"arm64_monterey": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:0a2bf97696f0e57e713db8f39dcff719fa17e0512b6ad14a7657a1c946943a85",
- "sha256": "0a2bf97696f0e57e713db8f39dcff719fa17e0512b6ad14a7657a1c946943a85"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:567a8c1ca45ffae17d4901b0aa729be8866b6cb93ee8224da98cacf36f73ed69",
+ "sha256": "567a8c1ca45ffae17d4901b0aa729be8866b6cb93ee8224da98cacf36f73ed69"
},
"arm64_big_sur": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:41d4f105cdef28417dae6c248a5819709967897071c34ed63fa5432644d944f2",
- "sha256": "41d4f105cdef28417dae6c248a5819709967897071c34ed63fa5432644d944f2"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:b21230f43123e6b1a832f87d30f040d9f4684bb19f62f69f651bb24ae1cfaaab",
+ "sha256": "b21230f43123e6b1a832f87d30f040d9f4684bb19f62f69f651bb24ae1cfaaab"
},
"monterey": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:761499a99dc029d5cafe075105827c4897d5e45dd53cfa7bf86ea51fc4f1afaf",
- "sha256": "761499a99dc029d5cafe075105827c4897d5e45dd53cfa7bf86ea51fc4f1afaf"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:68f7b9093d44fdef4210ffeaa8f88e8fa27bef356b4c8b2d4fc7749aab1d2614",
+ "sha256": "68f7b9093d44fdef4210ffeaa8f88e8fa27bef356b4c8b2d4fc7749aab1d2614"
},
"big_sur": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:73fc3e19b391c97806c44d2f2b38b5ddc28742d656ab6ca013371acc6cabd5bc",
- "sha256": "73fc3e19b391c97806c44d2f2b38b5ddc28742d656ab6ca013371acc6cabd5bc"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:f5dc03f040b2638a14e30c9fdeaaed616084539c0360fc916b53c3c8206259c4",
+ "sha256": "f5dc03f040b2638a14e30c9fdeaaed616084539c0360fc916b53c3c8206259c4"
},
"catalina": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:77c87b8f6ee51b65514b5688babcd83117b75feb2b40b0489bd0649cdeb3f3cb",
- "sha256": "77c87b8f6ee51b65514b5688babcd83117b75feb2b40b0489bd0649cdeb3f3cb"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:2edc8e221d28db3da039490d4728d3c5ac7ce38d5274418b4d5f0ca31dfc0b28",
+ "sha256": "2edc8e221d28db3da039490d4728d3c5ac7ce38d5274418b4d5f0ca31dfc0b28"
},
"x86_64_linux": {
"cellar": ":any_skip_relocation",
- "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:b18ff46bf3e0b18eace8b2a0754829555a991d58d6fe3f76681a8a057c48a04c",
- "sha256": "b18ff46bf3e0b18eace8b2a0754829555a991d58d6fe3f76681a8a057c48a04c"
+ "url": "https://ghcr.io/v2/homebrew/core/direnv/blobs/sha256:c0356bf7cc43d0a1eb7777e7ed390f47f9dd8fb51cc480c8fb87fcde5fba1b4a",
+ "sha256": "c0356bf7cc43d0a1eb7777e7ed390f47f9dd8fb51cc480c8fb87fcde5fba1b4a"
}
}
}
+ },
+ "imagemagick": {
+ "version": "7.1.0-43",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:0400965c5e3292014220eccc4c86c781322de2656a73521223dad47a5995d9d2",
+ "sha256": "0400965c5e3292014220eccc4c86c781322de2656a73521223dad47a5995d9d2"
+ },
+ "arm64_big_sur": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:c8b12081678eddd29bdd48e64d9920159f0ddc4c35e9b5dabc4f32b954b4d111",
+ "sha256": "c8b12081678eddd29bdd48e64d9920159f0ddc4c35e9b5dabc4f32b954b4d111"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:7ba74e818a3c320d0246d9d01f063ae1acb1bc7b9682afdb8477a3f9760cf003",
+ "sha256": "7ba74e818a3c320d0246d9d01f063ae1acb1bc7b9682afdb8477a3f9760cf003"
+ },
+ "big_sur": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:52a890f084a4dbe6da65b9a93d626b53d970ebccab4b5e7ba72a6f025be07f49",
+ "sha256": "52a890f084a4dbe6da65b9a93d626b53d970ebccab4b5e7ba72a6f025be07f49"
+ },
+ "catalina": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:f1f5c94945b0da6263fd55f206eebf04abfb24745f395f7574b81e2a03e782c5",
+ "sha256": "f1f5c94945b0da6263fd55f206eebf04abfb24745f395f7574b81e2a03e782c5"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/imagemagick/blobs/sha256:2b7e46bd4f6282f30ae1bd0bbe700460a057df23ea1697f02fd111cca5188f39",
+ "sha256": "2b7e46bd4f6282f30ae1bd0bbe700460a057df23ea1697f02fd111cca5188f39"
+ }
+ }
+ }
+ },
+ "ffmpeg": {
+ "version": "5.0.1_3",
+ "bottle": {
+ "rebuild": 0,
+ "root_url": "https://ghcr.io/v2/homebrew/core",
+ "files": {
+ "arm64_monterey": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:6aa7fa8dbc5fb950f1ef81c31e5c3af52d92c616ea9a4b46e58b42c51a0ba7d7",
+ "sha256": "6aa7fa8dbc5fb950f1ef81c31e5c3af52d92c616ea9a4b46e58b42c51a0ba7d7"
+ },
+ "arm64_big_sur": {
+ "cellar": "/opt/homebrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:a494fef2d5a93ecdadfce8530964af6ddcdb8662795bb7aa35ef8f8d8f659a01",
+ "sha256": "a494fef2d5a93ecdadfce8530964af6ddcdb8662795bb7aa35ef8f8d8f659a01"
+ },
+ "monterey": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:b835b65ef6d4b85e36b7a315133fd9310a4ab6184caef6e8c99174d4aeec7bbb",
+ "sha256": "b835b65ef6d4b85e36b7a315133fd9310a4ab6184caef6e8c99174d4aeec7bbb"
+ },
+ "big_sur": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:a65289290fb40e981887568f2711357402f2e9e7e42f57e2c4d3984f11b36f7a",
+ "sha256": "a65289290fb40e981887568f2711357402f2e9e7e42f57e2c4d3984f11b36f7a"
+ },
+ "catalina": {
+ "cellar": "/usr/local/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:d35f1a769b57ff7180076d53af7c1602ff7e3d3f29f81d6e5a6cb1a90cbc6a3a",
+ "sha256": "d35f1a769b57ff7180076d53af7c1602ff7e3d3f29f81d6e5a6cb1a90cbc6a3a"
+ },
+ "x86_64_linux": {
+ "cellar": "/home/linuxbrew/.linuxbrew/Cellar",
+ "url": "https://ghcr.io/v2/homebrew/core/ffmpeg/blobs/sha256:d020ef50ab876425fa9d9555473b3925da14fe80365a7dc39f53e98e5b7960de",
+ "sha256": "d020ef50ab876425fa9d9555473b3925da14fe80365a7dc39f53e98e5b7960de"
+ }
+ }
+ }
}
}
},
"system": {
"macos": {
"monterey": {
- "HOMEBREW_VERSION": "3.3.13",
- "HOMEBREW_PREFIX": "/usr/local",
- "Homebrew/homebrew-core": "18c5a8c7d1ed4d58a80c1b3d5485c26f290eaa01",
- "CLT": "12.5.0.22.11",
- "Xcode": "12.5.1",
- "macOS": "12.2"
+ "HOMEBREW_VERSION": "3.5.5",
+ "HOMEBREW_PREFIX": "/opt/homebrew",
+ "Homebrew/homebrew-core": "b0fa68871ce4b23a8ea225529e5bc4aea15bac95",
+ "CLT": "13.0.0.0.1.1627064638",
+ "Xcode": "13.2.1",
+ "macOS": "12.3"
}
}
}
Only in truth-new/opensource: CHANGELOG.md
Only in truth-new/opensource: CONTRIBUTING.md
Only in truth-old/opensource: Capfile
diff -ru truth-old/opensource/Gemfile truth-new/opensource/Gemfile
--- truth-old/opensource/Gemfile 2022-06-08 09:15:38
+++ truth-new/opensource/Gemfile 2024-04-05 09:07:34
@@ -15,24 +15,25 @@
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.2'
gem 'makara', '~> 0.5'
-gem 'pghero', '~> 2.8'
gem 'dotenv-rails', '~> 2.7'
+gem 'acts_as_list', '~> 1.1'
gem 'aws-sdk-s3', '~> 1.96', require: false
gem 'activerecord-import'
gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false
-gem 'paperclip', '~> 6.0'
+gem 'paperclip', '~> 6.0.0'
gem 'blurhash', '~> 0.1'
+gem 'composite_primary_keys', '~> 13.0'
gem 'active_model_serializers', '~> 0.10'
-gem 'addressable', '~> 2.7'
+gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.6.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
-gem 'chewy', '~> 5.2'
-gem 'cld3', '~> 3.4.2'
+gem 'chewy', '~> 7.2.4'
+gem 'cld3', '~> 3.5.0'
gem 'devise', '~> 4.8'
gem 'devise-two-factor', '~> 4.0'
@@ -46,10 +47,14 @@
gem 'omniauth', '~> 1.9'
gem 'omniauth-rails_csrf_protection', '~> 0.1'
+gem 'amqp', '~> 1.8.0'
+gem 'bunny', '~> 2.19.0'
gem 'color_diff', '~> 0.1'
gem 'discard', '~> 1.2'
gem 'doorkeeper', '~> 5.5'
gem 'ed25519', '~> 1.2'
+gem 'fabrication', '~> 2.22'
+gem 'faker', '~> 2.18'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'hiredis', '~> 0.6'
@@ -59,19 +64,20 @@
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.5.0'
gem 'idn-ruby', require: 'idn'
+gem 'jwe', '~> 0.4.0'
gem 'jwt', '~> 2.2'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.11'
-gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.11'
gem 'ox', '~> 2.14'
+gem 'panko_serializer', '~> 0.7.7'
gem 'parslet'
-gem 'parallel', '~> 1.20'
+gem 'phonelib', '~> 0.8.7'
gem 'posix-spawn'
gem 'prometheus_exporter', '~> 1.0'
-gem 'google-protobuf', '~> 3.19'
+gem 'ruby_proto_schemas', git: "https://gitlab.com/tmediatech/ruby-proto-schemas.git", tag: "v4"
gem 'premailer-rails'
gem 'pundit', '~> 2.1'
gem 'rack-attack', '~> 6.5'
@@ -87,28 +93,31 @@
gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.2'
gem 'sidekiq-scheduler', '~> 3.1'
-gem 'sidekiq-unique-jobs', '~> 7.0'
+gem 'sidekiq-unique-jobs', '~> 7.1'
gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.3'
gem 'simple_form', '~> 5.1'
+gem 'sneakers', '~> 2.3', '>= 2.3.5'
+gem 'sneakers_handlers', '~> 0.0.8'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.2.1'
gem 'strong_migrations', '~> 0.7'
+gem 'swagger-blocks'
gem 'tty-prompt', '~> 0.23', require: false
gem 'twitter-text', '~> 3.1.0'
gem 'tzinfo-data', '~> 1.2021'
gem 'webpacker', '~> 5.4.2'
gem 'webpush', '~> 0.3'
-gem 'webauthn', '~> 3.0.0.alpha1'
+gem 'webauthn', '~> 2.5'
gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.1'
gem 'rdf-normalize', '~> 0.4'
group :development, :test do
- gem 'fabrication', '~> 2.22'
gem 'fuubar', '~> 2.5'
gem 'i18n-tasks', '~> 0.9', require: false
+ gem 'oauth2'
gem 'pry-byebug', '~> 3.9'
gem 'pry-rails', '~> 0.3'
gem 'rspec-rails', '~> 5.0'
@@ -121,7 +130,6 @@
group :test do
gem 'capybara', '~> 3.35'
gem 'climate_control', '~> 0.2'
- gem 'faker', '~> 2.18'
gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'
@@ -140,16 +148,11 @@
gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler'
- gem 'rubocop', '~> 1.16', require: false
+ gem 'rubocop', '~> 1.60', require: false
gem 'rubocop-rails', '~> 2.10', require: false
+ gem 'rubocop-rspec', '~> 2.26', require: false
gem 'brakeman', '~> 5.0', require: false
gem 'bundler-audit', '~> 0.8', require: false
-
- gem 'capistrano', '~> 3.16'
- gem 'capistrano-rails', '~> 1.6'
- gem 'capistrano-rbenv', '~> 2.2'
- gem 'capistrano-yarn', '~> 2.0'
-
gem 'stackprof'
end
@@ -159,9 +162,7 @@
gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false
-
-gem 'xorcist', '~> 1.1'
gem 'resolv', '~> 0.1.0'
-gem 'newrelic_rpm', '~> 7.2'
\ No newline at end of file
+gem 'newrelic_rpm', '~> 7.2'
diff -ru truth-old/opensource/Gemfile.lock truth-new/opensource/Gemfile.lock
--- truth-old/opensource/Gemfile.lock 2022-06-08 09:15:38
+++ truth-new/opensource/Gemfile.lock 2024-04-05 09:07:34
@@ -1,3 +1,11 @@
+GIT
+ remote: https://gitlab.com/tmediatech/ruby-proto-schemas.git
+ revision: c1416cc1a1716b2d46d6a0dcd4470630182e5a86
+ tag: v4
+ specs:
+ ruby_proto_schemas (0.1.0)
+ google-protobuf (~> 3.19)
+
GEM
remote: https://rubygems.org/
specs:
@@ -68,10 +76,14 @@
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
- addressable (2.7.0)
- public_suffix (>= 2.0.2, < 5.0)
- airbrussh (1.4.0)
- sshkit (>= 1.6.1, != 1.7.0)
+ acts_as_list (1.1.0)
+ activerecord (>= 4.2)
+ addressable (2.8.6)
+ public_suffix (>= 2.0.2, < 6.0)
+ amq-protocol (2.3.2)
+ amqp (1.8.0)
+ amq-protocol (>= 2.2.0)
+ eventmachine
android_key_attestation (0.3.0)
annotate (3.1.1)
activerecord (>= 3.2, < 7.0)
@@ -79,7 +91,7 @@
ast (2.4.2)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
- awrence (1.1.1)
+ awrence (1.2.1)
aws-eventstream (1.1.1)
aws-partitions (1.465.0)
aws-sdk-core (3.114.1)
@@ -101,7 +113,7 @@
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
- bindata (2.4.8)
+ bindata (2.4.10)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.5)
@@ -120,22 +132,10 @@
bundler-audit (0.8.0)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
+ bunny (2.19.0)
+ amq-protocol (~> 2.3, >= 2.3.1)
+ sorted_set (~> 1, >= 1.0.2)
byebug (11.1.3)
- capistrano (3.16.0)
- airbrussh (>= 1.0.0)
- i18n
- rake (>= 10.0.0)
- sshkit (>= 1.9.0)
- capistrano-bundler (2.0.1)
- capistrano (~> 3.1)
- capistrano-rails (1.6.1)
- capistrano (~> 3.1)
- capistrano-bundler (>= 1.1, < 3)
- capistrano-rbenv (2.2.0)
- capistrano (~> 3.1)
- sshkit (~> 1.3)
- capistrano-yarn (2.0.2)
- capistrano (~> 3.0)
capybara (3.35.3)
addressable
mini_mime (>= 0.1.3)
@@ -148,21 +148,23 @@
activesupport
cbor (0.5.9.6)
charlock_holmes (0.7.7)
- chewy (5.2.0)
+ chewy (7.2.6)
activesupport (>= 5.2)
- elasticsearch (>= 2.0.0)
+ elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl
chunky_png (1.4.0)
- cld3 (3.4.2)
+ cld3 (3.5.0)
ffi (>= 1.1.0, < 1.16.0)
climate_control (0.2.0)
coderay (1.1.3)
color_diff (0.1)
- concurrent-ruby (1.1.9)
+ composite_primary_keys (13.0.3)
+ activerecord (~> 6.1.0)
+ concurrent-ruby (1.1.10)
connection_pool (2.2.5)
- cose (1.0.0)
+ cose (1.2.0)
cbor (~> 0.5.9)
- openssl-signature_algorithm (~> 0.4.0)
+ openssl-signature_algorithm (~> 1.0)
crack (0.4.5)
rexml
crass (1.0.6)
@@ -198,28 +200,47 @@
railties (>= 3.2)
e2mmap (0.1.0)
ed25519 (1.2.4)
- elasticsearch (7.10.1)
- elasticsearch-api (= 7.10.1)
- elasticsearch-transport (= 7.10.1)
- elasticsearch-api (7.10.1)
+ elasticsearch (7.13.3)
+ elasticsearch-api (= 7.13.3)
+ elasticsearch-transport (= 7.13.3)
+ elasticsearch-api (7.13.3)
multi_json
- elasticsearch-dsl (0.1.9)
- elasticsearch-transport (7.10.1)
+ elasticsearch-dsl (0.1.10)
+ elasticsearch-transport (7.13.3)
faraday (~> 1)
multi_json
encryptor (3.0.0)
erubi (1.10.0)
et-orbi (1.2.4)
tzinfo
+ eventmachine (1.2.7)
excon (0.76.0)
fabrication (2.22.0)
faker (2.18.0)
i18n (>= 1.6, < 2)
- faraday (1.3.0)
+ faraday (1.10.0)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
- multipart-post (>= 1.2, < 3)
- ruby2_keywords
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.0)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.0.4)
+ multipart-post (~> 2)
faraday-net_http (1.0.1)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
fast_blank (1.0.0)
fastimage (2.2.4)
ffi (1.15.5)
@@ -247,7 +268,7 @@
ruby-progressbar (~> 1.4)
globalid (0.4.2)
activesupport (>= 4.2.0)
- google-protobuf (3.19.3)
+ google-protobuf (3.25.3)
hamlit (2.13.0)
temple (>= 0.8.2)
thor
@@ -261,7 +282,7 @@
concurrent-ruby (~> 1.0)
hashdiff (1.0.1)
hashie (4.1.0)
- highline (2.0.3)
+ highline (2.1.0)
hiredis (0.6.3)
hkdf (0.3.0)
htmlentities (4.3.4)
@@ -279,9 +300,9 @@
httplog (1.5.0)
rack (>= 1.0)
rainbow (>= 2.0.0)
- i18n (1.8.10)
+ i18n (1.12.0)
concurrent-ruby (~> 1.0)
- i18n-tasks (0.9.34)
+ i18n-tasks (0.9.37)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
erubi
@@ -308,6 +329,7 @@
json-ld (~> 3.1)
rdf (~> 3.1)
jsonapi-renderer (0.2.2)
+ jwe (0.4.0)
jwt (2.2.2)
kaminari (1.2.1)
activesupport (>= 4.1.0)
@@ -321,6 +343,7 @@
activerecord
kaminari-core (= 1.2.1)
kaminari-core (1.2.1)
+ language_server-protocol (3.17.0.3)
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.7.0)
@@ -358,14 +381,12 @@
rake
mini_mime (1.0.3)
mini_portile2 (2.5.3)
- minitest (5.14.4)
+ minitest (5.16.2)
msgpack (1.4.2)
multi_json (1.15.0)
- multipart-post (2.1.1)
+ multi_xml (0.6.0)
+ multipart-post (2.2.3)
net-ldap (0.17.0)
- net-scp (3.0.0)
- net-ssh (>= 2.6.5, < 7.0.0)
- net-ssh (6.1.0)
newrelic_rpm (7.2.0)
nio4r (2.5.7)
nokogiri (1.11.7)
@@ -373,11 +394,13 @@
racc (~> 1.4)
nokogumbo (2.0.4)
nokogiri (~> 1.8, >= 1.8.4)
- nsa (0.2.8)
- activesupport (>= 4.2, < 7)
- concurrent-ruby (~> 1.0, >= 1.0.2)
- sidekiq (>= 3.5)
- statsd-ruby (~> 1.4, >= 1.4.0)
+ oauth2 (2.0.6)
+ faraday (>= 0.17.3, < 3.0)
+ jwt (>= 1.0, < 3.0)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 3)
+ rash_alt (>= 0.4, < 1)
+ version_gem (~> 1.1)
oj (3.11.5)
omniauth (1.9.1)
hashie (>= 3.4.6)
@@ -393,9 +416,13 @@
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.9)
openssl (2.2.0)
- openssl-signature_algorithm (0.4.0)
+ openssl-signature_algorithm (1.1.1)
+ openssl (~> 2.0)
orm_adapter (0.5.0)
ox (2.14.5)
+ panko_serializer (0.7.7)
+ activesupport
+ oj (> 3.11.0, < 4.0.0)
paperclip (6.0.0)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
@@ -405,14 +432,14 @@
parallel (1.20.1)
parallel_tests (3.7.0)
parallel
- parser (3.0.1.1)
+ parser (3.3.0.5)
ast (~> 2.4.1)
+ racc
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.2.3)
- pghero (2.8.1)
- activerecord (>= 5)
+ phonelib (0.8.7)
pkg-config (1.4.6)
posix-spawn (0.3.15)
premailer (1.14.2)
@@ -433,7 +460,7 @@
pry (~> 0.13.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
- public_suffix (4.0.6)
+ public_suffix (5.0.4)
puma (5.4.0)
nio4r (~> 2.0)
pundit (2.1.0)
@@ -490,6 +517,9 @@
activerecord (>= 6.0.4)
activesupport (>= 6.0.4)
i18n
+ rash_alt (0.4.12)
+ hashie (>= 3.4)
+ rbtree (0.4.5)
rdf (3.1.13)
hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8)
@@ -534,25 +564,35 @@
rspec-support (3.10.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
- rubocop (1.16.0)
+ rubocop (1.60.2)
+ json (~> 2.3)
+ language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
- parser (>= 3.0.0.0)
+ parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
- rexml
- rubocop-ast (>= 1.7.0, < 2.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.7.0)
- parser (>= 3.0.1.1)
+ unicode-display_width (>= 2.4.0, < 3.0)
+ rubocop-ast (1.30.0)
+ parser (>= 3.2.1.0)
+ rubocop-capybara (2.20.0)
+ rubocop (~> 1.41)
+ rubocop-factory_bot (2.25.1)
+ rubocop (~> 1.41)
rubocop-rails (2.10.1)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
+ rubocop-rspec (2.26.1)
+ rubocop (~> 1.40)
+ rubocop-capybara (~> 2.17)
+ rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.11.0)
ruby-saml (1.11.0)
nokogiri (>= 1.5.10)
- ruby2_keywords (0.0.4)
+ ruby2_keywords (0.0.5)
rufus-scheduler (3.7.0)
fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0)
@@ -564,8 +604,10 @@
scenic (1.5.4)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
- securecompare (1.0.0)
semantic_range (3.0.0)
+ serverengine (2.0.7)
+ sigdump (~> 0.2.2)
+ set (1.0.2)
sidekiq (6.2.1)
connection_pool (>= 2.2.2)
rack (~> 2.0)
@@ -579,11 +621,12 @@
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
- sidekiq-unique-jobs (7.0.12)
+ sidekiq-unique-jobs (7.1.25)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
- sidekiq (>= 5.0, < 7.0)
- thor (>= 0.20, < 2.0)
+ sidekiq (>= 5.0, < 8.0)
+ thor (>= 0.20, < 3.0)
+ sigdump (0.2.5)
simple-navigation (4.3.0)
activesupport (>= 2.3.2)
simple_form (5.1.0)
@@ -595,6 +638,17 @@
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.2)
+ sneakers (2.11.0)
+ bunny (~> 2.12)
+ concurrent-ruby (~> 1.0)
+ rake
+ serverengine (~> 2.0.5)
+ thor
+ sneakers_handlers (0.0.8)
+ sneakers (~> 2.0)
+ sorted_set (1.0.3)
+ rbtree
+ set (~> 1.0)
sprockets (3.7.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
@@ -602,26 +656,23 @@
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sshkit (1.21.2)
- net-scp (>= 1.1.2)
- net-ssh (>= 2.8.0)
stackprof (0.2.17)
- statsd-ruby (1.5.0)
stoplight (2.2.1)
strong_migrations (0.7.6)
activerecord (>= 5)
+ swagger-blocks (3.0.0)
temple (0.8.2)
- terminal-table (3.0.0)
- unicode-display_width (~> 1.1, >= 1.1.1)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0)
thor (1.1.0)
thwait (0.2.0)
e2mmap
tilt (2.0.10)
- tpm-key_attestation (0.9.0)
+ tpm-key_attestation (0.10.0)
bindata (~> 2.4)
- openssl-signature_algorithm (~> 0.4.0)
+ openssl-signature_algorithm (~> 1.0)
tty-color (0.6.0)
tty-cursor (0.7.1)
tty-prompt (0.23.1)
@@ -635,27 +686,27 @@
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.2021.1)
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.7)
- unicode-display_width (1.7.0)
+ unicode-display_width (2.5.0)
uniform_notifier (1.14.1)
+ version_gem (1.1.0)
warden (1.2.9)
rack (>= 2.0.9)
- webauthn (3.0.0.alpha1)
+ webauthn (2.5.1)
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)
safety_net_attestation (~> 0.4.0)
- securecompare (~> 1.0)
- tpm-key_attestation (~> 0.9.0)
+ tpm-key_attestation (~> 0.10.0)
webmock (3.13.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
@@ -673,10 +724,9 @@
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
wisper (2.0.1)
- xorcist (1.1.2)
xpath (3.2.0)
nokogiri (~> 1.8)
- zeitwerk (2.4.2)
+ zeitwerk (2.6.0)
PLATFORMS
ruby
@@ -685,7 +735,9 @@
active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.8)
activerecord-import
- addressable (~> 2.7)
+ acts_as_list (~> 1.1)
+ addressable (~> 2.8)
+ amqp (~> 1.8.0)
annotate (~> 3.1)
aws-sdk-s3 (~> 1.96)
better_errors (~> 2.9)
@@ -696,16 +748,14 @@
browser
bullet (~> 6.1)
bundler-audit (~> 0.8)
- capistrano (~> 3.16)
- capistrano-rails (~> 1.6)
- capistrano-rbenv (~> 2.2)
- capistrano-yarn (~> 2.0)
+ bunny (~> 2.19.0)
capybara (~> 3.35)
charlock_holmes (~> 0.7.7)
- chewy (~> 5.2)
- cld3 (~> 3.4.2)
+ chewy (~> 7.2.4)
+ cld3 (~> 3.5.0)
climate_control (~> 0.2)
color_diff (~> 0.1)
+ composite_primary_keys (~> 13.0)
concurrent-ruby
connection_pool
devise (~> 4.8)
@@ -722,7 +772,6 @@
fog-core (<= 2.1.0)
fog-openstack (~> 0.3)
fuubar (~> 2.5)
- google-protobuf (~> 3.19)
hamlit-rails (~> 0.2)
hiredis (~> 0.6)
htmlentities (~> 4.3)
@@ -734,6 +783,7 @@
iso-639
json-ld
json-ld-preloaded (~> 3.1)
+ jwe (~> 0.4.0)
jwt (~> 2.2)
kaminari (~> 1.2)
letter_opener (~> 1.7)
@@ -748,7 +798,7 @@
net-ldap (~> 0.17)
newrelic_rpm (~> 7.2)
nokogiri (~> 1.11)
- nsa (~> 0.2)
+ oauth2
oj (~> 3.11)
omniauth (~> 1.9)
omniauth-cas (~> 2.0)
@@ -756,12 +806,12 @@
omniauth-saml (~> 1.10)
openssl (~> 2.2.0)
ox (~> 2.14)
- paperclip (~> 6.0)
- parallel (~> 1.20)
+ panko_serializer (~> 0.7.7)
+ paperclip (~> 6.0.0)
parallel_tests (~> 3.7)
parslet
pg (~> 1.2)
- pghero (~> 2.8)
+ phonelib (~> 0.8.7)
pkg-config (~> 1.4)
posix-spawn
premailer-rails
@@ -787,35 +837,39 @@
rspec-rails (~> 5.0)
rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4)
- rubocop (~> 1.16)
+ rubocop (~> 1.60)
rubocop-rails (~> 2.10)
+ rubocop-rspec (~> 2.26)
ruby-progressbar (~> 1.11)
+ ruby_proto_schemas!
sanitize (~> 5.2)
scenic (~> 1.5)
sidekiq (~> 6.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.1)
- sidekiq-unique-jobs (~> 7.0)
+ sidekiq-unique-jobs (~> 7.1)
simple-navigation (~> 4.3)
simple_form (~> 5.1)
simplecov (~> 0.21)
+ sneakers (~> 2.3, >= 2.3.5)
+ sneakers_handlers (~> 0.0.8)
sprockets (~> 3.7.2)
sprockets-rails (~> 3.2)
stackprof
stoplight (~> 2.2.1)
strong_migrations (~> 0.7)
+ swagger-blocks
thor (~> 1.1)
tty-prompt (~> 0.23)
twitter-text (~> 3.1.0)
tzinfo-data (~> 1.2021)
- webauthn (~> 3.0.0.alpha1)
+ webauthn (~> 2.5)
webmock (~> 3.13)
webpacker (~> 5.4.2)
webpush (~> 0.3)
- xorcist (~> 1.1)
RUBY VERSION
ruby 2.7.2p137
BUNDLED WITH
- 2.2.29
+ 2.3.18
diff -ru truth-old/opensource/Rakefile truth-new/opensource/Rakefile
--- truth-old/opensource/Rakefile 2022-06-08 09:15:38
+++ truth-new/opensource/Rakefile 2024-04-01 14:59:13
@@ -1,5 +1,5 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
-# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+# for example lib/tasks/groups.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
diff -ru truth-old/opensource/app/chewy/accounts_index.rb truth-new/opensource/app/chewy/accounts_index.rb
--- truth-old/opensource/app/chewy/accounts_index.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/chewy/accounts_index.rb 2024-04-01 14:59:13
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AccountsIndex < Chewy::Index
- settings index: { refresh_interval: '5m' }, number_of_shards: '12', analysis: {
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: {
analyzer: {
content: {
tokenizer: 'whitespace',
@@ -12,8 +12,26 @@
tokenizer: 'edge_ngram',
filter: %w(lowercase asciifolding cjk_width),
},
+
+ exact: {
+ tokenizer: 'keyword',
+ filter: %w(lowercase asciifolding cjk_width),
+ },
+
+ phone: {
+ tokenizer: 'pattern',
+ filter: %w(phone),
+ },
},
+ filter: {
+ phone: {
+ type: 'pattern_capture',
+ preserve_original: false,
+ patterns: ['(\d+(\d{10}))'],
+ },
+ },
+
tokenizer: {
edge_ngram: {
type: 'edge_ngram',
@@ -23,23 +41,34 @@
},
}
- define_type ::Account.searchable.includes(:account_stat), delete_if: ->(account) {
- account.destroyed? || !account.searchable?
- } do
- root date_detection: false do
- field :id, type: 'long'
+ index_scope ::Account.includes(:account_follower, :account_following, :account_status), delete_if: ->(account) { account.destroyed? || !account.searchable? }
- field :display_name, type: 'text', analyzer: 'content' do
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
- end
+ root date_detection: false do
+ field :id, type: 'long'
- field :acct, type: 'text', analyzer: 'content', value: ->(account) { account.username } do
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
- end
+ field :display_name, type: 'text', analyzer: 'content' do
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
+ field :keyword, type: 'keyword'
+ end
- field :following_count, type: 'long', value: ->(account) { account.following_count.negative? ? 0 : account.following_count }
- field :followers_count, type: 'long', value: ->(account) { account.followers_count.negative? ? 0 : account.followers_count }
- field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
+ field :acct, type: 'text', analyzer: 'content', value: ->(account) { account.username } do
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
+ field :keyword, type: 'keyword'
end
+
+ field :following_count, type: 'long', value: ->(account) { account.following_count.negative? ? 0 : account.following_count }
+ field :followers_count, type: 'long', value: ->(account) { account.followers_count.negative? ? 0 : account.followers_count }
+ field :last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at }
+
+ field :suspended, type: 'boolean', value: ->(account) { account.suspended? }
+ field :disabled, type: 'boolean', value: ->(account) { account.user_disabled? }
+ field :hidden, type: 'boolean', value: -> { false }
+
+ field :email, type: 'text', analyzer: 'exact', value: ->(account) { account.user_email }do
+ field :keyword, type: 'keyword'
+ end
+ field :created_at, type: 'date', value: ->(account) { account.created_at }
+
+ field :sms, type: 'text', analyzer: 'phone', search_analyzer: 'exact', value: ->(account) { account.user_sms }
end
end
diff -ru truth-old/opensource/app/chewy/statuses_index.rb truth-new/opensource/app/chewy/statuses_index.rb
--- truth-old/opensource/app/chewy/statuses_index.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/chewy/statuses_index.rb 2024-04-01 14:59:13
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
- settings index: { refresh_interval: '15m' }, number_of_shards: '12', analysis: {
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: {
filter: {
english_stop: {
type: 'stop',
@@ -31,22 +31,22 @@
},
}
- define_type ::Status.unscoped.kept.without_reblogs.includes(:status_stat) do
+ index_scope ::Status.unscoped.kept.without_reblogs.includes(:status_favourite, :status_reply, :status_reblog)
- root date_detection: false do
- field :id, type: 'long'
- field :account_id, type: 'long'
+ root date_detection: false do
+ field :id, type: 'long'
+ field :account_id, type: 'long'
- field :text, type: 'text', value: ->(status) {
- [status.spoiler_text, Formatter.instance.plaintext(status)].reject(&:blank?).join("\n\n")
- } do
- field :stemmed, type: 'text', analyzer: 'content'
- end
+ field :text, type: 'text', value: ->(status) {
+ [status.spoiler_text, Formatter.instance.plaintext(status)].reject(&:blank?).join("\n\n")
+ } do
+ field :stemmed, type: 'text', analyzer: 'content'
+ end
- field :activity, type: 'integer', value: ->(status) { (status.reblogs_count * 3) + status.favourites_count }
+ field :activity, type: 'integer', value: ->(status) { (status.reblogs_count * 3) + status.favourites_count }
- field :created_at, type: 'date'
- end
- end
+ field :created_at, type: 'date'
+ field :text_hash, type: 'keyword'
+ end
end
diff -ru truth-old/opensource/app/chewy/tags_index.rb truth-new/opensource/app/chewy/tags_index.rb
--- truth-old/opensource/app/chewy/tags_index.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/chewy/tags_index.rb 2024-04-01 14:59:13
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class TagsIndex < Chewy::Index
- settings index: { refresh_interval: '15m' }, number_of_shards: '12', analysis: {
+ settings index: { refresh_interval: '1m' }, number_of_shards: '12', analysis: {
analyzer: {
content: {
tokenizer: 'keyword',
@@ -23,17 +23,15 @@
},
}
- define_type ::Tag.listable, delete_if: ->(tag) {
- tag.destroyed? || !tag.listable?
- } do
- root date_detection: false do
- field :name, type: 'text', analyzer: 'content' do
- field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
- end
+ index_scope ::Tag.listable, delete_if: ->(tag) { tag.destroyed? || !tag.listable? }
- field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
- field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
- field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
+ root date_detection: false do
+ field :name, type: 'text', analyzer: 'content' do
+ field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content'
end
+
+ field :reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? }
+ field :usage, type: 'long', value: ->(tag) { tag.history.reduce(0) { |total, day| total + day[:accounts].to_i } }
+ field :last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at }
end
end
diff -ru truth-old/opensource/app/controllers/accounts_controller.rb truth-new/opensource/app/controllers/accounts_controller.rb
--- truth-old/opensource/app/controllers/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/accounts_controller.rb 2024-04-01 14:59:13
@@ -28,7 +28,7 @@
return
end
- @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
+ @pinned_statuses = cache_collection(@account.pinned_statuses.merge!(StatusPin.profile_pins), Status) if show_pinned_statuses?
@statuses = cached_filtered_status_page
@rss_url = rss_url
diff -ru truth-old/opensource/app/controllers/activitypub/collections_controller.rb truth-new/opensource/app/controllers/activitypub/collections_controller.rb
--- truth-old/opensource/app/controllers/activitypub/collections_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/activitypub/collections_controller.rb 2024-04-01 14:59:13
@@ -20,7 +20,7 @@
def set_items
case params[:id]
when 'featured'
- @items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
+ @items = for_signed_account { cache_collection(@account.pinned_statuses.merge!(StatusPin.profile_pins), Status) }
when 'tags'
@items = for_signed_account { @account.featured_tags }
when 'devices'
Only in truth-old/opensource/app/controllers/activitypub: inboxes_controller.rb
Only in truth-old/opensource/app/controllers/activitypub: outboxes_controller.rb
diff -ru truth-old/opensource/app/controllers/admin/accounts_controller.rb truth-new/opensource/app/controllers/admin/accounts_controller.rb
--- truth-old/opensource/app/controllers/admin/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/admin/accounts_controller.rb 2024-04-01 14:59:13
@@ -43,13 +43,20 @@
def reject
authorize @account.user, :reject?
- DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ DeleteAccountService.new.call(
+ @account,
+ @current_account.id,
+ deletion_type: 'admin_reject',
+ reserve_email: false,
+ reserve_username: false,
+ skip_activitypub: true,
+ )
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end
def destroy
authorize @account, :destroy?
- Admin::AccountDeletionWorker.perform_async(@account.id)
+ Admin::AccountDeletionWorker.perform_async(@account.id, @current_account.id)
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
end
Only in truth-old/opensource/app/controllers/admin: trending_truths_controller.rb
diff -ru truth-old/opensource/app/controllers/api/base_controller.rb truth-new/opensource/app/controllers/api/base_controller.rb
--- truth-old/opensource/app/controllers/api/base_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/base_controller.rb 2024-04-12 09:09:08
@@ -3,8 +3,13 @@
class Api::BaseController < ApplicationController
DEFAULT_STATUSES_LIMIT = 20
DEFAULT_ACCOUNTS_LIMIT = 40
+ VERIFICATION_INTERVAL = 1.hour.ago.freeze
+ RATE_LIMIT_EXPIRE_AFTER = 7.days.seconds
include RateLimitHeaders
+ include Redisable
+ include AppAttestable
+ include Clientable
skip_before_action :store_current_location
skip_before_action :require_functional!, unless: :whitelist_mode?
@@ -17,76 +22,111 @@
skip_around_action :set_locale
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
- errror_message = e.to_s
+ error_message = e.to_s
if params[:controller] == 'api/v1/admin/accounts' && params[:action] == 'create'
filters = Rails.application.config.filter_parameters
f = ActiveSupport::ParameterFilter.new filters
filtered_params = f.filter params
- Rails.logger.info("Unsuccessful registration: #{errror_message}. params: #{filtered_params}")
+ Rails.logger.info("Unsuccessful registration: #{error_message}. params: #{filtered_params}")
end
- response = { error: errror_message }
+ error = error_message
+ additional_fields = {}
- # Output an errorCode key w/ value instead of shoving it into one giant string in the error key
- # Usage: object.errors.add(:errorCode, 'machine_readable_text')
- if (errror_message.include?('Errorcode'))
- message = errror_message.split(', Errorcode ')
- formatted_readable_message = message[0].sub('Validation failed: ', '')
- response = { error: formatted_readable_message, errorCode: message[1] }
+ # Output an error_code key w/ value instead of shoving it into one giant string in the error key
+ # Usage: object.errors.add(:error_code, 'machine_readable_text')
+ if error_message.include?('Error code')
+ message = error_message.split(', Error code ')
+ error = message[0].sub(I18n.t('activerecord.errors.messages.record_invalid').split(': ')[0], '').sub(': ', '') # there's not an easy way to remove the 'Validation failed' text in regards to localization
+ code = message[1]
+ # Need this for backwards compatibility
+ if message[1] == 'followLimitReached'
+ code = message[1].underscore
+ additional_fields[:errorCode] = message[1]
+ elsif request.path == validate_api_v1_groups_path
+ additional_fields[:message] = message[1]
+ else
+ additional_fields
+ end
end
- render json: response, status: 422
+ render_error(422, error, code, error, additional_fields)
end
- rescue_from ActiveRecord::RecordNotUnique do
- render json: { error: 'Duplicate record' }, status: 422
+ rescue_from ActiveRecord::RecordNotUnique do |e|
+ Rails.logger.error "RecordNotUnique: #{e}, user: #{current_user.id}, user_agent: #{request.user_agent}"
+ message = I18n.t('errors.api.duplicate')
+ render_error(422, message, message, message)
end
rescue_from ActiveRecord::RecordNotFound do
- render json: { error: 'Record not found' }, status: 404
+ message = I18n.t('errors.api.404')
+ render_error(404, message, nil, message)
end
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
- render json: { error: 'Remote data could not be fetched' }, status: 503
+ render json: { error: I18n.t('errors.api.data_fetch') }, status: 503
end
rescue_from OpenSSL::SSL::SSLError do
- render json: { error: 'Remote SSL certificate could not be verified' }, status: 503
+ render json: { error: I18n.t('errors.api.ssl') }, status: 503
end
rescue_from Mastodon::NotPermittedError do
- render json: { error: 'This action is not allowed' }, status: 403
+ message = I18n.t('errors.api.403')
+ render_error(403, message, nil, message)
end
+ rescue_from Mastodon::UnprocessableAssertion do |e|
+ alert(e.message) unless e.message == e.class.to_s
+ render json: { error: 'Unable to verify assertion' }, status: 422
+ end
+
+ rescue_from Mastodon::AttestationError do
+ render json: { error: 'Unable to verify attestation' }, status: 400
+ end
+
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do |e|
- Rails.logger.info("Network error: #{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") if e.class == Seahorse::Client::NetworkingError
- render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
+ Rails.logger.info("Network error: #{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}") if e.instance_of?(Seahorse::Client::NetworkingError)
+ render json: { error: I18n.t('errors.api.503') }, status: 503
end
rescue_from Mastodon::RateLimitExceededError do |e|
Rails.logger.info("#{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}")
+ track_rate_limited_user
render json: { error: I18n.t('errors.429') }, status: 429
end
+ rescue_from Mastodon::HostileRateLimitExceededError do |e|
+ Rails.logger.info("#{e.message} #{request.remote_ip} #{request.request_method} #{request.fullpath} #{current_user.id}")
+ track_hostile_rate_limited_user
+ render json: {}, status: 200
+ end
+
rescue_from ActionController::ParameterMissing do |e|
render json: { error: e.to_s }, status: 400
end
+ rescue_from WebAuthn::Error do |e|
+ render json: { error: e.to_s }, status: 400
+ end
+
def doorkeeper_unauthorized_render_options(error: nil)
- { json: { error: (error.try(:description) || 'Not authorized') } }
+ { json: { error: (error.try(:description) || I18n.t('errors.api.unauthorized')) } }
end
def doorkeeper_forbidden_render_options(*)
- { json: { error: 'This action is outside the authorized scopes' } }
+ { json: { error: I18n.t('errors.api.outside_scopes') } }
end
protected
- def set_pagination_headers(next_path = nil, prev_path = nil)
+ def set_pagination_headers(next_path = nil, prev_path = nil, offset = nil)
links = []
links << [next_path, [%w(rel next)]] if next_path
links << [prev_path, [%w(rel prev)]] if prev_path
+ links << [offset, [%w(rel self)]] if offset
response.headers['Link'] = LinkHeader.new(links) unless links.empty?
end
@@ -105,7 +145,7 @@
end
def current_resource_owner
- @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
+ @current_user ||= User.with_reverification.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end
def current_user
@@ -115,18 +155,25 @@
end
def require_authenticated_user!
- render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
+ render json: { error: I18n.t('errors.api.401') }, status: 401 unless current_user
end
- def require_user!(requires_approval: true)
+ def require_user!(requires_approval: true, skip_sms_reverification: false)
if !current_user
- render json: { error: 'This method requires an authenticated user' }, status: 422
+ alert('Current user is missing') if assert_request?
+ render json: { error: I18n.t('errors.api.401') }, status: 422
elsif !current_user.confirmed?
- render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
+ alert("Current user: #{current_user.id} is not confirmed") if assert_request?
+ render json: { error: I18n.t('errors.api.missing_email') }, status: 403
elsif requires_approval && !current_user.approved?
- render json: { error: 'Your login is currently pending approval' }, status: 403
+ alert("Current user: #{current_user.id} is not approved") if assert_request?
+ render json: { error: I18n.t('errors.api.login_pending') }, status: 403
elsif requires_approval && !current_user.functional?
- render json: { error: 'Your login is currently disabled' }, status: 403
+ alert("Current user: #{current_user.id} is not functional") if assert_request?
+ render json: { error: I18n.t('errors.api.login_disabled') }, status: 403
+ elsif !skip_sms_reverification && sms_reverification_required?
+ alert("Current user: #{current_user.id} is waiting sms verification") if assert_request?
+ render json: { error: I18n.t('errors.api.sms_reverification_pending') }, status: 403
else
update_user_sign_in
end
@@ -136,6 +183,20 @@
render json: {}, status: 200
end
+ # rubocop:disable Metrics/ParameterLists
+ def render_error(status, error_message = nil, code = nil, error = nil, additional_fields = {})
+ default_code = Rack::Utils::HTTP_STATUS_CODES[status]
+ response = {
+ error_message: error_message || default_code,
+ error_code: format_code(code || default_code),
+ error: error,
+ **additional_fields,
+ }.compact
+
+ render json: response, status: status
+ end
+ # rubocop:enable Metrics/ParameterLists
+
def authorize_if_got_token!(*scopes)
doorkeeper_authorize!(*scopes) if doorkeeper_token
end
@@ -146,5 +207,51 @@
def disallow_unauthenticated_api_access?
authorized_fetch_mode?
+ end
+
+ def track_rate_limited_user
+ redis_key = "rate_limit:#{DateTime.current.to_date}"
+ redis_element_key = "#{current_user.id}-#{request.remote_ip}"
+ Redis.current.zincrby(redis_key, 1, redis_element_key)
+ Redis.current.expire(redis_key, RATE_LIMIT_EXPIRE_AFTER)
+ end
+
+ def track_hostile_rate_limited_user
+ redis_key = "hostile_rate_limit:#{DateTime.current.to_date}"
+ redis_element_key = "#{current_user.id}-#{request.remote_ip}"
+ Redis.current.zincrby(redis_key, 1, redis_element_key)
+ Redis.current.expire(redis_key, RATE_LIMIT_EXPIRE_AFTER)
+ end
+
+ def raw_request_body
+ @request_body ||= JSON.parse(request.raw_post)
+ end
+
+ def assert_request?
+ request.path == '/api/v1/truth/ios_device_check/assert'
+ end
+
+ def sms_reverification_required?
+ return false unless current_user&.user_sms_reverification_required&.user_id
+
+ return false if app_attest_path?
+
+ if android_client?
+ return false if request.headers['x-tru-assertion']
+ credential = doorkeeper_token.integrity_credentials.order(last_verified_at: :desc).first
+ unverified_credential = credential.present? ? false : true
+ elsif ios_client?
+ credential = doorkeeper_token.token_webauthn_credentials.order(last_verified_at: :desc).first
+ unverified_credential = credential.present? ? false : true
+ else
+ unverified_credential = true
+ end
+
+ blocked_methods = %w(POST PUT PATCH)
+ !!(unverified_credential && blocked_methods.include?(request.request_method))
+ end
+
+ def app_attest_path?
+ request.path.start_with?('/api/v1/truth/ios_device_check')
end
end
Only in truth-new/opensource/app/controllers/api: docs_controller.rb
Only in truth-new/opensource/app/controllers/api: mock
diff -ru truth-old/opensource/app/controllers/api/oembed_controller.rb truth-new/opensource/app/controllers/api/oembed_controller.rb
--- truth-old/opensource/app/controllers/api/oembed_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/oembed_controller.rb 2024-04-01 14:59:13
@@ -7,7 +7,7 @@
before_action :require_public_status!
def show
- render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
+ render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default, has_video: has_video
end
private
@@ -17,18 +17,28 @@
end
def require_public_status!
- not_found if @status.hidden?
+ not_found if !distributable?
end
+ def distributable?
+ @status.public_visibility? || @status.unlisted_visibility? || @status.group&.everyone?
+ end
+
def status_finder
StatusFinder.new(params[:url])
end
def maxwidth_or_default
- (params[:maxwidth].presence || 400).to_i
+ (params[:maxwidth].presence || 600).to_i
end
def maxheight_or_default
params[:maxheight].present? ? params[:maxheight].to_i : nil
+ end
+
+ def has_video
+ if @status.with_media?
+ @status.media_attachments.first.video?
+ end
end
end
diff -ru truth-old/opensource/app/controllers/api/pleroma/accounts_controller.rb truth-new/opensource/app/controllers/api/pleroma/accounts_controller.rb
--- truth-old/opensource/app/controllers/api/pleroma/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/pleroma/accounts_controller.rb 2024-04-01 14:59:13
@@ -1,5 +1,7 @@
# frozen_string_literal: true
class Api::Pleroma::AccountsController < Api::BaseController
+ before_action -> { doorkeeper_authorize! :read }, only: [:mfa, :setup_totp, :backup_codes]
+ before_action -> { doorkeeper_authorize! :write }, only: [:confirm_totp, :delete_totp]
before_action :require_user!
before_action :prepare_two_factor, only: [:setup_totp]
before_action :validate_password, only: [:confirm_totp, :delete_totp]
diff -ru truth-old/opensource/app/controllers/api/pleroma/user_settings_controller.rb truth-new/opensource/app/controllers/api/pleroma/user_settings_controller.rb
--- truth-old/opensource/app/controllers/api/pleroma/user_settings_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/pleroma/user_settings_controller.rb 2024-04-01 14:59:13
@@ -1,15 +1,34 @@
# frozen_string_literal: true
class Api::Pleroma::UserSettingsController < Api::BaseController
+ before_action -> { doorkeeper_authorize! :write }
before_action :require_user!
before_action :validate_password
before_action :validate_email, only: :change_email
before_action :set_new_email, only: :change_email
+ around_action :set_locale, only: :change_password
def change_password
if current_user.reset_password(resource_params[:new_password], resource_params[:new_password_confirmation])
+ OauthAccessToken.where.not(token: doorkeeper_token.token).where(resource_owner_id: current_user.id).update_all(revoked_at: Time.now.utc)
+
render json: { status: :success }
else
- render json: { error: 'Password and password confirmation do not match.' }, status: 400
+ errors = current_user.errors.to_hash
+ password_invalid = errors[:password]&.pop
+ default_error = I18n.t('users.password_mismatch', locale: :en)
+ message, message_with_locale, code =
+ if password_invalid.present?
+ error = errors[:base]&.pop || default_error
+ [error, password_invalid, 'PASSWORD_INVALID']
+ else
+ [default_error, I18n.t('users.password_mismatch'), 'PASSWORD_MISMATCH']
+ end
+
+ render json: {
+ error: message,
+ error_code: code,
+ error_message: message_with_locale,
+ }, status: 400
end
end
@@ -40,7 +59,13 @@
def destroy_account!
current_account.suspend!(origin: :local)
- AccountDeletionWorker.perform_async(current_user.account_id)
+ account_id = current_user.account_id
+ AccountDeletionWorker.perform_async(
+ account_id,
+ account_id,
+ deletion_type: 'self_deletion',
+ skip_activitypub: true,
+ )
sign_out
end
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/credentials_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/credentials_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/credentials_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/credentials_controller.rb 2024-04-12 16:26:23
@@ -2,17 +2,23 @@
class Api::V1::Accounts::CredentialsController < Api::BaseController
include JwtConcern
+ include Clientable
- before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, except: [:update]
+ TV_REQUIRED_IOS_VERSION = ENV.fetch('TV_REQUIRED_IOS_VERSION', 0).to_i
+ TV_REQUIRED_ANDROID_VERSION = ENV.fetch('TV_REQUIRED_ANDROID_VERSION', 0).to_i
+
+ before_action -> { doorkeeper_authorize! :read, :'read:accounts', :ads }, except: [:update]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update]
skip_before_action :require_functional!, only: [:show]
before_action :require_user!, only: [:update, :chat_token]
before_action :set_account, only: [:update, :chat_token, :show]
before_action :verify_credentials_require_user!, only: [:show]
+ before_action :enable_feature_flag, only: [:show]
+ before_action :create_tv_user, only: [:show]
def show
- render json: @account, serializer: REST::CredentialAccountSerializer
+ render json: @account, serializer: REST::CredentialAccountSerializer, access_token: doorkeeper_token, android_client: android_client?, tv_account_lookup: true
end
def chat_token
@@ -29,6 +35,7 @@
def update
UpdateAccountService.new.call(@account, account_params, raise_error: true)
UserSettingsDecorator.new(current_user).update(user_settings_params) if user_settings_params
+ current_user.update!(unauth_visibility_params) if unauth_visibility_params
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
render json: @account, serializer: REST::CredentialAccountSerializer
end
@@ -36,9 +43,36 @@
private
def account_params
- params.permit(:display_name, :location, :website, :note, :avatar, :header, :locked, :discoverable, pleroma_settings_store: {}, fields_attributes: [:name, :value])
+ # Pleroma compatibility
+ params[:accepting_messages] = params[:accepting_messages] || accepts_chats_messages || !!@account.accepting_messages
+ params[:feeds_onboarded] = truthy_param?(:feeds_onboarded) if params[:feeds_onboarded]
+ params[:tv_onboarded] = truthy_param?(:tv_onboarded) if params[:tv_onboarded]
+ params[:show_nonmember_group_statuses] = truthy_param?(:show_nonmember_group_statuses) if params[:show_nonmember_group_statuses]
+ params[:receive_only_follow_mentions] = truthy_param?(:receive_only_follow_mentions) if params[:receive_only_follow_mentions]
+
+ params.permit(:display_name,
+ :location,
+ :website,
+ :note,
+ :avatar,
+ :header,
+ :locked,
+ :discoverable,
+ :accepting_messages,
+ :chats_onboarded,
+ :feeds_onboarded,
+ :tv_onboarded,
+ :show_nonmember_group_statuses,
+ :receive_only_follow_mentions,
+ pleroma_settings_store: {},
+ fields_attributes: [:name, :value]
+ )
end
+ def accepts_chats_messages
+ params[:accepts_chat_messages].to_s.empty? ? false : params[:accepts_chat_messages].to_s
+ end
+
def set_account
@account = current_account
end
@@ -55,6 +89,10 @@
}
end
+ def unauth_visibility_params
+ params.permit(:unauth_visibility)
+ end
+
def verify_credentials_require_user!
if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422
@@ -67,5 +105,38 @@
else
update_user_sign_in
end
+ end
+
+ def create_tv_user
+ return if invalid_ios_version? && !current_account.tv_enabled?
+
+ session_id = TvDeviceSession.find_by(oauth_access_token_id: doorkeeper_token.id)&.tv_session_id
+ tv_profile_id = TvAccount.find_by(account_id: current_account.id)&.p_profile_id
+
+ return if session_id.present? && tv_profile_id.present?
+
+ if tv_profile_id.nil?
+ TvAccountsCreateWorker.perform_async(current_account.id, doorkeeper_token.id)
+ else
+ TvAccountsLoginWorker.perform_async(current_account.id, doorkeeper_token.id)
+ end
+ end
+
+ def enable_feature_flag
+ return unless required_android_version
+ ::Configuration::AccountEnabledFeature.upsert(
+ account_id: current_account.id,
+ feature_flag_id: 1,
+ )
+ end
+
+ def invalid_ios_version?
+ ios_version = request&.user_agent&.strip&.match(/^TruthSocial\/(\d+) .+/) || []
+ ios_version[1].nil? || TV_REQUIRED_IOS_VERSION.zero? || ios_version[1].to_i < TV_REQUIRED_IOS_VERSION
+ end
+
+ def required_android_version
+ android_version = request&.user_agent&.strip&.match(/^TruthSocialAndroid\/okhttp\/.+\/(\d+)/) || []
+ !android_version[1].nil? && !TV_REQUIRED_ANDROID_VERSION.zero? && android_version[1].to_i == TV_REQUIRED_ANDROID_VERSION
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/follower_accounts_controller.rb 2024-04-01 14:59:13
@@ -14,6 +14,7 @@
def set_account
@account = Account.find(params[:account_id])
+ @pagination_enabled = (@account == current_account)
end
def load_accounts
@@ -29,30 +30,38 @@
end
def default_accounts
- Account.includes(:active_relationships, :account_stat).references(:active_relationships)
+ Account.includes(:active_relationships, :account_follower, :account_following, :account_status).references(:active_relationships)
end
def paginated_follows
- Follow.where(target_account: @account).paginate_by_min_id(
- limit_param(DEFAULT_ACCOUNTS_LIMIT),
- params[:min_id],
- params[:max_id]
- )
+ if @pagination_enabled
+ Follow.where(target_account: @account).paginate_by_max_id(
+ limit_param(DEFAULT_ACCOUNTS_LIMIT),
+ params[:max_id],
+ params[:since_id]
+ )
+ else
+ Follow.where(target_account: @account).paginate_by_min_id(
+ limit_param(DEFAULT_ACCOUNTS_LIMIT),
+ nil,
+ nil
+ )
+ end
end
def insert_pagination_headers
- set_pagination_headers(next_path, prev_path)
+ set_pagination_headers(next_path, prev_path) if @pagination_enabled
end
def next_path
if records_continue?
- api_v1_account_followers_url pagination_params(min_id: pagination_max_id)
+ api_v1_account_followers_url pagination_params(max_id: pagination_max_id)
end
end
def prev_path
unless @accounts.empty?
- api_v1_account_followers_url pagination_params(max_id: pagination_min_id)
+ api_v1_account_followers_url pagination_params(since_id: pagination_since_id)
end
end
@@ -60,7 +69,7 @@
@accounts.last.active_relationships.first.id
end
- def pagination_min_id
+ def pagination_since_id
@accounts.first.active_relationships.first.id
end
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/following_accounts_controller.rb 2024-04-01 14:59:13
@@ -29,7 +29,7 @@
end
def default_accounts
- Account.includes(:passive_relationships, :account_stat).references(:passive_relationships)
+ Account.includes(:passive_relationships, :account_follower, :account_following, :account_status).references(:passive_relationships)
end
def paginated_follows
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/lookup_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/lookup_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/lookup_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/lookup_controller.rb 2024-04-01 14:59:13
@@ -6,7 +6,7 @@
before_action :require_authenticated_user!, unless: :allowed_public_access?
def show
- render json: @account, serializer: REST::AccountSerializer
+ render json: @account, serializer: REST::AccountSerializer, tv_account_lookup: true
end
private
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/relationships_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/relationships_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/relationships_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/relationships_controller.rb 2024-04-05 09:07:34
@@ -6,16 +6,19 @@
def index
accounts = Account.without_suspended.where(id: account_ids).select('id')
- # .where doesn't guarantee that our results are in the same order
- # we requested them, so return the "right" order to the requestor.
- @accounts = accounts.index_by(&:id).values_at(*account_ids).compact
- render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
+ @accounts = accounts.index_by(&:id).values_at(*account_ids).compact # order results
+ render json: Panko::ArraySerializer.new(
+ @accounts, each_serializer: REST::V2::RelationshipSerializer,
+ context: {
+ relationships: relationships,
+ }
+ ).to_json
end
private
def relationships
- AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
+ V2::AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
end
def account_ids
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/search_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/search_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/search_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/search_controller.rb 2024-04-01 14:59:13
@@ -3,10 +3,11 @@
class Api::V1::Accounts::SearchController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action :require_user!
+ after_action :insert_pagination_headers, unless: -> { @accounts.empty? }, only: :show
def show
@accounts = account_search
- render json: @accounts, each_serializer: REST::AccountSerializer
+ render json: @accounts, each_serializer: REST::AccountSerializer, tv_account_lookup: true
end
private
@@ -18,7 +19,26 @@
limit: limit_param(DEFAULT_ACCOUNTS_LIMIT),
resolve: truthy_param?(:resolve),
following: truthy_param?(:following),
+ followers: truthy_param?(:followers),
offset: params[:offset]
)
+ end
+
+ def insert_pagination_headers
+ set_pagination_headers(next_path)
+ end
+
+ def next_path
+ if records_continue?
+ api_v1_accounts_search_url pagination_params(offset: @accounts.size + params[:offset].to_i)
+ end
+ end
+
+ def records_continue?
+ @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ end
+
+ def pagination_params(core_params)
+ params.slice(:limit, :followers, :q).permit(:limit, :followers, :q).merge(core_params)
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/accounts/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/accounts/statuses_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts/statuses_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts/statuses_controller.rb 2024-04-01 14:59:13
@@ -8,7 +8,11 @@
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
def index
- @statuses = load_statuses
+ @statuses = load_statuses
+ if (ad_indexes = ENV.fetch('X_TRUTH_AD_INDEXES', nil))
+ response.headers['x-truth-ad-indexes'] = ad_indexes
+ end
+
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
@@ -49,7 +53,7 @@
def pinned_scope
return Status.none if @account.blocking?(current_account)
- @account.pinned_statuses
+ @account.pinned_statuses.merge!(StatusPin.profile_pins)
end
def no_replies_scope
diff -ru truth-old/opensource/app/controllers/api/v1/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/accounts_controller.rb 2024-04-01 14:59:13
@@ -21,7 +21,7 @@
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: 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, 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 } }
export_prometheus_metric(:follows)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
@@ -33,7 +33,7 @@
end
def mute
- MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration]&.to_i || 0))
+ MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: params.fetch(:duration, 0).to_i)
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end
@@ -80,7 +80,6 @@
end
def export_prometheus_metric(metric_to_track)
- Prometheus::ApplicationExporter::increment(metric_to_track)
+ Prometheus::ApplicationExporter.increment(metric_to_track)
end
-
end
diff -ru truth-old/opensource/app/controllers/api/v1/admin/account_actions_controller.rb truth-new/opensource/app/controllers/api/v1/admin/account_actions_controller.rb
--- truth-old/opensource/app/controllers/api/v1/admin/account_actions_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/admin/account_actions_controller.rb 2024-04-01 14:59:13
@@ -11,7 +11,7 @@
account_action.current_account = current_account
account_action.save!
- render_empty
+ render json: @account, serializer: REST::Admin::AccountSerializer
end
private
@@ -27,7 +27,8 @@
:warning_preset_id,
:text,
:send_email_notification,
- :duration
+ :duration,
+ :feature_name
)
end
end
Only in truth-new/opensource/app/controllers/api/v1/admin/accounts: statuses_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin/accounts: webauthn_credentials_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/admin/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/admin/accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/admin/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/admin/accounts_controller.rb 2024-04-05 09:07:34
@@ -6,14 +6,21 @@
LIMIT = 100
+ before_action :set_log_level
+
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
before_action :require_staff!
before_action :set_accounts, only: :index
before_action :set_account, except: [:index, :create, :bulk_approve]
+ before_action :set_policy, only: :create
before_action :require_local_account!, only: [:enable, :approve, :reject]
+ before_action :set_geo, only: [:create]
+ before_action :set_registrations, only: [:create], if: -> { params[:token].present? }
after_action :insert_pagination_headers, only: :index
+ after_action :registration_cleanup, only: :create, if: -> { @user.persisted? && @registration }
+ after_action :revert_log_level
FILTER_PARAMS = %i(
local
@@ -33,11 +40,25 @@
sms
).freeze
+ GEO_PARAMS = %i(
+ country_name
+ country_code
+ city_name
+ region_code
+ region_name
+ ).freeze
+
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
def index
authorize :account, :index?
- render json: @accounts, each_serializer: REST::Admin::AccountSerializer
+ render json: Panko::ArraySerializer.new(
+ @accounts,
+ each_serializer: REST::V2::Admin::AccountSerializer,
+ context: {
+ advertisers: Account.recent_advertisers(@accounts.pluck(:id)),
+ }
+ ).to_json
end
def show
@@ -47,7 +68,7 @@
def update
update_hash = update_params.to_h
- if (!@account.user.not_ready_for_approval? && !@account.user.ready_by_csv_import?)
+ if !@account.user.not_ready_for_approval? && !@account.user.ready_by_csv_import?
update_hash[:approved] = true
export_prometheus_metric(:approves)
end
@@ -61,12 +82,15 @@
end
def create
+ Rails.logger.info("Sign-up logs: attempting to register: #{params[:email]}")
+
account = Account.new(
username: params[:username],
- discoverable: params[:role] != 'moderator'
+ discoverable: params[:role] != 'moderator',
+ feeds_onboarded: true
)
- user = User.new(
+ @user = User.new(
email: params[:email],
password: params[:password],
sms: params[:sms],
@@ -75,19 +99,26 @@
approved: ['true', true].include?(params[:approved]),
moderator: params[:role] == 'moderator',
confirmed_at: params[:confirmed] ? Time.now.utc : nil,
- bypass_invite_request_check: true
+ bypass_invite_request_check: true,
+ policy: @policy,
+ sign_up_city_id: @city,
+ sign_up_country_id: @country,
+ sign_up_ip: params[:sign_up_ip]
)
- user.account = account
+ @user.account = account
account.verify! if ['true', true].include?(params[:verified])
- user.set_waitlist_position unless user.approved
+ @user.set_waitlist_position unless @user.approved
- if user.save
- send_registration_email(user)
+ if @user.save
+ send_registration_email
export_prometheus_metric(:registrations)
- render json: user.account, serializer: REST::Admin::AccountCreateSerializer
+ dispatch_rmq_event(account, params)
+ log_successful_attempt
+ render json: @user.account, serializer: REST::Admin::AccountCreateSerializer
else
- user_errors = user.errors.to_h
+ user_errors = @user.errors.to_h
+ log_failed_attempt(user_errors)
render json: { errors: user_errors }, status: 422
end
end
@@ -126,13 +157,20 @@
def reject
authorize @account.user, :reject?
- DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
+ DeleteAccountService.new.call(
+ @account,
+ @current_account.id,
+ deletion_type: 'api_admin_reject',
+ reserve_email: false,
+ reserve_username: false,
+ skip_activitypub: true,
+ )
render json: @account, serializer: REST::Admin::AccountSerializer
end
def destroy
authorize @account, :destroy?
- Admin::AccountDeletionWorker.perform_async(@account.id)
+ Admin::AccountDeletionWorker.perform_async(@account.id, @current_account.id)
render json: @account, serializer: REST::Admin::AccountSerializer
end
@@ -231,14 +269,87 @@
end
def export_prometheus_metric(metric_type)
- Prometheus::ApplicationExporter::increment(metric_type)
+ Prometheus::ApplicationExporter.increment(metric_type)
end
- def send_registration_email(user)
- if user.approved?
- NotificationMailer.user_approved(user.account).deliver_later
+ def send_registration_email
+ if @user.approved?
+ NotificationMailer.user_approved(@user.account).deliver_later
else
- UserMailer.waitlisted(user).deliver_later
+ UserMailer.waitlisted(@user).deliver_later
end
+ end
+
+ def set_policy
+ @policy = Policy.last
+ end
+
+ def geo_params
+ params.permit(*GEO_PARAMS)
+ end
+
+ def set_geo
+ geo = GeoService.new(
+ city_name: geo_params[:city_name],
+ country_code: geo_params[:country_code],
+ country_name: geo_params[:country_name],
+ region_name: geo_params[:region_name],
+ region_code: geo_params[:region_code]
+ )
+
+ @city = geo.city
+ @country = geo.country
+ end
+
+ def set_log_level
+ @current_log_level = Rails.logger.level
+ Rails.logger.level = :debug
+ end
+
+ def revert_log_level
+ Rails.logger.level = @current_log_level
+ end
+
+ def set_registrations
+ @registration = Registration.find_by(token: params[:token])
+ if @registration&.ios_device?
+ @registration_credential = @registration.registration_webauthn_credential
+ @credential = @registration_credential&.webauthn_credential
+ credential_error = 'Webauthn Credential is already associated with an account'
+ render json: { errors: credential_error }, status: 422 and return if @credential&.user.present?
+ end
+ end
+
+ def registration_cleanup
+ if @registration.ios_device?
+ @credential&.update!(user: @user) # Do we want to fail loudly?
+ else
+ user_params = { user_id: @user.id }
+ verification = DeviceVerification.find_by("details ->> 'registration_token' = '#{ActiveRecord::Base.sanitize_sql(@registration.token)}'")
+ details = verification.details
+ new_details = details.merge(user_params)
+ verification.update(details: new_details)
+ end
+
+ registration_otc = @registration.registration_one_time_challenge
+ otc = registration_otc.one_time_challenge
+ otc.destroy # This will also cascade delete the RegistrationOneTimeChallenge record.
+ @registration.destroy
+ end
+
+ def dispatch_rmq_event(account, params)
+ EventProvider::EventProvider.new('account.created', AccountCreatedEvent, account, params).call
+ end
+
+ def log_failed_attempt(errors)
+ filters = Rails.application.config.filter_parameters
+ f = ActiveSupport::ParameterFilter.new filters
+ filtered_params = f.filter params
+
+ Rails.logger.info("Sign-up logs: unsuccessful registration: #{errors}. params: #{filtered_params}")
+ end
+
+ def log_successful_attempt
+ Rails.logger.info("Sign-up logs: successful registration: #{params[:email]}")
end
end
Only in truth-new/opensource/app/controllers/api/v1/admin: bulk_account_actions_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: chat_messages_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: groups
Only in truth-new/opensource/app/controllers/api/v1/admin: groups_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: links_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: policies_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: registrations_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/admin/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/admin/statuses_controller.rb
--- truth-old/opensource/app/controllers/api/v1/admin/statuses_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/admin/statuses_controller.rb 2024-04-12 16:08:26
@@ -4,8 +4,11 @@
include Authorization
include AccountableConcern
+ before_action :set_log_level
before_action -> { doorkeeper_authorize! :'admin:write' }
+ before_action :require_staff!
before_action -> { set_status }, except: :index
+ after_action :revert_log_level
def index
@statuses = Status.with_discarded.where(id: params[:ids])
@@ -42,16 +45,27 @@
@status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: resource_params[:moderator_id])
@status.update!(deleted_at: Time.current, deleted_by_id: resource_params[:moderator_id])
RemovalWorker.perform_async(@status.id, redraft: true, notify_user: resource_params[:notify_user], immediate: false)
- @status.account.statuses_count = @status.account.statuses_count - 1
- @status.account.save
invalidate_cache
render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true
end
+ def privatize
+ @status.privatize(resource_params[:moderator_id], resource_params[:notify_user])
+ invalidate_cache
+ render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true
+ end
+
+ def publicize
+ @status.publicize
+ invalidate_cache
+ render json: @status, serializer: REST::Admin::StatusSerializer, source_requested: true
+ end
+
private
def set_status
@status = Status.with_discarded.find(params[:id] || params[:status_id])
+ @status.performed_by_admin = true
end
def resource_params
@@ -63,4 +77,15 @@
InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", @status.id)
end
+ def set_log_level
+ return unless request.request_method == 'POST'
+ Rails.logger.info("Admin::StatusesController logs: #{params.inspect}")
+ @current_log_level = Rails.logger.level
+ Rails.logger.level = :debug
+ end
+
+ def revert_log_level
+ return unless request.request_method == 'POST'
+ Rails.logger.level = @current_log_level
+ end
end
Only in truth-new/opensource/app/controllers/api/v1/admin: tags_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_groups_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_statuses
diff -ru truth-old/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb truth-new/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb
--- truth-old/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/admin/trending_statuses_controller.rb 2024-04-01 14:59:13
@@ -1,21 +1,23 @@
# frozen_string_literal: true
class Api::V1::Admin::TrendingStatusesController < Api::BaseController
+ TRENDING_STATUS_LIMIT = 10
+
before_action -> { doorkeeper_authorize! :'admin:write' }
before_action :require_staff!
before_action :set_statuses, only: :index
- before_action :set_status, only: [:update, :destroy]
+ before_action :set_status, only: [:include, :exclude]
after_action :set_pagination_headers, only: :index
def index
render json: @statuses, each_serializer: REST::Admin::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
- def update
- mark_trending
+ def include
+ render json: TrendingStatusExcludedStatus.destroy(@status.id)
end
- def destroy
- remove_from_trending
+ def exclude
+ render json: TrendingStatusExcludedStatus.create(status_id: @status.id)
end
private
@@ -24,52 +26,14 @@
@status = Status.find(params[:id])
end
- def mark_trending
- trending = Trending.where(status: @status).first_or_initialize
- trending.user = current_user if trending.new_record?
- trending.save!
- end
-
- def remove_from_trending
- trending = Trending.find_by(status_id: @status.id)
- trending.delete if trending.present?
- end
-
def set_statuses
- @statuses = if params[:trending]
- Trending.includes(:status).all.flat_map(&:status)
- else
- fetch_recent_statuses
- end
+ @statuses = Status.trending_statuses.page(params[:page]).per(TRENDING_STATUS_LIMIT)
end
- def fetch_recent_statuses
- Rails.cache.fetch("admin:trending_statuses:#{params[:page]}", expires_in: 10.minutes) do
- Status
- .where(created_at: 1.day.ago.beginning_of_day..Time.now)
- .joins(:status_stat)
- .includes(:preview_cards, :status_stat, account: :account_stat)
- .order("status_stats.favourites_count desc")
- .reorder('')
- .page(params[:page])
- .per(10)
- .to_a
- end
- end
-
def set_pagination_headers
- response.headers["x-page-size"] = 10
- response.headers["x-page"] = params[:page] || 1
- response.headers["x-total"] = if @statuses.is_a?(Array)
- @statuses.size
- else
- @statuses.total_count
- end
-
- response.headers["x-total-pages"] = if @statuses.is_a?(Array)
- @statuses.size / 10
- else
- @statuses.total_pages
- end
+ response.headers['x-page-size'] = TRENDING_STATUS_LIMIT
+ response.headers['x-page'] = params[:page] || 1
+ response.headers['x-total'] = @statuses.size
+ response.headers['x-total-pages'] = @statuses.total_pages
end
end
Only in truth-new/opensource/app/controllers/api/v1/admin: trending_tags_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/admin: truth
Only in truth-new/opensource/app/controllers/api/v1/admin: tv
diff -ru truth-old/opensource/app/controllers/api/v1/blocks_controller.rb truth-new/opensource/app/controllers/api/v1/blocks_controller.rb
--- truth-old/opensource/app/controllers/api/v1/blocks_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/blocks_controller.rb 2024-04-01 14:59:13
@@ -17,7 +17,7 @@
end
def paginated_blocks
- @paginated_blocks ||= Block.eager_load(target_account: :account_stat)
+ @paginated_blocks ||= Block.eager_load(target_account: [:account_follower, :account_following, :account_status])
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account)
diff -ru truth-old/opensource/app/controllers/api/v1/endorsements_controller.rb truth-new/opensource/app/controllers/api/v1/endorsements_controller.rb
--- truth-old/opensource/app/controllers/api/v1/endorsements_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/endorsements_controller.rb 2024-04-01 14:59:13
@@ -25,7 +25,7 @@
end
def endorsed_accounts
- current_account.endorsed_accounts.includes(:account_stat).without_suspended
+ current_account.endorsed_accounts.includes(:account_follower, :account_following, :account_status).without_suspended
end
def insert_pagination_headers
diff -ru truth-old/opensource/app/controllers/api/v1/favourites_controller.rb truth-new/opensource/app/controllers/api/v1/favourites_controller.rb
--- truth-old/opensource/app/controllers/api/v1/favourites_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/favourites_controller.rb 2023-05-05 13:42:02
@@ -21,7 +21,7 @@
end
def results
- @_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id(
+ @_results ||= account_favourites.includes(:status).joins(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
Only in truth-new/opensource/app/controllers/api/v1: feeds_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/follow_requests_controller.rb truth-new/opensource/app/controllers/api/v1/follow_requests_controller.rb
--- truth-old/opensource/app/controllers/api/v1/follow_requests_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/follow_requests_controller.rb 2024-04-01 14:59:13
@@ -37,7 +37,7 @@
end
def default_accounts
- Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests)
+ Account.without_suspended.includes(:follow_requests, :account_follower, :account_following, :account_status).references(:follow_requests)
end
def paginated_follow_requests
Only in truth-new/opensource/app/controllers/api/v1: groups
Only in truth-new/opensource/app/controllers/api/v1: groups_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/lists/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/lists/accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/lists/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/lists/accounts_controller.rb 2024-04-01 14:59:13
@@ -37,9 +37,9 @@
def load_accounts
if unlimited?
- @list.accounts.without_suspended.includes(:account_stat).all
+ @list.accounts.without_suspended.includes(:account_follower, :account_following, :account_status).all
else
- @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
+ @list.accounts.without_suspended.includes(:account_follower, :account_following, :account_status).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
end
end
Only in truth-old/opensource/app/controllers/api/v1: ma1sd
diff -ru truth-old/opensource/app/controllers/api/v1/media_controller.rb truth-new/opensource/app/controllers/api/v1/media_controller.rb
--- truth-old/opensource/app/controllers/api/v1/media_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/media_controller.rb 2024-04-01 14:59:13
@@ -28,7 +28,8 @@
private
def status_code_for_media_attachment
- @media_attachment.not_processed? ? 206 : 200
+ # @media_attachment.not_processed? ? 206 : 200
+ 200
end
def set_media_attachment
diff -ru truth-old/opensource/app/controllers/api/v1/notifications_controller.rb truth-new/opensource/app/controllers/api/v1/notifications_controller.rb
--- truth-old/opensource/app/controllers/api/v1/notifications_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/notifications_controller.rb 2024-04-01 14:59:13
@@ -10,12 +10,12 @@
def index
@notifications = load_notifications
- render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
+ render json: @notifications, each_serializer: REST::NotificationSerializer, current_user: current_user, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
end
def show
@notification = current_account.notifications.without_suspended.find(params[:id])
- render json: @notification, serializer: REST::NotificationSerializer
+ render json: @notification, serializer: REST::NotificationSerializer, current_user: current_user
end
def clear
@@ -31,17 +31,23 @@
private
def load_notifications
- notifications = browserable_account_notifications.includes(from_account: :account_stat).to_a_paginated_by_id(
+ notifications = browserable_account_notifications.includes(from_account: [:account_follower, :account_following, :account_status]).to_a_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
- Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
+ notification_with_statuses = Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
cache_collection(target_statuses, Status)
end
+
+ Notification.exclude_self_statuses(notification_with_statuses)
end
def browserable_account_notifications
- current_account.notifications.without_suspended.browserable(exclude_types, from_account)
+ current_account.notifications.without_suspended.browserable(
+ types: Array(browserable_params[:types]),
+ exclude_types: Array(browserable_params[:exclude_types]),
+ from_account_id: browserable_params[:account_id]
+ )
end
def target_statuses_from_notifications
@@ -53,7 +59,7 @@
end
def next_path
- unless @notifications.empty?
+ if records_continue?
api_v1_notifications_url pagination_params(max_id: pagination_max_id)
end
end
@@ -72,19 +78,15 @@
@notifications.first.id
end
- def exclude_types
- val = params.permit(exclude_types: [])[:exclude_types] || []
- val = [val] unless val.is_a?(Enumerable)
- val_with_groups = val.clone
- val.each { |n| val_with_groups << "#{n}_group"}
- val_with_groups
+ def browserable_params
+ params.permit(:account_id, types: [], exclude_types: [])
end
- def from_account
- params[:account_id]
+ def pagination_params(core_params)
+ params.slice(:limit, :account_id, :types, :exclude_types).permit(:limit, :account_id, types: [], exclude_types: []).merge(core_params)
end
- def pagination_params(core_params)
- params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params)
+ def records_continue?
+ @notifications.size == limit_param(DEFAULT_NOTIFICATIONS_LIMIT)
end
end
Only in truth-new/opensource/app/controllers/api/v1: pleroma
diff -ru truth-old/opensource/app/controllers/api/v1/polls/votes_controller.rb truth-new/opensource/app/controllers/api/v1/polls/votes_controller.rb
--- truth-old/opensource/app/controllers/api/v1/polls/votes_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/polls/votes_controller.rb 2024-04-01 14:59:13
@@ -9,16 +9,28 @@
def create
VoteService.new.call(current_account, @poll, vote_params[:choices])
- render json: @poll, serializer: REST::PollSerializer
+ update_optimistic_data
+ render json: REST::V2::PollSerializer.new(context: { optimistic_data: @optimistic_data, current_user: current_user }).serialize(@poll)
end
private
def set_poll
- @poll = Poll.attached.find(params[:poll_id])
+ @poll = Poll.find(params[:poll_id])
+ @optimistic_data = { votes_count: @poll.votes_count.to_i, voters_count: @poll.voters_count.to_i, own_votes: @poll.own_votes(current_account), options: @poll.loaded_options }
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
not_found
+ end
+
+ def update_optimistic_data
+ @optimistic_data[:voters_count] += 1
+ vote_params[:choices].each do |choice|
+ @optimistic_data[:votes_count] += 1
+ @optimistic_data[:own_votes].push(choice.to_i) unless @optimistic_data[:own_votes].include?(choice.to_i)
+ @optimistic_data[:options][choice.to_i][:votes_count] += 1 if @optimistic_data[:options][choice.to_i]
+ end
+ @optimistic_data[:voted] = true
end
def vote_params
diff -ru truth-old/opensource/app/controllers/api/v1/polls_controller.rb truth-new/opensource/app/controllers/api/v1/polls_controller.rb
--- truth-old/opensource/app/controllers/api/v1/polls_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/polls_controller.rb 2024-04-01 14:59:13
@@ -5,7 +5,6 @@
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, only: :show
before_action :set_poll
- before_action :refresh_poll
def show
render json: @poll, serializer: REST::PollSerializer, include_results: true
@@ -14,13 +13,9 @@
private
def set_poll
- @poll = Poll.attached.find(params[:id])
+ @poll = Poll.find(params[:id])
authorize @poll.status, :show?
rescue Mastodon::NotPermittedError
not_found
- end
-
- def refresh_poll
- ActivityPub::FetchRemotePollService.new.call(@poll, current_account) if user_signed_in? && @poll.possibly_stale?
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/push/subscriptions_controller.rb truth-new/opensource/app/controllers/api/v1/push/subscriptions_controller.rb
--- truth-old/opensource/app/controllers/api/v1/push/subscriptions_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/push/subscriptions_controller.rb 2024-04-01 14:59:13
@@ -1,29 +1,24 @@
# frozen_string_literal: true
class Api::V1::Push::SubscriptionsController < Api::BaseController
+
before_action -> { doorkeeper_authorize! :push }
before_action -> { require_user!(requires_approval: false) }
+ before_action :set_log_level
+
before_action :set_push_subscription
- before_action :check_push_subscription, only: [:show, :update]
+ before_action :check_push_subscription, only: [:show]
+ before_action :create_new_record, only: [:update]
skip_before_action :require_functional!, only: [:create]
+ after_action :revert_log_level
+ DEBUG_ACCOUNT_ID = ENV.fetch('DEBUG_ACCOUNT_ID', 0).to_i
+
def create
@push_subscription&.destroy!
remove_old_subscriptions_for_device!
-
- @push_subscription = Web::PushSubscription.create!(
- endpoint: subscription_params[:endpoint],
- device_token: subscription_params[:device_token],
- platform: subscription_params[:platform] || 0,
- environment: subscription_params[:environment] || 0,
- key_p256dh: subscription_params.dig(:keys, :p256dh),
- key_auth: subscription_params.dig(:keys, :auth),
- data: data_params,
- user_id: current_user.id,
- access_token_id: doorkeeper_token.id
- )
-
+ @push_subscription = create_push_subscription
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
@@ -32,7 +27,7 @@
end
def update
- @push_subscription.update!(data: data_params)
+ @push_subscription.update!(data: data_params, device_token: subscription_params[:device_token] || nil)
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
@@ -43,6 +38,20 @@
private
+ def create_push_subscription
+ Web::PushSubscription.create!(
+ endpoint: subscription_params[:endpoint],
+ device_token: subscription_params[:device_token],
+ platform: subscription_params[:platform] || 0,
+ environment: subscription_params[:environment] || 0,
+ key_p256dh: subscription_params.dig(:keys, :p256dh),
+ key_auth: subscription_params.dig(:keys, :auth),
+ data: data_params,
+ user_id: current_user.id,
+ access_token_id: doorkeeper_token.id
+ )
+ end
+
def set_push_subscription
@push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end
@@ -51,6 +60,12 @@
not_found if @push_subscription.nil?
end
+ def create_new_record
+ return if @push_subscription
+ remove_old_subscriptions_for_device!
+ @push_subscription = create_push_subscription
+ end
+
def subscription_params
params.require(:subscription).permit(:endpoint, :device_token, :platform, :environment, keys: [:auth, :p256dh])
end
@@ -58,10 +73,34 @@
def data_params
return {} if params[:data].blank?
- params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :user_approved, :verify_sms_prompt])
+ params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :user_approved, :verify_sms_prompt, :chat, :group_favourite, :group_reblog, :group_mention, :group_approval, :group_delete, :group_role, :group_request, :group_promoted, :group_demoted])
end
def remove_old_subscriptions_for_device!
- Web::PushSubscription.destroy_by(device_token: subscription_params[:device_token]) if subscription_params[:platform].to_i > 0 && subscription_params[:device_token].present?
+ if subscription_params[:platform].to_i > 0 && subscription_params[:device_token].present?
+ duplicate_subs = Web::PushSubscription
+ .where("access_token_id != ?", doorkeeper_token.id)
+ .where(
+ device_token: subscription_params[:device_token],
+ platform: subscription_params[:platform],
+ user_id: current_user.id
+ )
+
+ duplicate_subs.destroy_all
+ end
end
+
+
+ def set_log_level
+ return unless current_account.id == DEBUG_ACCOUNT_ID
+ Rails.logger.info("Subscription logs: #{params.inspect}")
+ @current_log_level = Rails.logger.level
+ Rails.logger.level = :debug
+ end
+
+ def revert_log_level
+ return unless current_account.id == DEBUG_ACCOUNT_ID
+ Rails.logger.level = @current_log_level || :info
+ end
+
end
Only in truth-new/opensource/app/controllers/api/v1: push_notifications
Only in truth-new/opensource/app/controllers/api/v1: recommendations
diff -ru truth-old/opensource/app/controllers/api/v1/reports_controller.rb truth-new/opensource/app/controllers/api/v1/reports_controller.rb
--- truth-old/opensource/app/controllers/api/v1/reports_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/reports_controller.rb 2024-04-12 16:08:26
@@ -1,8 +1,13 @@
# frozen_string_literal: true
class Api::V1::ReportsController < Api::BaseController
+ include Authorization
+
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
before_action :require_user!
+ before_action :set_group, only: [:create], if: -> { report_params[:group_id] }
+ before_action :set_external_ad, only: [:create], if: -> { report_params[:external_ad_media_url] }
+ before_action :check_for_existing_report, only: [:create]
override_rate_limit_headers :create, family: :reports
@@ -13,7 +18,10 @@
status_ids: reported_status_ids,
comment: report_params[:comment],
forward: report_params[:forward],
- rule_ids: reported_rule_ids
+ rule_ids: reported_rule_ids,
+ message_ids: reported_message_ids,
+ group_id: group_id,
+ external_ad_id: external_ad_id
)
render json: @report, serializer: REST::ReportSerializer
@@ -22,13 +30,34 @@
private
def reported_status_ids
- reported_account.statuses.with_discarded.find(status_ids).pluck(:id)
+ return unless @group.nil? && @external_ad.nil?
+ find_statuses.pluck(:id)
end
+ def group_id
+ return @group.id if @group
+
+ group = Group.find_by(id: find_statuses.pick(:group_id))
+ authorize group, :show? if group
+ group&.id
+ end
+
+ def external_ad_id
+ return @external_ad.id if @external_ad
+ end
+
+ def find_statuses
+ @statuses ||= reported_account.statuses.with_discarded.find(status_ids)
+ end
+
def reported_rule_ids
Rule.find(rule_ids).pluck(:id)
end
+ def reported_message_ids
+ ChatMessage.visible_messages(report_params[:account_id].to_i, "{#{message_ids.map(&:to_i).join(',')}}")
+ end
+
def status_ids
Array(report_params[:status_ids])
end
@@ -37,11 +66,61 @@
Array(report_params[:rule_ids])
end
+ def message_ids
+ Array(report_params[:message_ids])
+ end
+
def reported_account
- Account.find(report_params[:account_id])
+ if report_params[:group_id]
+ GroupMembership.find_by!(group_id: report_params[:group_id], role: 'owner').account
+ elsif @external_ad
+ Account.find(ENV.fetch('TS_ADVERTISTING_ACCOUNT_ID', nil))
+ else
+ Account.find(report_params[:account_id])
+ end
end
def report_params
- params.permit(:account_id, :comment, :forward, status_ids: [], rule_ids: [])
+ params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, status_ids: [], rule_ids: [], message_ids: [])
end
+
+ def set_group
+ @group = Group.find(report_params[:group_id])
+ end
+
+ def set_external_ad
+ @external_ad = ExternalAd.find_or_create_by(media_url: report_params[:external_ad_media_url], description: report_params[:external_ad_description]) do |ad|
+ ad.ad_url = report_params[:external_ad_url]
+ end
+ end
+
+ def check_for_existing_report
+ existing_reports =
+ if @group
+ current_account.reports.where(target_account: reported_account, group_id: group_id, status_ids: [])
+ elsif @external_ad
+ current_account.reports.where(target_account: reported_account, external_ad_id: external_ad_id, status_ids: [])
+ else
+ current_account.reports.where(target_account: reported_account, status_ids: reported_status_ids, message_ids: reported_message_ids)
+ end
+
+ entity =
+ if status_ids.any?
+ 'Truth'
+ elsif message_ids.any?
+ 'message'
+ elsif group_id
+ 'group'
+ elsif external_ad_id
+ 'ad'
+ else
+ 'user'
+ end
+
+ e = "Thanks, but you have already reported this #{entity}."
+
+ render json: { error: e }, status: 422 if existing_reports.any?
+ end
end
+
+
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb 2024-04-01 14:59:13
@@ -17,20 +17,23 @@
def load_accounts
scope = default_accounts
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
- scope.merge(paginated_favourites).to_a
+ scope = scope.merge(paginated_favourites).to_a
+ @size = scope.size
+
+ @size > limit_param(DEFAULT_ACCOUNTS_LIMIT) ? scope.drop(1) : scope
end
def default_accounts
Account
.without_suspended
- .includes(:favourites, :account_stat)
+ .includes(:favourites, :account_follower, :account_following, :account_status)
.references(:favourites)
.where(favourites: { status_id: @status.id })
end
def paginated_favourites
Favourite.paginate_by_max_id(
- limit_param(DEFAULT_ACCOUNTS_LIMIT),
+ limit_param(DEFAULT_ACCOUNTS_LIMIT) + 1,
params[:max_id],
params[:since_id]
)
@@ -61,7 +64,7 @@
end
def records_continue?
- @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ @size > limit_param(DEFAULT_ACCOUNTS_LIMIT)
end
def set_status
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/favourites_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/favourites_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses/favourites_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses/favourites_controller.rb 2024-04-01 14:59:13
@@ -2,16 +2,22 @@
class Api::V1::Statuses::FavouritesController < Api::BaseController
include Authorization
+ include Divergable
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
+ before_action :diverge_users_without_current_ip, only: [:create]
before_action :set_status, only: [:create]
+ after_action :create_device_verification_favourite, only: :create
+ include Assertable
+
def create
cached_status = cache_collection([@status], Status).first
- FavouriteService.new.call(current_account, @status)
- cached_status.status_stat.favourites_count = cached_status.favourites_count + 1
- render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked']
+ @favourite = FavouriteService.new.call(current_account, @status, user_agent: request.user_agent)
+ cached_status.status_favourite || cached_status.build_status_favourite
+ cached_status.status_favourite.favourites_count = cached_status.favourites_count + 1
+ render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked'], relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => true })
end
def destroy
@@ -19,13 +25,18 @@
if fav
@status = fav.status
+ cached_status = cache_collection([@status], Status).first
+ cached_status.status_favourite || cached_status.build_status_favourite
+ cached_status.status_favourite.favourites_count = cached_status.favourites_count - 1
+
UnfavouriteWorker.perform_async(current_account.id, @status.id)
else
@status = Status.find(params[:status_id])
+ cached_status = @status
authorize @status, :show?
end
- render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
+ render json: cached_status, serializer: REST::StatusSerializer, replica_reads: ['reblogged', 'muted', 'bookmarked'], relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
rescue Mastodon::NotPermittedError
not_found
end
@@ -37,5 +48,25 @@
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
+ end
+
+ def validate_client
+ action_assertable?
+ end
+
+ def asserting?
+ request.headers['x-tru-assertion'] && action_assertable?
+ end
+
+ def action_assertable?
+ %w(create).include?(action_name) ? true : false
+ end
+
+ def log_android_activity?
+ current_user.user_sms_reverification_required && action_assertable?
+ end
+
+ def create_device_verification_favourite
+ DeviceVerificationFavourite.insert(verification_id: @device_verification.id, favourite_id: @favourite.id) if @device_verification && @favourite
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/mutes_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/mutes_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses/mutes_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses/mutes_controller.rb 2024-04-01 14:59:13
@@ -3,11 +3,28 @@
class Api::V1::Statuses::MutesController < Api::BaseController
include Authorization
- before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
+ before_action -> { doorkeeper_authorize! :write, :'write:mutes' }, only: [:create, :destroy]
+ before_action -> { doorkeeper_authorize! :write, :'read:mutes' }, only: :index
before_action :require_user!
- before_action :set_status
- before_action :set_conversation
+ before_action :set_status, only: [:create, :destroy]
+ before_action :set_conversation, only: [:create, :destroy]
+ after_action :insert_pagination_headers, only: :index
+ MUTED_CONVERSATIONS_LIMIT = 20
+
+ def index
+ @statuses = load_muted_conversations
+
+ render json: Panko::ArraySerializer.new(
+ @statuses,
+ each_serializer: REST::V2::StatusSerializer,
+ context: {
+ current_user: current_user,
+ relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
+ }
+ ).to_json
+ end
+
def create
current_account.mute_conversation!(@conversation)
@mutes_map = { @conversation.id => true }
@@ -34,5 +51,36 @@
def set_conversation
@conversation = @status.conversation
raise Mastodon::ValidationError if @conversation.nil?
+ end
+
+ def load_muted_conversations
+ scope = paginated_conversations
+ @size = scope.size
+ @size > limit_param(MUTED_CONVERSATIONS_LIMIT) ? scope.take(limit_param(MUTED_CONVERSATIONS_LIMIT)) : scope
+ end
+
+ def paginated_conversations
+ Status.muted_conversations_for_account(current_account.id).paginate_by_limit_offset(
+ limit_param(MUTED_CONVERSATIONS_LIMIT) + 1,
+ params_slice(:offset)
+ )
+ end
+
+ def insert_pagination_headers
+ set_pagination_headers(next_path)
+ end
+
+ def next_path
+ return unless records_continue?
+
+ api_v1_mutes_url pagination_params(offset: @statuses.size + params[:offset].to_i)
+ end
+
+ def records_continue?
+ @size > limit_param(MUTED_CONVERSATIONS_LIMIT)
+ end
+
+ def pagination_params(core_params)
+ params.slice(:limit).permit(:limit).merge(core_params)
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb 2024-04-01 14:59:13
@@ -21,11 +21,11 @@
end
def default_accounts
- Account.without_suspended.includes(:statuses, :account_stat).references(:statuses)
+ Account.without_suspended.includes(:statuses, :account_follower, :account_following, :account_status).references(:statuses)
end
def paginated_statuses
- Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id(
+ Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :group]).paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id],
params[:since_id]
diff -ru truth-old/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb truth-new/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses/reblogs_controller.rb 2024-04-01 14:59:13
@@ -2,16 +2,23 @@
class Api::V1::Statuses::ReblogsController < Api::BaseController
include Authorization
+ include Divergable
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user!
+ before_action :diverge_users_without_current_ip, only: [:create]
before_action :set_reblog, only: [:create]
+ after_action :create_device_verification_status, only: :create
+ include Assertable
+
override_rate_limit_headers :create, family: :statuses
def create
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
+ @status.reblog.status_reblog || @status.reblog.build_status_reblog
+ @status.reblog.status_reblog.reblogs_count = @status.reblog.reblogs_count + 1
render json: @status, serializer: REST::StatusSerializer
end
@@ -21,8 +28,9 @@
if @status
authorize @status, :unreblog?
@status.discard
- RemovalWorker.perform_async(@status.id, immediate: true)
+ ReblogRemovalWorker.perform_async(@status.id, immediate: true)
@reblog = @status.reblog
+ InteractionsTracker.new(current_account.id, @reblog.account_id, :reblog, current_account.following?(@reblog.account_id), @reblog.group).untrack
else
@reblog = Status.find(params[:status_id])
authorize @reblog, :show?
@@ -43,6 +51,26 @@
end
def reblog_params
- params.permit(:visibility)
+ params.permit(:visibility).merge(user_agent: request.user_agent)
+ end
+
+ def validate_client
+ action_assertable?
+ end
+
+ def asserting?
+ request.headers['x-tru-assertion'] && action_assertable?
+ end
+
+ def action_assertable?
+ %w(create).include?(action_name) ? true : false
+ end
+
+ def log_android_activity?
+ current_user.user_sms_reverification_required && action_assertable?
+ end
+
+ def create_device_verification_status
+ DeviceVerificationStatus.insert(verification_id: @device_verification.id, status_id: @status.id) if @device_verification && @status
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/statuses_controller.rb truth-new/opensource/app/controllers/api/v1/statuses_controller.rb
--- truth-old/opensource/app/controllers/api/v1/statuses_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/statuses_controller.rb 2024-04-12 09:09:08
@@ -2,6 +2,9 @@
class Api::V1::StatusesController < Api::BaseController
include Authorization
+ include Divergable
+ include Redisable
+ include AdsConcern
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :destroy]
@@ -9,8 +12,14 @@
before_action :set_status, only: [:show, :context, :ancestors, :descendants]
before_action :set_thread, only: [:create]
before_action :require_authenticated_user!, unless: :allowed_public_access?
+ before_action :diverge_users_without_current_ip, only: [:create]
+ before_action :set_group, only: [:create]
+ before_action :reject_duplicate_group_status, only: [:create]
after_action :insert_pagination_headers, only: :descendants
+ after_action :create_device_verification_status, only: :create
+ include Assertable
+
override_rate_limit_headers :create, family: :statuses
# This API was originally unlimited, pagination cannot be introduced without
@@ -19,10 +28,17 @@
# than this anyway
CONTEXT_LIMIT = 4_096
PAGINATED_LIMIT = 20
+ STATUS_HASH_CACHE_EXPIRE_AFTER = 1.hour.seconds
+ DUPLICATE_THRESHOLD = 3
def show
@status = cache_collection([@status], Status).first
- render json: @status, serializer: REST::StatusSerializer
+
+ if (@status.visibility == 'self' && current_user.account_id != @status.account.id) || @status.group&.discarded?
+ raise(ActiveRecord::RecordNotFound)
+ end
+
+ render json: REST::V2::StatusSerializer.new(context: { current_user: current_user }).serialize(@status)
end
def context
@@ -34,6 +50,8 @@
def descendants
@descendants = prepare_descendants(PAGINATED_LIMIT)
+ include_ad_indexes(@descendants)
+
render_context_subitems(@descendants)
end
@@ -46,6 +64,9 @@
end
def create
+ whitelisted_visibilities = ['public', 'group', nil]
+ render json: { error: 'This action is not allowed' }, status: 403 and return unless whitelisted_visibilities.include?(status_params[:visibility])
+
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
mentions: status_params[:to],
@@ -54,25 +75,30 @@
sensitive: status_params[:sensitive],
spoiler_text: status_params[:spoiler_text],
visibility: status_params[:visibility],
+ group: @group,
+ group_timeline_visible: status_params[:group_timeline_visible],
+ group_visibility: @group_visibility || nil,
scheduled_at: status_params[:scheduled_at],
application: doorkeeper_token.application,
poll: status_params[:poll],
quote_id: status_params[:quote_id],
idempotency: request.headers['Idempotency-Key'],
- with_rate_limit: true)
+ with_rate_limit: true,
+ ip_address: request.remote_ip,
+ domain: Addressable::URI.parse(request.url).normalized_host)
- render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
+ render json: REST::V2::StatusSerializer.new(context: { current_user: current_user }).serialize(@status)
end
def destroy
@status = Status.where(account_id: current_user.account).find(params[:id])
authorize @status, :destroy?
-
@status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: current_user&.account_id)
@status.update!(deleted_at: Time.current, deleted_by_id: current_user&.account_id)
- RemovalWorker.perform_async(@status.id, redraft: true)
+ @thread = Status.find_by(id: @status.in_reply_to_id) if @status.in_reply_to_id
+ RemovalWorker.perform_async(@status.id, redraft: true, called_by_id: current_account.id)
remove_from_whale_list if @status.account.whale?
- @status.account.statuses_count = @status.account.statuses_count - 1
+ @status.status_pins&.destroy_all
render json: @status, serializer: REST::StatusSerializer, source_requested: true
end
@@ -83,7 +109,7 @@
@status = Status.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
- not_found
+ raise ActiveRecord::RecordNotFound
end
def set_thread
@@ -92,6 +118,27 @@
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
end
+ def set_group
+ quoted = status_params[:quote_id].presence && Status.find(status_params[:quote_id])
+ group_id = status_params[:group_id].presence || quoted&.group&.id || @thread&.group&.id
+ group = Group.find_by(id: group_id) if group_id
+ @group = if group&.discarded?
+ false
+ elsif quoted
+ quoted.group&.everyone? ? group_member?(group) && group : group # We don't want to set group if quoted group is a public group and the "quoter" is not a member.
+ else
+ group
+ end
+
+ if @group.present?
+ policy = status_params[:quote_id].present? ? :show? : :post?
+ authorize(@group, policy)
+ @group_visibility = @group.statuses_visibility
+ end
+ rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
+ render json: { error: I18n.t('statuses.errors.not_permitted_to_post') }, status: 404
+ end
+
def status_params
params.permit(
:status,
@@ -99,13 +146,14 @@
:sensitive,
:spoiler_text,
:visibility,
+ :group_id,
+ :group_timeline_visible,
:scheduled_at,
:quote_id,
to: [],
media_ids: [],
poll: [
:multiple,
- :hide_totals,
:expires_in,
options: [],
]
@@ -143,5 +191,48 @@
def allowed_public_access?
current_user || (action_name == 'show' && @status&.account&.user&.unauth_visibility? && !@status&.reply?)
+ end
+
+ def validate_client
+ action_assertable?
+ end
+
+ def asserting?
+ request.headers['x-tru-assertion'] && action_assertable?
+ end
+
+ def action_assertable?
+ %w(create).include?(action_name) ? true : false
+ end
+
+ def log_android_activity?
+ current_user&.user_sms_reverification_required && action_assertable?
+ end
+
+ def create_device_verification_status
+ DeviceVerificationStatus.insert(verification_id: @device_verification.id, status_id: @status.id) if @device_verification && @status
+ end
+
+ def group_member?(group)
+ group&.members&.where(id: current_account&.id)&.exists?
+ end
+
+ def reject_duplicate_group_status
+ return if @group.blank?
+ return if status_params[:status].blank?
+
+ status_hash = hexdigest status_params[:status]
+ key = "status:#{current_account.id}:#{status_hash}"
+ # cached_value = redis.get(key).to_i
+ # configuration = ::Configuration::FeatureSetting.find_by(name: 'rate_limit_duplicate_group_status_enabled')
+ # render json: { error: I18n.t('errors.429') }, status: 429 and return if cached_value.to_i >= DUPLICATE_THRESHOLD && ActiveModel::Type::Boolean.new.cast(configuration&.value)
+
+ redis.incrby(key, 1)
+ redis.expire(key, STATUS_HASH_CACHE_EXPIRE_AFTER)
+
+ cached_value = redis.get(key).to_i
+ if cached_value.to_i >= DUPLICATE_THRESHOLD
+ Rails.logger.info "Groups rate limit: User -> #{current_user.id} has exceeded the threshold. Current hits -> #{cached_value.to_i}, remote_ip -> #{request.remote_ip}"
+ end
end
end
Only in truth-new/opensource/app/controllers/api/v1: tags
Only in truth-new/opensource/app/controllers/api/v1: tags_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/timelines: group_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/timelines: group_tag_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/timelines/home_controller.rb truth-new/opensource/app/controllers/api/v1/timelines/home_controller.rb
--- truth-old/opensource/app/controllers/api/v1/timelines/home_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/timelines/home_controller.rb 2024-04-01 14:59:13
@@ -6,14 +6,23 @@
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
- @statuses = load_statuses
+ @statuses = load_statuses
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
- render json: @statuses,
- each_serializer: REST::StatusSerializer,
- relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
- account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id),
- status: account_home_feed.regenerating? ? 206 : 200
+ if (ad_indexes = ENV.fetch('X_TRUTH_AD_INDEXES', nil))
+ response.headers['x-truth-ad-indexes'] = ad_indexes
+ end
+
+ render json: Panko::ArraySerializer.new(
+ @statuses,
+ each_serializer: REST::V2::StatusSerializer,
+ context: {
+ current_user: current_user,
+ relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
+ account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id),
+ status: account_home_feed.regenerating? ? 206 : 200,
+ }
+ ).to_json
end
private
diff -ru truth-old/opensource/app/controllers/api/v1/trends_controller.rb truth-new/opensource/app/controllers/api/v1/trends_controller.rb
--- truth-old/opensource/app/controllers/api/v1/trends_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/trends_controller.rb 2024-04-01 14:59:13
@@ -2,14 +2,47 @@
class Api::V1::TrendsController < Api::BaseController
before_action :set_tags
+ after_action :insert_pagination_headers, only: :index
+ DEFAULT_LIMIT = 20
+
def index
- render json: @tags, each_serializer: REST::TagSerializer
+ render json: @tags || []
end
private
def set_tags
- @tags = TrendingTags.get(limit_param(100))
+ @tags = TrendingTagsResult.load_results(
+ limit, # in_limit
+ offset # in_offset
+ )
+ end
+
+ def limit
+ params[:limit].present? ? params[:limit].to_i : DEFAULT_LIMIT
+ end
+
+ def offset
+ params[:offset].present? ? params[:offset].to_i : 0
+ end
+
+ def insert_pagination_headers
+ @tags = JSON.parse(@tags || '[]')
+ set_pagination_headers(next_path)
+ end
+
+ def next_path
+ if records_continue?
+ api_v1_trends_url pagination_params(offset: @tags.size + params[:offset].to_i)
+ end
+ end
+
+ def records_continue?
+ @tags.size == limit_param(DEFAULT_LIMIT)
+ end
+
+ def pagination_params(core_params)
+ params.slice(:limit, :page).permit(:limit, :page).merge(core_params)
end
end
diff -ru truth-old/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb truth-new/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb
--- truth-old/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/truth/admin/accounts_controller.rb 2024-04-12 16:08:26
@@ -1,15 +1,23 @@
# frozen_string_literal: true
class Api::V1::Truth::Admin::AccountsController < Api::BaseController
+ include EmailHelper
+
before_action :require_staff!
- before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:count, :update]
- before_action :set_account, only: [:update]
+ before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :blacklist, :count, :email_domain_blocks]
+ before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, only: [:update, :confirm_totp]
+ before_action :set_account, only: [:update, :confirm_totp]
+ before_action :set_email_domain_block, only: :email_domain_blocks
def index
- accounts = Account.includes(:account_stat, :user).ransack(params[:query])
- accounts.sorts = params[:sorts] || "id desc"
- accounts = accounts.result.page(params[:page])
- render json: accounts, each_serializer: REST::Admin::AccountSerializer
+ @accounts = account_search
+ render json: Panko::ArraySerializer.new(
+ @accounts,
+ each_serializer: REST::V2::Admin::AccountSerializer,
+ context: {
+ advertisers: Account.recent_advertisers(@accounts.pluck(:id)),
+ }
+ ).to_json
end
def update
@@ -22,32 +30,84 @@
end
end
+ def blacklist
+ render json: { blacklist: suspended_accounts_exist? }, status: 200
+ end
+
def count
render json: { count: number_of_accounts }, status: 200
end
+ def email_domain_blocks
+ render json: { disposable: !!@email_domain_block.disposable }, status: 200
+ end
+
+ def confirm_totp
+ unless @account.user.validate_and_consume_otp!(totp_params[:code])
+ render json: {
+ error_code: 'OTP_CODE_INVALID',
+ error_message: I18n.t('otp_authentication.invalid_code'),
+ }, status: 422
+ end
+ end
+
private
+ def account_search
+ oauth_token = params[:oauth_token]
+ if oauth_token.present?
+ find_by_token(oauth_token)
+ else
+ AdminAccountSearchService.new.call(
+ params[:query],
+ current_account,
+ limit: limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ )
+ end
+ end
+
+ def suspended_accounts_exist?
+ if Account.joins(:user).where.not(suspended_at: nil).where(user: { sms: blacklist_params[:sms] }).exists?
+ 1
+ else
+ 0
+ end
+ end
+
def number_of_accounts
- if count_params[:email].present?
- User.find_by(email: count_params[:email]).present? ? 1 : 0
+ if (email = count_params[:email])
+ count_by_email(email)
elsif count_params[:sms].present?
- User.where(sms: count_params[:sms]).size
+ # For admin users, allow unlimited accounts
+ if User.where(admin: true).where(sms: count_params[:sms]).exists?
+ 0
+ else
+ User.where(sms: count_params[:sms]).size
+ end
else
0
end
end
+ def count_by_email(email)
+ User.find_by(email: email).present? || CanonicalEmailBlock.block?(email) || UserBaseEmail.find_by(email: email_to_canonical_email(email)).present? ? 1 : 0
+ end
+
+ def blacklist_params
+ params.permit(:sms)
+ end
+
def count_params
params.permit(:email, :sms)
end
def account_params
- params.require(:account).permit(:username, :display_name, :note)
+ params.require(:account).permit(:username, :display_name, :note, :website)
end
def set_account
- @account = Account.find(params[:id])
+ account_id = params[:account_id] || params[:id]
+ @account = Account.find(account_id)
end
def set_new_email
@@ -61,12 +121,15 @@
render json: { status: :success }
end
- # TODO: Vlad to refactor share access-token removal
+ def set_email_domain_block
+ @email_domain_block = EmailDomainBlock.find_by!(domain: params.require(:domain))
+ end
+
def update_users_password
@account.user.skip_password_change_notification!
if @account.user.reset_password(params[:password], params[:password])
- Doorkeeper::AccessToken.where(resource_owner_id: @account.user.id).delete_all
+ OauthAccessToken.where(resource_owner_id: @account.user.id).delete_all
@account.user.session_activations.destroy_all
@account.user.forget_me!
render json: { status: :success }, status: 200
@@ -79,5 +142,14 @@
else
render json: @account.errors, status: :unprocessable_entity
end
+ end
+
+ def find_by_token(oauth_token)
+ doorkeeper_token = OauthAccessToken.find_by!(token: oauth_token, revoked_at: nil)
+ [User.find(doorkeeper_token&.resource_owner_id)&.account]
+ end
+
+ def totp_params
+ params.permit(:code)
end
-end
\ No newline at end of file
+end
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: channel_notifications_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: email_domain_blocks_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: marketing_notifications_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth/admin: media_attachments_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth: ads_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth: android_device_check
Only in truth-new/opensource/app/controllers/api/v1/truth: carousels
Only in truth-new/opensource/app/controllers/api/v1/truth: ios_device_check
Only in truth-new/opensource/app/controllers/api/v1/truth: oauth_tokens_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/truth/passwords_controller.rb truth-new/opensource/app/controllers/api/v1/truth/passwords_controller.rb
--- truth-old/opensource/app/controllers/api/v1/truth/passwords_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/truth/passwords_controller.rb 2024-04-01 14:59:13
@@ -5,13 +5,29 @@
before_action :set_confirm_user, only: :reset_confirm
before_action :set_request_user, only: :reset_request
before_action :validate_user_is_present, only: :reset_confirm
+ around_action :set_locale, only: :reset_confirm
def reset_confirm
if @user.reset_password(password_reset_confirm_params[:password], password_reset_confirm_params[:password])
update_users_password
render json: { status: :success }
else
- render json: { error: 'Password and password confirmation do not match.' }, status: 400
+ errors = @user.errors.to_hash
+ password_invalid = errors[:password]&.pop
+ default_error = I18n.t('users.password_mismatch', locale: :en)
+ message, message_with_locale, code =
+ if password_invalid.present?
+ error = errors[:base]&.pop || default_error
+ [error, password_invalid, 'PASSWORD_INVALID']
+ else
+ [default_error, I18n.t('users.password_mismatch'), 'PASSWORD_MISMATCH']
+ end
+
+ render json: {
+ error: message,
+ error_code: code,
+ error_message: message_with_locale,
+ }, status: 400
end
end
@@ -22,7 +38,7 @@
private
def validate_user_is_present
- forbidden unless @user.present?
+ forbidden if @user.blank?
end
def update_users_password
Only in truth-new/opensource/app/controllers/api/v1/truth: policies_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth: suggestions
Only in truth-new/opensource/app/controllers/api/v1/truth/trending: group_tags_controller.rb
Only in truth-new/opensource/app/controllers/api/v1/truth/trending: groups_controller.rb
diff -ru truth-old/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb truth-new/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb
--- truth-old/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/v1/truth/trending/truths_controller.rb 2024-04-01 14:59:13
@@ -1,33 +1,75 @@
# frozen_string_literal: true
class Api::V1::Truth::Trending::TruthsController < Api::BaseController
- before_action :set_trendings
- before_action :set_truths
+ before_action -> { doorkeeper_authorize! :read }
+ before_action :require_user!
+ after_action :insert_pagination_headers
- TRENDING_TAGS_LIMIT = 10
+ TRENDING_TRUTHS_LIMIT = 10
+ TRENDING_TRUTHS_DEFAULT_OFFSET = 10
def index
- render json: @truths, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@truths, current_user&.account_id)
+ render json: Panko::ArraySerializer.new(
+ truths,
+ each_serializer: REST::V2::StatusSerializer,
+ context: {
+ current_user: current_user,
+ relationships: StatusRelationshipsPresenter.new(@truths, current_user&.account_id),
+ }
+ ).to_json
end
private
- def set_trendings
- @trendings = Trending.limit(TRENDING_TAGS_LIMIT).all
+ def trending_truths
+ @trending_truths ||= Status.trending_statuses
+ .excluding_unauthorized_tv_statuses(current_account.id)
+ .paginate_by_limit_offset(
+ limit_param(TRENDING_TRUTHS_LIMIT),
+ params_slice(:offset)
+ )
end
- def set_truths
- @truths = load_statuses
+ def truths
+ @truths ||= load_cached_tagged_statuses
end
- def load_statuses
- cached_tagged_statuses
+ def load_cached_tagged_statuses
+ cache_collection(trending_truths, Status)
end
- def cached_tagged_statuses
- cache_collection(all_trending_timeline_statuses, Status)
+ def insert_pagination_headers
+ set_pagination_headers(next_path, prev_path)
end
- def all_trending_timeline_statuses
- @trendings.flat_map(&:status)
+ def next_path
+ api_v1_truth_trending_truths_url offset: max_pagination_offset if records_continue?
+ end
+
+ def prev_path
+ no_prev_path? ? nil : api_v1_truth_trending_truths_url(offset: min_pagination_offset)
+ end
+
+ def no_prev_path?
+ trending_truths.empty? || params[:offset]&.to_i&.zero? || !params[:offset]
+ end
+
+ def max_pagination_offset
+ params[:offset] ? params[:offset].to_i + TRENDING_TRUTHS_DEFAULT_OFFSET.to_i : TRENDING_TRUTHS_DEFAULT_OFFSET
+ end
+
+ def min_pagination_offset
+ params[:offset] ? params[:offset].to_i - TRENDING_TRUTHS_DEFAULT_OFFSET.to_i : nil
+ end
+
+ def pagination_max_id
+ trending_truths.last.id
+ end
+
+ def pagination_since_id
+ trending_truths.first.id
+ end
+
+ def records_continue?
+ trending_truths.size == limit_param(TRENDING_TRUTHS_LIMIT)
end
end
Only in truth-new/opensource/app/controllers/api/v1/truth: videos_controller.rb
Only in truth-new/opensource/app/controllers/api/v1: tv
Only in truth-new/opensource/app/controllers/api/v1: verify_sms
Only in truth-new/opensource/app/controllers/api/v2: feeds_controller.rb
Only in truth-new/opensource/app/controllers/api/v2: pleroma
Only in truth-new/opensource/app/controllers/api/v2: statuses_controller.rb
Only in truth-new/opensource/app/controllers/api: v4
diff -ru truth-old/opensource/app/controllers/api/web/push_subscriptions_controller.rb truth-new/opensource/app/controllers/api/web/push_subscriptions_controller.rb
--- truth-old/opensource/app/controllers/api/web/push_subscriptions_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/api/web/push_subscriptions_controller.rb 2024-04-01 14:59:13
@@ -26,6 +26,7 @@
mention: alerts_enabled,
poll: alerts_enabled,
status: alerts_enabled,
+ chat: alerts_enabled
},
}
@@ -61,6 +62,6 @@
end
def data_params
- @data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status])
+ @data_params ||= params.require(:data).permit(:policy, alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status, :chat])
end
end
Only in truth-new/opensource/app/controllers: apidocs_controller.rb
diff -ru truth-old/opensource/app/controllers/application_controller.rb truth-new/opensource/app/controllers/application_controller.rb
--- truth-old/opensource/app/controllers/application_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/application_controller.rb 2024-04-12 09:09:08
@@ -24,6 +24,7 @@
rescue_from ActionController::UnknownFormat, with: :not_acceptable
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
+ rescue_from Mastodon::UnprocessableEntityError, with: :unprocessable_with_message
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
@@ -99,6 +100,10 @@
respond_with_error(422)
end
+ def unprocessable_with_message(error_message)
+ render json: { error: error_message.to_s }, status: 422
+ end
+
def not_acceptable
respond_with_error(406)
end
@@ -144,10 +149,21 @@
'mastodon-light'
end
- def respond_with_error(code)
+ def respond_with_error(status)
+ code = Rack::Utils::HTTP_STATUS_CODES[status]
respond_to do |format|
- format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
- format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
+ format.json do
+ render json: {
+ error: code,
+ error_message: code,
+ error_code: format_code(code),
+ }, status: status
+ end
+ format.any { render "errors/#{status}", layout: 'error', status: status, formats: [:html] }
end
+ end
+
+ def format_code(string)
+ string.upcase.gsub(' ', '_')
end
end
diff -ru truth-old/opensource/app/controllers/auth/passwords_controller.rb truth-new/opensource/app/controllers/auth/passwords_controller.rb
--- truth-old/opensource/app/controllers/auth/passwords_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/auth/passwords_controller.rb 2024-04-01 14:59:13
@@ -11,7 +11,7 @@
if resource.errors.empty?
resource.session_activations.destroy_all
resource.forget_me!
- Doorkeeper::AccessToken.where(resource_owner_id: resource.id).update_all(revoked_at: Time.now.utc)
+ OauthAccessToken.where(resource_owner_id: resource.id).update_all(revoked_at: Time.now.utc)
end
end
end
Only in truth-new/opensource/app/controllers/concerns: ads_concern.rb
Only in truth-new/opensource/app/controllers/concerns: assertable.rb
diff -ru truth-old/opensource/app/controllers/concerns/cache_concern.rb truth-new/opensource/app/controllers/concerns/cache_concern.rb
--- truth-old/opensource/app/controllers/concerns/cache_concern.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/concerns/cache_concern.rb 2024-04-01 14:59:13
@@ -28,7 +28,7 @@
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
end
- def cache_collection(raw, klass)
+ def cache_collection(raw, klass, include_removed = false)
return raw unless klass.respond_to?(:with_includes)
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
@@ -37,13 +37,25 @@
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
+ raw_hash = raw.index_by(&:id)
+
+ if klass.has_attribute?(:tombstone)
+ cached_keys_with_value = reload_tombstone_value(cached_keys_with_value, raw_hash)
+ end
+
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
unless uncached_ids.empty?
- uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id)
+ uncached = klass
+ uncached = uncached.with_discarded if include_removed
+ uncached = uncached.where(id: uncached_ids).with_includes.index_by(&:id)
+ if klass.has_attribute?(:tombstone)
+ uncached = reload_tombstone_value(uncached, raw_hash)
+ end
+
uncached.each_value do |item|
- Rails.cache.write(item, item, expires_in: 60.minutes)
+ Rails.cache.write(item, item, expires_in: 1.hour)
end
end
@@ -52,5 +64,13 @@
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass
+ end
+
+ def reload_tombstone_value(collection, raw_hash)
+ collection.each_with_object({}) do |(k, v), hash|
+ next unless v.has_attribute?(:tombstone) && raw_hash[k].has_attribute?(:tombstone)
+ hash[k] = [v.tombstone = raw_hash[k].tombstone]
+ end
+ collection
end
end
Only in truth-new/opensource/app/controllers/concerns: clientable.rb
Only in truth-new/opensource/app/controllers/concerns: divergable.rb
diff -ru truth-old/opensource/app/controllers/concerns/status_controller_concern.rb truth-new/opensource/app/controllers/concerns/status_controller_concern.rb
--- truth-old/opensource/app/controllers/concerns/status_controller_concern.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/concerns/status_controller_concern.rb 2024-04-01 14:59:13
@@ -38,10 +38,7 @@
descendants = cache_collection(
@status.descendants(
DESCENDANTS_LIMIT,
- current_account,
- @max_descendant_thread_id,
- @since_descendant_thread_id,
- DESCENDANTS_DEPTH_LIMIT
+ current_account
),
Status
)
diff -ru truth-old/opensource/app/controllers/concerns/two_factor_authentication_concern.rb truth-new/opensource/app/controllers/concerns/two_factor_authentication_concern.rb
--- truth-old/opensource/app/controllers/concerns/two_factor_authentication_concern.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/concerns/two_factor_authentication_concern.rb 2024-04-01 14:59:13
@@ -39,8 +39,6 @@
if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
restart_session
- elsif user.webauthn_enabled? && user_params.key?(:credential) && session[:attempt_user_id]
- authenticate_with_two_factor_via_webauthn(user)
elsif user_params.key?(:otp_attempt) && session[:attempt_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password])
diff -ru truth-old/opensource/app/controllers/concerns/user_tracking_concern.rb truth-new/opensource/app/controllers/concerns/user_tracking_concern.rb
--- truth-old/opensource/app/controllers/concerns/user_tracking_concern.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/concerns/user_tracking_concern.rb 2024-04-01 14:59:13
@@ -1,9 +1,11 @@
# frozen_string_literal: true
module UserTrackingConcern
+ include Redisable
+
extend ActiveSupport::Concern
- TRACKED_CONTROLLERS = %w(home credentials)
- UPDATE_SIGN_IN_HOURS = 24
+ TRACKED_CONTROLLERS = %w(credentials)
+ INTERACTIONS_SCORE_TRACKED_CONTROLLER = 'credentials'
included do
before_action :update_user_sign_in
@@ -12,10 +14,27 @@
private
def update_user_sign_in
- current_user.update_sign_in!(request) if user_needs_sign_in_update?
+ if user_needs_sign_in_update?
+ current_user.update_sign_in!(request)
+ update_account_score
+ end
end
def user_needs_sign_in_update?
- TRACKED_CONTROLLERS.include?(controller_name) && user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < UPDATE_SIGN_IN_HOURS.hours.ago)
+ TRACKED_CONTROLLERS.include?(controller_name) && user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < Date.today)
+ end
+
+ def update_account_score
+ return unless controller_name == INTERACTIONS_SCORE_TRACKED_CONTROLLER && current_account
+
+ current_week = Time.now.strftime('%U').to_i
+ last_week = current_week - 1
+ key1 = "interactions_score:#{current_account.id}:#{current_week}"
+ key2 = "interactions_score:#{current_account.id}:#{last_week}"
+
+ scores = Redis.current.mget(key1, key2)
+ scores_sum = scores.compact.map(&:to_i).sum.to_i
+
+ current_account.update(interactions_score: scores_sum) if scores_sum
end
end
Only in truth-new/opensource/app/controllers: link_controller.rb
diff -ru truth-old/opensource/app/controllers/oauth/mfa_controller.rb truth-new/opensource/app/controllers/oauth/mfa_controller.rb
--- truth-old/opensource/app/controllers/oauth/mfa_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/oauth/mfa_controller.rb 2023-05-05 13:42:02
@@ -5,6 +5,7 @@
def challenge
params[:grant_type] = "password"
+ request.params[:username] = false
create
end
diff -ru truth-old/opensource/app/controllers/settings/deletes_controller.rb truth-new/opensource/app/controllers/settings/deletes_controller.rb
--- truth-old/opensource/app/controllers/settings/deletes_controller.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/controllers/settings/deletes_controller.rb 2024-04-01 14:59:13
@@ -43,7 +43,9 @@
def destroy_account!
current_account.suspend!(origin: :local)
- AccountDeletionWorker.perform_async(current_user.account_id)
+ acct_id = current_account.id
+ # Self deletion uses acct_id as the deleted_by_id
+ AccountDeletionWorker.perform_async(acct_id, acct_id, skip_activitypub: true)
sign_out
end
end
Only in truth-new/opensource/app/controllers/well_known: skadnetwork_controller.rb
diff -ru truth-old/opensource/app/helpers/application_helper.rb truth-new/opensource/app/helpers/application_helper.rb
--- truth-old/opensource/app/helpers/application_helper.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/helpers/application_helper.rb 2024-04-01 14:59:13
@@ -92,7 +92,7 @@
elsif status.private_visibility? || status.limited_visibility?
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
elsif status.direct_visibility?
- fa_icon('envelope', title: I18n.t('statuses.visibilities.direct'))
+ fa_icon('at', title: I18n.t('statuses.visibilities.direct'))
end
end
Only in truth-new/opensource/app/helpers: discarded_helper.rb
diff -ru truth-old/opensource/app/helpers/email_helper.rb truth-new/opensource/app/helpers/email_helper.rb
--- truth-old/opensource/app/helpers/email_helper.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/helpers/email_helper.rb 2024-04-01 14:59:13
@@ -1,15 +1,27 @@
# frozen_string_literal: true
module EmailHelper
+ BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS = ENV.fetch('BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS', false)
+
def self.included(base)
base.extend(self)
end
def email_to_canonical_email(str)
+ username, domain = email_to_canonical_email_by_username_and_domain(str).values_at(:username, :domain)
+ "#{username}@#{domain}"
+ end
+
+ def email_to_canonical_email_by_username_and_domain(str)
username, domain = str.downcase.split('@', 2)
- username, = username.gsub('.', '').split('+', 2)
- "#{username}@#{domain}"
+ if BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS && BASE_EMAIL_DOMAINS_VALIDATION_STRIP_DOTS.split(',').map(&:strip).include?(domain)
+ username = username.gsub('.', '')
+ end
+
+ username, = username.split('+', 2)
+
+ { username: username, domain: domain }
end
def email_to_canonical_email_hash(str)
diff -ru truth-old/opensource/app/helpers/statuses_helper.rb truth-new/opensource/app/helpers/statuses_helper.rb
--- truth-old/opensource/app/helpers/statuses_helper.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/helpers/statuses_helper.rb 2024-04-01 14:59:13
@@ -101,7 +101,7 @@
when 'private'
fa_icon 'lock fw'
when 'direct'
- fa_icon 'envelope fw'
+ fa_icon 'at fw'
end
end
diff -ru truth-old/opensource/app/javascript/mastodon/components/status.js truth-new/opensource/app/javascript/mastodon/components/status.js
--- truth-old/opensource/app/javascript/mastodon/components/status.js 2022-06-08 09:15:38
+++ truth-new/opensource/app/javascript/mastodon/components/status.js 2024-04-01 14:59:13
@@ -457,8 +457,6 @@
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
};
- const visibilityIcon = visibilityIconInfo[status.get('visibility')];
-
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
Only in truth-new/opensource/app/javascript/mastodon/locales/locale-data: README.md
diff -ru truth-old/opensource/app/javascript/mastodon/selectors/index.js truth-new/opensource/app/javascript/mastodon/selectors/index.js
--- truth-old/opensource/app/javascript/mastodon/selectors/index.js 2022-06-08 09:15:38
+++ truth-new/opensource/app/javascript/mastodon/selectors/index.js 2024-04-01 14:59:13
@@ -3,12 +3,11 @@
import { me } from '../initial_state';
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
-const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]);
export const makeGetAccount = () => {
- return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => {
+ return createSelector([getAccountBase, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => {
if (base === null) {
return null;
}
diff -ru truth-old/opensource/app/javascript/packs/public.js truth-new/opensource/app/javascript/packs/public.js
--- truth-old/opensource/app/javascript/packs/public.js 2022-06-08 09:15:38
+++ truth-new/opensource/app/javascript/packs/public.js 2024-04-01 14:59:13
@@ -318,6 +318,10 @@
}
});
});
+
+ delegate(document, '[data-behavior="close-window"]', 'click', () => {
+ window.open('', '_parent', '').close();
+ });
}
loadPolyfills()
diff -ru truth-old/opensource/app/javascript/styles/application.scss truth-new/opensource/app/javascript/styles/application.scss
--- truth-old/opensource/app/javascript/styles/application.scss 2022-06-08 09:15:38
+++ truth-new/opensource/app/javascript/styles/application.scss 2024-04-01 14:59:13
@@ -28,3 +28,5 @@
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'mastodon/inbox';
+@import 'mastodon/links';
+@import 'mastodon/utils';
Only in truth-new/opensource/app/javascript/styles: docs.scss
Only in truth-new/opensource/app/javascript/styles/mastodon: links.scss
Only in truth-new/opensource/app/javascript/styles/mastodon: utils.scss
diff -ru truth-old/opensource/app/lib/access_token_extension.rb truth-new/opensource/app/lib/access_token_extension.rb
--- truth-old/opensource/app/lib/access_token_extension.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/access_token_extension.rb 2024-04-01 14:59:13
@@ -3,6 +3,8 @@
module AccessTokenExtension
extend ActiveSupport::Concern
+ include Paginable
+
included do
after_commit :push_to_streaming_api
end
diff -ru truth-old/opensource/app/lib/activitypub/activity/create.rb truth-new/opensource/app/lib/activitypub/activity/create.rb
--- truth-old/opensource/app/lib/activitypub/activity/create.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/activitypub/activity/create.rb 2024-04-01 15:02:56
@@ -111,7 +111,7 @@
thread: replied_to_status,
conversation: conversation_from_uri(@object['conversation']),
media_attachment_ids: process_attachments.take(4).map(&:id),
- poll: process_poll,
+ polls: process_poll || [],
quote: quote_from_url(@object['quoteUrl']),
}
end
@@ -284,15 +284,13 @@
items = @object['oneOf']
end
- voters_count = @object['votersCount']
+ poll_options = items.map.with_index { |v, i| { option_number: i, text: v['name'] } }
- @account.polls.new(
+ [Poll.new(
multiple: multiple,
expires_at: expires_at,
- options: items.map { |item| item['name'].presence || item['content'] }.compact,
- cached_tallies: items.map { |item| item.dig('replies', 'totalItems') || 0 },
- voters_count: voters_count
- )
+ options_attributes: poll_options
+ )]
end
def poll_vote?
@@ -313,7 +311,6 @@
end
increment_voters_count! unless already_voted
- ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, replied_to_status.id) unless replied_to_status.preloadable_poll.hide_totals?
end
def resolve_thread(status)
diff -ru truth-old/opensource/app/lib/activitypub/activity/delete.rb truth-new/opensource/app/lib/activitypub/activity/delete.rb
--- truth-old/opensource/app/lib/activitypub/activity/delete.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/activitypub/activity/delete.rb 2024-04-01 14:59:13
@@ -13,7 +13,13 @@
def delete_person
lock_or_return("delete_in_progress:#{@account.id}") do
- DeleteAccountService.new.call(@account, reserve_username: false, skip_activitypub: true)
+ DeleteAccountService.new.call(
+ @account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'activitypub_delete_person',
+ reserve_username: false,
+ skip_activitypub: true,
+ )
end
end
diff -ru truth-old/opensource/app/lib/activitypub/activity/update.rb truth-new/opensource/app/lib/activitypub/activity/update.rb
--- truth-old/opensource/app/lib/activitypub/activity/update.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/activitypub/activity/update.rb 2024-04-01 14:59:13
@@ -26,7 +26,5 @@
status = Status.find_by(uri: object_uri, account_id: @account.id)
return if status.nil? || status.preloadable_poll.nil?
-
- ActivityPub::ProcessPollService.new.call(status.preloadable_poll, @object)
end
end
diff -ru truth-old/opensource/app/lib/activitypub/tag_manager.rb truth-new/opensource/app/lib/activitypub/tag_manager.rb
--- truth-old/opensource/app/lib/activitypub/tag_manager.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/activitypub/tag_manager.rb 2024-04-01 14:59:13
@@ -27,6 +27,12 @@
when :note, :comment, :activity
return activity_account_status_url(target.account, target) if target.reblog?
short_account_status_url(target.account, target)
+ when :group
+ group_url(target)
+ when :group_note
+ group_status_url(target)
+ when :group_request
+ group_request_url(target)
end
end
@@ -35,12 +41,18 @@
case target.object_type
when :person
- target.instance_actor? ? instance_actor_url : account_url(target)
+ account_url(target)
when :note, :comment, :activity
return activity_account_status_url(target.account, target) if target.reblog?
account_status_url(target.account, target)
when :emoji
emoji_url(target)
+ when :group
+ group_url(target)
+ when :group_note
+ group_status_url(target)
+ when :group_request
+ group_request_url(target)
end
end
@@ -48,6 +60,12 @@
account_url(username: username)
end
+ def url_for_chat_message(id)
+ message = ChatMessage.find(id)
+ chat = message.chat
+ "#{root_url}chats/#{chat.id}/messages/#{id}"
+ end
+
def generate_uri_for(_target)
URI.join(root_url, 'payloads', SecureRandom.uuid)
end
@@ -168,5 +186,19 @@
end
rescue ActiveRecord::RecordNotFound
nil
+ end
+
+ private
+
+ def group_status_url(target)
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.group.slug}/statuses/#{target.id}"
+ end
+
+ def group_request_url(target)
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.group.slug}/manage/requests"
+ end
+
+ def group_url(target)
+ "https://#{Rails.configuration.x.web_domain}/group/#{target.slug}"
end
end
Only in truth-new/opensource/app/lib: event_provider
Only in truth-new/opensource/app/lib: events
diff -ru truth-old/opensource/app/lib/feed_manager.rb truth-new/opensource/app/lib/feed_manager.rb
--- truth-old/opensource/app/lib/feed_manager.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/feed_manager.rb 2024-04-01 14:59:13
@@ -153,11 +153,11 @@
# @param [Account] into_account
# @return [void]
def unmerge_from_home(from_account, into_account)
- timeline_key = key(:home, into_account.id)
- oldest_home_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
+ timeline_key = key(:home, into_account.id)
+ timeline_status_ids = redis.zrange(timeline_key, 0, -1)
- from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_home_score).reorder(nil).find_each do |status|
- remove_from_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?)
+ from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status|
+ remove_from_feed(:home, into_account.id, status, aggregate_reblogs: into_account.user&.aggregates_reblogs?)
end
end
@@ -166,11 +166,11 @@
# @param [List] list
# @return [void]
def unmerge_from_list(from_account, list)
- timeline_key = key(:list, list.id)
- oldest_list_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
+ timeline_key = key(:list, list.id)
+ timeline_status_ids = redis.zrange(timeline_key, 0, -1)
- from_account.statuses.select('id, reblog_of_id').where('id > ?', oldest_list_score).reorder(nil).find_each do |status|
- remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
+ from_account.statuses.select('id, reblog_of_id').where(id: timeline_status_ids).reorder(nil).find_each do |status|
+ remove_from_feed(:list, list.id, status, aggregate_reblogs: list.account.user&.aggregates_reblogs?)
end
end
@@ -180,7 +180,7 @@
# @return [void]
def clear_from_home(account, target_account)
timeline_key = key(:home, account.id)
- timeline_status_ids = redis_timelines.zrange(timeline_key, 0, -1)
+ timeline_status_ids = status_ids_to_plain_numbers(redis_timelines.zrange(timeline_key, 0, -1))
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id)
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id)
@@ -200,7 +200,7 @@
# @return [void]
def clear_from_list(list, target_account)
timeline_key = key(:list, list.id)
- timeline_status_ids = redis_timelines.zrange(timeline_key, 0, -1)
+ timeline_status_ids = status_ids_to_plain_numbers(redis_timelines.zrange(timeline_key, 0, -1))
statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a
reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id)
with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id)
@@ -232,11 +232,11 @@
aggregate = account.user&.aggregates_reblogs?
timeline_key = key(:home, account.id)
- account.statuses.limit(limit).each do |status|
+ account.statuses.where.not(visibility: :group).limit(limit).each do |status|
add_to_feed(:home, account.id, status, aggregate)
end
- account.following.includes(:account_stat).find_each do |target_account|
+ account.following.includes(:account_follower, :account_following, :account_status).find_each do |target_account|
if redis_timelines.zcard(timeline_key) >= limit
oldest_home_score = redis_timelines.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
last_status_score = Mastodon::Snowflake.id_at(account.last_status_at)
@@ -282,7 +282,7 @@
# references to.
redis_timelines.pipelined do
reblogged_id_sets.each do |feed_id, future|
- future.value.each do |reblogged_id|
+ status_ids_to_plain_numbers(future.value).each do |reblogged_id|
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}")
redis_timelines.del(reblog_set_key)
end
@@ -310,6 +310,10 @@
redis.publish("timeline:whale:#{status.account_id}", Oj.dump(event: :delete, payload: status.id.to_s))
true
+ end
+
+ def status_ids_to_plain_numbers(status_ids)
+ status_ids.map { |id| (id.is_a? Integer) || ((id.is_a? String) && id.force_encoding('UTF-8').valid_encoding? && (!id.include? '\\')) ? id : id.reverse.unpack('q').first }
end
private
diff -ru truth-old/opensource/app/lib/formatter.rb truth-new/opensource/app/lib/formatter.rb
--- truth-old/opensource/app/lib/formatter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/formatter.rb 2024-04-01 14:59:13
@@ -33,9 +33,11 @@
linkable_accounts = get_linkable_accounts(status)
linkable_accounts << status.account
+ external_links = options[:external_links] ? { external_links: options[:external_links].index_by(&:url) } : {}
+
html = raw_content
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
- html = encode_and_link_urls(html, linkable_accounts)
+ html = encode_and_link_urls(html, linkable_accounts, external_links)
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
html = simple_format(html, {}, sanitize: false)
html = quotify(html, status) if status.quote? && !options[:escape_quotify]
@@ -103,6 +105,21 @@
html.html_safe # rubocop:disable Rails/OutputSafety
end
+ def format_chat_message(message)
+ linkable_usernames = []
+
+ message.scan(Account::MENTION_RE).each do |match|
+ username = match[1]
+ linkable_usernames << username
+ end
+
+ linkable_accounts = Account.ci_find_by_usernames(linkable_usernames).to_a
+
+ html = encode_and_link_urls(message, linkable_accounts)
+ html = simple_format(html, {}, sanitize: false)
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
def linkify(text)
html = encode_and_link_urls(text)
html = simple_format(html, {}, sanitize: false)
@@ -155,7 +172,8 @@
def count_tag_nesting(tag)
if tag[1] == '/' then -1
elsif tag[-2] == '/' then 0
- else 1
+ else
+ 1
end
end
@@ -282,8 +300,11 @@
end
def link_to_url(entity, options = {})
- url = Addressable::URI.parse(entity[:url])
-
+ url = if options[:external_links] && (link_id = options[:external_links][entity[:url]]&.id)
+ link_url(link_id, subdomain: 'links')
+ else
+ Addressable::URI.parse(entity[:url])
+ end
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
Only in truth-new/opensource/app/lib: hostile_rate_limiter.rb
diff -ru truth-old/opensource/app/lib/inline_renderer.rb truth-new/opensource/app/lib/inline_renderer.rb
--- truth-old/opensource/app/lib/inline_renderer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/inline_renderer.rb 2024-04-01 14:59:13
@@ -21,11 +21,13 @@
serializer = REST::ReactionSerializer
when :encrypted_message
serializer = REST::EncryptedMessageSerializer
+ when :chat
+ serializer = REST::ChatSerializer
else
return
end
- serializable_resource = ActiveModelSerializers::SerializableResource.new(@object, serializer: serializer, scope: current_user, scope_name: :current_user)
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(@object, serializer: serializer, scope: current_user, scope_name: :current_user, current_user: @current_account&.user)
serializable_resource.as_json
end
Only in truth-new/opensource/app/lib: interactions_tracker.rb
Only in truth-old/opensource/app/lib: potential_friendship_tracker.rb
diff -ru truth-old/opensource/app/lib/prometheus/application_exporter.rb truth-new/opensource/app/lib/prometheus/application_exporter.rb
--- truth-old/opensource/app/lib/prometheus/application_exporter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/prometheus/application_exporter.rb 2024-04-01 14:59:13
@@ -18,19 +18,37 @@
follows: 'number of accounts following account',
unfollows: 'number of accounts unfollowing accounts',
links: 'number of posted links',
- approves: 'number of approved users'
+ approves: 'number of approved users',
+ ad_impressions: 'number of ad impressions',
+ chats: 'number of chats',
+ chat_messages: 'number of chat messages',
}
+ @histogram_instances = {}
+ histogram_metrics = {
+ video_passthrough_encoding: 'duration for processing passthrough video encoding',
+ }
+
prometheus_client = PrometheusExporter::Client.default
counter_metrics.each do |key, value|
@counter_instances[key] = prometheus_client.register(:counter, key, value)
end
+ histogram_metrics.each do |key, value|
+ @histogram_instances[key] = prometheus_client.register(:histogram, key, value)
+ end
+
def increment(metric, labels = {})
return if Rails.env.test? || Rails.env.development?
- @counter_instances[metric]&.increment(labels)
+ @counter_instances[metric]&.increment(labels)
+ end
+
+ def observe_duration(metric, duration, labels = {})
+ return if Rails.env.test? || Rails.env.development?
+
+ @histogram_instances[metric]&.observe(duration, labels)
end
end
-end
\ No newline at end of file
+end
Only in truth-new/opensource/app/lib: queue_manager.rb
diff -ru truth-old/opensource/app/lib/rate_limiter.rb truth-new/opensource/app/lib/rate_limiter.rb
--- truth-old/opensource/app/lib/rate_limiter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/rate_limiter.rb 2023-11-29 12:30:01
@@ -5,7 +5,7 @@
FAMILIES = {
follows: {
- limit: 400,
+ limit: 250,
period: 24.hours.freeze,
}.freeze,
@@ -23,8 +23,8 @@
def initialize(by, options = {})
@by = by
@family = options[:family]
- @limit = FAMILIES[@family][:limit]
- @period = FAMILIES[@family][:period].to_i
+ @limit = self.class::FAMILIES[@family][:limit]
+ @period = self.class::FAMILIES[@family][:period].to_i
end
def record!
@@ -32,16 +32,21 @@
if count.nil?
redis.set(key, 0)
+ count = 0
redis.expire(key, (@period - (last_epoch_time % @period) + 1).to_i)
end
- raise Mastodon::RateLimitExceededError, "Rate limit hit by RateLimiter #{@family}" if count.present? && count.to_i >= @limit && ENV['SKIP_IP_RATE_LIMITING'] != 'true'
+ raise error, "Rate limit hit by #{self.class.name} #{@family}" if count.to_i >= @limit && ENV['SKIP_IP_RATE_LIMITING'] != 'true'
redis.incr(key)
end
def rollback!
redis.decr(key)
+ end
+
+ def error
+ Mastodon::RateLimitExceededError
end
def to_headers(now = Time.now.utc)
diff -ru truth-old/opensource/app/lib/request.rb truth-new/opensource/app/lib/request.rb
--- truth-old/opensource/app/lib/request.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/request.rb 2024-04-01 14:59:13
@@ -12,6 +12,27 @@
@socket = socket_class.open(host, port)
@socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
end
+
+ def reset_counter
+ @deadline = nil
+ end
+
+ def readpartial(size, buffer = nil)
+ @deadline ||= Process.clock_gettime(Process::CLOCK_MONOTONIC) + @read_timeout
+
+ timeout = false
+ loop do
+ result = @socket.read_nonblock(size, buffer, exception: false)
+
+ return :eof if result.nil?
+
+ remaining_time = @deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
+ raise HTTP::TimeoutError, "Read timed out after #{@read_timeout} seconds" if timeout || remaining_time <= 0
+ return result if result != :wait_readable
+
+ timeout = true unless @socket.to_io.wait_readable(remaining_time)
+ end
+ end
end
class Request
@@ -101,10 +122,9 @@
private
def set_common_headers!
- parsed_url = Addressable::URI.parse(@url)
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
@headers['User-Agent'] = Mastodon::Version.user_agent
- @headers['Host'] = "#{@url.host}:#{parsed_url.inferred_port}"
+ @headers['Host'] = "#{@url.host}"
@headers['Date'] = Time.now.utc.httpdate
@headers['Accept-Encoding'] = 'gzip' if @verb != :head
end
@@ -142,7 +162,7 @@
end
def use_proxy?(url)
- parsed = URI.parse(url)
+ parsed = URI.parse(Addressable::URI.encode(url))
return false if private_address?(parsed.host)
Rails.configuration.x.http_client_proxy.present?
end
@@ -161,6 +181,7 @@
address = Resolv.getaddress(hostname)
[
+ IPAddr.new('127.0.0.1'),
IPAddr.new('10.0.0.0/8'),
IPAddr.new('172.16.0.0/12'),
IPAddr.new('192.168.0.0/16'),
@@ -174,7 +195,7 @@
end
module ClientLimit
- def body_with_limit(limit = 1.megabyte)
+ def body_with_limit(limit = 4.megabyte)
raise Mastodon::LengthValidationError if content_length.present? && content_length > limit
if charset.nil?
diff -ru truth-old/opensource/app/lib/status_filter.rb truth-new/opensource/app/lib/status_filter.rb
--- truth-old/opensource/app/lib/status_filter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/status_filter.rb 2024-04-01 14:59:13
@@ -3,15 +3,18 @@
class StatusFilter
attr_reader :status, :account
- def initialize(status, account, preloaded_relations = {})
+ def initialize(status, account, preloaded_relations = {}, root_status = nil, urls = [], marketing_push_notification = false)
@status = status
@account = account
@preloaded_relations = preloaded_relations
+ @root_status = root_status
+ @urls = urls
+ @marketing_push_notification = marketing_push_notification
end
def filtered?
- return false if !account.nil? && account.id == status.account_id
- blocked_by_policy? || (account_present? && filtered_status?) || silenced_account?
+ return false if !account.nil? && account.id == status.account_id && !deleted_status?
+ blocked_by_policy? || (account_present? && filtered_status?) || silenced_account? || privatized_status? || contains_recent_link? || contains_bad_link? || deleted_status?
end
private
@@ -54,5 +57,30 @@
def policy_allows_show?
StatusPolicy.new(account, status, @preloaded_relations).show?
+ end
+
+ def privatized_status?
+ status.visibility == 'self' && status.account != account
+ end
+
+ def contains_recent_link?
+ return unless @root_status
+ time_difference = (Time.now - status.created_at).round
+ delay_minutes = @marketing_push_notification ? 900 : 300
+
+ @urls.any? && !status.account.whale? && status.account != account && time_difference < delay_minutes
+ end
+
+ def contains_bad_link?
+ return unless @root_status && @marketing_push_notification && @urls.any?
+
+ time_difference = (status.created_at - @marketing_push_notification.created_at).round
+ return if time_difference > 6.hours.to_i
+
+ !status.account.whale? && status.account != account
+ end
+
+ def deleted_status?
+ status.deleted_at?
end
end
diff -ru truth-old/opensource/app/lib/status_finder.rb truth-new/opensource/app/lib/status_finder.rb
--- truth-old/opensource/app/lib/status_finder.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/lib/status_finder.rb 2024-04-01 14:59:13
@@ -3,8 +3,9 @@
class StatusFinder
attr_reader :url
- def initialize(url)
+ def initialize(url, allow_activity: false)
@url = url
+ @allowed_actions = allow_activity ? ['show', 'activity'] : ['show']
end
def status
@@ -27,8 +28,6 @@
end
def verify_action!
- unless recognized_params[:action] == 'show'
- raise ActiveRecord::RecordNotFound
- end
+ raise ActiveRecord::RecordNotFound unless @allowed_actions.include?(recognized_params[:action])
end
end
Only in truth-old/opensource/app/lib: status_reach_finder.rb
Only in truth-new/opensource/app/lib: url_placeholder.rb
diff -ru truth-old/opensource/app/mailers/notification_mailer.rb truth-new/opensource/app/mailers/notification_mailer.rb
--- truth-old/opensource/app/mailers/notification_mailer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/mailers/notification_mailer.rb 2024-04-01 14:59:13
@@ -42,9 +42,16 @@
return unless @me.user.functional? && @status.present?
+ subject =
+ if notification.count
+ I18n.t('notification_mailer.favourite_group.subject', name: @account.acct, count_others: notification.count - 1, actor: "others")
+ else
+ I18n.t('notification_mailer.favourite.subject', name: @account.acct)
+ end
+
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
- mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
+ mail to: @me.user.email, subject: subject
end
end
@@ -103,7 +110,7 @@
return unless @resource.active_for_authentication?
I18n.with_locale(@resource.locale || I18n.default_locale) do
- mail to: @resource.email, subject: I18n.t('notification_mailer.user_approved.web.subject')
+ mail to: @resource.email, subject: I18n.t('notification_mailer.user_approved.title', name: @resource.account.username)
end
end
diff -ru truth-old/opensource/app/models/account.rb truth-new/opensource/app/models/account.rb
--- truth-old/opensource/app/models/account.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/account.rb 2024-04-05 09:07:34
@@ -3,7 +3,6 @@
#
# Table name: accounts
#
-# id :bigint(8) not null, primary key
# username :string default(""), not null
# domain :string
# private_key :text
@@ -31,6 +30,7 @@
# shared_inbox_url :string default(""), not null
# followers_url :string default(""), not null
# protocol :integer default("ostatus"), not null
+# id :bigint(8) not null, primary key
# memorial :boolean default(FALSE), not null
# moved_to_account_id :bigint(8)
# featured_collection_url :string
@@ -52,6 +52,14 @@
# location :text default(""), not null
# website :text default(""), not null
# whale :boolean default(FALSE)
+# interactions_score :integer
+# file_s3_host :string(64)
+# accepting_messages :boolean default(TRUE), not null
+# chats_onboarded :boolean default(FALSE), not null
+# feeds_onboarded :boolean default(FALSE), not null
+# show_nonmember_group_statuses :boolean default(TRUE), not null
+# tv_onboarded :boolean default(FALSE), not null
+# receive_only_follow_mentions :boolean default(FALSE), not null
#
class Account < ApplicationRecord
@@ -63,6 +71,8 @@
hub_url
)
+ attribute :message_expiration, :interval
+
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i
@@ -83,6 +93,7 @@
TRUST_LEVELS = {
untrusted: 0,
trusted: 1,
+ hostile: -1,
}.freeze
enum protocol: [:ostatus, :activitypub]
@@ -100,9 +111,15 @@
validates :display_name, length: { maximum: 30 }, if: -> { local? && will_save_change_to_display_name? }
validates :note, note_length: { maximum: 500 }, if: -> { local? && will_save_change_to_note? }
validates :fields, length: { maximum: 4 }, if: -> { local? && will_save_change_to_fields? }
+ validates :location, length: { maximum: 500 }, if: -> { local? && will_save_change_to_location? }
+ validates :url, length: { maximum: 500 }, if: -> { local? && will_save_change_to_url? }
+ validates :website, length: { maximum: 500 }, if: -> { local? && will_save_change_to_website? }
validate :check_website_field_for_javascript
+ after_update_commit :invalidate_statuses, if: -> { saved_change_to_username? }
+ after_update_commit :invalidate_ads_cache
+
scope :remote, -> { where.not(domain: nil) }
scope :local, -> { where(domain: nil) }
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
@@ -122,12 +139,14 @@
scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) }
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).left_outer_joins(:account_stat) }
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
- scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc, accounts.id desc')) }
+ # TODO: evaluate last_status_at/current_sign_in_at nulls last instead of case statements
+ scope :by_recent_status, -> { order(Arel.sql('(case when last_status_at is null then 1 else 0 end) asc, last_status_at desc, accounts.id desc')) }
scope :by_recent_sign_in, -> { order(Arel.sql('(case when users.current_sign_in_at is null then 1 else 0 end) asc, users.current_sign_in_at desc, accounts.id desc')) }
scope :popular, -> { order('account_stats.followers_count desc') }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches("%.#{domain}"))) }
scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) }
+ scope :excluded_by_group_account_block, ->(group_id) { where.not(GroupAccountBlock.where('group_account_blocks.account_id = accounts.id').where('group_account_blocks.group_id = ?', group_id).arel.exists) }
delegate :email,
:unconfirmed_email,
@@ -145,14 +164,17 @@
:locale,
:hides_network?,
:shows_application?,
+ :sms,
to: :user,
prefix: true,
allow_nil: true
delegate :chosen_languages, to: :user, prefix: false, allow_nil: true
- update_index 'accounts#account', :self
+ attr_accessor :seen
+ update_index 'accounts', :self
+
def contains_prohibited_terms?
user_and_display_name_downcase = "#{username} #{display_name}".downcase
Status::PROHIBITED_TERMS_ON_INDEX.any? { |term| user_and_display_name_downcase.include? term }
@@ -203,11 +225,11 @@
end
def to_webfinger_s
- "acct:#{local_username_and_domain}"
+ "acct:#{username}@#{Rails.configuration.x.local_domain}"
end
def searchable?
- !(suspended? || moved?)
+ !moved?
end
def possibly_stale?
@@ -252,25 +274,33 @@
update!(suspended_at: date, suspension_origin: origin)
end
create_canonical_email_block!
+ InteractionsTracker.new(id).remove_total_score
end
def unsuspend!
transaction do
deletion_request&.destroy!
update!(suspended_at: nil, suspension_origin: nil)
+ user&.enable!
destroy_canonical_email_block!
end
end
+ def deleted?
+ user.nil?
+ end
+
def verify!
transaction do
update!(verified: true)
+ user.update!(unauth_visibility: true)
end
end
def unverify!
transaction do
update!(verified: false)
+ user.update!(unauth_visibility: false)
end
end
@@ -294,6 +324,14 @@
update!(memorial: true)
end
+ def accept_messages!
+ update!(accepting_messages: true)
+ end
+
+ def unaccept_messages!
+ update!(accepting_messages: false)
+ end
+
def sign?
true
end
@@ -350,6 +388,10 @@
self[:fields] = fields
end
+ def account_fields
+ (self[:fields] || []).map { |f| AccountField.new(self, f) }
+ end
+
DEFAULT_FIELDS_SIZE = 4
def build_fields
@@ -391,7 +433,7 @@
end
def check_website_field_for_javascript
- errors.add(:base, "Please enter a valid website") if JAVASCRIPT_RE.match(website)
+ errors.add(:base, 'Please enter a valid website') if JAVASCRIPT_RE.match(website)
end
# TODO: follow_requests profile feature toggle "locked"
@@ -430,7 +472,14 @@
WhaleCacheInvalidationWorker.perform_async(id)
end
+ def tv_enabled?
+ feature_enabled? 'tv'
+ end
+ def for_you_enabled?
+ feature_enabled? 'for_you'
+ end
+
class Field < ActiveModelSerializers::Model
attributes :name, :value, :verified_at, :account
@@ -471,6 +520,45 @@
end
end
+ class AccountField
+ attr_reader :name, :value, :account
+ attr_accessor :verified_at
+
+ def initialize(account, attributes)
+ @original_field = attributes
+ string_limit = account.local? ? 255 : 2047
+ @account = account
+ @name = attributes['name'].strip[0, string_limit]
+ @value = attributes['value'].strip[0, string_limit]
+ @verified_at = attributes['verified_at']&.to_datetime
+ end
+
+ def verified?
+ verified_at.present?
+ end
+
+ def value_for_verification
+ @value_for_verification ||= if account.local?
+ value
+ else
+ ActionController::Base.helpers.strip_tags(value)
+ end
+ end
+
+ def verifiable?
+ value_for_verification.present? && value_for_verification.start_with?('http://', 'https://')
+ end
+
+ def mark_verified!
+ self.verified_at = Time.now.utc
+ @original_field['verified_at'] = verified_at
+ end
+
+ def to_h
+ { name: name, value: value, verified_at: verified_at }
+ end
+ end
+
class << self
def readonly_attributes
super - %w(statuses_count following_count followers_count)
@@ -484,13 +572,13 @@
def ci_find_by_username(username = nil)
return nil unless username.present?
- includes(:user).where("LOWER(username) = ?", username.downcase).take
+ includes(:user).find_by('LOWER(username) = ?', username.downcase)
end
def ci_find_by_usernames(usernames = [])
return Account.none if usernames.empty?
- where("LOWER(username) IN (?)", usernames.compact.map { |un| un.downcase })
+ where('LOWER(username) IN (?)', usernames.compact.map { |un| un.downcase })
end
def search_for(terms, limit = 10, offset = 0)
@@ -509,7 +597,7 @@
SQL
records = find_by_sql([sql, limit, offset])
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account])
records
end
@@ -558,7 +646,7 @@
records = find_by_sql([sql, account.id, account.id, limit, offset])
end
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account])
records
end
@@ -591,7 +679,31 @@
@emojis ||= CustomEmoji.from_text(emojifiable_text, domain)
end
+ def recent_ads
+ statuses.where('created_at > ?', 1.month.ago)
+ .where(in_reply_to_id: nil)
+ .where(Ad.where('statuses.id = ads.status_id').arel.exists)
+ end
+
+ # Identifies the accounts that have advertised recently based on a list of account_ids.
+ #
+ # @param [Array<Integer>] account_ids The list of account IDs to check.
+ # @return [Array<Integer>] Returns an array containing the account IDs that have advertised recently.
+ #
+ def self.recent_advertisers(account_ids, recently = 1.month.ago)
+ Status
+ .select(:account_id)
+ .where(account_id: account_ids)
+ .where('statuses.created_at > ?', recently)
+ .where(in_reply_to_id: nil)
+ .joins(:ad)
+ .group(:account_id)
+ .reorder('')
+ .pluck(:account_id)
+ end
+
before_create :generate_keys
+ before_save :set_file_s3_host, if: -> { will_save_change_to_avatar_file_name? || will_save_change_to_header_file_name? }
before_validation :prepare_contents, if: :local?
before_validation :prepare_username, on: :create
before_destroy :clean_feed_manager
@@ -631,6 +743,7 @@
def create_canonical_email_block!
return unless local? && user_email.present?
+ return if CanonicalEmailBlock.block?(user_email)
CanonicalEmailBlock.create(reference_account: self, email: user_email)
rescue ActiveRecord::RecordNotUnique
@@ -641,5 +754,38 @@
return unless local?
CanonicalEmailBlock.where(reference_account: self).delete_all
+ end
+
+ def set_file_s3_host
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name]
+ end
+
+ def invalidate_statuses
+ InvalidateAccountStatusesWorker.perform_async(id)
+ end
+
+ def invalidate_ads_cache
+ InvalidateAdsAccountsWorker.perform_async(id) if OauthAccessToken.exists?(resource_owner_id: user&.id, scopes: 'ads')
+ end
+
+ def fields_changed(account)
+ updatable_fields = %w(bio display_name avatar_url header_url followers_count following_count website location username verified)
+ changed_fields = account.saved_changes.keys
+
+ updated_fields = changed_fields.select { |f| updatable_fields.include?(f) }
+ updated_fields << 'avatar_url' if changed_fields.include?('avatar_file_name')
+ updated_fields << 'header_url' if changed_fields.include?('header_file_name')
+ updated_fields.map(&:upcase)
+ end
+
+ def feature_enabled?(feature)
+ feature_flag = ::Configuration::FeatureFlag
+ .joins("LEFT JOIN configuration.account_enabled_features ON configuration.feature_flags.feature_flag_id = configuration.account_enabled_features.feature_flag_id AND configuration.account_enabled_features.account_id = #{id}")
+ .where(name: feature)
+ .select('configuration.feature_flags.name, configuration.feature_flags.status, configuration.account_enabled_features.account_id')
+ .to_a
+ .first
+
+ feature_flag&.enabled? == true || (feature_flag&.account_based? == true && !feature_flag&.account_id.nil?)
end
end
diff -ru truth-old/opensource/app/models/account_domain_block.rb truth-new/opensource/app/models/account_domain_block.rb
--- truth-old/opensource/app/models/account_domain_block.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/account_domain_block.rb 2024-04-01 14:59:13
@@ -27,6 +27,6 @@
end
def remove_relationship_cache
- Rails.cache.delete_matched("relationship:#{account_id}:*")
+ Rails.cache.delete_matched("relationship/#{account_id}/*")
end
end
Only in truth-new/opensource/app/models: account_follower_statistic.rb
Only in truth-new/opensource/app/models: account_following_statistic.rb
diff -ru truth-old/opensource/app/models/account_stat.rb truth-new/opensource/app/models/account_stat.rb
--- truth-old/opensource/app/models/account_stat.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/account_stat.rb 2024-04-01 14:59:13
@@ -18,7 +18,7 @@
belongs_to :account, inverse_of: :account_stat
- update_index('accounts#account', :account)
+ update_index('accounts', :account)
def following_count
[attributes['following_count'], 0].max
@@ -31,4 +31,4 @@
def statuses_count
[attributes['statuses_count'], 0].max
end
-end
+end
\ No newline at end of file
Only in truth-new/opensource/app/models: account_status_statistic.rb
diff -ru truth-old/opensource/app/models/account_suggestions.rb truth-new/opensource/app/models/account_suggestions.rb
--- truth-old/opensource/app/models/account_suggestions.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/account_suggestions.rb 2024-04-01 14:59:13
@@ -2,24 +2,17 @@
class AccountSuggestions
SOURCES = [
- AccountSuggestions::SettingSource,
- AccountSuggestions::PastInteractionsSource,
- AccountSuggestions::GlobalSource,
+ {klass: AccountSuggestions::SettingSource, limit: 300},
+ {klass: AccountSuggestions::PastInteractionsSource, limit: 25},
+ {klass: AccountSuggestions::GlobalSource, limit: 25}
].freeze
- # Since we iterate through 3 arrays, this number is the max # of suggestions that will be returned
- # Ex: if the total limit is 120 and the client requests 5 at a time, the total # of pages that can be returned is 24
- TOTAL_RESULTS_LIMIT = 150
-
- # The total limit divided by the # of sources
- ARRAY_LIMIT = TOTAL_RESULTS_LIMIT / SOURCES.length.floor
-
def self.get(account)
- SOURCES.each_with_object([]) do |source_class, suggestions|
- source_suggestions = source_class.new.get(
+ SOURCES.each_with_object([]) do |obj, suggestions|
+ source_suggestions = obj[:klass].new.get(
account,
skip_account_ids: suggestions.map(&:account_id),
- limit: ARRAY_LIMIT
+ limit: obj[:limit]
)
suggestions.concat(source_suggestions)
@@ -27,8 +20,8 @@
end
def self.remove(account, target_account_id)
- SOURCES.each do |source_class|
- source = source_class.new
+ SOURCES.each do |obj|
+ source = obj[:klass].new
source.remove(account, target_account_id)
end
end
Only in truth-new/opensource/app/models: ad.rb
Only in truth-new/opensource/app/models: ad_attribution.rb
diff -ru truth-old/opensource/app/models/admin/account_action.rb truth-new/opensource/app/models/admin/account_action.rb
--- truth-old/opensource/app/models/admin/account_action.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/admin/account_action.rb 2024-04-01 14:59:13
@@ -19,6 +19,9 @@
unsuspend
verify
unverify
+ enable_sms_reverification
+ disable_sms_reverification
+ enable_feature
).freeze
attr_accessor :target_account,
@@ -26,7 +29,8 @@
:type,
:text,
:report_id,
- :warning_preset_id
+ :warning_preset_id,
+ :feature_name
attr_reader :warning, :send_email_notification, :include_statuses, :duration
@@ -54,7 +58,6 @@
end
process_email!
- process_reports!
process_queue!
end
@@ -106,6 +109,12 @@
handle_remove_avatar!
when 'remove_header'
handle_remove_header!
+ when 'enable_sms_reverification'
+ handle_enable_sms_reverification!
+ when 'disable_sms_reverification'
+ handle_disable_sms_reverification!
+ when 'enable_feature'
+ handle_enable_feature!
end
end
@@ -152,18 +161,24 @@
log_action(:ban, target_account.user)
target_account.suspend!
target_account.user.disable!
+ remove_scheduled_unsuspensions
+ InteractionsTracker.new(target_account).remove_total_score
+ DisabledUserUnfollowWorker.perform_async(target_account.id)
+ revoke_access_tokens(target_account)
end
def handle_disable!
authorize(target_account.user, :disable?)
log_action(:disable, target_account.user)
target_account.user&.disable!
+ DisabledUserUnfollowWorker.perform_in(7.days, target_account.id)
end
def handle_enable!
authorize(target_account.user, :enable?)
log_action(:enable, target_account.user)
target_account.user&.enable!
+ DisabledUserRefollowWorker.perform_async(target_account.id)
end
def handle_sensitive!
@@ -188,6 +203,7 @@
authorize(target_account, :suspend?)
log_action(:suspend, target_account)
target_account.suspend!(origin: :local)
+ InteractionsTracker.new(target_account).remove_total_score
schedule_unsuspension! unless account_suspension_policy.strikes_expended?
end
@@ -196,6 +212,7 @@
authorize(target_account, :unsuspend?)
log_action(:unsuspend, target_account)
target_account.unsuspend!
+ DisabledUserRefollowWorker.perform_async(target_account.id)
end
def handle_verify!
@@ -224,16 +241,37 @@
target_account.save!
end
- def text_for_warning
- [warning_preset&.text, text].compact.join("\n\n")
+ def handle_enable_sms_reverification!
+ authorize(target_account.user, :enable_sms_reverification?)
+ log_action(:enable_sms_reverification, target_account.user)
+ user = target_account.user
+ UserSmsReverificationRequired.create(user: user) if user
end
- def queue_suspension_worker!
- Admin::SuspensionWorker.perform_async(target_account.id)
+ def handle_disable_sms_reverification!
+ authorize(target_account.user, :disable_sms_reverification?)
+ log_action(:disable_sms_reverification, target_account.user)
+ user = target_account.user
+ UserSmsReverificationRequired.find(user.id)&.destroy if user
end
+ def handle_enable_feature!
+ authorize(target_account.user, :enable_feature?)
+ feature_flag = ::Configuration::FeatureFlag.find_by!(name: feature_name)
+ ::Configuration::AccountEnabledFeature.create!(account_id: target_account.id, feature_flag: feature_flag)
+ end
+
+ def text_for_warning
+ [warning_preset&.text, text].compact.join("\n\n")
+ end
+
def process_queue!
- queue_suspension_worker! if type == 'suspend'
+ case type
+ when 'suspend', 'ban'
+ Admin::SuspensionWorker.perform_async(target_account.id)
+ when 'unsuspend'
+ Admin::UnsuspensionWorker.perform_async(target_account.id)
+ end
end
def process_email!
@@ -272,5 +310,15 @@
def account_suspension_policy
@account_suspension_policy ||= AccountSuspensionPolicy.new(target_account)
+ end
+
+ def remove_scheduled_unsuspensions
+ queue = Sidekiq::ScheduledSet.new
+ jobs = queue.select { |job| job.klass == 'Admin::UnsuspensionWorker' && job.args[0] == target_account.id }
+ jobs.each(&:delete)
+ end
+
+ def revoke_access_tokens(target_account)
+ OauthAccessToken.where(resource_owner_id: target_account.user.id).update_all(revoked_at: Time.now.utc)
end
end
diff -ru truth-old/opensource/app/models/announcement.rb truth-new/opensource/app/models/announcement.rb
--- truth-old/opensource/app/models/announcement.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/announcement.rb 2024-04-01 14:59:13
@@ -77,7 +77,7 @@
end
end
- ActiveRecord::Associations::Preloader.new.preload(records, :custom_emoji)
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status])
records
end
diff -ru truth-old/opensource/app/models/application_record.rb truth-new/opensource/app/models/application_record.rb
--- truth-old/opensource/app/models/application_record.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/application_record.rb 2024-04-01 14:59:13
@@ -7,7 +7,7 @@
class << self
def update_index(_type_name, *_args, &_block)
- super if Chewy.enabled?
+ super if Chewy.indexing_enabled?
end
end
Only in truth-new/opensource/app/models: avatars_carousel.rb
Only in truth-new/opensource/app/models: banned_word.rb
Only in truth-new/opensource/app/models: blocked_link.rb
diff -ru truth-old/opensource/app/models/bookmark.rb truth-new/opensource/app/models/bookmark.rb
--- truth-old/opensource/app/models/bookmark.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/bookmark.rb 2024-04-01 14:59:13
@@ -13,7 +13,7 @@
class Bookmark < ApplicationRecord
include Paginable
- update_index('statuses#status', :status) if Chewy.enabled?
+ update_index('statuses', :status) if Chewy.indexing_enabled?
belongs_to :account, inverse_of: :bookmarks
belongs_to :status, inverse_of: :bookmarks
Only in truth-new/opensource/app/models: chat.rb
Only in truth-new/opensource/app/models: chat_event.rb
Only in truth-new/opensource/app/models: chat_member.rb
Only in truth-new/opensource/app/models: chat_member_removal.rb
Only in truth-new/opensource/app/models: chat_message.rb
Only in truth-new/opensource/app/models: chat_message_hidden.rb
Only in truth-new/opensource/app/models: chat_message_reaction.rb
Only in truth-new/opensource/app/models: chat_search_result.rb
Only in truth-new/opensource/app/models: city.rb
diff -ru truth-old/opensource/app/models/concerns/account_associations.rb truth-new/opensource/app/models/concerns/account_associations.rb
--- truth-old/opensource/app/models/concerns/account_associations.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/account_associations.rb 2024-04-12 09:09:08
@@ -30,7 +30,7 @@
# Media
has_many :media_attachments, dependent: :destroy
- has_many :polls, dependent: :destroy
+ has_many :polls, dependent: :destroy, through: :statuses
# Report relationships
has_many :reports, dependent: :destroy, inverse_of: :account
@@ -66,5 +66,27 @@
# Follow recommendations
has_one :follow_recommendation_suppression, inverse_of: :account, dependent: :destroy
+
+ # Chats
+ has_many :chat_accounts
+ has_many :chats, -> { distinct }, through: :chat_accounts
+ has_many :chat_messages
+
+ # Groups
+ has_many :group_memberships
+ has_many :group_mutes
+
+ has_one :tv_account
+ has_one :tv_channel_account
+ has_and_belongs_to_many :tv_channels, join_table: 'tv.channel_accounts', association_foreign_key: 'channel_id', inverse_of: :account
+ # Feeds
+ has_many :account_feeds, class_name: 'Feeds::AccountFeed'
+ has_many :feeds, through: :account_feeds
+
+ # Recommendation suppressions
+ has_many :group_recommendation_suppressions, class_name: 'Recommendations::GroupSuppression'
+ has_many :account_recommendation_suppressions, class_name: 'Recommendations::AccountSuppression'
+ # Features
+ has_and_belongs_to_many :feature_flags, class_name: 'Configuration::FeatureFlag', join_table: 'configuration.account_enabled_features', association_foreign_key: 'feature_flag_id', inverse_of: :account
end
end
diff -ru truth-old/opensource/app/models/concerns/account_counters.rb truth-new/opensource/app/models/concerns/account_counters.rb
--- truth-old/opensource/app/models/concerns/account_counters.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/account_counters.rb 2024-04-01 14:59:13
@@ -3,85 +3,30 @@
module AccountCounters
extend ActiveSupport::Concern
- ALLOWED_COUNTER_KEYS = %i(statuses_count following_count followers_count).freeze
-
included do
- has_one :account_stat, inverse_of: :account
- after_save :save_account_stat
+ has_one :account_follower, class_name: AccountFollowerStatistic.name, inverse_of: :account
+ has_one :account_following, class_name: AccountFollowingStatistic.name, inverse_of: :account
+ has_one :account_status, class_name: AccountStatusStatistic.name, inverse_of: :account
+
end
- delegate :statuses_count,
- :statuses_count=,
- :following_count,
- :following_count=,
- :followers_count,
- :followers_count=,
- :last_status_at,
- to: :account_stat
-
- # @param [Symbol] key
- def increment_count!(key)
- update_count!(key, 1)
+ def followers_count
+ account_follower&.followers_count || 0
end
- # @param [Symbol] key
- def decrement_count!(key)
- update_count!(key, -1)
+ def following_count
+ account_following&.following_count || 0
end
- # @param [Symbol] key
- # @param [Integer] value
- def update_count!(key, value)
- raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key)
- raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id)
-
- value = value.to_i
- default_value = value.positive? ? value : 0
-
- # We do an upsert using manually written SQL, as Rails' upsert method does
- # not seem to support writing expressions in the UPDATE clause, but only
- # re-insert the provided values instead.
- # Even ARel seem to be missing proper handling of upserts.
- sql = if value.positive? && key == :statuses_count
- <<-SQL.squish
- INSERT INTO account_stats(account_id, #{key}, created_at, updated_at, last_status_at)
- VALUES (:account_id, :default_value, now(), now(), now())
- ON CONFLICT (account_id) DO UPDATE
- SET #{key} = account_stats.#{key} + :value,
- last_status_at = now(),
- updated_at = now()
- RETURNING id;
- SQL
- else
- <<-SQL.squish
- INSERT INTO account_stats(account_id, #{key}, created_at, updated_at)
- VALUES (:account_id, :default_value, now(), now())
- ON CONFLICT (account_id) DO UPDATE
- SET #{key} = account_stats.#{key} + :value,
- updated_at = now()
- RETURNING id;
- SQL
- end
-
- sql = AccountStat.sanitize_sql([sql, account_id: id, default_value: default_value, value: value])
- account_stat_id = AccountStat.connection.exec_query(sql)[0]['id']
-
- # Reload account_stat if it was loaded, taking into account newly-created unsaved records
- if association(:account_stat).loaded?
- account_stat.id = account_stat_id if account_stat.new_record?
- account_stat.reload
- end
+ def statuses_count
+ account_status&.statuses_count || 0
end
- def account_stat
- super || build_account_stat
+ def last_status_at
+ account_status&.last_status_at
end
- private
-
- def save_account_stat
- return unless association(:account_stat).loaded? && account_stat&.changed?
-
- account_stat.save
+ def last_following_status_at
+ account_status&.last_following_status_at
end
end
diff -ru truth-old/opensource/app/models/concerns/account_interactions.rb truth-new/opensource/app/models/concerns/account_interactions.rb
--- truth-old/opensource/app/models/concerns/account_interactions.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/account_interactions.rb 2024-04-01 14:59:13
@@ -80,6 +80,7 @@
has_many :following, -> { order('follows.id desc') }, through: :active_relationships, source: :target_account
has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
+ has_many :followers_unordered, through: :passive_relationships, source: :account
# Block relationships
has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
@@ -95,6 +96,10 @@
has_many :conversation_mutes, dependent: :destroy
has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
has_many :announcement_mutes, dependent: :destroy
+
+ # Group relationships
+ has_many :group_membership_requests, dependent: :destroy
+ has_many :group_account_blocks, dependent: :destroy
end
def follow!(other_account, reblogs: nil, notify: nil, uri: nil, rate_limit: false, bypass_limit: false)
@@ -127,6 +132,7 @@
def block!(other_account, uri: nil)
remove_potential_friendship(other_account)
+ remove_follower_interactions(other_account)
block_relationships.create_with(uri: uri)
.find_or_create_by!(target_account: other_account)
end
@@ -138,6 +144,7 @@
mute.save!
remove_potential_friendship(other_account)
+ remove_follower_interactions(other_account)
# When toggling a mute between hiding and allowing notifications, the mute will already exist, so the find_or_create_by! call will return the existing Mute without updating the hide_notifications attribute. Therefore, we check that hide_notifications? is what we want and set it if it isn't.
if mute.hide_notifications? != notifications
@@ -251,26 +258,6 @@
.where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)
end
- def remote_followers_hash(url_prefix)
- Rails.cache.fetch("followers_hash:#{id}:#{url_prefix}") do
- digest = "\x00" * 32
- followers.where(Account.arel_table[:uri].matches("#{url_prefix}%", false, true)).pluck_each(:uri) do |uri|
- Xorcist.xor!(digest, Digest::SHA256.digest(uri))
- end
- digest.unpack('H*')[0]
- end
- end
-
- def local_followers_hash
- Rails.cache.fetch("followers_hash:#{id}:local") do
- digest = "\x00" * 32
- followers.where(domain: nil).pluck_each(:username) do |username|
- Xorcist.xor!(digest, Digest::SHA256.digest(ActivityPub::TagManager.instance.uri_for_username(username)))
- end
- digest.unpack('H*')[0]
- end
- end
-
def whale_following
following.where(whale: true)
end
@@ -278,7 +265,13 @@
private
def remove_potential_friendship(other_account, mutual = false)
- PotentialFriendshipTracker.remove(id, other_account.id)
- PotentialFriendshipTracker.remove(other_account.id, id) if mutual
+ InteractionsTracker.new(id, other_account.id).remove
+ InteractionsTracker.new(other_account.id, id).remove if mutual
+ end
+
+ def remove_follower_interactions(other_account)
+ InteractionsTracker.new(id, other_account.id, false, true).remove
+ Redis.current.del("avatars_carousel_list_#{id}")
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", id)
end
end
diff -ru truth-old/opensource/app/models/concerns/account_merging.rb truth-new/opensource/app/models/concerns/account_merging.rb
--- truth-old/opensource/app/models/concerns/account_merging.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/account_merging.rb 2024-04-01 14:59:13
@@ -12,16 +12,17 @@
# to check for (and skip past) uniqueness errors
owned_classes = [
- Status, StatusPin, MediaAttachment, Poll, Report, Tombstone, Favourite,
+ Status, StatusPin, MediaAttachment, Report, Tombstone, Favourite,
Follow, FollowRequest, Block, Mute, AccountIdentityProof,
- AccountModerationNote, AccountPin, AccountStat, ListAccount,
- PollVote, Mention, AccountDeletionRequest, AccountNote, FollowRecommendationSuppression
+ AccountModerationNote, AccountPin, AccountFollowerStatistic,
+ AccountFollowingStatistic, AccountStatusStatistic, ListAccount,
+ Mention, AccountDeletionRequest, AccountNote, FollowRecommendationSuppression
]
owned_classes.each do |klass|
klass.where(account_id: other_account.id).find_each do |record|
record.update_attribute(:account_id, id)
- rescue ActiveRecord::RecordNotUnique
+ rescue
next
end
end
@@ -45,7 +46,7 @@
# Some follow relationships have moved, so the cache is stale
Rails.cache.delete_matched("followers_hash:#{id}:*")
- Rails.cache.delete_matched("relationships:#{id}:*")
- Rails.cache.delete_matched("relationships:*:#{id}")
+ Rails.cache.delete_matched("relationships/#{id}/*")
+ Rails.cache.delete_matched("relationships/*/#{id}")
end
end
diff -ru truth-old/opensource/app/models/concerns/attachmentable.rb truth-new/opensource/app/models/concerns/attachmentable.rb
--- truth-old/opensource/app/models/concerns/attachmentable.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/attachmentable.rb 2024-04-01 14:59:13
@@ -20,10 +20,10 @@
).freeze
included do
- before_post_process :obfuscate_file_name
- before_post_process :set_file_extensions
- before_post_process :check_image_dimensions
- before_post_process :set_file_content_type
+ before_validation :obfuscate_file_name
+ before_validation :set_file_extensions
+ before_validation :check_image_dimensions
+ before_validation :set_file_content_type
end
private
Only in truth-new/opensource/app/models/concerns: group_counters.rb
Only in truth-new/opensource/app/models/concerns: group_relationship_cacheable.rb
diff -ru truth-old/opensource/app/models/concerns/paginable.rb truth-new/opensource/app/models/concerns/paginable.rb
--- truth-old/opensource/app/models/concerns/paginable.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/paginable.rb 2024-04-01 14:59:13
@@ -28,5 +28,11 @@
paginate_by_max_id(limit, options[:max_id], options[:since_id]).to_a
end
end
+
+ def self.paginate_by_limit_offset(limit, params)
+ query = limit(limit)
+ query = query.offset(params[:offset]) if params[:offset].present?
+ query
+ end
end
end
Only in truth-new/opensource/app/models/concerns: queriable.rb
diff -ru truth-old/opensource/app/models/concerns/rate_limitable.rb truth-new/opensource/app/models/concerns/rate_limitable.rb
--- truth-old/opensource/app/models/concerns/rate_limitable.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/rate_limitable.rb 2023-05-05 13:42:02
@@ -14,7 +14,11 @@
def rate_limiter(by, options = {})
return @rate_limiter if defined?(@rate_limiter)
- @rate_limiter = RateLimiter.new(by, options)
+ @rate_limiter = if by.is_a?(Account) && by.trust_level == Account::TRUST_LEVELS[:hostile]
+ HostileRateLimiter.new(by, options)
+ else
+ RateLimiter.new(by, options)
+ end
end
class_methods do
diff -ru truth-old/opensource/app/models/concerns/relationship_cacheable.rb truth-new/opensource/app/models/concerns/relationship_cacheable.rb
--- truth-old/opensource/app/models/concerns/relationship_cacheable.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/relationship_cacheable.rb 2024-04-01 14:59:13
@@ -10,7 +10,7 @@
private
def remove_relationship_cache
- Rails.cache.delete("relationship:#{account_id}:#{target_account_id}")
- Rails.cache.delete("relationship:#{target_account_id}:#{account_id}")
+ Rails.cache.delete("relationship/#{account_id}/#{target_account_id}")
+ Rails.cache.delete("relationship/#{target_account_id}/#{account_id}")
end
end
diff -ru truth-old/opensource/app/models/concerns/remotable.rb truth-new/opensource/app/models/concerns/remotable.rb
--- truth-old/opensource/app/models/concerns/remotable.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/remotable.rb 2024-04-01 14:59:13
@@ -28,11 +28,11 @@
public_send("#{attachment_name}=", ResponseWithLimit.new(response, limit))
end
rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
- Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
+ Rails.logger.info "Error fetching remote #{attachment_name}: #{e}"
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present?
raise e unless suppress_errors && !should_raise_error(parsed_url.host)
rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e
- Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
+ Rails.logger.info "Error fetching remote #{attachment_name}: #{e}"
public_send("#{attachment_name}=", nil) if public_send("#{attachment_name}_file_name").present?
end
diff -ru truth-old/opensource/app/models/concerns/status_threading_concern.rb truth-new/opensource/app/models/concerns/status_threading_concern.rb
--- truth-old/opensource/app/models/concerns/status_threading_concern.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/concerns/status_threading_concern.rb 2024-04-01 14:59:13
@@ -2,151 +2,16 @@
module StatusThreadingConcern
extend ActiveSupport::Concern
- include Redisable
def ancestors(limit, account = nil)
- find_statuses_from_tree_path(ancestor_ids(limit), account)
+ StatusRepliesV1.new(self).ancestors(limit, account)
end
def descendants(limit, account = nil, offset = 0, max_child_id = nil, since_child_id = nil, depth = nil)
- find_statuses_from_tree_path(descendant_ids(limit, offset, max_child_id, since_child_id, depth), account, promote: true)
+ StatusRepliesV1.new(self).descendants(limit, account, offset, max_child_id, since_child_id, depth)
end
def self_replies(limit)
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit)
- end
-
- private
-
- def ancestor_ids(limit)
- key = "ancestors:#{id}"
- ancestors = Rails.cache.fetch(key)
-
- if ancestors.nil? || ancestors[:limit] < limit
- ids = ancestor_statuses(limit).pluck(:id).reverse!
- Rails.cache.write key, limit: limit, ids: ids
- ids
- else
- ancestors[:ids].last(limit)
- end
- end
-
- def ancestor_statuses(limit)
- Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id, limit: limit])
- WITH RECURSIVE search_tree(id, in_reply_to_id, path)
- AS (
- SELECT id, in_reply_to_id, ARRAY[id]
- FROM statuses
- WHERE id = :id
- UNION ALL
- SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id
- FROM search_tree
- JOIN statuses ON statuses.id = search_tree.in_reply_to_id
- WHERE NOT statuses.id = ANY(path)
- )
- SELECT id
- FROM search_tree
- ORDER BY path
- LIMIT :limit
- SQL
- end
-
- def descendant_ids(limit, offset, max_child_id, since_child_id, depth)
- key = "descendants:#{conversation_id}"
- field = "#{id}:#{limit}:#{offset}"
- if (cached_descendants = get_descendants_from_cache(key, field))
- cached_descendants
- else
- ids = descendant_statuses(limit, offset, max_child_id, since_child_id, depth).pluck(:id)
- redis.hset(key, field, ids.to_json)
- redis.expire(key, 1.hour.seconds)
- ids
- end
- end
-
- def descendant_statuses(limit, offset, max_child_id, since_child_id, depth)
- # use limit + 1 and depth + 1 because 'self' is included
- depth += 1 if depth.present?
- offset += 1 if offset.present?
-
- descendants_with_self = Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, offset: offset, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth])
- WITH RECURSIVE search_tree(id, path)
- AS (
- SELECT id, ARRAY[id]
- FROM statuses
- WHERE id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE)
- UNION ALL
- SELECT statuses.id, path || statuses.id
- FROM search_tree
- JOIN statuses ON statuses.in_reply_to_id = search_tree.id
- WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path)
- )
- SELECT id
- FROM search_tree
- ORDER BY path
- OFFSET :offset
- LIMIT :limit
- SQL
-
- descendants_with_self
- end
-
- def find_statuses_from_tree_path(ids, account, promote: false)
- statuses = Status.with_accounts(ids).to_a
- account_ids = statuses.map(&:account_id).uniq
- domains = statuses.filter_map(&:account_domain).uniq
- relations = relations_map_for_account(account, account_ids, domains)
-
- statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? }
-
- # Order ancestors/descendants by tree path
- statuses.sort_by! { |status| ids.index(status.id) }
-
- # Bring self-replies to the top
- if promote
- promote_by!(statuses) { |status| status.in_reply_to_account_id == status.account_id }
- else
- statuses
- end
- end
-
- def promote_by!(arr)
- insert_at = arr.find_index { |item| !yield(item) }
-
- return arr if insert_at.nil?
-
- arr.each_with_index do |item, index|
- next if index <= insert_at || !yield(item)
-
- arr.insert(insert_at, arr.delete_at(index))
- insert_at += 1
- end
-
- arr
- end
-
- def relations_map_for_account(account, account_ids, domains)
- return {} if account.nil?
-
- {
- blocking: Account.blocking_map(account_ids, account.id),
- blocked_by: Account.blocked_by_map(account_ids, account.id),
- muting: Account.muting_map(account_ids, account.id),
- following: Account.following_map(account_ids, account.id),
- domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
- }
- end
-
- def get_descendants_from_cache(key, field)
- cached_descendants_raw = redis.hget(key, field)
-
- return if cached_descendants_raw.nil?
-
- begin
- parsed = JSON.parse(cached_descendants_raw)
- parsed.is_a?(Array) ? parsed : false
- rescue JSON::ParserError
- false
- end
end
end
Only in truth-new/opensource/app/models/concerns: status_threading_concern_v2.rb
Only in truth-new/opensource/app/models: configuration
Only in truth-new/opensource/app/models: country.rb
Only in truth-new/opensource/app/models: current.rb
diff -ru truth-old/opensource/app/models/device.rb truth-new/opensource/app/models/device.rb
--- truth-old/opensource/app/models/device.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/device.rb 2024-04-01 14:59:13
@@ -15,7 +15,7 @@
#
class Device < ApplicationRecord
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken'
+ belongs_to :access_token, class_name: 'OauthAccessToken'
belongs_to :account
has_many :one_time_keys, dependent: :destroy, inverse_of: :device
Only in truth-new/opensource/app/models: device_verification.rb
Only in truth-new/opensource/app/models: device_verification_chat_message.rb
Only in truth-new/opensource/app/models: device_verification_favourite.rb
Only in truth-new/opensource/app/models: device_verification_registration.rb
Only in truth-new/opensource/app/models: device_verification_status.rb
Only in truth-new/opensource/app/models: device_verification_user.rb
diff -ru truth-old/opensource/app/models/email_domain_block.rb truth-new/opensource/app/models/email_domain_block.rb
--- truth-old/opensource/app/models/email_domain_block.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/email_domain_block.rb 2024-04-01 14:59:13
@@ -3,11 +3,12 @@
#
# Table name: email_domain_blocks
#
-# id :bigint(8) not null, primary key
-# domain :string default(""), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# parent_id :bigint(8)
+# id :bigint(8) not null, primary key
+# domain :string default(""), not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# parent_id :bigint(8)
+# disposable :boolean
#
class EmailDomainBlock < ApplicationRecord
@@ -17,6 +18,7 @@
has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
validates :domain, presence: true, uniqueness: true, domain: true
+ validate :real_domain
def with_dns_records=(val)
@with_dns_records = ActiveModel::Type::Boolean.new.cast(val)
@@ -28,6 +30,16 @@
alias with_dns_records with_dns_records?
+ def with_domain_validation=(val)
+ @with_domain_validation = ActiveModel::Type::Boolean.new.cast(val)
+ end
+
+ def with_domain_validation?
+ @with_domain_validation
+ end
+
+ alias with_domain_validation with_domain_validation?
+
def self.block?(email)
_, domain = email.split('@', 2)
@@ -40,5 +52,19 @@
end
where(domain: domain).exists?
+ end
+
+ private
+
+ def real_domain
+ if @with_domain_validation && domain.present? && !domain.nil?
+ begin
+ if Addressable::URI.parse("http://#{domain}").domain.nil?
+ errors.add(:domain, 'is invalid domain')
+ end
+ rescue Addressable::URI::InvalidURIError
+ errors.add(:domain, 'is invalid domain')
+ end
+ end
end
end
Only in truth-new/opensource/app/models: external_ad.rb
diff -ru truth-old/opensource/app/models/favourite.rb truth-new/opensource/app/models/favourite.rb
--- truth-old/opensource/app/models/favourite.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/favourite.rb 2024-04-01 14:59:13
@@ -12,8 +12,9 @@
class Favourite < ApplicationRecord
include Paginable
+ extend Queriable
- update_index('statuses#status', :status)
+ update_index('statuses', :status)
belongs_to :account, inverse_of: :favourites
belongs_to :status, inverse_of: :favourites
@@ -24,19 +25,5 @@
before_validation do
self.status = status.reblog if status&.reblog?
- end
-
- after_create :increment_cache_counters
- after_destroy :decrement_cache_counters
-
- private
-
- def increment_cache_counters
- status&.increment_count!(:favourites_count)
- end
-
- def decrement_cache_counters
- return if association(:status).loaded? && status.marked_for_destruction?
- status&.decrement_count!(:favourites_count)
end
end
diff -ru truth-old/opensource/app/models/feed.rb truth-new/opensource/app/models/feed.rb
--- truth-old/opensource/app/models/feed.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/feed.rb 2024-04-01 14:59:13
@@ -17,18 +17,28 @@
@fanout_key = feed_key(@type, @account.id)
status_ids = !@whale_following.empty? && @type == :home ? get_with_whales : get_fanout_only
- Status.where(id: status_ids).cache_ids
+
+ Status.
+ where(id: status_ids).
+ where.not(visibility: "self").
+ or(Status.where(id: status_ids).where(account_id: @account.id)).
+ cache_ids
end
+ def clear!
+ key_to_clear = feed_key(@type, @account.id)
+ redis_timelines.del(key_to_clear)
+ end
+
protected
def get_fanout_only
@max_id = '+inf' if @max_id.blank?
if @min_id.blank?
@since_id = '-inf' if @since_id.blank?
- redis_timelines.zrevrangebyscore(@fanout_key, "(#{@max_id}", "(#{@since_id}", limit: [0, @limit], with_scores: true).map(&:first).map(&:to_i)
+ FeedManager.instance.status_ids_to_plain_numbers(redis_timelines.zrevrangebyscore(@fanout_key, "(#{@max_id}", "(#{@since_id}", limit: [0, @limit], with_scores: true).map(&:first)).map(&:to_i)
else
- redis_timelines.zrangebyscore(@fanout_key, "(#{@min_id}", "(#{@max_id}", limit: [0, @limit], with_scores: true).map(&:first).map(&:to_i)
+ FeedManager.instance.status_ids_to_plain_numbers(redis_timelines.zrangebyscore(@fanout_key, "(#{@min_id}", "(#{@max_id}", limit: [0, @limit], with_scores: true).map(&:first)).map(&:to_i)
end
end
@@ -63,7 +73,7 @@
pipeline.zrange(feed_key(:whale, account_id), '0', '-1')
end
end
- subsets.flatten.map(&:to_i)
+ FeedManager.instance.status_ids_to_plain_numbers(subsets.flatten).map(&:to_i)
end
def whale_following
Only in truth-new/opensource/app/models: feeds
diff -ru truth-old/opensource/app/models/follow.rb truth-new/opensource/app/models/follow.rb
--- truth-old/opensource/app/models/follow.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/follow.rb 2024-04-01 14:59:13
@@ -40,10 +40,8 @@
end
before_validation :set_uri, only: :create
- after_create :increment_cache_counters
after_create :invalidate_hash_cache
after_destroy :remove_endorsements
- after_destroy :decrement_cache_counters
after_destroy :invalidate_hash_cache
private
@@ -54,16 +52,6 @@
def remove_endorsements
AccountPin.where(target_account_id: target_account_id, account_id: account_id).delete_all
- end
-
- def increment_cache_counters
- account&.increment_count!(:following_count)
- target_account&.increment_count!(:followers_count)
- end
-
- def decrement_cache_counters
- account&.decrement_count!(:following_count)
- target_account&.decrement_count!(:followers_count)
end
def invalidate_hash_cache
Only in truth-new/opensource/app/models: follow_delete.rb
Only in truth-new/opensource/app/models: follow_suggestion.rb
diff -ru truth-old/opensource/app/models/form/account_batch.rb truth-new/opensource/app/models/form/account_batch.rb
--- truth-old/opensource/app/models/form/account_batch.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/form/account_batch.rb 2024-04-01 14:59:13
@@ -81,7 +81,16 @@
records = accounts.includes(:user)
records.each { |account| authorize(account.user, :reject?) }
- .each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) }
+ .each do |account|
+ DeleteAccountService.new.call(
+ account,
+ current_account.id,
+ deletion_type: 'account_batch_reject',
+ reserve_email: false,
+ reserve_username: false,
+ skip_activitypub: true,
+ )
+ end
end
def suppress_follow_recommendation!
diff -ru truth-old/opensource/app/models/form/status_batch.rb truth-new/opensource/app/models/form/status_batch.rb
--- truth-old/opensource/app/models/form/status_batch.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/form/status_batch.rb 2024-04-01 14:59:13
@@ -34,8 +34,9 @@
def delete_statuses
Status.where(id: status_ids).reorder(nil).find_each do |status|
+ status.reblogs.update_all(deleted_at: Time.current, deleted_by_id: current_account.id)
status.update!(deleted_at: Time.current, deleted_by_id: current_account.id)
- RemovalWorker.perform_async(status.id, immediate: true)
+ RemovalWorker.perform_async(status.id, immediate: false)
Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true)
log_action :destroy, status
end
Only in truth-new/opensource/app/models: group.rb
Only in truth-new/opensource/app/models: group_account_block.rb
Only in truth-new/opensource/app/models: group_avatar.rb
Only in truth-new/opensource/app/models: group_deletion_request.rb
Only in truth-new/opensource/app/models: group_feed.rb
Only in truth-new/opensource/app/models: group_filter.rb
Only in truth-new/opensource/app/models: group_header.rb
Only in truth-new/opensource/app/models: group_membership.rb
Only in truth-new/opensource/app/models: group_membership_request.rb
Only in truth-new/opensource/app/models: group_mute.rb
Only in truth-new/opensource/app/models: group_stat.rb
Only in truth-new/opensource/app/models: group_suggestion.rb
Only in truth-new/opensource/app/models: group_suggestion_delete.rb
Only in truth-new/opensource/app/models: group_tag.rb
Only in truth-new/opensource/app/models: groups_carousel.rb
Only in truth-new/opensource/app/models: groups_feed.rb
Only in truth-new/opensource/app/models: link.rb
Only in truth-new/opensource/app/models: logs
diff -ru truth-old/opensource/app/models/media_attachment.rb truth-new/opensource/app/models/media_attachment.rb
--- truth-old/opensource/app/models/media_attachment.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/media_attachment.rb 2024-04-12 09:09:12
@@ -27,12 +27,13 @@
# thumbnail_updated_at :datetime
# thumbnail_remote_url :string
# external_video_id :string
+# file_s3_host :string(64)
#
class MediaAttachment < ApplicationRecord
self.inheritance_column = nil
- enum type: [:image, :gifv, :video, :unknown, :audio]
+ enum type: [:image, :gifv, :video, :unknown, :audio, :tv]
enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true
MAX_DESCRIPTION_LENGTH = 1_500
@@ -48,10 +49,10 @@
small
).freeze
- IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze
- VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
- VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
- AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
+ IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif).freeze
+ VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg application/octet-stream).freeze
+ VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime application/octet-stream).freeze
+ AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/ogg audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
BLURHASH_OPTIONS = {
x_comp: 4,
@@ -152,13 +153,14 @@
}.freeze
IMAGE_LIMIT = 10.megabytes
- VIDEO_LIMIT = 40.megabytes
+ VIDEO_LIMIT = 450.megabytes
- MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
- MAX_VIDEO_FRAME_RATE = 60
+ MAX_VIDEO_MATRIX_LIMIT = 8_294_400 # 3840 x 2160
+ MAX_VIDEO_FRAME_RATE = 60
+ MAX_VIDEO_DURATION_LIMIT = 900 # 900 seconds
- belongs_to :account, inverse_of: :media_attachments, optional: true
- belongs_to :status, inverse_of: :media_attachments, optional: true
+ belongs_to :account, inverse_of: :media_attachments, optional: true
+ belongs_to :status, inverse_of: :media_attachments, optional: true
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
has_many :moderation_records
@@ -169,13 +171,14 @@
convert_options: GLOBAL_CONVERT_OPTIONS
before_file_post_process :set_type_and_extension
- before_file_post_process :do_not_process_video_files
+ before_file_post_process :check_video_dimensions
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
- validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format?
- validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format?
+ validates_attachment_size :file, less_than: ->(m) { m.larger_media_format? ? VIDEO_LIMIT : IMAGE_LIMIT }
remotable_attachment :file, VIDEO_LIMIT, suppress_errors: false, download_on_assign: false, attribute_name: :remote_url
+ remotable_attachment :file, IMAGE_LIMIT
+
has_attached_file :thumbnail,
styles: THUMBNAIL_STYLES,
processors: [:lazy_thumbnail, :blurhash_transcoder, :color_extractor],
@@ -183,7 +186,7 @@
validates_attachment_content_type :thumbnail, content_type: IMAGE_MIME_TYPES
validates_attachment_size :thumbnail, less_than: IMAGE_LIMIT
- remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false
+ remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true
include Attachmentable
@@ -193,7 +196,7 @@
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) }
- scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) }
+ scope :unattached, -> { select('*').from('(select a.* from media_attachments a where a.status_id is null and a.scheduled_status_id is null and not exists (select 1 from chats.message_media_attachments m where m.media_attachment_id = a.id)) as media_attachments') }
scope :local, -> { where(remote_url: '') }
scope :remote, -> { where.not(remote_url: '') }
scope :cached, -> { remote.where.not(file_file_name: nil) }
@@ -255,10 +258,13 @@
end
after_commit :enqueue_processing, on: :create
+ after_commit :publish_event, if: -> { saved_change_to_processing?(to: 'complete') }
after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local?
before_create :set_shortcode
+ before_save :set_file_s3_host
+
before_create :set_processing
after_post_process :set_meta
@@ -275,7 +281,9 @@
private
def file_styles(attachment)
- if attachment.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(attachment.instance.file_content_type)
+ # only convert gifs to video. Don't convert hevc and webm videos
+ # Let those fall to the video_styles where they will just be passthrough encoded
+ if attachment.instance.file_content_type == 'image/gif' # || VIDEO_CONVERTIBLE_MIME_TYPES.include?(attachment.instance.file_content_type)
VIDEO_CONVERTED_STYLES
elsif IMAGE_MIME_TYPES.include?(attachment.instance.file_content_type)
IMAGE_STYLES
@@ -312,6 +320,10 @@
end
end
+ def set_file_s3_host
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name]
+ end
+
def prepare_description
self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil?
end
@@ -354,6 +366,10 @@
file.instance_write :meta, populate_meta
end
+ def publish_event
+ EventProvider::EventProvider.new('asset.created', ::AssetCreatedEvent, self).call
+ end
+
def populate_meta
meta = (file.instance_read(:meta) || {}).with_indifferent_access.slice(*META_KEYS)
@@ -402,10 +418,10 @@
def enqueue_processing
if video?
- self.processing = :complete
- self.save
- else
- PostProcessMediaWorker.perform_async(id) if delay_processing?
+ self.processing = :queued
+ save
+ elsif delay_processing?
+ PostProcessMediaWorker.perform_async(id)
end
end
diff -ru truth-old/opensource/app/models/notification.rb truth-new/opensource/app/models/notification.rb
--- truth-old/opensource/app/models/notification.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/notification.rb 2024-04-01 14:59:13
@@ -15,6 +15,8 @@
#
class Notification < ApplicationRecord
+ self.primary_key = :id
+
self.inheritance_column = nil
include Paginable
@@ -27,7 +29,7 @@
'FollowRequest' => :follow_request,
'Favourite' => :favourite,
'Poll' => :poll,
- 'User' => :user_approved,
+ 'User' => :user_approved
}.freeze
TYPES = %i(
@@ -41,10 +43,27 @@
poll
user_approved
verify_sms_prompt
+ chat
+ chat_message
+ chat_message_deleted
mention_group
reblog_group
follow_group
favourite_group
+ group_favourite
+ group_favourite_group
+ group_reblog
+ group_reblog_group
+ group_mention
+ group_mention_group
+ group_approval
+ group_delete
+ group_role
+ group_request
+ group_request_group
+ group_accepted
+ group_promoted
+ group_demoted
).freeze
TARGET_STATUS_INCLUDES_BY_TYPE = {
@@ -59,6 +78,12 @@
poll: [poll: :status],
user_approved: :user,
verify_sms_prompt: :user,
+ group_favourite: [favourite: :status],
+ group_favourite_group: [favourite: :status],
+ group_reblog: [status: :reblog],
+ group_reblog_group: [status: :reblog],
+ group_mention: [mention: :status],
+ group_mention_group: [mention: :status]
}.freeze
attr_accessor :passed_from_account
@@ -75,41 +100,61 @@
belongs_to :favourite, foreign_key: 'activity_id', optional: true
belongs_to :poll, foreign_key: 'activity_id', optional: true
belongs_to :user, foreign_key: 'activity_id', optional: true
+ belongs_to :group, foreign_key: 'activity_id', optional: true
+ belongs_to :group_membership_request, foreign_key: 'activity_id', optional: true
validates :type, inclusion: { in: TYPES }
scope :without_suspended, -> { joins(:from_account).merge(Account.without_suspended) }
- scope :browserable, ->(exclude_types = [], account_id = nil) {
- types = TYPES - exclude_types.map(&:to_sym)
-
- if account_id.nil?
- where(type: types)
- else
- where(type: types, from_account_id: account_id)
- end
- }
-
def type
@type ||= (super || LEGACY_TYPE_CLASS_MAP[activity_type]).to_sym
end
def target_status
case type
- when :status, :favourite_group, :mention_group, :reblog_group
+ when :status, :favourite_group, :mention_group, :reblog_group, :group_favourite_group, :group_mention_group, :group_reblog_group
status
- when :reblog
+ when :reblog, :group_reblog
status&.reblog
- when :favourite
+ when :favourite, :group_favourite
favourite&.status
- when :mention
+ when :mention, :group_mention
mention&.status
when :poll
poll&.status
end
end
+ def target_group
+ case type
+ when :group_approval, :group_promoted, :group_demoted, :group_delete
+ group
+ when :group_request
+ group_membership_request
+ end
+ end
+
class << self
+ def browserable(types: [], exclude_types: [], from_account_id: nil)
+ requested_types = begin
+ if types.empty?
+ TYPES
+ else
+ types.map(&:to_sym) & TYPES
+ end
+ end
+
+ exclude_types_with_groups = exclude_types.clone
+ exclude_types.each { |n| exclude_types_with_groups << "#{n}_group"}
+ requested_types -= exclude_types_with_groups.map(&:to_sym)
+
+ all.tap do |scope|
+ scope.merge!(where(from_account_id: from_account_id)) if from_account_id.present?
+ scope.merge!(where(type: requested_types)) unless requested_types.size == TYPES.size
+ end
+ end
+
def preload_cache_collection_target_statuses(notifications, &_block)
notifications.group_by(&:type).each do |type, grouped_notifications|
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
@@ -130,13 +175,13 @@
cached_status = cached_statuses_by_id[notification.target_status.id]
case notification.type
- when :status, :favourite_group, :mention_group, :reblog_group
+ when :status, :favourite_group, :mention_group, :reblog_group, :group_favourite_group, :group_mention_group, :group_reblog_group
notification.status = cached_status
- when :reblog
+ when :reblog, :group_reblog
notification.status.reblog = cached_status
- when :favourite
+ when :favourite, :group_favourite
notification.favourite.status = cached_status
- when :mention
+ when :mention, :group_mention
notification.mention.status = cached_status
when :poll
notification.poll.status = cached_status
@@ -145,6 +190,10 @@
notifications
end
+
+ def exclude_self_statuses(notifications)
+ notifications.delete_if { |n| n.target_status&.visibility == 'self' }
+ end
end
after_initialize :set_from_account
@@ -166,7 +215,22 @@
self.from_account_id = activity&.users&.first&.account_id
when 'User'
self.from_account_id = activity&.account_id
+ when 'ChatMessage'
+ self.from_account_id = activity&.created_by_account_id
+ when 'Group'
+ self.from_account_id = set_by_notification_type
+ when 'GroupMembershipRequest'
+ self.from_account_id = activity&.account&.id
end
+ end
+ end
+
+ def set_by_notification_type
+ case type
+ when :group_delete
+ activity&.owner_account&.id
+ else
+ activity&.owner_account&.id
end
end
end
Only in truth-new/opensource/app/models: notifications_marketing.rb
Only in truth-new/opensource/app/models: notifications_marketing_analytic.rb
Only in truth-new/opensource/app/models: oauth_access_token.rb
Only in truth-new/opensource/app/models: oauth_access_tokens
Only in truth-new/opensource/app/models: one_time_challenge.rb
Only in truth-new/opensource/app/models: password_history.rb
Only in truth-new/opensource/app/models: policy.rb
diff -ru truth-old/opensource/app/models/poll.rb truth-new/opensource/app/models/poll.rb
--- truth-old/opensource/app/models/poll.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/poll.rb 2024-04-01 14:59:13
@@ -1,115 +1,82 @@
# frozen_string_literal: true
# == Schema Information
#
-# Table name: polls
+# Table name: polls.polls
#
-# id :bigint(8) not null, primary key
-# account_id :bigint(8)
-# status_id :bigint(8)
-# expires_at :datetime
-# options :string default([]), not null, is an Array
-# cached_tallies :bigint(8) default([]), not null, is an Array
-# multiple :boolean default(FALSE), not null
-# hide_totals :boolean default(FALSE), not null
-# votes_count :bigint(8) default(0), not null
-# last_fetched_at :datetime
-# created_at :datetime not null
-# updated_at :datetime not null
-# lock_version :integer default(0), not null
-# voters_count :bigint(8)
+# poll_id :integer not null, primary key
+# expires_at :datetime not null
+# multiple_choice :boolean default(FALSE), not null
#
class Poll < ApplicationRecord
include Expireable
- belongs_to :account
- belongs_to :status
+ self.table_name = 'polls.polls'
+ self.primary_key = :poll_id
- has_many :votes, class_name: 'PollVote', inverse_of: :poll, dependent: :delete_all
+ has_and_belongs_to_many :statuses, join_table: 'polls.status_polls'
+ has_many :options, class_name: 'PollOption', inverse_of: :poll, dependent: :delete_all
+ has_many :votes, class_name: 'PollVote', inverse_of: :poll
has_many :notifications, as: :activity, dependent: :destroy
+ has_one :statistic_polls, class_name: 'StatisticPoll', inverse_of: :poll
+ has_one :status_polls, class_name: 'StatusPolls'
+ has_one :status, through: :status_polls, source: :status
+ has_one :account, through: :status
- validates :options, presence: true
- validates :expires_at, presence: true, if: :local?
- validates_with PollValidator, on: :create, if: :local?
+ accepts_nested_attributes_for :options
- scope :attached, -> { where.not(status_id: nil) }
- scope :unattached, -> { where(status_id: nil) }
+ validates :expires_at, presence: true
+ validates_with PollValidator, on: :create
- before_validation :prepare_options, if: :local?
- before_validation :prepare_votes_count
+ alias_attribute :multiple, :multiple_choice
- after_initialize :prepare_cached_tallies
-
- after_commit :reset_parent_cache, on: :update
-
def loaded_options
- options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? cached_tallies[key] : nil) }
+ loaded_poll_options
end
- def possibly_stale?
- remote? && last_fetched_before_expiration? && time_passed_since_last_fetch?
+ def loaded_poll_options
+ options = PollOption
+ .joins('LEFT JOIN statistics.poll_options using("poll_id", "option_number")')
+ .where(options: { poll_id: id })
+ .order(option_number: :asc)
+ .select('polls.options.*, statistics.poll_options.votes votes_count')
+ options.map { |option| { title: option.text, votes_count: option.has_attribute?(:votes_count) && option.votes_count ? option.votes_count : 0 } }
end
def voted?(account)
- account.id == account_id || votes.where(account: account).exists?
+ PollVote.where(poll_id: id, account: account).exists?
end
def own_votes(account)
- votes.where(account: account).pluck(:choice)
+ PollVote.where(poll_id: id, account: account).pluck(:option_number) || []
end
delegate :local?, to: :account
+ def local?
+ true
+ end
+
def remote?
- !local?
+ false
end
def emojis
- @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain)
+ []
end
- class Option < ActiveModelSerializers::Model
- attributes :id, :title, :votes_count, :poll
-
- def initialize(poll, id, title, votes_count)
- super(
- poll: poll,
- id: id,
- title: title,
- votes_count: votes_count,
- )
- end
+ def votes_count
+ statistic_polls&.votes || 0
end
- private
-
- def prepare_cached_tallies
- self.cached_tallies = options.map { 0 } if cached_tallies.empty?
+ def voters_count
+ statistic_polls&.voters || 0
end
- def prepare_votes_count
- self.votes_count = cached_tallies.sum unless cached_tallies.empty?
+ def statistic_polls
+ super || build_statistic_polls
end
- def prepare_options
- self.options = options.map(&:strip).reject(&:blank?)
- end
-
- def reset_parent_cache
- return if status_id.nil?
- Rails.cache.delete("statuses/#{status_id}")
- end
-
- def last_fetched_before_expiration?
- last_fetched_at.nil? || expires_at.nil? || last_fetched_at < expires_at
- end
-
- def time_passed_since_last_fetch?
- last_fetched_at.nil? || last_fetched_at < 1.minute.ago
- end
-
- def show_totals_now?
- expired? || !hide_totals?
- end
+ delegate :id, to: :account, prefix: true
end
Only in truth-new/opensource/app/models: poll_option.rb
diff -ru truth-old/opensource/app/models/poll_vote.rb truth-new/opensource/app/models/poll_vote.rb
--- truth-old/opensource/app/models/poll_vote.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/poll_vote.rb 2024-04-01 14:59:13
@@ -1,39 +1,24 @@
# frozen_string_literal: true
# == Schema Information
#
-# Table name: poll_votes
+# Table name: polls.votes
#
-# id :bigint(8) not null, primary key
-# account_id :bigint(8)
-# poll_id :bigint(8)
-# choice :integer default(0), not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# uri :string
+# poll_id :integer not null, primary key
+# option_number :integer not null, primary key
+# account_id :bigint(8) not null, primary key
#
class PollVote < ApplicationRecord
+ self.table_name = 'polls.votes'
+ self.primary_keys = :poll_id, :option_number, :account_id
+
belongs_to :account
- belongs_to :poll, inverse_of: :votes
+ belongs_to :poll
- validates :choice, presence: true
+ validates :poll_id, :option_number, :account_id, presence: true
validates_with VoteValidator
- after_create_commit :increment_counter_cache
-
- delegate :local?, to: :account
-
def object_type
:vote
- end
-
- private
-
- def increment_counter_cache
- poll.cached_tallies[choice] = (poll.cached_tallies[choice] || 0) + 1
- poll.save
- rescue ActiveRecord::StaleObjectError
- poll.reload
- retry
end
end
diff -ru truth-old/opensource/app/models/preview_card.rb truth-new/opensource/app/models/preview_card.rb
--- truth-old/opensource/app/models/preview_card.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/preview_card.rb 2024-04-01 14:59:13
@@ -24,11 +24,12 @@
# embed_url :string default(""), not null
# image_storage_schema_version :integer
# blurhash :string
+# file_s3_host :string(64)
#
class PreviewCard < ApplicationRecord
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
- LIMIT = 1.megabytes
+ LIMIT = 2_500.kilobytes
BLURHASH_OPTIONS = {
x_comp: 4,
@@ -50,9 +51,12 @@
validates_attachment_size :image, less_than: LIMIT
remotable_attachment :image, LIMIT
+ attribute :ad, :boolean, default: false
+
scope :cached, -> { where.not(image_file_name: [nil, '']) }
before_save :extract_dimensions, if: :link?
+ before_save :set_file_s3_host, if: -> { will_save_change_to_image_file_name? }
def local?
false
@@ -74,6 +78,7 @@
# rubocop:disable Naming/MethodParameterName
def image_styles(f)
+ return {} if f.instance.ad && f.instance.image_content_type == 'image/gif'
styles = {
original: {
geometry: '800x800>',
@@ -102,5 +107,10 @@
self.width = width
self.height = height
+ end
+
+
+ def set_file_s3_host
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name]
end
end
Only in truth-new/opensource/app/models: procedure.rb
Only in truth-new/opensource/app/models: recommendations
Only in truth-new/opensource/app/models: region.rb
Only in truth-new/opensource/app/models: registration.rb
Only in truth-new/opensource/app/models: registration_one_time_challenge.rb
Only in truth-new/opensource/app/models: registration_webauthn_credential.rb
diff -ru truth-old/opensource/app/models/relationship_filter.rb truth-new/opensource/app/models/relationship_filter.rb
--- truth-old/opensource/app/models/relationship_filter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/relationship_filter.rb 2024-04-01 14:59:13
@@ -60,13 +60,13 @@
def relationship_scope(value)
case value
when 'following'
- account.following.eager_load(:account_stat).reorder(nil)
+ account.following.eager_load(:account_status).reorder(nil)
when 'followed_by'
- account.followers.eager_load(:account_stat).reorder(nil)
+ account.followers.eager_load(:account_status).reorder(nil)
when 'mutual'
- account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following))
+ account.followers.eager_load(:account_status).reorder(nil).merge(Account.where(id: account.following))
when 'invited'
- Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil)
+ Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_status).reorder(nil)
else
raise "Unknown relationship: #{value}"
end
@@ -112,7 +112,7 @@
def activity_scope(value)
case value
when 'dormant'
- AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
+ AccountStatusStatistic.where(last_status_at: nil).or(AccountStatusStatistic.where(AccountStatusStatistic.arel_table[:last_status_at].lt(1.month.ago)))
else
raise "Unknown activity: #{value}"
end
diff -ru truth-old/opensource/app/models/report.rb truth-new/opensource/app/models/report.rb
--- truth-old/opensource/app/models/report.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/report.rb 2024-04-01 14:59:13
@@ -3,7 +3,6 @@
#
# Table name: reports
#
-# id :bigint(8) not null, primary key
# status_ids :bigint(8) default([]), not null, is an Array
# comment :text default(""), not null
# action_taken :boolean default(FALSE), not null
@@ -11,11 +10,15 @@
# updated_at :datetime not null
# account_id :bigint(8) not null
# action_taken_by_account_id :bigint(8)
+# id :bigint(8) not null, primary key
# target_account_id :bigint(8) not null
# assigned_account_id :bigint(8)
# uri :string
# forwarded :boolean
# rule_ids :integer default([]), not null, is an Array
+# message_ids :bigint(8) default([]), is an Array
+# group_id :bigint(8)
+# external_ad_id :integer
#
class Report < ApplicationRecord
@@ -28,6 +31,7 @@
belongs_to :target_account, class_name: 'Account'
belongs_to :action_taken_by_account, class_name: 'Account', optional: true
belongs_to :assigned_account, class_name: 'Account', optional: true
+ belongs_to :external_ad, optional: true
has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy
@@ -76,7 +80,6 @@
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
end
- RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] }
update!(action_taken: true, action_taken_by_account_id: acting_account.id)
end
diff -ru truth-old/opensource/app/models/rule.rb truth-new/opensource/app/models/rule.rb
--- truth-old/opensource/app/models/rule.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/rule.rb 2024-04-01 14:59:13
@@ -12,13 +12,14 @@
# updated_at :datetime not null
# rule_type :integer default("content")
# subtext :text default(""), not null
+# name :text default(""), not null
#
class Rule < ApplicationRecord
include Discard::Model
self.discard_column = :deleted_at
- enum rule_type: { content: 0, account: 1 }
+ enum rule_type: { content: 0, account: 1, rule_type_group: 2 }
validates :text, presence: true, length: { maximum: 300 }
diff -ru truth-old/opensource/app/models/search.rb truth-new/opensource/app/models/search.rb
--- truth-old/opensource/app/models/search.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/search.rb 2024-04-01 14:59:13
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class Search < ActiveModelSerializers::Model
- attributes :accounts, :statuses, :hashtags
+ attributes :accounts, :statuses, :hashtags, :groups
end
diff -ru truth-old/opensource/app/models/session_activation.rb truth-new/opensource/app/models/session_activation.rb
--- truth-old/opensource/app/models/session_activation.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/session_activation.rb 2024-04-01 14:59:13
@@ -16,7 +16,7 @@
class SessionActivation < ApplicationRecord
belongs_to :user, inverse_of: :session_activations
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', dependent: :destroy, optional: true
+ belongs_to :access_token, class_name: 'OauthAccessToken', dependent: :destroy, optional: true
belongs_to :web_push_subscription, class_name: 'Web::PushSubscription', dependent: :destroy, optional: true
delegate :token,
@@ -70,7 +70,7 @@
end
def assign_access_token
- self.access_token = Doorkeeper::AccessToken.create!(access_token_attributes)
+ self.access_token = OauthAccessToken.create!(access_token_attributes)
end
def access_token_attributes
diff -ru truth-old/opensource/app/models/site_upload.rb truth-new/opensource/app/models/site_upload.rb
--- truth-old/opensource/app/models/site_upload.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/site_upload.rb 2023-05-05 13:42:02
@@ -12,6 +12,7 @@
# meta :json
# created_at :datetime not null
# updated_at :datetime not null
+# file_s3_host :string(64)
#
class SiteUpload < ApplicationRecord
@@ -22,6 +23,7 @@
validates :var, presence: true, uniqueness: true
before_save :set_meta
+ before_save :set_file_s3_host
after_commit :clear_cache
def cache_key
@@ -41,5 +43,10 @@
def clear_cache
Rails.cache.delete(cache_key)
+ end
+
+
+ def set_file_s3_host
+ self.file_s3_host = Paperclip::Attachment.default_options[:s3_host_name]
end
end
Only in truth-new/opensource/app/models: statistic_poll.rb
Only in truth-new/opensource/app/models: statistic_poll_option.rb
diff -ru truth-old/opensource/app/models/status.rb truth-new/opensource/app/models/status.rb
--- truth-old/opensource/app/models/status.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/status.rb 2024-04-12 16:08:26
@@ -21,20 +21,25 @@
# account_id :bigint(8) not null
# application_id :bigint(8)
# in_reply_to_account_id :bigint(8)
-# poll_id :bigint(8)
-# deleted_at :datetime
# quote_id :bigint(8)
+# deleted_at :datetime
# deleted_by_id :bigint(8)
+# group_id :bigint(8)
+# group_timeline_visible :boolean default(FALSE)
+# has_poll :boolean default(FALSE), not null
#
class Status < ApplicationRecord
+
before_destroy :unlink_from_conversations
include Discard::Model
include Paginable
include Cacheable
include StatusThreadingConcern
+ include StatusThreadingConcernV2
include RateLimitable
+ extend LinksParserConcern
rate_limit by: :account, family: :statuses
@@ -43,22 +48,34 @@
# If `override_timestamps` is set at creation time, Snowflake ID creation
# will be based on current time instead of `created_at`
attr_accessor :override_timestamps
+ attr_accessor :interactive_ad, :statuses_count_before_filter
- update_index 'statuses#status', :proper, unless: -> { skip_indexing? }
+ attr_accessor :seen
- enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility
+ attribute :tv_program_status?, :boolean, default: false
+ # Used to bypass some validations if we know the operation was initiated from an admin
+ attr_accessor :performed_by_admin
+
+ # attr_accessor :tombstone
+ attribute :tombstone, :boolean, default: false
+
+ update_index 'statuses', :proper, unless: -> { skip_indexing? }
+
+ enum visibility: [:public, :unlisted, :private, :direct, :limited, :self, :group], _suffix: :visibility
+
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
belongs_to :account, inverse_of: :statuses
belongs_to :in_reply_to_account, foreign_key: 'in_reply_to_account_id', class_name: 'Account', optional: true
belongs_to :conversation, optional: true
- belongs_to :preloadable_poll, class_name: 'Poll', foreign_key: 'poll_id', optional: true
- belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
+ belongs_to :thread, -> { with_discarded }, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true
- belongs_to :quote, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quoted, optional: true
+ belongs_to :quote, -> { with_discarded }, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quoted, optional: true
+ belongs_to :group, inverse_of: :statuses, optional: true
+
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :bookmarks, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
@@ -68,25 +85,40 @@
has_many :media_attachments, dependent: :nullify
has_many :quoted, foreign_key: 'quote_id', class_name: 'Status', inverse_of: :quote, dependent: :nullify
has_many :moderation_records, dependent: :nullify
+ has_many :status_pins, inverse_of: :status, dependent: :destroy
+ has_many :moderation_results, dependent: :destroy, class_name: Statuses::ModerationResult.name
has_and_belongs_to_many :tags
has_and_belongs_to_many :preview_cards
+ has_and_belongs_to_many :links
has_one :notification, as: :activity, dependent: :destroy
- has_one :status_stat, inverse_of: :status
- has_one :poll, inverse_of: :status, dependent: :destroy
- has_one :trending, dependent: :destroy
+ has_one :status_favourite, class_name: StatusFavouriteStatistic.name, inverse_of: :status
+ has_one :status_reply, class_name: StatusReplyStatistic.name, inverse_of: :status
+ has_one :status_reblog, class_name: StatusReblogStatistic.name, inverse_of: :status
+ has_one :analysis, class_name: Statuses::Analysis.name
+ has_and_belongs_to_many :polls, inverse_of: :status, join_table: 'polls.status_polls'
+ accepts_nested_attributes_for :polls
+
+ has_one :status_polls, class_name: 'StatusPolls'
+ has_one :preloadable_poll, through: :status_polls, source: :poll
+
+ has_one :trending_status, class_name: 'TrendingStatus', dependent: :destroy
+ has_one :ad
+ has_one :tv_program_status
+ has_one :tv_program, through: :tv_program_status
+ has_one :tv_status
+
validates :uri, uniqueness: true, presence: true, unless: :local?
- validates :text, presence: true, unless: -> { with_media? || reblog? }
+ validates :text, presence: true, unless: -> { with_media? || reblog? || ad? }
validates_with StatusLengthValidator
validates_with DisallowedHashtagsValidator
+ validates_with StatusGroupValidator, unless: -> { performed_by_admin }
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
- validates :quote_visibility, inclusion: { in: %w(public unlisted) }, if: :quote?
+ validates :quote_visibility, inclusion: { in: %w(public unlisted group) }, if: :quote?
- accepts_nested_attributes_for :poll
-
default_scope { recent.kept }
scope :recent, -> { reorder(id: :desc) }
@@ -104,54 +136,79 @@
scope :not_domain_blocked_by_account, ->(account) { account.excluded_from_timeline_domains.blank? ? left_outer_joins(:account) : left_outer_joins(:account).where('accounts.domain IS NULL OR accounts.domain NOT IN (?)', account.excluded_from_timeline_domains) }
scope :tagged_with_all, ->(tag_ids) {
Array(tag_ids).reduce(self) do |result, id|
- result.joins("INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}")
+ tag_id = id.to_i
+ result.joins("INNER JOIN statuses_tags t#{tag_id} ON t#{tag_id}.status_id = statuses.id AND t#{tag_id}.tag_id = #{tag_id}")
end
}
scope :tagged_with_none, ->(tag_ids) {
Array(tag_ids).reduce(self) do |result, id|
- result.joins("LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}")
- .where("t#{id}.tag_id IS NULL")
+ tag_id = id.to_i
+ result.joins("LEFT OUTER JOIN statuses_tags t#{tag_id} ON t#{tag_id}.status_id = statuses.id AND t#{tag_id}.tag_id = #{tag_id}")
+ .where("t#{tag_id}.tag_id IS NULL")
end
}
+ scope :trending_statuses, -> { joins(:trending_status).reorder('sort_order ASC') }
+ scope :excluding_unauthorized_tv_statuses, lambda { |account_id|
+ where.not(TvProgramStatus.where('tv.program_statuses.status_id = statuses.id')
+ .where.not(Configuration::AccountEnabledFeature.where(feature_flag_id: Configuration::FeatureFlag.where(name: 'tv'), account_id: account_id).arel.exists)
+ .arel.exists)
+ }
cache_associated :application,
:media_attachments,
:conversation,
- :status_stat,
+ :status_favourite,
+ :status_reply,
+ :status_reblog,
:tags,
:preview_cards,
- :preloadable_poll,
- account: [:account_stat, :user],
- active_mentions: { account: :account_stat },
+ :ad,
+ :links,
+ tv_program: [:tv_channel],
+ account: [:account_follower, :account_following, :account_status, :user],
+ active_mentions: { account: [:account_follower, :account_following, :account_status] },
reblog: [
:application,
:tags,
:preview_cards,
:media_attachments,
:conversation,
- :status_stat,
- :preloadable_poll,
- account: [:account_stat, :user],
- active_mentions: { account: :account_stat },
+ :status_favourite,
+ :status_reply,
+ :status_reblog,
+ :ad,
+ :links,
+ account: [:account_follower, :account_following, :account_status, :user],
+ active_mentions: { account: [:account_follower, :account_following, :account_status] },
],
quote: [
- :application,
- :tags,
- :preview_cards,
- :media_attachments,
- :conversation,
- :status_stat,
- account: [:account_stat, :user],
- active_mentions: { account: :account_stat },
- ],
+ :application,
+ :tags,
+ :preview_cards,
+ :media_attachments,
+ :conversation,
+ :status_favourite,
+ :status_reply,
+ :status_reblog,
+ :links,
+ :ad,
+ account: [:account_follower, :account_following, :account_status, :user],
+ active_mentions: { account: [:account_follower, :account_following, :account_status] },
+ ],
thread: [
- :application,
- :tags,
- :preview_cards,
- :media_attachments,
- account: [:account_stat, :user],
- active_mentions: { account: :account_stat },
- ]
+ :application,
+ :tags,
+ :preview_cards,
+ :media_attachments,
+ :links,
+ :ad,
+ account: [:account_follower, :account_following, :account_status, :user],
+ active_mentions: { account: [:account_follower, :account_following, :account_status] },
+ ],
+ group: [
+ :group_stat,
+ :tags,
+ ]
delegate :domain, to: :account, prefix: true
@@ -175,14 +232,22 @@
!reblog_of_id.nil?
end
+ def ad?
+ !!(interactive_ad || ad)
+ end
+
def trending?
- trending.present?
+ trending_status.present?
end
def quote?
!quote_id.nil? && quote
end
+ def group?
+ !group_id.nil? && group
+ end
+
def quote_visibility
quote&.visibility
end
@@ -200,7 +265,13 @@
end
def object_type
- reply? ? :comment : :note
+ if group?
+ :group_note
+ elsif reply?
+ :comment
+ else
+ :note
+ end
end
def proper
@@ -224,6 +295,7 @@
end
def distributable?
+ # TODO: how do we consider group posts? they may need LDSigning for efficiency
public_visibility? || unlisted_visibility?
end
@@ -245,21 +317,20 @@
return @emojis if defined?(@emojis)
fields = [spoiler_text, text]
- fields += preloadable_poll.options unless preloadable_poll.nil?
@emojis = CustomEmoji.from_text(fields.join(' '), account.domain) + (quote? ? CustomEmoji.from_text([quote.spoiler_text, quote.text].join(' '), quote.account.domain) : [])
end
def replies_count
- status_stat&.replies_count || 0
+ status_reply&.replies_count || 0
end
def reblogs_count
- status_stat&.reblogs_count || 0
+ status_reblog&.reblogs_count || 0
end
def favourites_count
- status_stat&.favourites_count || 0
+ status_favourite&.favourites_count || 0
end
def skip_indexing?
@@ -271,20 +342,39 @@
PROHIBITED_TERMS_ON_INDEX.any? { |term| text_downcase.include? term }
end
- def increment_count!(key)
- update_status_stat!(key => public_send(key) + 1)
+ def text_hash
+ return if text.blank?
+
+ Digest::SHA2.hexdigest(text.strip)
end
- def decrement_count!(key)
- update_status_stat!(key => [public_send(key) - 1, 0].max)
+ def privatize(mod_id, _notify_user)
+ logger.debug "Status: #{id} PRIVATIZED"
+ reblogs.update_all(deleted_at: Time.current, deleted_by_id: mod_id)
+ update!(visibility: :self)
+ purge_cache
+ account.save
+ save
end
- after_create_commit :increment_counter_caches
- after_destroy_commit :decrement_counter_caches
+ def publicize
+ if visibility == 'self'
+ reblogs.update_all(deleted_at: nil, deleted_by_id: nil)
+ update!(visibility: group? ? :group : :public)
+ purge_cache
+ account.save
+ save
+ end
+ end
+ after_create_commit :increment_counter_caches, if: :group?
+ after_destroy_commit :decrement_counter_caches, if: :group?
+
after_create_commit :store_uri, if: :local?
after_create_commit :update_statistics, if: :local?
+ after_create_commit :mark_tv_status
+
around_create Mastodon::Snowflake::Callbacks
before_validation :prepare_contents, if: :local?
@@ -293,11 +383,9 @@
before_validation :set_conversation
before_validation :set_local
- after_create :set_poll_id
-
class << self
def selectable_visibilities
- visibilities.keys - %w(direct limited)
+ %w(public unlisted private)
end
def favourites_map(status_ids, account_id)
@@ -316,27 +404,76 @@
ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
end
- def pins_map(status_ids, account_id)
- StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
+ def pins_map(status_ids, account_id, group_id = nil)
+ StatusPin
+ .select('status_id')
+ .where(status_id: status_ids)
+ .where(account_id: account_id)
+ .where(pin_location: group_id ? :group : :profile)
+ .each_with_object({}) { |p, h| h[p.status_id] = true }
end
+ def groups_map(statuses)
+ statuses_slugs = statuses.map { |s| [s.id, extract_group_slugs(s.text)] }.to_h
+ slugs = statuses_slugs.values.uniq
+ return {} unless slugs.any?
+
+ groups = Group.where(slug: slugs).includes(:tags, :group_stat).references(:tags, :group_stat).index_by(&:slug)
+ statuses_slugs.map { |status_id, slug| [status_id, groups[slug]] }.to_h.compact
+ end
+
+ def polls_map(statuses, current_account_id)
+ statuses_with_polls = statuses.map { |s| s.id if s.has_poll }.compact.uniq
+ return {} unless statuses_with_polls.any?
+
+ rendered_polls = StatusPolls.polls(account_id: current_account_id, status_ids: statuses_with_polls)
+ return {} unless rendered_polls
+
+ rendered_polls.map { |poll| [poll['status_id'], poll['poll_json']] }.to_h.compact
+ end
+
def reload_stale_associations!(cached_items)
account_ids = []
+ status_ids = []
+ group_ids = []
cached_items.each do |item|
account_ids << item.account_id
account_ids << item.reblog.account_id if item.reblog? && item.reblog&.account_id
+ status_ids << item.id
+ group_ids << item.group_id if item.group_id
end
account_ids.uniq!
return if account_ids.empty?
- accounts = Account.where(id: account_ids).includes(:account_stat, :user).index_by(&:id)
+ accounts = Account.where(id: account_ids).includes(:account_follower, :account_following, :account_status, :user).references(:account_follower, :account_following, :account_status, :user).index_by(&:id)
+ statuses = Status.with_discarded.select([:favourites_count, :replies_count, :reblogs_count]).where(id: status_ids).includes(:status_favourite, :status_reply, :status_reblog).references(:status_favourite, :status_reply, :status_reblog).index_by(&:id)
+ groups = Group.where(id: group_ids).index_by(&:id)
cached_items.each do |item|
item.account = accounts[item.account_id]
item.reblog.account = accounts[item.reblog.account_id] if item.reblog? && item.reblog&.account_id
+ item.group = groups[item.group.id] if item.group?
+
+ if statuses[item.id].status_favourite
+ item.status_favourite = statuses[item.id].status_favourite
+ else
+ item.build_status_favourite
+ end
+
+ if statuses[item.id].status_reply
+ item.status_reply = statuses[item.id].status_reply
+ else
+ item.build_status_reply
+ end
+
+ if statuses[item.id].status_reblog
+ item.status_reblog = statuses[item.id].status_reblog
+ else
+ item.build_status_reblog
+ end
end
end
@@ -375,20 +512,26 @@
status&.distributable? ? status : nil
end
end
- end
- def status_stat
- super || build_status_stat
+ def muted_conversations_for_account(account_id)
+ sanitized_id = connection.quote(account_id.to_i)
+ select('*').from("(select s.* from statuses s
+ inner join conversations c on c.id = s.conversation_id
+ inner join conversation_mutes cm on cm.conversation_id = c.id
+ where cm.account_id = #{sanitized_id} and in_reply_to_id is null) as statuses")
+ end
+
+ def tv_channels_statuses
+ find_by_sql("
+ WITH distinct_statuses_by_channel AS(
+ select * from (select distinct on (channel_id) channel_id, status_id from tv.program_statuses tvp inner join tv.channels tvc using(channel_id) where tvc.enabled = true order by channel_id, start_time desc) sub order by channel_id asc
+ )
+ select * from statuses s inner join distinct_statuses_by_channel dsc on s.id = dsc.status_id order by s.created_at desc")
+ end
end
private
- def update_status_stat!(attrs)
- return if marked_for_destruction? || destroyed?
-
- status_stat.update(attrs)
- end
-
def store_uri
update_column(:uri, ActivityPub::TagManager.instance.uri_for(self)) if uri.nil?
end
@@ -402,10 +545,6 @@
self.reblog = reblog.reblog if reblog? && reblog.reblog?
end
- def set_poll_id
- update_column(:poll_id, poll.id) unless poll.nil?
- end
-
def set_visibility
self.visibility = reblog.visibility if reblog? && visibility.nil?
self.visibility = (account.locked? ? :private : :public) if visibility.nil?
@@ -421,6 +560,7 @@
self.in_reply_to_account_id = carried_over_reply_to_account_id
self.conversation_id = thread.conversation_id if conversation_id.nil?
redis.del("descendants:#{conversation_id}")
+ InvalidateSecondaryCacheService.new.call('InvalidateDescendantsCacheWorker', conversation_id)
elsif conversation_id.nil?
self.conversation = Conversation.new
end
@@ -445,19 +585,11 @@
end
def increment_counter_caches
- return if direct_visibility?
-
- account&.increment_count!(:statuses_count)
- reblog&.increment_count!(:reblogs_count) if reblog?
- thread&.increment_count!(:replies_count) if in_reply_to_id.present? && distributable?
+ group&.increment_count!(:statuses_count)
end
def decrement_counter_caches
- return if direct_visibility?
-
- account&.decrement_count!(:statuses_count)
- reblog&.decrement_count!(:reblogs_count) if reblog?
- thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && distributable?
+ group&.decrement_count!(:statuses_count)
end
def unlink_from_conversations
@@ -468,6 +600,19 @@
inbox_owners.each do |inbox_owner|
AccountConversation.remove_status(inbox_owner, self)
+ end
+ end
+
+ def purge_cache
+ Rails.cache.delete(self)
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', self)
+ end
+
+ def mark_tv_status
+ related_status_id = reblog_of_id.presence || quote_id.presence || in_reply_to_id.presence
+
+ if tv_program_status? || (related_status_id && TvStatus.find_by(status_id: related_status_id).present?)
+ TvStatus.create!(status: self)
end
end
end
Only in truth-new/opensource/app/models: status_distribution_br2.rb
Only in truth-new/opensource/app/models: status_distribution_or1.rb
Only in truth-new/opensource/app/models: status_favourite_statistic.rb
diff -ru truth-old/opensource/app/models/status_pin.rb truth-new/opensource/app/models/status_pin.rb
--- truth-old/opensource/app/models/status_pin.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/status_pin.rb 2024-04-01 14:59:13
@@ -3,16 +3,27 @@
#
# Table name: status_pins
#
-# id :bigint(8) not null, primary key
-# account_id :bigint(8) not null
-# status_id :bigint(8) not null
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :bigint(8) not null, primary key
+# account_id :bigint(8) not null
+# status_id :bigint(8) not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# pin_location :enum default("profile"), not null
#
class StatusPin < ApplicationRecord
belongs_to :account
belongs_to :status
- validates_with StatusPinValidator
+ enum pin_location: { group: 'group', profile: 'profile' }, _suffix: :location
+
+ validates_with StatusPinValidator, unless: -> { is_group_pin }
+ validates_with GroupStatusPinValidator, if: -> { is_group_pin }
+
+ scope :profile_pins, -> { where(pin_location: :profile) }
+ scope :group_pins, -> { where(pin_location: :group) }
+
+ def is_group_pin
+ group_location?
+ end
end
Only in truth-new/opensource/app/models: status_polls.rb
Only in truth-new/opensource/app/models: status_reblog_statistic.rb
Only in truth-new/opensource/app/models: status_replies.rb
Only in truth-new/opensource/app/models: status_replies_v1.rb
Only in truth-new/opensource/app/models: status_replies_v2.rb
Only in truth-new/opensource/app/models: status_reply_statistic.rb
diff -ru truth-old/opensource/app/models/status_stat.rb truth-new/opensource/app/models/status_stat.rb
--- truth-old/opensource/app/models/status_stat.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/status_stat.rb 2024-04-01 14:59:13
@@ -29,8 +29,6 @@
[attributes['favourites_count'], 0].max
end
- private
-
def reset_parent_cache
Rails.cache.delete("statuses/#{status_id}")
InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", status_id)
Only in truth-new/opensource/app/models: status_suggestion.rb
Only in truth-new/opensource/app/models: statuses
Only in truth-new/opensource/app/models: suggestions_carousel.rb
diff -ru truth-old/opensource/app/models/tag.rb truth-new/opensource/app/models/tag.rb
--- truth-old/opensource/app/models/tag.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/tag.rb 2024-04-01 14:59:13
@@ -8,7 +8,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# usable :boolean
-# trendable :boolean
+# trendable :boolean default(TRUE), not null
# listable :boolean
# reviewed_at :datetime
# requested_review_at :datetime
@@ -18,6 +18,9 @@
#
class Tag < ApplicationRecord
+ extend Queriable
+ include Paginable
+
has_and_belongs_to_many :statuses
has_and_belongs_to_many :accounts
@@ -30,16 +33,20 @@
validates :name, presence: true, format: { with: /\A(#{HASHTAG_NAME_RE})\z/i }
validate :validate_name_change, if: -> { !new_record? && name_changed? }
+ before_create :unlist_bannable_tags
+
scope :reviewed, -> { where.not(reviewed_at: nil) }
scope :unreviewed, -> { where(reviewed_at: nil) }
scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
scope :usable, -> { where(usable: [true, nil]) }
scope :listable, -> { where(listable: [true, nil]) }
- scope :trendable, -> { where(trendable: true).order(last_status_at: :desc) }
+ scope :trendable, -> { where(trendable: true).where.not(max_score: nil).order(max_score: :desc, last_status_at: :desc) }
+ scope :only_trendable, -> { where(trendable: true).order(max_score: :desc, last_status_at: :desc) }
scope :recently_used, ->(account) { joins(:statuses).where(statuses: { id: account.statuses.select(:id).limit(1000) }).group(:id).order(Arel.sql('count(*) desc')) }
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches("#{sanitize_sql_like(Tag.normalize(term.downcase))}%", nil, true)) } # Search with case-sensitive to use B-tree index
+ scope :search, ->(query) { where('LOWER(tags.name) LIKE :search', search: "%#{sanitize_sql_like(query&.downcase)}%") }
- update_index 'tags#tag', :self
+ update_index 'tags', :self
def contains_prohibited_terms?
name_downcase = name.downcase
@@ -91,7 +98,7 @@
def history
days = []
- 7.times do |i|
+ 1.upto(6) do |i|
day = i.days.ago.beginning_of_day.to_i
days << {
@@ -115,15 +122,9 @@
end
end
- def search_for(term, limit = 5, offset = 0, options = {})
- stripped_term = term.strip
-
- query = Tag.listable.matches_name(stripped_term)
- query = query.merge(matching_name(stripped_term).or(where.not(reviewed_at: nil))) if options[:exclude_unreviewed]
-
- query.order(Arel.sql('length(name) ASC, name ASC'))
- .limit(limit)
- .offset(offset)
+ # options = [in_search_query text, in_limit smallint, in_offset integer]
+ def search_for(*options)
+ execute_query('select mastodon_api.search_tags ($1, $2, $3)', options).to_a.first['search_tags']
end
def find_normalized(name)
@@ -150,6 +151,17 @@
end
private
+
+ def unlist_bannable_tags
+ banned_words = BannedWord.pluck(:word)
+ regexp = Regexp.new(banned_words.join('|'), true)
+ bannable = regexp === name
+
+ if bannable
+ self.listable = false
+ self.trendable = false
+ end
+ end
def validate_name_change
errors.add(:name, I18n.t('tags.does_not_match_previous_name')) unless name_was.mb_chars.casecmp(name.mb_chars).zero?
diff -ru truth-old/opensource/app/models/tag_feed.rb truth-new/opensource/app/models/tag_feed.rb
--- truth-old/opensource/app/models/tag_feed.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/tag_feed.rb 2024-04-01 14:59:13
@@ -14,6 +14,7 @@
# @option [Boolean] :only_media
def initialize(tag, account, options = {})
@tag = tag
+ @group_id = options[:group_id]
super(account, options)
end
@@ -23,7 +24,7 @@
# @param [Integer] min_id
# @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil)
- scope = public_scope
+ scope = @group_id ? group_scope : public_scope
scope.merge!(tagged_with_any_scope)
scope.merge!(tagged_with_all_scope)
@@ -52,5 +53,12 @@
def tags_for(names)
Tag.matching_name(Array(names).take(LIMIT_PER_MODE)).pluck(:id) if names.present?
+ end
+
+ def group_scope
+ Status.without_reblogs
+ .where(group_id: @group_id, reply: false, quote_id: nil)
+ .joins(:account)
+ .merge(Account.without_suspended.without_silenced.excluded_by_group_account_block(@group_id))
end
end
Only in truth-old/opensource/app/models: trending.rb
Only in truth-new/opensource/app/models: trending_status.rb
Only in truth-new/opensource/app/models: trending_status_excluded_expression.rb
Only in truth-new/opensource/app/models: trending_status_excluded_status.rb
Only in truth-new/opensource/app/models: trending_status_setting.rb
diff -ru truth-old/opensource/app/models/trending_tags.rb truth-new/opensource/app/models/trending_tags.rb
--- truth-old/opensource/app/models/trending_tags.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/trending_tags.rb 2024-04-01 14:59:13
@@ -13,23 +13,11 @@
class << self
include Redisable
- def record_use!(tag, account, status: nil, at_time: Time.now.utc)
- return unless tag.usable? && !account.silenced?
+ def record_use!(tag, account, status: nil, at_time: Time.now.utc); end
- # Even if a tag is not allowed to trend, we still need to
- # record the stats since they can be displayed in other places
- increment_historical_use!(tag.id, at_time)
- increment_unique_use!(tag.id, account.id, at_time)
- increment_use!(tag.id, at_time)
-
- # Only update when the tag was last used once every 12 hours
- # and only if a status is given (lets use ignore reblogs)
- tag.update(last_status_at: at_time) if status.present? && (tag.last_status_at.nil? || (tag.last_status_at < at_time && tag.last_status_at < 12.hours.ago))
- end
-
def update!(at_time = Time.now.utc)
tag_ids = redis.smembers("#{KEY}:used:#{at_time.beginning_of_day.to_i}") + redis.zrange(KEY, 0, -1)
- tags = Tag.trendable.where(id: tag_ids.uniq)
+ tags = Tag.where(trendable: true).where(id: tag_ids.uniq)
# First pass to calculate scores and update the set
Only in truth-new/opensource/app/models: trending_tags_result.rb
Only in truth-new/opensource/app/models: tv_account.rb
Only in truth-new/opensource/app/models: tv_carousel.rb
Only in truth-new/opensource/app/models: tv_channel.rb
Only in truth-new/opensource/app/models: tv_channel_account.rb
Only in truth-new/opensource/app/models: tv_device_session.rb
Only in truth-new/opensource/app/models: tv_program.rb
Only in truth-new/opensource/app/models: tv_program_status.rb
Only in truth-new/opensource/app/models: tv_program_temporary.rb
Only in truth-new/opensource/app/models: tv_reminder.rb
Only in truth-new/opensource/app/models: tv_status.rb
diff -ru truth-old/opensource/app/models/user.rb truth-new/opensource/app/models/user.rb
--- truth-old/opensource/app/models/user.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/user.rb 2024-04-05 09:07:34
@@ -3,7 +3,6 @@
#
# Table name: users
#
-# id :bigint(8) not null, primary key
# email :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
@@ -31,6 +30,7 @@
# otp_backup_codes :string is an Array
# filtered_languages :string default([]), not null, is an Array
# account_id :bigint(8) not null
+# id :bigint(8) not null, primary key
# disabled :boolean default(FALSE), not null
# moderator :boolean default(FALSE), not null
# invite_id :bigint(8)
@@ -46,12 +46,16 @@
# waitlist_position :integer
# unsubscribe_from_emails :boolean default(FALSE)
# ready_to_approve :integer default("not_ready_for_approval")
-# unauth_visibility :boolean
+# unauth_visibility :boolean default(TRUE), not null
+# policy_id :bigint(8)
+# sign_up_city_id :integer not null
+# sign_up_country_id :integer not null
#
class User < ApplicationRecord
include Settings::Extend
include UserRoles
+ include EmailHelper
# The home and list feeds will be stored in Redis for this amount
# of time, and status fan-out to followers will include only people
@@ -61,7 +65,15 @@
# RegenerationWorker jobs that need to be run when those people come
# to check their feed
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
- WAITLIST_PADDING = ENV.fetch('WAITLIST_PADDING', 50000).to_i
+ WAITLIST_PADDING = ENV.fetch('WAITLIST_PADDING', 50_000).to_i
+ BASE_EMAIL_DOMAINS_VALIDATION = ENV.fetch('BASE_EMAIL_DOMAINS_VALIDATION', false)
+ VERIFICATION_INTERVAL = 1.hour.ago.freeze
+ INTEGRITY_STATUSES = {
+ favourite: 'favourite',
+ status: 'status',
+ chat_message: 'chat_message',
+ reblog: 'reblog',
+ }.freeze
devise :two_factor_authenticatable,
otp_secret_encryption_key: Rails.configuration.x.otp_secret
@@ -79,6 +91,9 @@
belongs_to :account, inverse_of: :user
belongs_to :invite, counter_cache: :uses, optional: true
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
+ belongs_to :policy, optional: true
+ belongs_to :city, class_name: 'City', foreign_key: 'sign_up_city_id', optional: true
+ belongs_to :country, class_name: 'Country', foreign_key: 'sign_up_country_id', optional: true
accepts_nested_attributes_for :account
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
@@ -86,15 +101,16 @@
has_many :invites, inverse_of: :user
has_many :markers, inverse_of: :user, dependent: :destroy
has_many :webauthn_credentials, dependent: :destroy
+ has_many :one_time_challenges, dependent: :destroy
+ has_many :password_histories, class_name: 'PasswordHistory'
has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy
- has_one :trending
+ has_one :user_current_information
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? && !Setting.require_invite_text }
validates :invite_request, presence: true, on: :create, if: :invite_text_required?
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
validates_with BlacklistedEmailValidator, on: :create
- validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
# Those are honeypot/antispam fields
@@ -102,8 +118,11 @@
validates_with RegistrationFormTimeValidator, on: :create
validates :website, absence: true, on: :create
+ validates :password, unique_password: true
validates :confirm_password, absence: true, on: :create
+ validates_with BaseEmailValidator, on: :create
+
scope :recent, -> { order(id: :desc) }
scope :pending, -> { where(approved: false) }
scope :approved, -> { where(approved: true) }
@@ -122,6 +141,8 @@
before_create :skip_confirmation_if_invited
after_commit :send_pending_devise_notifications
after_update_commit :send_approved_notification
+ after_create :create_base_email
+ after_save :store_password_history
# This avoids a deprecation warning from Rails 5.1
# It seems possible that a future release of devise-two-factor will
@@ -130,6 +151,11 @@
has_many :session_activations, dependent: :destroy
+ has_one :user_base_email
+
+ has_one :user_sms_reverification_required
+ scope :with_reverification, -> { eager_load(:user_sms_reverification_required) }
+
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
@@ -141,7 +167,7 @@
attr_writer :external, :bypass_invite_request_check
enum ready_to_approve: { not_ready_for_approval: 0, ready_by_csv_import: 1, ready_by_sms_verification: 2, sent_one_push_notification: 3, sent_two_push_notifications: 4, sent_three_push_notifications: 5 }
- self.ignored_columns = ["reviewed_for_approval"]
+ self.ignored_columns = ['reviewed_for_approval']
def confirmed?
confirmed_at.present?
@@ -185,7 +211,7 @@
end
def confirm!
- new_user = !confirmed?
+ new_user = !confirmed?
skip_confirmation!
save!
@@ -209,12 +235,6 @@
if new_sign_in
self.sign_in_count ||= 0
self.sign_in_count += 1
- else
- query = if old_current_sign_in.nil?
- query.where('current_sign_in_at' => nil)
- else
- query.where('current_sign_in_at < :time', time: UserTrackingConcern::UPDATE_SIGN_IN_HOURS.hours.ago)
- end
end
unless new_record?
@@ -224,6 +244,17 @@
current_sign_in_ip: current_sign_in_ip,
sign_in_count: sign_in_count)
end
+
+ UserCurrentInformation.upsert(
+ user_id: id,
+ current_sign_in_at: new_current_sign_in,
+ current_sign_in_ip: new_current_ip,
+ current_city_id: geo(request).city,
+ current_country_id: geo(request).country
+ )
+
+ EventProvider::EventProvider.new('session.updated', SessionUpdatedEvent, { user_id: id, account_id: account_id, ip_address: new_current_ip, timestamp: new_current_sign_in }).call
+
prepare_returning_user!
end
@@ -232,13 +263,13 @@
end
def self.get_user_from_token(user_token)
- id, _updated_at_s = EncryptAttrService.decrypt(user_token).split("+=")
+ id, _updated_at_s = EncryptAttrService.decrypt(user_token).split('+=')
find_by(id: id)
end
def validate_user_token(user_token)
- _id, updated_at_s = EncryptAttrService.decrypt(user_token).split("+=")
+ _id, updated_at_s = EncryptAttrService.decrypt(user_token).split('+=')
updated_at.to_s == updated_at_s
end
@@ -251,6 +282,23 @@
sms.present?
end
+ # remove once all devices have completed the force update
+ def integrity_score
+ return 0 unless ActiveModel::Type::Boolean.new.cast(ENV.fetch('PLAY_INTEGRITY_ENABLED', true)) # Enable/Disable app integrity for all users
+
+ last_status_at = AccountStatusStatistic.find_by(account_id: account.id)&.last_status_at
+ first_status_today = last_status_at ? last_status_at < Time.zone.now.midnight : true
+ first_status_today ? 1 : 0
+ end
+
+ def integrity_status(token, android_client)
+ return [] unless android_client
+ return [] unless user_sms_reverification_required
+
+ integrity_credential = token.integrity_credentials.order(last_verified_at: :desc).first
+ integrity_credential&.last_verified_at&.send(:>, VERIFICATION_INTERVAL) ? [] : INTEGRITY_STATUSES.values
+ end
+
def active_for_authentication?
!account.memorial?
end
@@ -288,7 +336,7 @@
end
def two_factor_enabled?
- otp_required_for_login? || webauthn_credentials.any?
+ otp_required_for_login?
end
def disable_two_factor!
@@ -296,8 +344,6 @@
self.otp_secret = nil
otp_backup_codes&.clear
- webauthn_credentials.destroy_all if webauthn_enabled?
-
save!
end
@@ -305,7 +351,7 @@
return 0 if approved?
most_recent_user = User.pending.order(waitlist_position: :desc).first
- position = most_recent_user&.waitlist_position || 11342 # this is a magic number means nothing could be anything
+ position = most_recent_user&.waitlist_position || 11_342 # this is a magic number means nothing could be anything
self.waitlist_position = position + 1
save!
@@ -357,7 +403,7 @@
# rubocop:disable Naming/MethodParameterName
def token_for_app(a)
return nil if a.nil? || a.owner != self
- Doorkeeper::AccessToken.find_or_create_by(application_id: a.id, resource_owner_id: id) do |t|
+ OauthAccessToken.find_or_create_by(application_id: a.id, resource_owner_id: id) do |t|
t.scopes = a.scopes
t.expires_in = Doorkeeper.configuration.access_token_expires_in
t.use_refresh_token = Doorkeeper.configuration.refresh_token_enabled?
@@ -456,6 +502,10 @@
nil
end
+ def sms_country
+ Phonelib.parse(sms).country
+ end
+
protected
def send_devise_notification(notification, *args, **kwargs)
@@ -540,7 +590,7 @@
def prepare_returning_user!
ActivityTracker.record('activity:logins', id)
- regenerate_feed! if needs_feed_update?
+ clear_feeds! if needs_feed_update?
end
def notify_staff_about_pending_account!
@@ -554,6 +604,13 @@
RegenerationWorker.perform_async(account_id) if Redis.current.set("account:#{account_id}:regeneration", true, nx: true, ex: 1.day.seconds)
end
+ def clear_feeds!
+ home_feed = HomeFeed.new(account)
+ home_feed.clear!
+ groups_feed = GroupsFeed.new(account)
+ groups_feed.clear!
+ end
+
def needs_feed_update?
last_sign_in_at < ACTIVE_DURATION.ago
end
@@ -573,16 +630,40 @@
end
def hourly_limit_reached?
- key = "approved_users_per_hour:#{DateTime.current.strftime("%Y-%m-%d:%H:00")}"
+ key = "approved_users_per_hour:#{DateTime.current.strftime('%Y-%m-%d:%H:00')}"
return unless (limit_per_hour = ENV['USERS_PER_HOUR'].to_i) > 0
current_limit = Redis.current.scard(key)
current_limit.present? && current_limit.to_i >= limit_per_hour
end
def track_approved_user
- key = "approved_users_per_hour:#{DateTime.current.strftime("%Y-%m-%d:%H:00")}"
+ key = "approved_users_per_hour:#{DateTime.current.strftime('%Y-%m-%d:%H:00')}"
Redis.current.sadd(key, id)
Redis.current.expire(key, 65.minutes.seconds)
- Prometheus::ApplicationExporter::increment(:approves)
+ Prometheus::ApplicationExporter.increment(:approves)
+ end
+
+ def geo(request)
+ @geo_object ||= GeoService.new(
+ city_name: request.headers['Geoip-City-Name'],
+ country_code: request.headers['Geoip-Country-Code'],
+ country_name: request.headers['Geoip-Country-Name'],
+ region_name: request.headers['Geoip-Region-Name'],
+ region_code: request.headers['Geoip-Region-Code']
+ )
+ end
+
+ def create_base_email
+ return unless BASE_EMAIL_DOMAINS_VALIDATION
+
+ username, domain = email_to_canonical_email_by_username_and_domain(email).values_at(:username, :domain)
+
+ return unless BASE_EMAIL_DOMAINS_VALIDATION.split(',').map(&:strip).include? domain
+
+ UserBaseEmail.create(user_id: id, email: "#{username}@#{domain}")
+ end
+
+ def store_password_history
+ PasswordHistory.create!(user: self, encrypted_password: encrypted_password) if saved_change_to_encrypted_password?
end
end
Only in truth-new/opensource/app/models: user_base_email.rb
Only in truth-new/opensource/app/models: user_current_information.rb
Only in truth-new/opensource/app/models: user_sms_reverification_required.rb
Only in truth-new/opensource/app/models: users_one_time_challenge.rb
diff -ru truth-old/opensource/app/models/web/push_subscription.rb truth-new/opensource/app/models/web/push_subscription.rb
--- truth-old/opensource/app/models/web/push_subscription.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/web/push_subscription.rb 2024-04-01 14:59:13
@@ -19,7 +19,7 @@
class Web::PushSubscription < ApplicationRecord
belongs_to :user, optional: true
- belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', optional: true
+ belongs_to :access_token, class_name: 'OauthAccessToken', optional: true
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
@@ -74,7 +74,7 @@
class << self
def unsubscribe_for(application_id, resource_owner)
- access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id)
+ access_token_ids = OauthAccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id)
where(access_token_id: access_token_ids).delete_all
end
end
@@ -82,7 +82,7 @@
private
def find_or_create_access_token
- Doorkeeper::AccessToken.find_or_create_for(
+ OauthAccessToken.find_or_create_for(
application: Doorkeeper::Application.find_by(superapp: true),
resource_owner: user_id || session_activation.user_id,
scopes: Doorkeeper::OAuth::Scopes.from_string('read write follow push'),
diff -ru truth-old/opensource/app/models/webauthn_credential.rb truth-new/opensource/app/models/webauthn_credential.rb
--- truth-old/opensource/app/models/webauthn_credential.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/models/webauthn_credential.rb 2024-04-01 14:59:13
@@ -3,20 +3,65 @@
#
# Table name: webauthn_credentials
#
-# id :bigint(8) not null, primary key
-# external_id :string not null
-# public_key :string not null
-# nickname :string not null
-# sign_count :bigint(8) default(0), not null
-# user_id :bigint(8)
-# created_at :datetime not null
-# updated_at :datetime not null
+# id :bigint(8) not null, primary key
+# external_id :string not null
+# public_key :string not null
+# nickname :string not null
+# sign_count :bigint(8) default(0), not null
+# user_id :bigint(8)
+# created_at :datetime not null
+# updated_at :datetime not null
+# receipt :text
+# fraud_metric :integer
+# receipt_updated_at :datetime
+# baseline_fraud_metric :integer default(0), not null
+# sandbox :boolean default(FALSE), not null
#
class WebauthnCredential < ApplicationRecord
+ extend AppAttestable
+
validates :external_id, :public_key, :nickname, :sign_count, presence: true
validates :external_id, uniqueness: true
validates :nickname, uniqueness: { scope: :user_id }
validates :sign_count,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**63 - 1 }
+
+ has_one :one_time_challenge
+ has_one :registration_webauthn_credential
+ has_one :registration, through: :registration_webauthn_credential
+ belongs_to :user, optional: true
+ has_many :token_credentials, class_name: 'OauthAccessTokens::WebauthnCredential'
+
+ class << self
+ def decode_receipt(encoded_receipt:, all_fields: false)
+ receipt = Base64.strict_decode64(encoded_receipt)
+ certificates, payload = verify_and_decode receipt
+ fields_hash = extract_field_values(payload)
+
+ {
+ **user_sensitive_fields(all_fields, certificates, fields_hash),
+ receipt_type: fields_hash[6],
+ creation_time: fields_hash[12],
+ risk_metric: fields_hash[17],
+ not_before: fields_hash[19],
+ expiration_time: fields_hash[21],
+ }
+ end
+
+ private
+
+ def user_sensitive_fields(all_fields, certificates, fields_hash)
+ return {} unless all_fields
+
+ {
+ certificate_chain: certificates,
+ app_id: fields_hash[2],
+ attested_public_key: fields_hash[3],
+ client_hash: fields_hash[4],
+ token: fields_hash[5],
+ }
+ end
+ end
end
+
Only in truth-new/opensource/app/policies: feed_policy.rb
Only in truth-new/opensource/app/policies: group_membership_policy.rb
Only in truth-new/opensource/app/policies: group_membership_request_policy.rb
Only in truth-new/opensource/app/policies: group_policy.rb
diff -ru truth-old/opensource/app/policies/poll_policy.rb truth-new/opensource/app/policies/poll_policy.rb
--- truth-old/opensource/app/policies/poll_policy.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/policies/poll_policy.rb 2024-04-01 14:59:13
@@ -2,6 +2,6 @@
class PollPolicy < ApplicationPolicy
def vote?
- StatusPolicy.new(current_account, record.status).show? && !current_account.blocking?(record.account) && !record.account.blocking?(current_account)
+ StatusPolicy.new(current_account, record.status).show? && !current_account.blocking?(record.status.account) && !record.status.account.blocking?(current_account)
end
end
diff -ru truth-old/opensource/app/policies/status_policy.rb truth-new/opensource/app/policies/status_policy.rb
--- truth-old/opensource/app/policies/status_policy.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/policies/status_policy.rb 2024-04-12 09:09:08
@@ -14,10 +14,14 @@
def show?
return false if author.suspended?
- if requires_mention?
+ if group?
+ owned? || public_group? || private_group_member?
+ elsif requires_mention?
owned? || mention_exists?
elsif private?
owned? || following_author? || mention_exists?
+ elsif tv?
+ current_account.tv_enabled?
else
current_account.nil? || (!author_blocking? && !author_blocking_domain?)
end
@@ -51,10 +55,18 @@
author.id == current_account&.id
end
+ def group?
+ record.group_visibility?
+ end
+
def private?
record.private_visibility?
end
+ def tv?
+ !!record.tv_program
+ end
+
def mention_exists?
return false if current_account.nil?
@@ -87,6 +99,22 @@
return false if current_account.nil?
@preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author)
+ end
+
+ def public_group?
+ record.group.everyone?
+ end
+
+ def private_group_member?
+ return false unless record.group.members_only?
+
+ group_member?
+ end
+
+ def group_member?
+ return false if current_account.nil? || record.group_id.nil?
+
+ record.group.members.where(id: current_account&.id).exists?
end
def author
diff -ru truth-old/opensource/app/policies/user_policy.rb truth-new/opensource/app/policies/user_policy.rb
--- truth-old/opensource/app/policies/user_policy.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/policies/user_policy.rb 2024-04-01 14:59:13
@@ -49,6 +49,20 @@
admin? && !record.admin? && demoteable?
end
+ def enable_sms_reverification?
+ admin?
+ end
+
+ def disable_sms_reverification?
+ admin?
+ end
+
+
+ def enable_feature?
+ admin?
+ end
+
+
private
def promoteable?
diff -ru truth-old/opensource/app/presenters/account_relationships_presenter.rb truth-new/opensource/app/presenters/account_relationships_presenter.rb
--- truth-old/opensource/app/presenters/account_relationships_presenter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/presenters/account_relationships_presenter.rb 2024-04-01 14:59:13
@@ -49,36 +49,38 @@
account_note: {},
}
- @uncached_account_ids = []
+ @uncached_account_ids = @account_ids.uniq
- @account_ids.each do |account_id|
- maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}")
-
- if maps_for_account.is_a?(Hash)
- @cached.deep_merge!(maps_for_account)
- else
- @uncached_account_ids << account_id
- end
+ cache_ids = @account_ids.map { |account_id| relationship_cache_key(account_id) }
+ Rails.cache.read_multi(*cache_ids).each do |key, maps_for_account|
+ @cached.deep_merge!(maps_for_account)
+ @uncached_account_ids.delete(key.last)
end
@cached
end
def cache_uncached!
- @uncached_account_ids.each do |account_id|
+ to_cache = @uncached_account_ids.to_h do |account_id|
maps_for_account = {
- following: { account_id => following[account_id] },
- followed_by: { account_id => followed_by[account_id] },
- blocking: { account_id => blocking[account_id] },
- blocked_by: { account_id => blocked_by[account_id] },
- muting: { account_id => muting[account_id] },
- requested: { account_id => requested[account_id] },
+ following: { account_id => following[account_id] },
+ followed_by: { account_id => followed_by[account_id] },
+ blocking: { account_id => blocking[account_id] },
+ blocked_by: { account_id => blocked_by[account_id] },
+ muting: { account_id => muting[account_id] },
+ requested: { account_id => requested[account_id] },
domain_blocking: { account_id => domain_blocking[account_id] },
- endorsed: { account_id => endorsed[account_id] },
- account_note: { account_id => account_note[account_id] },
+ endorsed: { account_id => endorsed[account_id] },
+ account_note: { account_id => account_note[account_id] },
}
- Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
+ [relationship_cache_key(account_id), maps_for_account]
end
+
+ Rails.cache.write_multi(to_cache, expires_in: 1.day)
+ end
+
+ def relationship_cache_key(account_id)
+ ['relationship', @current_account_id, account_id]
end
end
Only in truth-new/opensource/app/presenters: feed_relationships_presenter.rb
Only in truth-new/opensource/app/presenters: group_relationships_presenter.rb
diff -ru truth-old/opensource/app/presenters/instance_presenter.rb truth-new/opensource/app/presenters/instance_presenter.rb
--- truth-old/opensource/app/presenters/instance_presenter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/presenters/instance_presenter.rb 2024-04-01 14:59:13
@@ -29,7 +29,7 @@
end
def status_count
- Rails.cache.fetch('local_status_count') { Account.local.joins(:account_stat).sum('account_stats.statuses_count') }.to_i
+ Rails.cache.fetch('local_status_count') { Account.local.joins(:account_status).sum('statuses_count') }.to_i
end
def domain_count
diff -ru truth-old/opensource/app/presenters/status_relationships_presenter.rb truth-new/opensource/app/presenters/status_relationships_presenter.rb
--- truth-old/opensource/app/presenters/status_relationships_presenter.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/presenters/status_relationships_presenter.rb 2024-04-01 14:59:13
@@ -2,26 +2,32 @@
class StatusRelationshipsPresenter
attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map,
- :bookmarks_map
+ :bookmarks_map, :groups_map, :polls_map
- def initialize(statuses, current_account_id = nil, **options)
+ def initialize(statuses, current_account_id = nil, group_id = nil, **options)
if current_account_id.nil?
@reblogs_map = {}
@favourites_map = {}
@bookmarks_map = {}
+ @groups_map = {}
@mutes_map = {}
@pins_map = {}
+ @polls_map = {}
else
statuses = statuses.compact
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
conversation_ids = statuses.filter_map(&:conversation_id).uniq
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && %w(public unlisted).include?(s.visibility) }
+ pinnable_group_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.group_visibility? }
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {})
@favourites_map = Status.favourites_map(status_ids, current_account_id).merge(options[:favourites_map] || {})
- @bookmarks_map = Status.bookmarks_map(status_ids, current_account_id).merge(options[:bookmarks_map] || {})
+ @bookmarks_map = {}
@mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {})
- @pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {})
+ @group_pins_map = Status.pins_map(pinnable_group_status_ids, current_account_id, group_id).merge(options[:pins_map] || {})
+ @pins_map = group_id ? @group_pins_map : Status.pins_map(pinnable_status_ids, current_account_id, group_id).merge(options[:pins_map] || {})
+ @groups_map = Status.groups_map(statuses)
+ @polls_map = Status.polls_map(statuses, current_account_id)
end
end
end
Only in truth-new/opensource/app/presenters: v2
diff -ru truth-old/opensource/app/serializers/activitypub/actor_serializer.rb truth-new/opensource/app/serializers/activitypub/actor_serializer.rb
--- truth-old/opensource/app/serializers/activitypub/actor_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/activitypub/actor_serializer.rb 2024-04-01 14:59:13
@@ -10,7 +10,7 @@
:discoverable, :olm, :suspended
attributes :id, :type, :following, :followers,
- :inbox, :outbox, :featured, :featured_tags,
+ :featured, :featured_tags,
:preferred_username, :name, :summary,
:url, :manually_approves_followers,
:discoverable, :published
@@ -27,12 +27,6 @@
class EndpointsSerializer < ActivityPub::Serializer
include RoutingHelper
-
- attributes :shared_inbox
-
- def shared_inbox
- inbox_url
- end
end
has_one :endpoints, serializer: EndpointsSerializer
@@ -43,7 +37,7 @@
delegate :suspended?, :instance_actor?, to: :object
def id
- object.instance_actor? ? instance_actor_url : account_url(object)
+ account_url(object)
end
def type
@@ -66,16 +60,8 @@
account_followers_url(object)
end
- def inbox
- object.instance_actor? ? instance_actor_inbox_url : account_inbox_url(object)
- end
-
def devices
account_collection_url(object, :devices)
- end
-
- def outbox
- object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object)
end
def featured
diff -ru truth-old/opensource/app/serializers/manifest_serializer.rb truth-new/opensource/app/serializers/manifest_serializer.rb
--- truth-old/opensource/app/serializers/manifest_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/manifest_serializer.rb 2023-05-05 13:42:02
@@ -68,6 +68,48 @@
sizes: '512x512',
type: 'image/png',
},
+ {
+ src: '/icons/icon-maskable-48x48.png',
+ sizes: '48x48',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-72x72.png',
+ sizes: '72x72',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-96x96.png',
+ sizes: '96x96',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-128x128.png',
+ sizes: '128x128',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-192x192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-384x384.png',
+ sizes: '384x384',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
+ {
+ src: '/icons/icon-maskable-512x512.png',
+ sizes: '512x512',
+ type: 'image/png',
+ purpose: 'maskable',
+ },
]
end
diff -ru truth-old/opensource/app/serializers/mobile/notification_serializer.rb truth-new/opensource/app/serializers/mobile/notification_serializer.rb
--- truth-old/opensource/app/serializers/mobile/notification_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/mobile/notification_serializer.rb 2024-04-12 09:09:08
@@ -1,11 +1,14 @@
# frozen_string_literal: true
-class Mobile::NotificationSerializer < ActiveModel::Serializer
+class Mobile::NotificationSerializer < NotificationSerializer
include RoutingHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::SanitizeHelper
attributes :token, :category, :platform, :message, :extend
+ attribute :title, if: :chat?
+ attribute :mutable_content
+ attribute :thread_id, if: :chat?
def token
[current_push_subscription.device_token]
@@ -23,29 +26,52 @@
object_type
end
+ def chat?
+ object.type == :chat
+ end
+
delegate :platform, to: :current_push_subscription
def message
- params = {name: object.from_account.display_name.presence || object.from_account.username}
- if object.count.to_i > 1
- template = "#{object_type}_group"
- params[:count_others] = object.count - 1
- params[:actor] = "other"
- params[:actor] += "s" if object.count.to_i > 2
- else
- template = object_type
- end
- I18n.t("notification_mailer.#{template}.subject", params)
+ chat? ? chat_message : I18n.t("notification_mailer.#{template}.#{notification_mailer_subject}", template_params)
end
+ def title
+ "@#{object.from_account.username}"
+ end
+
+ def title_with_display_name
+ object.from_account.display_name.presence || "@#{object.from_account.username}"
+ end
+
+ def mutable_content
+ true
+ end
+
+ def thread_id
+ chat_message_object['id']
+ end
+
def extend
url = notification_url(object.type)
- if (url.nil? || url.empty?)
+ if url.nil? || url.empty?
Rails.logger.info("Empty mobile push notification status detected. Object ID: #{object.id}. Object Type: #{object.type}")
end
- [{"key" => "truthLink", "val" => url}]
+ payload = []
+ payload.push({ 'key' => 'truthLink', 'val' => url })
+ payload.push({ 'key' => 'title', 'val' => chat? ? title_with_display_name : 'Truth Social' }) unless android?
+ payload.push({ 'key' => 'accountId', 'val' => object.account_id.to_s })
+ payload.push({ 'key' => 'chat', 'val' => extended_chat_fields }) if extended_chat_fields.present?
+ payload.push({ 'key' => 'category', 'val' => object_type })
+
+ if android?
+ payload.push({ 'key' => 'fromAccountId', 'val' => object.from_account_id.to_s })
+ payload.push({ 'key' => 'title', 'val' => android_title })
+ end
+
+ payload
end
def body
@@ -53,18 +79,101 @@
truncate(HTMLEntities.new.decode(str.to_str), length: 140) # Do not encode entities, since this value will not be used in HTML
end
+ def extended_chat_fields
+ return unless chat?
+
+ attachments = chat_message_attachments ? { 'media_attachments': chat_message_attachments } : {}
+ if android?
+ {
+ 'title': title_with_display_name,
+ 'chat_message_id': chat_message_object['id'],
+ 'chat_message_created_at': chat_message_object['created_at'],
+ 'from_account_id': chat_message_object['account_id'],
+ **attachments,
+ }
+ else
+ attachments
+ end
+ end
+
+ def chat_message_object
+ message = ChatMessage.find_message(object.account_id, object.activity.chat_id, object.activity_id)
+ ActiveSupport::JSON.decode(message) if message
+ end
+
+ def chat_message
+ chat_message_object['content'] ? strip_tags(chat_message_object['content']) : I18n.t("notification_mailer.chat.sent_message")
+ end
+
+ def chat_message_attachments
+ chat_message_object['media_attachments']
+ .pluck('id', 'type', 'preview_url')
+ .map { |p| { id: p[0], type: p[1], preview_url: p[2] } } if chat_message_object['media_attachments']
+ end
+
private
+
def notification_url(type)
- if %i[reblog reblog_group mention mention_group favourite favourite_group].include? type
+ if %i(reblog
+ reblog_group
+ mention
+ mention_group
+ favourite
+ favourite_group
+ ).include? type
object.target_status.uri
- elsif %i[follow follow_group].include? type
+ elsif %i(
+ follow
+ follow_group).include? type
ActivityPub::TagManager.instance.url_for(object.from_account)
+ elsif %i(
+ group_request
+ group_approval
+ group_promoted
+ group_demoted
+ group_delete).include? type
+ ActivityPub::TagManager.instance.url_for(object.target_group)
+ elsif %i(
+ group_favourite
+ group_favourite_group
+ group_mention
+ group_mention_group
+ group_reblog
+ group_reblog_group).include? type
+ ActivityPub::TagManager.instance.url_for(object.target_status)
+ elsif type == :chat
+ ActivityPub::TagManager.instance.url_for_chat_message(object.activity_id)
else
- ""
+ ''
end
end
def object_type
object.type.to_s.gsub '_group', ''
+ end
+
+ def android?
+ platform == 2
+ end
+
+ def notification_mailer_subject
+ android? ? 'subject_android' : 'subject'
+ end
+
+ def template_params
+ @template_params ||= mailer_params
+ end
+
+ def android_title
+ handle = "@#{object.from_account.username}"
+ if %i(group_request
+ group_approval
+ group_promoted
+ group_demoted
+ group_delete).include? object.type
+ template_params[:group] || handle
+ else
+ handle
+ end
end
end
Only in truth-new/opensource/app/serializers: notification_serializer.rb
diff -ru truth-old/opensource/app/serializers/oembed_serializer.rb truth-new/opensource/app/serializers/oembed_serializer.rb
--- truth-old/opensource/app/serializers/oembed_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/oembed_serializer.rb 2024-04-01 14:59:13
@@ -39,7 +39,7 @@
def html
attributes = {
src: embed_short_account_status_url(object.account, object),
- class: 'mastodon-embed',
+ class: "truthsocial-embed#{' truthsocial-video' if has_video}",
style: 'max-width: 100%; border: 0',
width: width,
height: height,
@@ -55,5 +55,9 @@
def height
instance_options[:height]
+ end
+
+ def has_video
+ instance_options[:has_video]
end
end
diff -ru truth-old/opensource/app/serializers/rest/account_serializer.rb truth-new/opensource/app/serializers/rest/account_serializer.rb
--- truth-old/opensource/app/serializers/rest/account_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/account_serializer.rb 2024-04-05 09:07:34
@@ -5,7 +5,8 @@
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :avatar, :avatar_static, :header, :header_static, :followers_count,
- :following_count, :statuses_count, :last_status_at, :verified, :location, :website
+ :following_count, :statuses_count, :last_status_at, :verified, :location, :website, :accepting_messages, :chats_onboarded,
+ :feeds_onboarded, :tv_onboarded, :show_nonmember_group_statuses, :pleroma, :tv_account, :receive_only_follow_mentions
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
@@ -29,10 +30,10 @@
delegate :verified?, to: :object
- delegate :location, to: :object
+ def website
+ object.suspended? ? '' : object.website
+ end
- delegate :website, to: :object
-
def acct
object.pretty_acct
end
@@ -42,7 +43,7 @@
end
def url
- ActivityPub::TagManager.instance.url_for(object)
+ object.suspended? ? '' : ActivityPub::TagManager.instance.url_for(object)
end
def avatar
@@ -54,15 +55,23 @@
end
def header
- object&.header_file_name ? full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url) : ''
+ if object&.header_file_name
+ full_asset_url(object.suspended? ? object.header.default_url : object.header_original_url)
+ else
+ ''
+ end
end
def header_static
- object&.header_file_name ? full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url) : ''
+ if object&.header_file_name
+ full_asset_url(object.suspended? ? object.header.default_url : object.header_static_url)
+ else
+ ''
+ end
end
def created_at
- object.created_at.midnight.as_json
+ object.created_at.as_json
end
def last_status_at
@@ -101,9 +110,27 @@
object.suspended?
end
+ def pleroma
+ {
+ accepts_chat_messages: object.accepting_messages,
+ }
+ end
+
+ def location
+ object.suspended? ? '' : object.location
+ end
+
delegate :suspended?, to: :object
def moved_and_not_nested?
object.moved? && object.moved_to_account.moved_to_account_id.nil?
+ end
+
+ def tv_account
+ instance_options[:tv_account_lookup] && instance_options[:tv_account_lookup] == true ? !!object.tv_channel_account : false
+ end
+
+ def chats_onboarded
+ true
end
end
Only in truth-new/opensource/app/serializers/rest: ad_metric_serializer.rb
diff -ru truth-old/opensource/app/serializers/rest/admin/account_serializer.rb truth-new/opensource/app/serializers/rest/admin/account_serializer.rb
--- truth-old/opensource/app/serializers/rest/admin/account_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/admin/account_serializer.rb 2024-04-01 14:59:13
@@ -1,10 +1,10 @@
# frozen_string_literal: true
class REST::Admin::AccountSerializer < ActiveModel::Serializer
- attributes :id, :username, :domain, :created_at,
+ attributes :id, :username, :domain, :created_at, :deleted,
:email, :ip, :role, :confirmed, :suspended,
:silenced, :disabled, :approved, :locale,
- :invite_request, :verified, :location, :website
+ :invite_request, :verified, :location, :website, :sms, :sms_reverification_required, :updated_at, :advertiser
attribute :created_by_application_id, if: :created_by_application?
attribute :invited_by_account_id, if: :invited?
@@ -21,10 +21,18 @@
delegate :website, to: :object
+ def deleted
+ object.deleted?
+ end
+
def email
object.user_email
end
+ def sms
+ object.user_sms
+ end
+
def ip
object.user_current_sign_in_ip.to_s.presence
end
@@ -79,5 +87,17 @@
def created_by_application?
object.user&.created_by_application_id&.present?
+ end
+
+ def sms_reverification_required
+ !!object.user&.user_sms_reverification_required&.user_id
+ end
+
+ def updated_at
+ object.updated_at
+ end
+
+ def advertiser
+ !!object.recent_ads.presence
end
end
Only in truth-new/opensource/app/serializers/rest/admin: chat_message_serializer.rb
Only in truth-new/opensource/app/serializers/rest/admin: one_time_challenge_serializer.rb
Only in truth-new/opensource/app/serializers/rest/admin: tag_search_serializer.rb
Only in truth-new/opensource/app/serializers/rest/admin: tag_serializer.rb
Only in truth-new/opensource/app/serializers/rest/admin: webauthn_credential_serializer.rb
Only in truth-new/opensource/app/serializers/rest: avatars_carousel_serializer.rb
Only in truth-new/opensource/app/serializers/rest: chat_member_removal_serializer.rb
Only in truth-new/opensource/app/serializers/rest: chat_message_serializer.rb
Only in truth-new/opensource/app/serializers/rest: chat_serializer.rb
Only in truth-new/opensource/app/serializers/rest: chat_silence_serializer.rb
diff -ru truth-old/opensource/app/serializers/rest/credential_account_serializer.rb truth-new/opensource/app/serializers/rest/credential_account_serializer.rb
--- truth-old/opensource/app/serializers/rest/credential_account_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/credential_account_serializer.rb 2024-04-05 09:09:43
@@ -1,11 +1,12 @@
# frozen_string_literal: true
class REST::CredentialAccountSerializer < REST::AccountSerializer
- attributes :source, :pleroma
+ attributes :source, :pleroma, :features
+ SMS_REVERIFICATION_DEADLINE = 90
def source
user = object.user
- waitlist_enabled = ENV.fetch("WAITLIST_ENABLED", "true")
+ waitlist_enabled = ENV.fetch('WAITLIST_ENABLED', 'true')
source = {
privacy: user.setting_default_privacy,
@@ -18,17 +19,45 @@
sms_verified: (user.not_ready_for_approval? || user.ready_by_csv_import? || user.sms_verified?),
ready_by_sms_verification: (!user.not_ready_for_approval? && !user.ready_by_csv_import?),
follow_requests_count: FollowRequest.where(target_account: object).limit(40).count,
+ accepting_messages: object.accepting_messages,
+ chats_onboarded: true,
+ feeds_onboarded: object.feeds_onboarded,
+ tv_onboarded: object.tv_onboarded,
+ show_nonmember_group_statuses: object.show_nonmember_group_statuses,
+ unauth_visibility: !!user.unauth_visibility,
+ integrity: user.integrity_score,
+ integrity_status: user.integrity_status(instance_options[:access_token], instance_options[:android_client]),
+ sms_reverification_required: !!user.user_sms_reverification_required&.user_id,
+ sms: user.sms.present?,
+ sms_country: user.sms_country,
+ receive_only_follow_mentions: object.receive_only_follow_mentions
}
- source[:unapproved_position] = user.get_position_in_waitlist_queue if waitlist_enabled == "true"
- return source
+ source[:unapproved_position] = user.get_position_in_waitlist_queue if waitlist_enabled == 'true'
+ source[:sms_last_four_digits] = user.sms.last(4) if user.sms.present?
+ source[:sms_reverification_days_left] = sms_reverification_days_left(user) if user.user_sms_reverification_required&.user_id
+ source
end
+ def sms_reverification_days_left(user)
+ action_date = Admin::ActionLog.select(:created_at).where(target_type: 'User', target_id: user.id, action: 'enable_sms_reverification').order('created_at DESC').first&.created_at
+ return SMS_REVERIFICATION_DEADLINE unless action_date
+ [SMS_REVERIFICATION_DEADLINE - ((Time.now - action_date) / 1.day).round, 0].max
+ end
+
def pleroma
{
+ accepts_chat_messages: object.accepting_messages,
settings_store: object.settings_store,
- is_admin: object.user.admin,
- is_moderator: object.user.moderator
}
+ end
+
+ def features
+ enabled_features = object.feature_flags.pluck(:name)
+
+ ::Configuration::FeatureFlag.all.each_with_object({}) do |feature, hash|
+ name = feature.name
+ hash[name] = feature.enabled? || feature.account_based? && enabled_features.include?(name)
+ end
end
end
Only in truth-new/opensource/app/serializers/rest: feed_serializer.rb
Only in truth-new/opensource/app/serializers/rest: group_membership_serializer.rb
Only in truth-new/opensource/app/serializers/rest: group_relationship_serializer.rb
Only in truth-new/opensource/app/serializers/rest: group_serializer.rb
Only in truth-new/opensource/app/serializers/rest: group_suggestion_serializer.rb
Only in truth-new/opensource/app/serializers/rest: groups_avatar_serializer.rb
Only in truth-new/opensource/app/serializers/rest: groups_carousel_serializer.rb
diff -ru truth-old/opensource/app/serializers/rest/instance_serializer.rb truth-new/opensource/app/serializers/rest/instance_serializer.rb
--- truth-old/opensource/app/serializers/rest/instance_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/instance_serializer.rb 2024-04-01 14:59:13
@@ -6,12 +6,8 @@
attributes :uri, :title, :short_description, :description, :email,
:version, :urls, :thumbnail, :languages, :registrations,
:approval_required, :invites_enabled, :configuration,
- :feature_quote
+ :feature_quote, :rules
- has_many :rules, serializer: REST::RuleSerializer
-
- delegate :rules, to: :instance_presenter
-
def uri
Rails.configuration.x.local_domain
end
@@ -33,7 +29,8 @@
end
def version
- "#{Mastodon::Version} (compatible; TruthSocial 1.0.0)"
+ is_staging = ActiveModel::Type::Boolean.new.cast(ENV['IS_STAGING'])
+ "#{Mastodon::Version} (compatible; TruthSocial 1.0.0#{is_staging ? '+unreleased' : ''})"
end
def thumbnail
@@ -45,20 +42,29 @@
end
def configuration
+ ads_configuration = JSON.parse(ENV.fetch('ADS_CONFIGURATION', '[{}]'))
+
{
statuses: {
max_characters: StatusLengthValidator::MAX_CHARS,
max_media_attachments: 4,
- characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
+ characters_reserved_per_url: URLPlaceholder::LENGTH,
},
+ chats: {
+ max_characters: ChatMessage::MAX_CHARS,
+ max_messages_per_minute: ChatMessage::MAX_MESSAGES_PER_MIN,
+ max_media_attachments: ENV.fetch('MAX_ATTACHMENTS_ALLOWED_PER_MESSAGE', 4).to_i,
+ },
+
media_attachments: {
- supported_mime_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES,
+ supported_mime_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES,
image_size_limit: MediaAttachment::IMAGE_LIMIT,
image_matrix_limit: Attachmentable::MAX_MATRIX_LIMIT,
video_size_limit: MediaAttachment::VIDEO_LIMIT,
video_frame_rate_limit: MediaAttachment::MAX_VIDEO_FRAME_RATE,
video_matrix_limit: MediaAttachment::MAX_VIDEO_MATRIX_LIMIT,
+ video_duration_limit: MediaAttachment::MAX_VIDEO_DURATION_LIMIT,
},
polls: {
@@ -67,6 +73,22 @@
min_expiration: PollValidator::MIN_EXPIRATION,
max_expiration: PollValidator::MAX_EXPIRATION,
},
+
+ ads: {
+ algorithm: {
+ name: ads_configuration[0]&.[]('value'),
+ configuration: {
+ frequency: ads_configuration[1]&.[]('value').to_i,
+ phase_min: ads_configuration[2]&.[]('value').to_f,
+ phase_max: ads_configuration[3]&.[]('value').to_f,
+ },
+ },
+ },
+ groups: {
+ max_characters_name: ENV.fetch('MAX_GROUP_NAME_CHARS', 35).to_i,
+ max_characters_description: ENV.fetch('MAX_GROUP_NOTE_CHARS', 160).to_i,
+ max_admins_allowed: ENV.fetch('MAX_GROUP_ADMINS_ALLOWED', 10).to_i,
+ },
}
end
@@ -87,7 +109,11 @@
end
def feature_quote
- false
+ true
+ end
+
+ def rules
+ ActiveModelSerializers::SerializableResource.new(Rule.ordered, each_serializer: REST::RuleSerializer).as_json
end
private
diff -ru truth-old/opensource/app/serializers/rest/media_attachment_serializer.rb truth-new/opensource/app/serializers/rest/media_attachment_serializer.rb
--- truth-old/opensource/app/serializers/rest/media_attachment_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/media_attachment_serializer.rb 2024-04-01 14:59:13
@@ -5,14 +5,14 @@
attributes :id, :type, :url, :preview_url, :external_video_id,
:remote_url, :preview_remote_url, :text_url, :meta,
- :description, :blurhash
+ :description, :blurhash, :tv
def id
object.id.to_s
end
def url
- if object.not_processed?
+ if object.type != 'video' && object.not_processed?
nil
elsif object.needs_redownload?
media_proxy_url(object.id, :original)
@@ -26,8 +26,8 @@
end
def preview_url
- if object.type == "video"
- #TODO: replace the image and upload it to CDN
+ if object.type == 'video'
+ # TODO: replace the image and upload it to CDN
object.external_video_id && object.status.preview_card&.image? ? full_asset_url(object.status.preview_card.image.url(:original)) : full_asset_url('/icons/missing.png')
elsif object.needs_redownload?
media_proxy_url(object.id, :small)
@@ -48,5 +48,14 @@
def meta
object.file.meta
+ end
+
+ def external_video_id
+ return nil if object.not_processed?
+ object.external_video_id
+ end
+
+ def tv
+ REST::TvProgramSerializer.new(instance_options[:tv_program]) if instance_options && instance_options[:tv_program]
end
end
diff -ru truth-old/opensource/app/serializers/rest/notification_serializer.rb truth-new/opensource/app/serializers/rest/notification_serializer.rb
--- truth-old/opensource/app/serializers/rest/notification_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/notification_serializer.rb 2024-04-01 14:59:13
@@ -3,9 +3,10 @@
class REST::NotificationSerializer < ActiveModel::Serializer
attributes :id, :type, :total_count, :created_at
attribute :total_count, if: -> { object.count.present? }
+ attribute :target_chat_message, if: -> { object.type == :chat || object.type == :chat_message_deleted }
+ attribute :target_status, key: :status, if: :status_type?
belongs_to :from_account, key: :account, serializer: REST::AccountSerializer
- belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
def id
object.id.to_s
@@ -19,7 +20,26 @@
object.count
end
+ def target_status
+ REST::V2::StatusSerializer.new(context: { current_user: instance_options[:current_user] }).serialize(object.target_status) if object.target_status
+ end
+
def status_type?
- [:favourite, :favourite_group, :reblog, :reblog_group, :status, :mention, :mention_group, :poll].include?(object.type)
+ [:favourite, :favourite_group, :group_favourite, :group_favourite_group,
+ :reblog, :reblog_group, :group_reblog, :group_reblog_group,
+ :status,
+ :mention, :mention_group, :group_mention, :group_mention_group,
+ :poll].include?(object.type)
+ end
+
+ def target_chat_message
+ chat_message = ChatMessage.find_message(object.account_id, object.activity.chat_id, object.activity_id)
+
+ if chat_message
+ decoded = ActiveSupport::JSON.decode(chat_message)
+ chat_message_obj = ChatMessage.new(decoded)
+
+ ActiveModelSerializers::SerializableResource.new(chat_message_obj, serializer: REST::ChatMessageSerializer).as_json
+ end
end
end
Only in truth-new/opensource/app/serializers/rest: oauth_token_serializer.rb
diff -ru truth-old/opensource/app/serializers/rest/preview_card_serializer.rb truth-new/opensource/app/serializers/rest/preview_card_serializer.rb
--- truth-old/opensource/app/serializers/rest/preview_card_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/preview_card_serializer.rb 2024-04-01 14:59:13
@@ -8,7 +8,37 @@
:provider_url, :html, :width, :height,
:image, :embed_url, :blurhash
+ attribute :group, if: -> { instance_options && instance_options[:group] }
+
def image
object.image? ? full_asset_url(object.image.url(:original)) : nil
+ end
+
+ def url
+ url = object.url
+
+ if instance_options && instance_options[:external_links]
+ links = instance_options[:external_links].index_by(&:url)
+ if (link_id = links[object&.url]&.id)
+ url = link_url(link_id, subdomain: 'links')
+ end
+ end
+ url
+ end
+
+ def provider_name
+ url = object.provider_name
+ if url.blank?
+ url = Addressable::URI.parse(object.url)&.host || ''
+ end
+ url
+ end
+
+ def group
+ REST::GroupSerializer.new(instance_options[:group])
+ end
+
+ def html
+ Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED)
end
end
Only in truth-new/opensource/app/serializers/rest: revcontent_ad_serializer.rb
Only in truth-new/opensource/app/serializers/rest: revcontent_ads_serializer.rb
diff -ru truth-old/opensource/app/serializers/rest/rule_serializer.rb truth-new/opensource/app/serializers/rest/rule_serializer.rb
--- truth-old/opensource/app/serializers/rest/rule_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/rule_serializer.rb 2024-04-01 14:59:13
@@ -6,4 +6,16 @@
def id
object.id.to_s
end
+
+ def rule_type
+ object.rule_type == "rule_type_group" ? :group : object.rule_type
+ end
+
+ def text
+ I18n.t("admin.instances.rules.#{object.name}.text")
+ end
+
+ def subtext
+ I18n.t("admin.instances.rules.#{object.name}.subtext")
+ end
end
diff -ru truth-old/opensource/app/serializers/rest/search_serializer.rb truth-new/opensource/app/serializers/rest/search_serializer.rb
--- truth-old/opensource/app/serializers/rest/search_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/search_serializer.rb 2024-04-12 09:09:08
@@ -1,7 +1,14 @@
# frozen_string_literal: true
class REST::SearchSerializer < ActiveModel::Serializer
- has_many :accounts, serializer: REST::AccountSerializer
+ attributes :hashtags, :accounts
+
has_many :statuses, serializer: REST::StatusSerializer
- has_many :hashtags, serializer: REST::TagSerializer
+ delegate :hashtags, to: :object
+ has_many :groups, serializer: REST::GroupSerializer
+
+ def accounts
+ ActiveModel::SerializableResource.new(object.accounts, each_serializer: REST::AccountSerializer, tv_account_lookup: true)
+ end
+
end
diff -ru truth-old/opensource/app/serializers/rest/status_serializer.rb truth-new/opensource/app/serializers/rest/status_serializer.rb
--- truth-old/opensource/app/serializers/rest/status_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/rest/status_serializer.rb 2024-04-01 14:59:13
@@ -3,11 +3,12 @@
class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
- :uri, :url
+ :uri, :url, :sponsored, :tombstone, :tv
attribute :replies_count
attribute :reblogs_count
attribute :favourites_count
+ attribute :group_timeline_visible
attribute :favourited, if: :current_user?
attribute :reblogged, if: :current_user?
attribute :muted, if: :current_user?
@@ -18,17 +19,19 @@
attribute :text, if: :source_requested?
attribute :quote_id, if: -> { object.quote? }
+ attribute :metrics, if: -> { object.ad.present? }
+ attribute :preview_card, key: :card
+ attribute :media_attachments
belongs_to :reblog, serializer: REST::StatusSerializer
belongs_to :application, if: :show_application?
belongs_to :account, serializer: REST::AccountSerializer
+ belongs_to :group, serializer: REST::GroupSerializer
- has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
has_many :ordered_mentions, key: :mentions
has_many :tags
has_many :emojis, serializer: REST::CustomEmojiSerializer
- has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
def id
@@ -47,6 +50,20 @@
object.quote_id.to_s
end
+ def metrics
+ REST::AdMetricSerializer.new.serialize(object.ad)
+ end
+
+ def preview_card
+ group = if instance_options && instance_options[:relationships]
+ instance_options[:relationships].groups_map[object.id] || false
+ else
+ Status.groups_map([object])[object.id] || false
+ end
+
+ REST::PreviewCardSerializer.new(object.preview_card, external_links: object.links, group: group) if object.preview_card
+ end
+
def current_user?
if defined?(current_user)
!current_user.nil?
@@ -81,7 +98,7 @@
end
def content
- Formatter.instance.format(object)
+ Formatter.instance.format(object, { external_links: object.links })
end
def url
@@ -159,6 +176,24 @@
object.active_mentions.to_a.sort_by(&:id)
end
+ def sponsored
+ !!object.ad
+ end
+
+ def tombstone
+ nil
+ end
+
+ def media_attachments
+ object.media_attachments.map do |attachment|
+ REST::MediaAttachmentSerializer.new(attachment, tv_program: object.tv_program)
+ end
+ end
+
+ def tv
+ REST::TvProgramSerializer.new(object.tv_program) if object.tv_program
+ end
+
class ApplicationSerializer < ActiveModel::Serializer
attributes :name, :website
end
@@ -174,6 +209,11 @@
object.account_username
end
+ def group_timeline_visible
+ object.group ? object.group_timeline_visible : true
+ end
+
+
def url
ActivityPub::TagManager.instance.url_for(object.account)
end
@@ -204,7 +244,7 @@
if instance_options && instance_options[:account_relationships]
instance_options[:account_relationships].muting[object.account_id] ? true : false || instance_options[:account_relationships].blocking[object.account_id] || instance_options[:account_relationships].blocked_by[object.account_id] || instance_options[:account_relationships].domain_blocking[object.account_id] || false
else
- current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain)
+ current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain)
end
end
end
@@ -228,6 +268,6 @@
end
class REST::StatusSerializer < ActiveModel::Serializer
- belongs_to :quote, serializer: REST::NestedQuoteSerializer
- belongs_to :thread, serializer: REST::InReplySerializer, key: :in_reply_to, if: -> { !@instance_options[:exclude_reply_previews] }
+ belongs_to :quote, serializer: REST::NestedQuoteSerializer, if: -> { (object&.quote&.visibility != 'self' || (current_user? && current_user.account_id == object&.quote&.account_id)) }
+ belongs_to :thread, serializer: REST::InReplySerializer, key: :in_reply_to, if: -> { !@instance_options[:exclude_reply_previews] && (object&.thread&.visibility != 'self' || (current_user? && current_user.account_id == object&.thread&.account_id)) }
end
Only in truth-new/opensource/app/serializers/rest: suggestions_carousel_serializer.rb
Only in truth-new/opensource/app/serializers/rest: truth
Only in truth-new/opensource/app/serializers/rest: tv_program_serializer.rb
Only in truth-new/opensource/app/serializers/rest: v2
diff -ru truth-old/opensource/app/serializers/web/notification_serializer.rb truth-new/opensource/app/serializers/web/notification_serializer.rb
--- truth-old/opensource/app/serializers/web/notification_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/web/notification_serializer.rb 2023-05-05 13:42:02
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Web::NotificationSerializer < ActiveModel::Serializer
+class Web::NotificationSerializer < NotificationSerializer
include RoutingHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::SanitizeHelper
@@ -29,7 +29,7 @@
end
def title
- I18n.t("notification_mailer.#{object.type}.subject", name: object.from_account.display_name.presence || object.from_account.username)
+ I18n.t("notification_mailer.#{template}.subject", mailer_params)
end
def body
diff -ru truth-old/opensource/app/serializers/webfinger_serializer.rb truth-new/opensource/app/serializers/webfinger_serializer.rb
--- truth-old/opensource/app/serializers/webfinger_serializer.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/serializers/webfinger_serializer.rb 2024-04-01 14:59:13
@@ -10,25 +10,14 @@
end
def aliases
- if object.instance_actor?
- [instance_actor_url]
- else
- [short_account_url(object), account_url(object)]
- end
+ [short_account_url(object), account_url(object)]
end
def links
- if object.instance_actor?
- [
- { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: about_more_url(instance_actor: true) },
- { rel: 'self', type: 'application/activity+json', href: instance_actor_url },
- ]
- else
- [
- { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
- { rel: 'self', type: 'application/activity+json', href: account_url(object) },
- { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
- ]
- end
+ [
+ { rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(object) },
+ { rel: 'self', type: 'application/activity+json', href: account_url(object) },
+ # { rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_interaction_url}?uri={uri}" },
+ ]
end
end
diff -ru truth-old/opensource/app/services/account_search_service.rb truth-new/opensource/app/services/account_search_service.rb
--- truth-old/opensource/app/services/account_search_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/account_search_service.rb 2024-04-01 14:59:13
@@ -17,7 +17,8 @@
private
def search_service_results
- return [] if query.blank? || limit < 1
+ return [] if limit < 1
+ return [] if query.blank? && !options[:followers]
[exact_match] + search_results
end
@@ -30,6 +31,7 @@
match = Account.find_local(query)
match = nil if !match.nil? && !account.nil? && options[:following] && !account.following?(match)
+ match = nil if options[:followers] && !match&.following?(account)
@exact_match = match
end
@@ -38,7 +40,7 @@
return [] if limit_for_non_exact_results.zero?
@search_results ||= begin
- results = from_elasticsearch if Chewy.enabled?
+ results = from_elasticsearch if Chewy.enabled? && !options[:followers]
results ||= from_database
results
end
@@ -57,14 +59,20 @@
end
def advanced_search_results
- Account.advanced_search_for(query, account, limit_for_non_exact_results, options[:following], offset)
+ Account.advanced_search_for(query, account, limit, options[:following], offset)
end
def follower_search
- account.followers.where('LOWER(username) LIKE ?', '%' + query.downcase + '%').limit(20)
+ account
+ .followers_unordered
+ .where('LOWER(username) LIKE :search OR LOWER(display_name) LIKE :search', search: "%#{sanitize_search(query&.downcase)}%")
+ .where(accepting_messages: true)
+ .limit(limit)
+ .offset(offset)
+ .order(username: :asc)
end
- def fields
+ def fields
if likely_username?
%w(acct.edge_ngram acct)
elsif likely_display_name?
@@ -89,14 +97,23 @@
functions = [reputation_score_function, followers_score_function, time_distance_function]
- records = AccountsIndex.query(function_score: { query: fields_query, functions: functions, boost_mode: 'multiply', score_mode: 'multiply' })
+ records = AccountsIndex.query(
+ function_score: {
+ query: fields_query,
+ functions: functions,
+ boost_mode: 'multiply',
+ score_mode: 'multiply',
+ }
+ )
.filter(SearchService::PROHIBITED_FILTERS)
+ .filter(term: { suspended: false })
+ .filter(exists: { field: 'email' })
.limit(limit_for_non_exact_results)
.offset(offset)
.objects
.compact
- ActiveRecord::Associations::Preloader.new.preload(records, :account_stat)
+ ActiveRecord::Associations::Preloader.new.preload(records, [:account_follower, :account_following, :account_status, :tv_channel_account, :moved_to_account])
records
rescue Faraday::ConnectionFailed, Parslet::ParseFailed
@@ -168,4 +185,7 @@
@acct_hint
end
+ def sanitize_search(query)
+ ActiveRecord::Base.sanitize_sql_like(query || '')
+ end
end
Only in truth-old/opensource/app/services/activitypub: prepare_followers_synchronization_service.rb
Only in truth-old/opensource/app/services/activitypub: process_collection_service.rb
diff -ru truth-old/opensource/app/services/activitypub/process_poll_service.rb truth-new/opensource/app/services/activitypub/process_poll_service.rb
--- truth-old/opensource/app/services/activitypub/process_poll_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/activitypub/process_poll_service.rb 2024-04-12 09:09:08
@@ -8,7 +8,7 @@
return unless expected_type?
- previous_expires_at = poll.expires_at
+ #previous_expires_at = poll.expires_at
expires_at = if @json['closed'].is_a?(String)
@json['closed']
@@ -47,9 +47,9 @@
# If the poll had no expiration date set but now has, and people have voted,
# schedule a notification.
- if previous_expires_at.nil? && poll.expires_at.present? && poll.votes.exists?
- PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id)
- end
+ # if previous_expires_at.nil? && poll.expires_at.present? && poll.votes.exists?
+ # PollExpirationNotifyWorker.perform_at(poll.expires_at + 5.minutes, poll.id)
+ # end
end
private
Only in truth-new/opensource/app/services: admin_account_search_service.rb
Only in truth-new/opensource/app/services: ads_service.rb
diff -ru truth-old/opensource/app/services/after_unallow_domain_service.rb truth-new/opensource/app/services/after_unallow_domain_service.rb
--- truth-old/opensource/app/services/after_unallow_domain_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/after_unallow_domain_service.rb 2024-04-01 14:59:13
@@ -3,7 +3,13 @@
class AfterUnallowDomainService < BaseService
def call(domain)
Account.where(domain: domain).find_each do |account|
- DeleteAccountService.new.call(account, reserve_username: false)
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'service_unallowed_domain',
+ reserve_username: false,
+ skip_activitypub: true,
+ )
end
end
end
Only in truth-new/opensource/app/services: android_device_check
diff -ru truth-old/opensource/app/services/app_sign_up_service.rb truth-new/opensource/app/services/app_sign_up_service.rb
--- truth-old/opensource/app/services/app_sign_up_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/app_sign_up_service.rb 2024-04-01 14:59:13
@@ -11,7 +11,7 @@
invite_request_params = { text: params[:reason] }
user = User.create!(user_params.merge(created_by_application: app, sign_up_ip: remote_ip, password_confirmation: user_params[:password], account_attributes: account_params, invite_request_attributes: invite_request_params))
- Doorkeeper::AccessToken.create!(application: app,
+ OauthAccessToken.create!(application: app,
resource_owner_id: user.id,
scopes: app.scopes,
expires_in: Doorkeeper.configuration.access_token_expires_in,
Only in truth-new/opensource/app/services: assertion_service.rb
Only in truth-new/opensource/app/services: authorize_membership_service.rb
diff -ru truth-old/opensource/app/services/batched_remove_status_service.rb truth-new/opensource/app/services/batched_remove_status_service.rb
--- truth-old/opensource/app/services/batched_remove_status_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/batched_remove_status_service.rb 2023-05-05 13:42:02
@@ -31,7 +31,7 @@
# Since we skipped all callbacks, we also need to manually
# deindex the statuses
- Chewy.strategy.current.update(StatusesIndex::Status, statuses_and_reblogs) if Chewy.enabled?
+ Chewy.strategy.current.update(StatusesIndex, statuses_and_reblogs) if Chewy.enabled?
return if options[:skip_side_effects]
diff -ru truth-old/opensource/app/services/block_domain_service.rb truth-new/opensource/app/services/block_domain_service.rb
--- truth-old/opensource/app/services/block_domain_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/block_domain_service.rb 2024-04-01 14:59:13
@@ -37,7 +37,14 @@
blocked_domain_accounts.without_suspended.in_batches.update_all(suspended_at: @domain_block.created_at, suspension_origin: :local)
blocked_domain_accounts.where(suspended_at: @domain_block.created_at).reorder(nil).find_each do |account|
- DeleteAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at)
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'service_block_domain',
+ reserve_username: true,
+ skip_activitypub: true,
+ suspended_at: @domain_block.created_at,
+ )
end
end
diff -ru truth-old/opensource/app/services/block_service.rb truth-new/opensource/app/services/block_service.rb
--- truth-old/opensource/app/services/block_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/block_service.rb 2024-04-01 14:59:13
@@ -6,12 +6,20 @@
def call(account, target_account)
return if account.id == target_account.id
- UnfollowService.new.call(account, target_account) if account.following?(target_account)
- UnfollowService.new.call(target_account, account) if target_account.following?(account)
+ if account.following?(target_account)
+ UnfollowService.new.call(account, target_account)
+ FollowDelete.where(account_id: account.id).destroy_all
+ end
+
+ if target_account.following?(account)
+ UnfollowService.new.call(target_account, account)
+ FollowDelete.where(target_account_id: target_account).destroy_all
+ end
+
RejectFollowService.new.call(target_account, account) if target_account.requested?(account)
block = account.block!(target_account)
-
+ invalidate_secondary_caches(account, target_account)
BlockWorker.perform_async(account.id, target_account.id)
create_notification(block) if !target_account.local? && target_account.activitypub?
export_prometheus_metric
@@ -19,6 +27,11 @@
end
private
+
+ def invalidate_secondary_caches(account, target_account)
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", account.id, target_account.id, target_account.whale?)
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", target_account.id, account.id, account.whale?)
+ end
def create_notification(block)
ActivityPub::DeliveryWorker.perform_async(build_json(block), block.account_id, block.target_account.inbox_url)
Only in truth-new/opensource/app/services: canonical_request_service.rb
Only in truth-new/opensource/app/services: chat_message_reaction_service.rb
Only in truth-new/opensource/app/services: chat_message_service.rb
Only in truth-new/opensource/app/services: chat_service.rb
Only in truth-new/opensource/app/services/concerns: app_attestable.rb
Only in truth-new/opensource/app/services/concerns: challengeable.rb
Only in truth-new/opensource/app/services/concerns: group_cachable.rb
Only in truth-new/opensource/app/services/concerns: links_parser_concern.rb
Only in truth-new/opensource/app/services/concerns: upload_video_concern.rb
diff -ru truth-old/opensource/app/services/delete_account_service.rb truth-new/opensource/app/services/delete_account_service.rb
--- truth-old/opensource/app/services/delete_account_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/delete_account_service.rb 2024-04-01 14:59:13
@@ -1,8 +1,18 @@
# frozen_string_literal: true
+class UnknownDeletionType < StandardError; end
+
class DeleteAccountService < BaseService
include Payloadable
+ # This can be passed to #call as the deleted_by_id to indicate that
+ # this account was deleted by an automated process, rather than by
+ # a user or moderator. This is then passed to Janus.
+ #
+ # NOTE: The deletion_type option should be used to give a more specific
+ # details on how the account was deleted.
+ DELETED_BY_SERVICE = -99
+
ASSOCIATIONS_ON_SUSPEND = %w(
account_pins
active_relationships
@@ -72,9 +82,15 @@
# @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads
# @option [Boolean] :skip_activitypub Skip sending ActivityPub payloads. Implied by :skip_side_effects
# @option [Time] :suspended_at Only applicable when :reserve_username is true
- def call(account, **options)
+ def call(account, deleted_by_id, **options)
@account = account
- @options = { reserve_username: true, reserve_email: true }.merge(options)
+ @deleted_by_id = deleted_by_id
+ defaults = {
+ deletion_type: 'unknown',
+ reserve_email: true,
+ reserve_username: true,
+ }
+ @options = defaults.merge(options)
if @account.local? && @account.user_unconfirmed_or_pending?
@options[:reserve_email] = false
@@ -87,10 +103,34 @@
distribute_activities!
purge_content!
fulfill_deletion_request!
+ publish_delete_event
+ track_account_deletion
end
private
+ def track_account_deletion
+ Logs::AccountDeletion.create!(
+ account_id: @account.id,
+ user_id: @account&.user&.id,
+ username: @account.username,
+ email: @account&.user_email,
+ deleted_at: Time.now.utc,
+ account_deletion_type: @options[:deletion_type],
+ deleted_by_account_id: @deleted_by_id,
+ )
+ if @options[:deletion_type] == 'unknown'
+ raise UnknownDeletionType
+ end
+ rescue UnknownDeletionType => e
+ Rails.logger.warn('Track account deletion: Unknown deletion type')
+ Rails.logger.warn(e.backtrace.join("\n"))
+ rescue ActiveRecord::NotNullViolation => e
+ Rails.logger.info("Failed to track account deletion: #{e.message}")
+ rescue ActiveRecord::RecordNotUnique => e
+ Rails.logger.info("Failed to track account deletion, account_id: #{@account.id}, #{e.message}")
+ end
+
def distribute_activities!
return if skip_activitypub?
@@ -173,21 +213,22 @@
end
def purge_polls!
- @account.polls.reorder(nil).where.not(status_id: reported_status_ids).in_batches.delete_all
+ @account.polls.reorder(nil).merge(Status.where.not(id: reported_status_ids)).in_batches.delete_all
end
def purge_generated_notifications!
# By deleting polls and statuses without callbacks, we've left behind
# polymorphically associated notifications generated by this account
- Notification.where(from_account: @account).in_batches.delete_all
+ Notification.where(from_account: @account).in_batches do |batch|
+ batch.delete_all
+ end
end
def purge_favourites!
@account.favourites.in_batches do |favourites|
ids = favourites.pluck(:status_id)
- StatusStat.where(status_id: ids).update_all('favourites_count = GREATEST(0, favourites_count - 1)')
- Chewy.strategy.current.update(StatusesIndex::Status, ids) if Chewy.enabled?
+ Chewy.strategy.current.update(StatusesIndex, ids) if Chewy.enabled?
Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" })
favourites.delete_all
end
@@ -195,7 +236,7 @@
def purge_bookmarks!
@account.bookmarks.in_batches do |bookmarks|
- Chewy.strategy.current.update(StatusesIndex::Status, bookmarks.pluck(:status_id)) if Chewy.enabled?
+ Chewy.strategy.current.update(StatusesIndex, bookmarks.pluck(:status_id)) if Chewy.enabled?
bookmarks.delete_all
end
end
@@ -229,9 +270,6 @@
@account.display_name = ''
@account.note = ''
@account.fields = []
- @account.statuses_count = 0
- @account.followers_count = 0
- @account.following_count = 0
@account.moved_to_account = nil
@account.also_known_as = []
@account.trust_level = :untrusted
@@ -302,5 +340,9 @@
def skip_activitypub?
@options[:skip_activitypub]
+ end
+
+ def publish_delete_event
+ EventProvider::EventProvider.new('account.deleted', AccountDeletedEvent, {account_id: @account.id, deleted_by_id: @deleted_by_id}).call
end
end
Only in truth-new/opensource/app/services: delete_group_service.rb
Only in truth-new/opensource/app/services: destroy_group_service.rb
Only in truth-new/opensource/app/services: disabled_user_refollow_service.rb
Only in truth-new/opensource/app/services: disabled_user_unfollow_service.rb
diff -ru truth-old/opensource/app/services/fan_out_on_write_service.rb truth-new/opensource/app/services/fan_out_on_write_service.rb
--- truth-old/opensource/app/services/fan_out_on_write_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/fan_out_on_write_service.rb 2024-04-01 14:59:13
@@ -25,7 +25,7 @@
deliver_to_hashtags(status)
- return if status.reply? && status.in_reply_to_account_id != status.account_id
+ nil if status.reply? && status.in_reply_to_account_id != status.account_id
end
@@ -73,8 +73,7 @@
end
def render_anonymous_payload(status)
- @payload = InlineRenderer.render(status, nil, :status)
- @payload = Oj.dump(event: :update, payload: @payload)
+ @payload = REST::V2::StatusSerializer.new.serialize_to_json(status)
end
def deliver_to_hashtags(status)
diff -ru truth-old/opensource/app/services/favourite_service.rb truth-new/opensource/app/services/favourite_service.rb
--- truth-old/opensource/app/services/favourite_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/favourite_service.rb 2024-04-01 14:59:13
@@ -3,18 +3,32 @@
class FavouriteService < BaseService
include Authorization
include Payloadable
+ include Redisable
+ DUPLICATE_FAVOURITE_EXPIRE_AFTER = 7.days.seconds
+
# Favourite a status and notify remote user
# @param [Account] account
# @param [Status] status
# @return [Favourite]
- def call(account, status)
+ def call(account, status, options = {})
authorize_with account, status, :favourite?
favourite = Favourite.find_by(account: account, status: status)
- return favourite unless favourite.nil?
+ unless favourite.nil?
+ if options[:user_agent]
+ redis_key = "duplicate_favourites:#{DateTime.current.to_date}"
+ redis_element_key = options[:user_agent]
+ redis.zincrby(redis_key, 1, redis_element_key)
+ redis.expire(redis_key, DUPLICATE_FAVOURITE_EXPIRE_AFTER)
+ Rails.logger.error "duplicate_favourites: #{favourite.id}, difference: #{Time.now.to_i - favourite.created_at.to_i}, user_agent: #{redis_element_key}"
+ end
+
+ return favourite
+ end
+
favourite = Favourite.create!(account: account, status: status)
read_from_replica do
@@ -31,9 +45,10 @@
def create_notification(favourite)
status = favourite.status
+ type = status.group ? :group_favourite : :favourite
if status.account.local?
- NotifyService.new.call(status.account, :favourite, favourite)
+ NotifyService.new.call(status.account, type, favourite)
elsif status.account.activitypub?
ActivityPub::DeliveryWorker.perform_async(build_json(favourite), favourite.account_id, status.account.inbox_url)
end
@@ -41,8 +56,7 @@
def bump_potential_friendship(account, status)
ActivityTracker.increment('activity:interactions')
- return if account.following?(status.account_id)
- PotentialFriendshipTracker.record(account.id, status.account_id, :favourite)
+ InteractionsTracker.new(account.id, status.account_id, :favourite, account.following?(status.account_id), status.group).track
end
def build_json(favourite)
diff -ru truth-old/opensource/app/services/fetch_link_card_service.rb truth-new/opensource/app/services/fetch_link_card_service.rb
--- truth-old/opensource/app/services/fetch_link_card_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/fetch_link_card_service.rb 2024-04-01 14:59:13
@@ -1,21 +1,12 @@
# frozen_string_literal: true
-require "./lib/proto/serializers/card_joined_event.rb"
class FetchLinkCardService < BaseService
- URL_PATTERN = %r{
- (#{Twitter::TwitterText::Regex[:valid_url_preceding_chars]}) # $1 preceeding chars
- ( # $2 URL
- (https?:\/\/) # $3 Protocol (required)
- (#{Twitter::TwitterText::Regex[:valid_domain]}) # $4 Domain(s)
- (?::(#{Twitter::TwitterText::Regex[:valid_port_number]}))? # $5 Port number (optional)
- (/#{Twitter::TwitterText::Regex[:valid_url_path]}*)? # $6 URL Path and anchor
- (\?#{Twitter::TwitterText::Regex[:valid_url_query_chars]}*#{Twitter::TwitterText::Regex[:valid_url_query_ending_chars]})? # $7 Query String
- )
- }iox
+ include LinksParserConcern
- def call(status, url = nil)
+ def call(status, url = nil, request_domain = nil)
@status = status
- @url = url || parse_urls
+ @url = url || parse_urls
+ @request_domain = request_domain
@known_oembed_paths = {
"rumble.com": {
@@ -23,7 +14,6 @@
format: :json,
},
}
-
return if @url.nil? || @status.preview_cards.any?
@url = @url.to_s
@@ -32,13 +22,13 @@
parsed_uri = Addressable::URI.parse(full_url.to_s)
check_known_short_links(parsed_uri)
- Prometheus::ApplicationExporter::increment(:links, {domain: parsed_uri.normalized_host})
+ Prometheus::ApplicationExporter.increment(:links, { domain: parsed_uri.normalized_host })
end
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
@card = PreviewCard.find_by(url: @url)
- process_url if @card.nil? || @card.updated_at <= 2.weeks.ago || @card.missing_image?
+ process_url if @card.nil? || @card.updated_at <= 2.weeks.ago || @card.missing_image? && !interactive_ad?
else
raise Mastodon::RaceConditionError
end
@@ -48,7 +38,6 @@
attach_card
publish_card_joined_event
end
-
rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e
Rails.logger.info "Error fetching link #{@url}: #{e}"
nil
@@ -61,23 +50,19 @@
path = uri.omit(:scheme, :authority, :host).to_s[1..-1]
short_links = {
- "youtu.be": "https://www.youtube.com/watch?v=#{path.sub('?', '&')}"
+ "youtu.be": "https://www.youtube.com/watch?v=#{path.sub('?', '&')}",
}
@url = short_links[domain.to_sym] if short_links[domain.to_sym]
end
def publish_card_joined_event
- Redis.current.publish(
- CardJoinedEvent::EVENT_KEY,
- CardJoinedEvent.new(@card).serialize
- )
+ EventProvider::EventProvider.new('card.joined', CardJoinedEvent, @card).call
end
def process_url
@card ||= PreviewCard.new(url: @url)
-
- attempt_oembed || attempt_opengraph
+ parsed_url.normalized_host == 'rumble.com' ? (attempt_oembed || attempt_opengraph) : (attempt_group || attempt_opengraph || attempt_oembed)
end
def html
@@ -97,7 +82,7 @@
def attach_card
@status.preview_cards << @card
Rails.cache.delete(@status)
- InvalidateSecondaryCacheService.new.call("InvalidateStatusCacheWorker", @status.id)
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', @status.id)
end
def parse_urls
@@ -108,15 +93,9 @@
links = html.css(':not(.quote-inline) > a')
@all_urls = links.filter_map { |a| Addressable::URI.parse(a['href']) unless skip_link?(a) }.filter_map(&:normalize)
end
-
- @all_urls.reject { |uri| bad_url?(uri) }.first
+ @all_urls.reject { |uri| bad_url_with_group?(uri) }.first
end
- def bad_url?(uri)
- # Avoid local instance URLs and invalid URLs
- uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
- end
-
# rubocop:disable Naming/MethodParameterName
def mention_link?(a)
@status.mentions.any? do |mention|
@@ -132,20 +111,25 @@
def attempt_oembed
service = FetchOEmbedService.new
- url_domain = Addressable::URI.parse(@url).normalized_host
+ url_domain = parsed_url.normalized_host
cached_endpoint = Rails.cache.read("oembed_endpoint:#{url_domain}")
embed = service.call(@url, cached_endpoint: cached_endpoint) unless cached_endpoint.nil?
embed ||= service.call(@url, cached_endpoint: @known_oembed_paths[url_domain.to_sym]) if @known_oembed_paths.key?(url_domain.to_sym)
- embed ||= service.call(@url, html: html) unless html.nil?
+ if !embed && !html.nil?
+ service.call(@url, html: html)
+ end
+
return false if embed.nil?
url = Addressable::URI.parse(service.endpoint_url)
+ raise Mastodon::UnexpectedResponseError, service.endpoint_url unless embed[:thumbnail_url].present?
+
@card.type = embed[:type]
@card.title = embed[:title].present? ? CGI.unescapeHTML(embed[:title]) : ''
- @card.author_name = embed[:author_name] || ''
+ @card.author_name = embed[:author_name] || ''
@card.author_url = embed[:author_url].present? ? (url + embed[:author_url]).to_s : ''
@card.provider_name = embed[:provider_name] || ''
@card.provider_url = embed[:provider_url].present? ? (url + embed[:provider_url]).to_s : ''
@@ -202,18 +186,37 @@
@card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || ''
@card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || ''
- @card.image_remote_url = (Addressable::URI.parse(@url) + meta_property(page, 'og:image')).to_s if meta_property(page, 'og:image')
+ @card.image_remote_url = (parsed_url + meta_property(page, 'og:image')).to_s if meta_property(page, 'og:image')
return if @card.title.blank? && @card.html.blank?
@card.save_with_optional_image!
end
+ def attempt_group
+ return unless group_url?(parsed_url.to_s)
+
+ group_slug = extract_group_slug(parsed_url.to_s)
+ group = Group.find_by!({ slug: group_slug.to_s })
+ @card.title = group.display_name
+ @card.description = group.note
+ @card.image_remote_url = full_asset_url(group.header_static_url) if group.header_file_name
+ @card.save_with_optional_image!
+ end
+
def meta_property(page, property)
page.at_xpath("//meta[contains(concat(' ', normalize-space(@property), ' '), ' #{property} ')]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
end
def lock_options
{ redis: Redis.current, key: "fetch:#{@url}", autorelease: 15.minutes.seconds }
+ end
+
+ def interactive_ad?
+ !!@card&.statuses&.last&.ad
+ end
+
+ def parsed_url
+ Addressable::URI.parse(@url)
end
end
diff -ru truth-old/opensource/app/services/fetch_oembed_service.rb truth-new/opensource/app/services/fetch_oembed_service.rb
--- truth-old/opensource/app/services/fetch_oembed_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/fetch_oembed_service.rb 2024-04-12 09:09:12
@@ -5,6 +5,17 @@
attr_reader :url, :options, :format, :endpoint_url
+ #
+ # Calls the service with a given URL and options.
+ # If the options hash contains a `:cached_endpoint`, it will parse the cached endpoint.
+ # Otherwise, it will discover the endpoint by fetching the HTML of the page and looking
+ # for OEmbed links in the head of the document. After the endpoint is determined, it fetches
+ # the OEmbed data from the endpoint URL.
+ #
+ # @param url [String] the URL to fetch OEmbed data from
+ # @param options [Hash] a hash of options. Can contain a `:cached_endpoint` key with a cached endpoint to use.
+ # @return [Hash, nil] the fetched OEmbed data, or nil if an error occurred
+ #
def call(url, options = {})
@url = url
@options = options
diff -ru truth-old/opensource/app/services/follow_service.rb truth-new/opensource/app/services/follow_service.rb
--- truth-old/opensource/app/services/follow_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/follow_service.rb 2024-04-01 14:59:13
@@ -14,10 +14,11 @@
# @option [Boolean] :bypass_locked
# @option [Boolean] :bypass_limit Allow following past the total follow number
# @option [Boolean] :with_rate_limit
+ # @option [Boolean] :skip_notification Do not notify followed user
def call(source_account, target_account, options = {})
@source_account = source_account
@target_account = target_account
- @options = { bypass_locked: false, bypass_limit: false, with_rate_limit: false }.merge(options)
+ @options = { bypass_locked: false, bypass_limit: false, with_rate_limit: false, skip_notification: false }.merge(options)
raise ActiveRecord::RecordNotFound if following_not_possible?
raise Mastodon::NotPermittedError if following_not_allowed?
@@ -79,12 +80,16 @@
def direct_follow!
follow = @source_account.follow!(@target_account, reblogs: @options[:reblogs], notify: @options[:notify], rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit])
- LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, :follow)
+ LocalNotificationWorker.perform_async(@target_account.id, follow.id, follow.class.name, :follow) unless @options[:skip_notification]
MergeWorker.perform_async(@target_account.id, @source_account.id)
redis.del("whale:following:#{@source_account.id}") if @target_account.whale?
InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", @source_account.id, @target_account.id, @target_account.whale?)
+
+ redis.del("avatars_carousel_list_#{@source_account.id}") if @source_account.following_count.to_i < 10
+
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", @source_account.id)
follow
end
Only in truth-new/opensource/app/services: geo_service.rb
Only in truth-new/opensource/app/services: group_membership_validation_service.rb
Only in truth-new/opensource/app/services: group_search_service.rb
Only in truth-new/opensource/app/services: inspect_link_service.rb
Only in truth-new/opensource/app/services: interactive_ads_service.rb
diff -ru truth-old/opensource/app/services/invalidate_secondary_cache_service.rb truth-new/opensource/app/services/invalidate_secondary_cache_service.rb
--- truth-old/opensource/app/services/invalidate_secondary_cache_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/invalidate_secondary_cache_service.rb 2024-04-01 14:59:13
@@ -5,7 +5,7 @@
return unless secondary_dcs
secondary_dcs.split(',').map(&:strip).each do |dc|
- worker_name.constantize.set(queue: dc).perform_in(1.second, *args)
+ worker_name.constantize.set(queue: dc).perform_async(*args)
end
end
end
Only in truth-new/opensource/app/services: ios_device_check
Only in truth-new/opensource/app/services: join_group_service.rb
Only in truth-new/opensource/app/services: leave_group_service.rb
diff -ru truth-old/opensource/app/services/notify_service.rb truth-new/opensource/app/services/notify_service.rb
--- truth-old/opensource/app/services/notify_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/notify_service.rb 2024-04-01 14:59:13
@@ -15,6 +15,7 @@
@activity = activity
@type = type
@notification = Notification.new(account: @recipient, type: type, activity: @activity)
+
return if recipient.user.nil? || blocked?
@target_status = target_status
@@ -61,6 +62,50 @@
false
end
+ def blocked_chat?
+ false
+ end
+
+ def blocked_chat_message_deleted?
+ false
+ end
+
+ def blocked_group_mention?
+ FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
+ end
+
+ def blocked_group_reblog?
+ false
+ end
+
+ def blocked_group_follow?
+ false
+ end
+
+ def blocked_group_favourite?
+ false
+ end
+
+ def blocked_group_delete?
+ false
+ end
+
+ def blocked_group_approval?
+ false
+ end
+
+ def blocked_group_request?
+ false
+ end
+
+ def blocked_group_promoted?
+ false
+ end
+
+ def blocked_group_demoted?
+ false
+ end
+
def following_sender?
return @following_sender if defined?(@following_sender)
@following_sender = @recipient.following?(@notification.from_account) || @recipient.requested?(@notification.from_account)
@@ -94,6 +139,10 @@
message? && target_status.direct_visibility?
end
+ def chat?
+ @notification.type == :chat
+ end
+
def response_to_recipient?
target_status.in_reply_to_account_id == @recipient.id && target_status.thread&.direct_visibility?
end
@@ -131,6 +180,7 @@
blocked ||= domain_blocking? # Skip for domain blocked accounts
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
+ blocked ||= @notification.from_account.blocking?(@recipient) # Skip for blocked accounts - the other direction
blocked ||= @recipient.muting_notifications?(@notification.from_account)
blocked ||= hellbanned? # Hellban
blocked ||= optional_non_follower? # Options
Only in truth-new/opensource/app/services: p_tv
diff -ru truth-old/opensource/app/services/post_distribution_service.rb truth-new/opensource/app/services/post_distribution_service.rb
--- truth-old/opensource/app/services/post_distribution_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/post_distribution_service.rb 2024-04-05 09:07:34
@@ -1,24 +1,36 @@
+# frozen_string_literal: true
class PostDistributionService < BaseService
include Redisable
BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || "0").to_i
- def call(status)
- if rand(1..100) <= BAILEY_PERCENTAGE && !status.account.whale?
- send_to_bailey(status)
+ # Enqueue jobs for both author and followers
+ def distribute_to_author(status)
+ if rand(1..100) <= BAILEY_PERCENTAGE
+ QueueManager.enqueue_status_for_author_distribution(status.id)
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}")
else
FanOutOnWriteService.new.call(status)
end
end
- def send_to_bailey(status)
- if status.account.silenced? || !status.public_visibility? || status.reblog?
- rendered = nil
+ def distribute_to_followers(status)
+ if rand(1..100) <= BAILEY_PERCENTAGE
+ QueueManager.enqueue_status_for_follower_distribution(status.id)
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}")
else
- rendered = InlineRenderer.render(status, nil, :status)
- rendered = Oj.dump(event: :update, payload: rendered)
+ FanOutOnWriteService.new.call(status)
end
- Redis.current.lpush('elixir:distribution', Oj.dump(job_type: "status_created", status_id: status.id, rendered: rendered))
- Rails.logger.debug("bailey_debug: sending #{rendered.nil? ? 'nil' : 'value'} for rendered for status #{status.id}")
end
+
+ def distribute_to_author_and_followers(status)
+ if rand(1..100) <= BAILEY_PERCENTAGE
+ QueueManager.enqueue_status_for_author_distribution(status.id)
+ QueueManager.enqueue_status_for_follower_distribution(status.id)
+ Rails.logger.debug("bailey_debug: enqueuing status #{status.id}")
+ else
+ FanOutOnWriteService.new.call(status)
+ end
+ end
+
end
diff -ru truth-old/opensource/app/services/post_status_service.rb truth-new/opensource/app/services/post_status_service.rb
--- truth-old/opensource/app/services/post_status_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/post_status_service.rb 2024-04-12 09:09:12
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "./lib/proto/serializers/status_created_event.rb"
class PostStatusService < BaseService
include Redisable
@@ -14,6 +13,7 @@
# @option [Status] :thread Optional status to reply to
# @option [Boolean] :sensitive
# @option [String] :visibility
+ # @option [Group] :group Optional group to post to, `visibility` must be set to 'group' in that case
# @option [String] :spoiler_text
# @option [String] :language
# @option [String] :scheduled_at
@@ -22,18 +22,26 @@
# @option [Doorkeeper::Application] :application
# @option [String] :idempotency Optional idempotency key
# @option [Boolean] :with_rate_limit
+ # @option [String] :ip_address IP address of where the request originated
# @return [Status]
def call(account, options = {})
- @account = account
- @options = options
- @text = @options[:text] || ''
- @in_reply_to = @options[:thread]
- @quote_id = @options[:quote_id]
- @mentions = @options[:mentions] || []
+ @account = account
+ @options = options
+ @text = @options[:text] || ''
+ @in_reply_to = @options[:thread]
+ @quote_id = @options[:quote_id]
+ @mentions = @options[:mentions] || []
+ @ip_address = @options[:ip_address] || ''
+ @group = @options[:group]
+ @group_timeline_visible = @options[:group_timeline_visible]
+ @group_visibility = @options[:group_visibility]
+ @domain = @options[:domain]
+ @links_service = process_links_service
+
return idempotency_duplicate if idempotency_given? && idempotency_duplicate?
- validate_media!
+ @media = validate_media!
preprocess_attributes!
preprocess_quote!
@@ -43,24 +51,21 @@
process_status!
postprocess_status!
bump_potential_friendship!
- create_status_event
+ publish_status_event
export_prometheus_metric
end
redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given?
- send_video_to_upload_worker(@media_ids.first) if video_status?
+ send_video_to_upload_worker
@status
end
private
- def create_status_event
- Redis.current.publish(
- StatusCreatedEvent::EVENT_KEY,
- StatusCreatedEvent.new(@status).serialize
- )
+ def publish_status_event
+ EventProvider::EventProvider.new('status.created', StatusCreatedEvent, @status, @ip_address).call unless @group_visibility == :members_only
end
def status_from_uri(uri)
@@ -90,6 +95,8 @@
@quote_id = quote_from_url(md[1])&.id
@text.sub!(/RT:\s*\[.*?\]/, '')
end
+
+ @text = @links_service.resolve_urls(@text)
rescue ArgumentError
raise ActiveRecord::RecordInvalid
end
@@ -104,13 +111,13 @@
def process_status!
# The following transaction block is needed to wrap the UPDATEs to
# the media attachments when the status is created
-
ApplicationRecord.transaction do
@status = @account.statuses.create!(status_attributes)
+ ProcessMentionsService.new.call(@status, @mentions, @in_reply_to)
end
process_hashtags_service.call(@status)
- process_mentions_service.call(@status, @mentions)
+ @links_service.call(@status)
end
def schedule_status!
@@ -131,45 +138,59 @@
end
def postprocess_status!
- LinkCrawlWorker.perform_async(@status.id) unless @status.spoiler_text? || video_status?
- post_distribution_service.call(@status)
- PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
+ LinkCrawlWorker.perform_async(@status.id, nil, @domain) unless @status.spoiler_text? || video_status?
+ PostDistributionService.new.distribute_to_author(@status)
+ # PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll
end
+ #
+ # @return [Array] Returns an empty array if there are no media_ids or if media_ids is not an Enumerable.
+ # Otherwise, it returns the media attachments associated with the account that have not been assigned a status yet.
+ #
+ # @raise [Mastodon::ValidationError] If there are more than 4 media attachments or if a poll is present.
+ # @raise [Mastodon::ValidationError] If any of the media attachments are not processed yet.
+ #
def validate_media!
- return if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
+ return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable)
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 || @options[:poll].present?
+ if @options[:media_ids].size > 4 || @options[:poll].present?
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many')
+ end
- @media_ids = @options[:media_ids].take(4).map(&:to_i)
- @media = @account.media_attachments.where(status_id: nil).where(id: @media_ids).sort_by {|m| @media_ids.index(m.id)}
+ media_ids = @options[:media_ids].map(&:to_i)
+ media = @account
+ .media_attachments
+ .where(status_id: nil)
+ .where(id: media_ids)
+ .sort_by { |m| media_ids.index(m.id) }
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?)
- raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?)
+ if media.any? { |m| !m.video? && m.not_processed? }
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready')
+ end
+
+ media
end
def video_upload_enabled?
ENV['VIDEO_REMOTE_UPLOAD_ENABLED'] == 'true'
end
- def send_video_to_upload_worker(media_attachment_id)
- VideoUploadWorker.perform_async(media_attachment_id, @status.id)
+ def send_video_to_upload_worker
+ @media.each do |m|
+ UploadVideoStatusWorker.perform_async(m.id, @status.id) if m.video?
+ end
end
def language_from_option(str)
ISO_639.find(str)&.alpha2
end
- def process_mentions_service
- ProcessMentionsService.new
- end
-
def process_hashtags_service
ProcessHashtagsService.new
end
- def post_distribution_service
- PostDistributionService.new
+ def process_links_service
+ ProcessStatusLinksService.new
end
def scheduled?
@@ -201,10 +222,13 @@
end
def bump_potential_friendship!
- return if !@status.reply? || @account.id == @status.in_reply_to_account_id
- ActivityTracker.increment('activity:interactions')
- return if @account.following?(@status.in_reply_to_account_id)
- PotentialFriendshipTracker.record(@account.id, @status.in_reply_to_account_id, :reply)
+ if @status.reply? && @account.id != @status.in_reply_to_account_id
+ ActivityTracker.increment('activity:interactions')
+ InteractionsTracker.new(@account.id, @status.in_reply_to_account_id, :reply, @account.following?(@status.in_reply_to_account_id), @status.group).track
+ elsif @status.quote? && @account.id != @status.quote.account_id
+ ActivityTracker.increment('activity:interactions')
+ InteractionsTracker.new(@account.id, @status.quote.account_id, :quote, @account.following?(@status.quote.account_id), @status.quote.group).track
+ end
end
def status_attributes
@@ -212,7 +236,10 @@
text: @text,
media_attachments: @media || [],
thread: @in_reply_to,
- poll_attributes: poll_attributes,
+ group: @group,
+ group_timeline_visible: @group_timeline_visible || false,
+ polls: poll_attributes,
+ has_poll: @options[:poll].present?,
sensitive: @sensitive,
spoiler_text: @options[:spoiler_text] || '',
visibility: @visibility,
@@ -233,13 +260,14 @@
def poll_attributes
return if @options[:poll].blank?
-
- @options[:poll].merge(account: @account, voters_count: 0)
+ @options[:poll][:options_attributes] = @options[:poll].delete(:options).map.with_index { |v, i| { option_number: i, text: v } }
+ [Poll.new(@options[:poll])]
end
def scheduled_options
@options.tap do |options_hash|
options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id
+ options_hash[:group_id] = options_hash.delete(:group)&.id
options_hash[:application_id] = options_hash.delete(:application)&.id
options_hash[:scheduled_at] = nil
options_hash[:idempotency] = nil
@@ -249,10 +277,10 @@
def export_prometheus_metric
metric_type = @in_reply_to ? :replies : :statuses
- Prometheus::ApplicationExporter::increment(metric_type)
+ Prometheus::ApplicationExporter.increment(metric_type)
end
def video_status?
- @media.present? && @media.first.video? && video_upload_enabled?
+ video_upload_enabled? && @media.any?(&:video?)
end
end
Only in truth-new/opensource/app/services: privatize_media_attachment_service.rb
Only in truth-new/opensource/app/services: process_chat_links_service.rb
diff -ru truth-old/opensource/app/services/process_mentions_service.rb truth-new/opensource/app/services/process_mentions_service.rb
--- truth-old/opensource/app/services/process_mentions_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/process_mentions_service.rb 2024-04-12 09:09:08
@@ -2,50 +2,97 @@
class ProcessMentionsService < BaseService
include Payloadable
+ include Redisable
MAX_MENTIONS = ENV.fetch('MAX_MENTIONS', 15).to_i
+ NOTIFICATIONS_TRESHOLD = 100
+ MENTION_MISMATCH_EXPIRE_AFTER = 7.days.seconds
# Scan status for mentions and fetch remote mentioned users, create
# local mention pointers, send Salmon notifications to mentioned
# remote users
# @param [Status] status
# @param [Enumerable] mentions an array of usernames
- def call(status, mentions)
+ # @param [Status] thread in_reply_to status
+ def call(status, mentions, thread = nil)
+ @passed_mentions = mentions
@status = status
+ @status_mentions = scan_mentions(@status).map(&:downcase)
+ @thread = thread
+ mention_notifications = []
- mentions = mentions.first(MAX_MENTIONS) if mentions.length > MAX_MENTIONS
+ if @status.reply?
+ previous_mentioned_usernames = Account.joins(:mentions).where('mentions.status_id = ?', @thread.id).pluck(:username)
+ @previously_mentioned = (previous_mentioned_usernames << @thread.account.username).map(&:downcase)
+ end
+ group = @status.group_visibility?.presence && @status.group
+ mentions = mentions.first(MAX_MENTIONS) if mentions.length > MAX_MENTIONS
mentioned_accounts = Account.ci_find_by_usernames(mentions)
+ accounts_with_mention_preference = mentioned_accounts.where(receive_only_follow_mentions: true)
+ if accounts_with_mention_preference.any?
+ followers = Follow.where(account: accounts_with_mention_preference, target_account: @status.account).pluck(:account_id)
+ end
+
mentioned_accounts.each do |acc|
next acc if mention_undeliverable?(acc) || acc.suspended?
+ reject_missing_status_mention(acc.username.downcase)
+ # Since mentions are currently tied to audience and notifications, skip mentions
+ # of non-members if private group
+ next acc if (group&.members_only? && !group.members&.where(id: acc.id)&.exists?) || skip_new_account_mentions(acc)
+
+ next acc if acc.receive_only_follow_mentions && !followers.include?(acc.id)
+
new_mention = acc.mentions.new(status: status)
- create_notification(new_mention) if new_mention.save
+ mention_notifications << new_mention if new_mention.save
end
+
+ mention_notifications
end
+ def self.create_notification(status, mention)
+ mentioned_account = mention.account
+ type = status.group ? :group_mention : :mention
+
+ if status.account.followers_count < NOTIFICATIONS_TRESHOLD
+ ProcessMentionNotificationsWorker.perform_in(61.seconds, status.id, mention.id, type.to_sym)
+ else
+ LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name, type.to_sym)
+ end
+ end
+
private
def mention_undeliverable?(mentioned_account)
mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus?)
end
- def create_notification(mention)
- mentioned_account = mention.account
+ def scan_mentions(status)
+ status.text.scan(Account::MENTION_RE).map(&:second).map(&:downcase)
+ end
- if mentioned_account.local?
- LocalNotificationWorker.perform_async(mentioned_account.id, mention.id, mention.class.name, :mention)
- elsif mentioned_account.activitypub?
- ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url, { synchronize_followers: !mention.status.distributable? })
+ def reject_missing_status_mention(username)
+ return if @status_mentions.include? username
+
+ if (quote = @status.quote?)
+ return if username == quote.account.username.downcase
+ elsif @status.reply?
+ return if @previously_mentioned.include? username
end
- end
- def activitypub_json
- return @activitypub_json if defined?(@activitypub_json)
- @activitypub_json = Oj.dump(serialize_payload(ActivityPub::ActivityPresenter.from_status(@status), ActivityPub::ActivitySerializer, signer: @status.account))
+ Rails.logger.info "mention_mismatch #{@status.account_id} thread_id: #{@thread&.id} reply_text: #{@status.text} passed_mentions: #{@passed_mentions} previously_mentioned: #{@previously_mentioned}"
+
+ redis_key = "mention_mismatch:#{DateTime.current.to_date}"
+ redis_element_key = @status.account_id
+ redis.zincrby(redis_key, 1, redis_element_key)
+ redis.expire(redis_key, MENTION_MISMATCH_EXPIRE_AFTER)
+
+ raise Mastodon::ValidationError, I18n.t('statuses.errors.mention_mismatch')
end
- def resolve_account_service
- ResolveAccountService.new
+ def skip_new_account_mentions(acc)
+ return false if (Time.now - @status.account.created_at).round > 7.days
+ !@status.reply? || (@status.reply? && !@previously_mentioned.include?(acc.username.downcase))
end
end
Only in truth-new/opensource/app/services: process_status_links_service.rb
Only in truth-new/opensource/app/services: publish_media_attachment_service.rb
diff -ru truth-old/opensource/app/services/reblog_service.rb truth-new/opensource/app/services/reblog_service.rb
--- truth-old/opensource/app/services/reblog_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/reblog_service.rb 2024-04-05 09:07:34
@@ -3,7 +3,10 @@
class ReblogService < BaseService
include Authorization
include Payloadable
+ include Redisable
+ DUPLICATE_REBLOG_EXPIRE_AFTER = 7.days.seconds
+
# Reblog a status and notify its remote author
# @param [Account] account Account to reblog from
# @param [Status] reblogged_status Status to be reblogged
@@ -18,17 +21,27 @@
reblog = account.statuses.find_by(reblog: reblogged_status)
- return reblog unless reblog.nil?
+ unless reblog.nil?
+ if options[:user_agent]
+ redis_key = "duplicate_reblogs:#{DateTime.current.to_date}"
+ redis_element_key = options[:user_agent]
+ redis.zincrby(redis_key, 1, redis_element_key)
+ redis.expire(redis_key, DUPLICATE_REBLOG_EXPIRE_AFTER)
+ end
+ return reblog
+ end
+
visibility = if reblogged_status.hidden?
reblogged_status.visibility
else
options[:visibility] || account.user&.setting_default_privacy
end
- reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit])
+ reblog_params = reblogged_status.group_visibility? ? { group_id: reblogged_status.group.id } : {}
+ reblog = account.statuses.create!(reblog: reblogged_status, text: '', visibility: visibility, rate_limit: options[:with_rate_limit], **reblog_params)
- PostDistributionService.new.call(reblog)
+ PostDistributionService.new.distribute_to_author_and_followers(reblog)
ActivityPub::DistributionWorker.perform_async(reblog.id)
create_notification(reblog)
@@ -43,9 +56,10 @@
def create_notification(reblog)
reblogged_status = reblog.reblog
+ type = reblogged_status.group ? :group_reblog : :reblog
if reblogged_status.account.local?
- LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, :reblog)
+ LocalNotificationWorker.perform_async(reblogged_status.account_id, reblog.id, reblog.class.name, type)
elsif reblogged_status.account.activitypub? && !reblogged_status.account.following?(reblog.account)
ActivityPub::DeliveryWorker.perform_async(build_json(reblog), reblog.account_id, reblogged_status.account.inbox_url)
end
@@ -53,10 +67,7 @@
def bump_potential_friendship(account, reblog)
ActivityTracker.increment('activity:interactions')
-
- return if account.following?(reblog.reblog.account_id)
-
- PotentialFriendshipTracker.record(account.id, reblog.reblog.account_id, :reblog)
+ InteractionsTracker.new(account.id, reblog.reblog.account_id, :reblog, account.following?(reblog.reblog.account_id), reblog.group).track
end
def record_use(account, reblog)
Only in truth-new/opensource/app/services: registration_service.rb
Only in truth-new/opensource/app/services: reject_membership_service.rb
diff -ru truth-old/opensource/app/services/remove_status_service.rb truth-new/opensource/app/services/remove_status_service.rb
--- truth-old/opensource/app/services/remove_status_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/remove_status_service.rb 2024-04-01 14:59:13
@@ -4,7 +4,7 @@
include Redisable
include Payloadable
- BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || "0").to_i
+ BAILEY_PERCENTAGE = (ENV['BAILEY_PERCENTAGE'] || '0').to_i
# Delete a status
# @param [Status] status
@@ -17,13 +17,18 @@
@status = status
@account = status.account
@immediate = options.key?(:immediate) ? options[:immediate] : false
- @options = options
+ @options = options
@status.discard
+ EventProvider::EventProvider.new('status.removed', StatusRemovedEvent, @status, options[:called_by_id]).call if options[:called_by_id] == @status.account_id
+
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
+ remove_user_interactions!
+ remove_from_timeline_cache
+
if rand(1..100) <= BAILEY_PERCENTAGE
send_to_bailey
else
@@ -33,12 +38,7 @@
remove_from_lists
- # There is no reason to send out Undo activities when the
- # cause is that the original object has been removed, since
- # original object being removed implicitly removes reblogs
- # of it. The Delete activity of the original is forwarded
- # separately.
- remove_from_remote_reach if @account.local? && !@options[:original_removed]
+ remove_from_group
unless @status.reblog?
remove_reblogs
@@ -56,6 +56,9 @@
notify_user if options[:notify_user]
end
+ remove_ad_data if @status.ad?
+ purge_cache
+
@status.destroy! if @immediate
else
raise Mastodon::RaceConditionError
@@ -81,7 +84,6 @@
end
end
-
def remove_from_whale_list
FeedManager.instance.remove_from_whale(@status)
end
@@ -102,19 +104,6 @@
end
end
- def remove_from_remote_reach
- # Followers, relays, people who got mentioned in the status,
- # or who reblogged it from someone else might not follow
- # the author and wouldn't normally receive the delete
- # notification - so here, we explicitly send it to them
-
- status_reach_finder = StatusReachFinder.new(@status)
-
- ActivityPub::DeliveryWorker.push_bulk(status_reach_finder.inboxes) do |inbox_url|
- [signed_activity_json, @account.id, inbox_url]
- end
- end
-
def signed_activity_json
@signed_activity_json ||= Oj.dump(serialize_payload(@status, @status.reblog? ? ActivityPub::UndoAnnounceSerializer : ActivityPub::DeleteSerializer, signer: @account))
end
@@ -142,6 +131,12 @@
end
end
+ def remove_from_group
+ return unless @status.group_visibility?
+
+ redis.publish("timeline:group:#{@status.group_id}", @payload)
+ end
+
def remove_media
return if @options[:redraft] || !@immediate
@@ -153,8 +148,35 @@
end
def send_to_bailey
- Redis.current.lpush('elixir:distribution', @payload)
- Rails.logger.info("bailey_debug: sending for deletion status #{@status.id}")
+ # Don't create job. Bailey not cleaning up deletes at the moment.
end
+ def remove_user_interactions!
+ if @status.reply? && @account.id != @status.in_reply_to_account_id
+ InteractionsTracker.new(@account.id, @status.in_reply_to_account_id, :reply, @account.following?(@status.in_reply_to_account_id), @status.group).untrack
+ elsif @status.quote? && @account.id != @status.quote.account_id
+ InteractionsTracker.new(@account.id, @status.quote.account_id, :quote, @account.following?(@status.quote.account_id), @status.quote.group).untrack
+ end
+ end
+
+ def remove_from_timeline_cache
+ redis.del("sevro:#{@status.id}")
+ end
+
+ def remove_ad_data
+ @status.preview_cards.each(&:destroy)
+ @status.ad&.destroy
+ end
+end
+
+def purge_cache
+ purge_status(@status)
+ Status.where(in_reply_to_id: @status.id).or(Status.where(quote_id: @status.id)).in_batches.each_record do |reply|
+ purge_status(reply)
+ end
+end
+
+def purge_status(status)
+ Rails.cache.delete(status)
+ InvalidateSecondaryCacheService.new.call('InvalidateStatusCacheWorker', status)
end
diff -ru truth-old/opensource/app/services/report_service.rb truth-new/opensource/app/services/report_service.rb
--- truth-old/opensource/app/services/report_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/report_service.rb 2024-04-01 14:59:13
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-require "./lib/proto/serializers/report_created_event.rb"
class ReportService < BaseService
include Payloadable
@@ -8,12 +7,13 @@
@source_account = source_account
@target_account = target_account
@status_ids = options.delete(:status_ids) || []
+ @message_ids = options.delete(:message_ids) || []
@rule_ids = options.delete(:rule_ids) || []
@comment = options.delete(:comment) || ''
+ @group_id = options.delete(:group_id) || nil
+ @external_ad_id = options.delete(:external_ad_id) || nil
@options = options
- raise ActiveRecord::RecordNotFound if @target_account.suspended?
-
create_report!
publish_event!
#notify_staff!
@@ -28,10 +28,14 @@
@report = @source_account.reports.create!(
target_account: @target_account,
status_ids: @status_ids,
+ message_ids: @message_ids,
rule_ids: @rule_ids,
comment: @comment,
uri: @options[:uri],
- forwarded: ActiveModel::Type::Boolean.new.cast(@options[:forward])
+ forwarded: ActiveModel::Type::Boolean.new.cast(@options[:forward]),
+ rate_limit: true,
+ group_id: @group_id,
+ external_ad_id: @external_ad_id
)
end
@@ -42,24 +46,45 @@
@report.status_ids << nil
end
- @report.status_ids.each do |status_id|
- attachments = MediaAttachment.where(status_id: status_id)
+ if @report.message_ids.empty?
+ @report.message_ids << nil
+ end
+ if @group_id && @report.status_ids[0].nil?
+ # reporting a group
report_data = OpenStruct.new(
@report.attributes.merge(
- status_id: status_id,
- status_ids: @report.status_ids.compact,
report_set_id: set_uuid,
- account_username: @report.account.username,
- target_account_username: @report.target_account.username,
- image_ids: attachments.select { |a| a.image? }.pluck(:id),
- video_ids: attachments.select { |a| a.video? }.pluck(:id)
+ display_name: @report.account.username,
+ owner_id: @target_account.id,
+ group_id: @group_id
)
)
- Redis.current.publish(
- ReportCreatedEvent::EVENT_KEY,
- ReportCreatedEvent.new(report_data).serialize
- )
+ EventProvider::EventProvider.new("group_report.created", GroupReportCreatedEvent, report_data).call
+ elsif @report.message_ids[0].nil?
+ # reporting a status
+ @report.status_ids.each do |status_id|
+ attachments = status_id ? MediaAttachment.where(status_id: status_id) : []
+
+ report_data = OpenStruct.new(
+ @report.attributes.merge(
+ status_id: status_id,
+ report_set_id: set_uuid,
+ image_ids: attachments.select { |a| a.image? }.pluck(:id),
+ video_ids: attachments.select { |a| a.video? }.pluck(:id),
+ group_id: @group_id
+ )
+ )
+ EventProvider::EventProvider.new("report.created", ReportCreatedEvent, report_data).call
+ end
+ elsif @external_ad_id.nil?
+ # reporting a message
+ @report.message_ids.each do |message_id|
+ report_data = OpenStruct.new(
+ @report.attributes.merge(message_id: message_id)
+ )
+ EventProvider::EventProvider.new("chat_report.created", ChatMessageReportCreatedEvent, report_data).call
+ end
end
end
diff -ru truth-old/opensource/app/services/resolve_account_service.rb truth-new/opensource/app/services/resolve_account_service.rb
--- truth-old/opensource/app/services/resolve_account_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/resolve_account_service.rb 2024-04-01 14:59:13
@@ -140,7 +140,7 @@
end
def queue_deletion!
- AccountDeletionWorker.perform_async(@account.id, reserve_username: false, skip_activitypub: true)
+ AccountDeletionWorker.perform_async(@account.id, -99, reserve_username: false, skip_activitypub: true)
end
def lock_options
Only in truth-new/opensource/app/services: revoke_membership_service.rb
Only in truth-new/opensource/app/services: rumble
diff -ru truth-old/opensource/app/services/search_service.rb truth-new/opensource/app/services/search_service.rb
--- truth-old/opensource/app/services/search_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/search_service.rb 2024-04-12 09:09:12
@@ -7,9 +7,11 @@
PROHIBITED_TERMS = ENV.fetch('PROHIBITED_TERMS', '').split(',').freeze
PROHIBITED_FILTERS = { bool: { must_not: PROHIBITED_TERMS.map { |term| { multi_match: { type: 'most_fields', query: term } } } } }.freeze
+ # https://github.com/franklsf95/ruby-emoji-regex
+ EMOJI_REGEX = /[\u{00A9}\u{00AE}\u{203C}\u{2049}\u{2122}\u{2139}\u{2194}-\u{2199}\u{21A9}-\u{21AA}\u{231A}-\u{231B}\u{2328}\u{23CF}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{24C2}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2600}-\u{2604}\u{260E}\u{2611}\u{2614}-\u{2615}\u{2618}\u{261D}\u{2620}\u{2622}-\u{2623}\u{2626}\u{262A}\u{262E}-\u{262F}\u{2638}-\u{263A}\u{2640}\u{2642}\u{2648}-\u{2653}\u{2660}\u{2663}\u{2665}-\u{2666}\u{2668}\u{267B}\u{267F}\u{2692}-\u{2697}\u{2699}\u{269B}-\u{269C}\u{26A0}-\u{26A1}\u{26AA}-\u{26AB}\u{26B0}-\u{26B1}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26C8}\u{26CE}-\u{26CF}\u{26D1}\u{26D3}-\u{26D4}\u{26E9}-\u{26EA}\u{26F0}-\u{26F5}\u{26F7}-\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}\u{2712}\u{2714}\u{2716}\u{271D}\u{2721}\u{2728}\u{2733}-\u{2734}\u{2744}\u{2747}\u{274C}\u{274E}\u{2753}-\u{2755}\u{2757}\u{2763}-\u{2764}\u{2795}-\u{2797}\u{27A1}\u{27B0}\u{27BF}\u{2934}-\u{2935}\u{2B05}-\u{2B07}\u{2B1B}-\u{2B1C}\u{2B50}\u{2B55}\u{3030}\u{303D}\u{3297}\u{3299}\u{1F004}\u{1F0CF}\u{1F170}-\u{1F171}\u{1F17E}-\u{1F17F}\u{1F18E}\u{1F191}-\u{1F19A}\u{1F1E6}-\u{1F1FF}\u{1F201}-\u{1F202}\u{1F21A}\u{1F22F}\u{1F232}-\u{1F23A}\u{1F250}-\u{1F251}\u{1F300}-\u{1F321}\u{1F324}-\u{1F393}\u{1F396}-\u{1F397}\u{1F399}-\u{1F39B}\u{1F39E}-\u{1F3F0}\u{1F3F3}-\u{1F3F5}\u{1F3F7}-\u{1F4FD}\u{1F4FF}-\u{1F53D}\u{1F549}-\u{1F54E}\u{1F550}-\u{1F567}\u{1F56F}-\u{1F570}\u{1F573}-\u{1F57A}\u{1F587}\u{1F58A}-\u{1F58D}\u{1F590}\u{1F595}-\u{1F596}\u{1F5A4}-\u{1F5A5}\u{1F5A8}\u{1F5B1}-\u{1F5B2}\u{1F5BC}\u{1F5C2}-\u{1F5C4}\u{1F5D1}-\u{1F5D3}\u{1F5DC}-\u{1F5DE}\u{1F5E1}\u{1F5E3}\u{1F5E8}\u{1F5EF}\u{1F5F3}\u{1F5FA}-\u{1F64F}\u{1F680}-\u{1F6C5}\u{1F6CB}-\u{1F6D2}\u{1F6E0}-\u{1F6E5}\u{1F6E9}\u{1F6EB}-\u{1F6EC}\u{1F6F0}\u{1F6F3}-\u{1F6F8}\u{1F910}-\u{1F93A}\u{1F93C}-\u{1F93E}\u{1F940}-\u{1F945}\u{1F947}-\u{1F94C}\u{1F950}-\u{1F96B}\u{1F980}-\u{1F997}\u{1F9C0}\u{1F9D0}-\u{1F9E6}\u{200D}\u{20E3}\u{FE0F}\u{E0020}-\u{E007F}\u{2388}\u{2605}\u{2607}-\u{260D}\u{260F}-\u{2610}\u{2612}\u{2616}-\u{2617}\u{2619}-\u{261C}\u{261E}-\u{261F}\u{2621}\u{2624}-\u{2625}\u{2627}-\u{2629}\u{262B}-\u{262D}\u{2630}-\u{2637}\u{263B}-\u{263F}\u{2641}\u{2643}-\u{2647}\u{2654}-\u{265F}\u{2661}-\u{2662}\u{2664}\u{2667}\u{2669}-\u{267A}\u{267C}-\u{267E}\u{2680}-\u{2691}\u{2698}\u{269A}\u{269D}-\u{269F}\u{26A2}-\u{26A9}\u{26AC}-\u{26AF}\u{26B2}-\u{26BC}\u{26BF}-\u{26C3}\u{26C6}-\u{26C7}\u{26C9}-\u{26CD}\u{26D0}\u{26D2}\u{26D5}-\u{26E8}\u{26EB}-\u{26EF}\u{26F6}\u{26FB}-\u{26FC}\u{26FE}-\u{2701}\u{2703}-\u{2704}\u{270E}\u{2710}-\u{2711}\u{2765}-\u{2767}\u{1F000}-\u{1F003}\u{1F005}-\u{1F0CE}\u{1F0D0}-\u{1F0FF}\u{1F10D}-\u{1F10F}\u{1F12F}\u{1F16C}-\u{1F16F}\u{1F1AD}-\u{1F1E5}\u{1F203}-\u{1F20F}\u{1F23C}-\u{1F23F}\u{1F249}-\u{1F24F}\u{1F252}-\u{1F2FF}\u{1F322}-\u{1F323}\u{1F394}-\u{1F395}\u{1F398}\u{1F39C}-\u{1F39D}\u{1F3F1}-\u{1F3F2}\u{1F3F6}\u{1F4FE}\u{1F53E}-\u{1F548}\u{1F54F}\u{1F568}-\u{1F56E}\u{1F571}-\u{1F572}\u{1F57B}-\u{1F586}\u{1F588}-\u{1F589}\u{1F58E}-\u{1F58F}\u{1F591}-\u{1F594}\u{1F597}-\u{1F5A3}\u{1F5A6}-\u{1F5A7}\u{1F5A9}-\u{1F5B0}\u{1F5B3}-\u{1F5BB}\u{1F5BD}-\u{1F5C1}\u{1F5C5}-\u{1F5D0}\u{1F5D4}-\u{1F5DB}\u{1F5DF}-\u{1F5E0}\u{1F5E2}\u{1F5E4}-\u{1F5E7}\u{1F5E9}-\u{1F5EE}\u{1F5F0}-\u{1F5F2}\u{1F5F4}-\u{1F5F9}\u{1F6C6}-\u{1F6CA}\u{1F6D3}-\u{1F6DF}\u{1F6E6}-\u{1F6E8}\u{1F6EA}\u{1F6ED}-\u{1F6EF}\u{1F6F1}-\u{1F6F2}\u{1F6F9}-\u{1F6FF}\u{1F774}-\u{1F77F}\u{1F7D5}-\u{1F7FF}\u{1F80C}-\u{1F80F}\u{1F848}-\u{1F84F}\u{1F85A}-\u{1F85F}\u{1F888}-\u{1F88F}\u{1F8AE}-\u{1F90F}\u{1F93F}\u{1F94D}-\u{1F94F}\u{1F96C}-\u{1F97F}\u{1F998}-\u{1F9BF}\u{1F9C1}-\u{1F9CF}]/.freeze
def call(query, account, limit, options = {})
- @query = query&.strip
+ @query = create_query(query)
@account = account
@options = options
@limit = limit.to_i
@@ -25,10 +27,15 @@
results[:accounts] = perform_accounts_search! if account_searchable?
results[:statuses] = perform_statuses_search! if full_text_searchable?
results[:hashtags] = perform_hashtags_search! if hashtag_searchable?
+ results[:groups] = perform_groups_search! if group_searchable?
end
end
end
+ def create_query(q)
+ q&.gsub(EMOJI_REGEX, '')&.strip
+ end
+
private
def perform_accounts_search!
@@ -37,11 +44,19 @@
@account,
limit: @limit,
resolve: @resolve,
- offset: @offset
+ offset: @offset,
)
end
add_method_tracer :perform_accounts_search!, 'SearchService/perform_accounts_search!'
+ def perform_groups_search!
+ GroupSearchService.new(
+ @query,
+ limit: @limit,
+ offset: @offset,
+ ).call
+ end
+
def perform_statuses_search!
functions = [activity_score_function, time_distance_function]
@@ -55,13 +70,13 @@
end
results = StatusesIndex
- .query(function_score: function_score_query)
- .filter(range: { id: id_range })
- .filter(PROHIBITED_FILTERS)
- .limit(@limit)
- .offset(@offset)
- .objects
- .compact
+ .query(function_score: function_score_query)
+ .filter(range: { id: id_range })
+ .filter(PROHIBITED_FILTERS)
+ .limit(@limit)
+ .offset(@offset)
+ .objects
+ .compact
account_ids = results.map(&:account_id)
account_domains = results.map(&:account_domain)
@@ -97,17 +112,14 @@
end
def perform_hashtags_search!
- TagSearchService.new.call(
- @query,
- limit: @limit,
- offset: @offset,
- exclude_unreviewed: @options[:exclude_unreviewed]
- )
+ result = Tag.search_for(@query, @limit, @offset)
+ JSON.parse(result || '[]')
end
+
add_method_tracer :perform_hashtags_search!, 'SearchService/perform_hashtags_search!'
def default_results
- { accounts: [], hashtags: [], statuses: [] }
+ { accounts: [], hashtags: [], statuses: [], groups: [] }
end
def url_query?
@@ -136,6 +148,10 @@
account_search? && !(@query.start_with?('#') || (@query.include?('@') && @query.include?(' ')))
end
+ def group_searchable?
+ group_search? && !(@query.start_with?('#') || (@query.include?('@') && @query.include?(' ')))
+ end
+
def hashtag_searchable?
hashtag_search? && !@query.include?('@')
end
@@ -150,6 +166,10 @@
def statuses_search?
@options[:type].blank? || @options[:type] == 'statuses'
+ end
+
+ def group_search?
+ @options[:type].blank? || @options[:type] == 'groups'
end
def relations_map_for_account(account, account_ids, domains)
Only in truth-new/opensource/app/services: sk_ad_network_service.rb
diff -ru truth-old/opensource/app/services/suspend_account_service.rb truth-new/opensource/app/services/suspend_account_service.rb
--- truth-old/opensource/app/services/suspend_account_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/suspend_account_service.rb 2024-04-01 14:59:13
@@ -8,10 +8,9 @@
suspend!
reject_remote_follows!
- distribute_update_actor!
unmerge_from_home_timelines!
unmerge_from_list_timelines!
- privatize_media_attachments!
+ Admin::MediaPrivatizationWorker.perform_async(account.id)
end
private
@@ -60,40 +59,6 @@
def unmerge_from_list_timelines!
@account.lists_for_local_distribution.find_each do |list|
FeedManager.instance.unmerge_from_list(@account, list)
- end
- end
-
- def privatize_media_attachments!
- attachment_names = MediaAttachment.attachment_definitions.keys
-
- @account.media_attachments.find_each do |media_attachment|
- attachment_names.each do |attachment_name|
- attachment = media_attachment.public_send(attachment_name)
- styles = [:original] | attachment.styles.keys
-
- next if attachment.blank?
-
- styles.each do |style|
- case Paperclip::Attachment.default_options[:storage]
- when :s3
- begin
- attachment.s3_object(style).acl.put(acl: 'private')
- rescue Aws::S3::Errors::NoSuchKey
- Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}"
- end
- when :fog
- # Not supported
- when :filesystem
- begin
- FileUtils.chmod(0o600 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
- rescue Errno::ENOENT
- Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
- end
- end
-
- CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
- end
- end
end
end
Only in truth-old/opensource/app/services: tag_search_service.rb
diff -ru truth-old/opensource/app/services/unblock_service.rb truth-new/opensource/app/services/unblock_service.rb
--- truth-old/opensource/app/services/unblock_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/unblock_service.rb 2024-04-01 14:59:13
@@ -7,6 +7,7 @@
return unless account.blocking?(target_account)
unblock = account.unblock!(target_account)
+ InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", account.id, target_account.id, target_account.whale?)
create_notification(unblock) if !target_account.local? && target_account.activitypub?
unblock
end
diff -ru truth-old/opensource/app/services/unfavourite_service.rb truth-new/opensource/app/services/unfavourite_service.rb
--- truth-old/opensource/app/services/unfavourite_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/unfavourite_service.rb 2024-04-01 14:59:13
@@ -7,6 +7,7 @@
favourite = Favourite.find_by!(account: account, status: status)
favourite.destroy!
create_notification(favourite) if !status.account.local? && status.account.activitypub?
+ InteractionsTracker.new(account.id, status.account_id, :favourite, account.following?(status.account_id), status.group).untrack
favourite
end
diff -ru truth-old/opensource/app/services/unfollow_service.rb truth-new/opensource/app/services/unfollow_service.rb
--- truth-old/opensource/app/services/unfollow_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/unfollow_service.rb 2024-04-01 14:59:13
@@ -34,6 +34,8 @@
InvalidateSecondaryCacheService.new.call("InvalidateFollowCacheWorker", @source_account.id, @target_account.id, @target_account.whale?)
+ remove_follower_interactions(@source_account.id, @target_account.id)
+
follow
end
@@ -63,5 +65,11 @@
def build_reject_json(follow)
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
+ end
+
+ def remove_follower_interactions(source_account_id, target_account_id)
+ InteractionsTracker.new(source_account_id, target_account_id, false, true).remove
+ redis.del("avatars_carousel_list_#{source_account_id}")
+ InvalidateSecondaryCacheService.new.call("InvalidateAvatarsCarouselCacheWorker", source_account_id)
end
end
diff -ru truth-old/opensource/app/services/unsuspend_account_service.rb truth-new/opensource/app/services/unsuspend_account_service.rb
--- truth-old/opensource/app/services/unsuspend_account_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/unsuspend_account_service.rb 2024-04-01 14:59:13
@@ -11,8 +11,7 @@
merge_into_home_timelines!
merge_into_list_timelines!
- publish_media_attachments!
- distribute_update_actor!
+ Admin::MediaPublicationWorker.perform_async(account.id)
end
private
@@ -56,40 +55,6 @@
def merge_into_list_timelines!
@account.lists_for_local_distribution.find_each do |list|
FeedManager.instance.merge_into_list(@account, list)
- end
- end
-
- def publish_media_attachments!
- attachment_names = MediaAttachment.attachment_definitions.keys
-
- @account.media_attachments.find_each do |media_attachment|
- attachment_names.each do |attachment_name|
- attachment = media_attachment.public_send(attachment_name)
- styles = [:original] | attachment.styles.keys
-
- next if attachment.blank?
-
- styles.each do |style|
- case Paperclip::Attachment.default_options[:storage]
- when :s3
- begin
- attachment.s3_object(style).acl.put(acl: Paperclip::Attachment.default_options[:s3_permissions])
- rescue Aws::S3::Errors::NoSuchKey
- Rails.logger.warn "Tried to change acl on non-existent key #{attachment.s3_object(style).key}"
- end
- when :fog
- # Not supported
- when :filesystem
- begin
- FileUtils.chmod(0o666 & ~File.umask, attachment.path(style)) unless attachment.path(style).nil?
- rescue Errno::ENOENT
- Rails.logger.warn "Tried to change permission on non-existent file #{attachment.path(style)}"
- end
- end
-
- CacheBusterWorker.perform_async(attachment.path(style)) if Rails.configuration.x.cache_buster_enabled
- end
- end
end
end
diff -ru truth-old/opensource/app/services/update_account_service.rb truth-new/opensource/app/services/update_account_service.rb
--- truth-old/opensource/app/services/update_account_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/update_account_service.rb 2024-04-01 14:59:13
@@ -1,6 +1,4 @@
# frozen_string_literal: true
-require "./lib/proto/serializers/account_updated_event.rb"
-
class UpdateAccountService < BaseService
def call(account, params, raise_error: false)
was_locked = account.locked
@@ -8,6 +6,7 @@
params['settings_store'] = params['pleroma_settings_store']
params.delete('pleroma_settings_store')
+ params.delete('chats_onboarded')
if !params['settings_store']
params.delete('settings_store')
@@ -16,12 +15,15 @@
account.send(update_method, params).tap do |ret|
next unless ret
+ # Allow resetting header images by passing in empty string
+ if params[:header] && params[:header].to_s.empty?
+ account.header_file_name = nil
+ account.header_content_type = nil
+ account.header_file_size = nil
+ end
+
authorize_all_follow_requests(account) if was_locked && !account.locked
check_links(account)
- Redis.current.publish(
- AccountUpdatedEvent::EVENT_KEY,
- AccountUpdatedEvent.new(account, fields_changed(account)).serialize
- )
process_hashtags(account)
end
rescue Mastodon::DimensionsValidationError, Mastodon::StreamValidationError => e
@@ -45,14 +47,5 @@
def process_hashtags(account)
account.tags_as_strings = Extractor.extract_hashtags(account.note)
- end
-
- def fields_changed(account)
- updatable_fields = %w(display_name avatar_url header_url website bio location)
- changed_fields = account.saved_changes.keys
- updated_fields = changed_fields.select { |f| updatable_fields.include?(f) }
- updated_fields << "avatar_url" if changed_fields.include?("avatar_file_name")
- updated_fields << "header_url" if changed_fields.include?("header_file_name")
- updated_fields.map(&:upcase)
end
end
Only in truth-new/opensource/app/services: update_feed_service.rb
Only in truth-new/opensource/app/services: update_group_service.rb
Only in truth-new/opensource/app/services: upload_video_chat_service.rb
Only in truth-old/opensource/app/services: upload_video_service.rb
Only in truth-new/opensource/app/services: upload_video_status_service.rb
Only in truth-new/opensource/app/services: video_encoding_status_service.rb
Only in truth-new/opensource/app/services: video_preview_service.rb
diff -ru truth-old/opensource/app/services/vote_service.rb truth-new/opensource/app/services/vote_service.rb
--- truth-old/opensource/app/services/vote_service.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/services/vote_service.rb 2024-04-12 09:09:08
@@ -16,11 +16,11 @@
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
- already_voted = @poll.votes.where(account: @account).exists?
+ already_voted = @poll.voted?(@account)
ApplicationRecord.transaction do
@choices.each do |choice|
- @votes << @poll.votes.create!(account: @account, choice: Integer(choice))
+ @votes << @poll.votes.create!(account: @account, option_number: Integer(choice))
end
end
else
@@ -28,52 +28,18 @@
end
end
- increment_voters_count! unless already_voted
-
ActivityTracker.increment('activity:interactions')
-
- if @poll.account.local?
- distribute_poll!
- else
- deliver_votes!
- queue_final_poll_check!
- end
end
private
- def distribute_poll!
- return if @poll.hide_totals?
- ActivityPub::DistributePollUpdateWorker.perform_in(3.minutes, @poll.status.id)
- end
-
def queue_final_poll_check!
return unless @poll.expires?
- PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id)
+ #PollExpirationNotifyWorker.perform_at(@poll.expires_at + 5.minutes, @poll.id)
end
- def deliver_votes!
- @votes.each do |vote|
- ActivityPub::DeliveryWorker.perform_async(
- build_json(vote),
- @account.id,
- @poll.account.inbox_url
- )
- end
- end
-
def build_json(vote)
Oj.dump(serialize_payload(vote, ActivityPub::VoteSerializer))
- end
-
- def increment_voters_count!
- unless @poll.voters_count.nil?
- @poll.voters_count = @poll.voters_count + 1
- @poll.save
- end
- rescue ActiveRecord::StaleObjectError
- @poll.reload
- retry
end
def lock_options
Only in truth-new/opensource/app/validators: account_feed_validator.rb
Only in truth-new/opensource/app/validators: base_email_validator.rb
Only in truth-new/opensource/app/validators: feed_validator.rb
diff -ru truth-old/opensource/app/validators/follow_limit_validator.rb truth-new/opensource/app/validators/follow_limit_validator.rb
--- truth-old/opensource/app/validators/follow_limit_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/follow_limit_validator.rb 2024-04-01 14:59:13
@@ -9,7 +9,7 @@
if limit_reached?(follow.account)
follow.errors.add(:base, I18n.t('users.follow_limit_reached', limit: self.class.limit_for_account(follow.account)))
- follow.errors.add(:errorCode, 'followLimitReached')
+ follow.errors.add(:error_code, 'followLimitReached')
end
end
Only in truth-new/opensource/app/validators: group_status_pin_validator.rb
Only in truth-new/opensource/app/validators: max_group_admin_validator.rb
Only in truth-new/opensource/app/validators: max_group_tag_validator.rb
diff -ru truth-old/opensource/app/validators/note_length_validator.rb truth-new/opensource/app/validators/note_length_validator.rb
--- truth-old/opensource/app/validators/note_length_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/note_length_validator.rb 2024-04-01 14:59:13
@@ -14,9 +14,8 @@
def countable_text(value)
return '' if value.nil?
- value.dup.tap do |new_text|
- new_text.gsub!(FetchLinkCardService::URL_PATTERN, StatusLengthValidator::URL_PLACEHOLDER)
- new_text.gsub!(Account::MENTION_RE, '@\2')
- end
+ value
+ .gsub(FetchLinkCardService::URL_PATTERN) { |url| URLPlaceholder.generate(url) }
+ .gsub(Account::MENTION_RE, '@\2')
end
end
diff -ru truth-old/opensource/app/validators/poll_validator.rb truth-new/opensource/app/validators/poll_validator.rb
--- truth-old/opensource/app/validators/poll_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/poll_validator.rb 2024-04-01 14:59:13
@@ -1,9 +1,9 @@
# frozen_string_literal: true
class PollValidator < ActiveModel::Validator
- MAX_OPTIONS = 4
+ MAX_OPTIONS = 6
MAX_OPTION_CHARS = 50
- MAX_EXPIRATION = 1.month.freeze
+ MAX_EXPIRATION = 7.days.freeze
MIN_EXPIRATION = 5.minutes.freeze
def validate(poll)
@@ -11,8 +11,7 @@
poll.errors.add(:options, I18n.t('polls.errors.too_few_options')) unless poll.options.size > 1
poll.errors.add(:options, I18n.t('polls.errors.too_many_options', max: MAX_OPTIONS)) if poll.options.size > MAX_OPTIONS
- poll.errors.add(:options, I18n.t('polls.errors.over_character_limit', max: MAX_OPTION_CHARS)) if poll.options.any? { |option| option.mb_chars.grapheme_length > MAX_OPTION_CHARS }
- poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq.size == poll.options.size
+ poll.errors.add(:options, I18n.t('polls.errors.duplicate_options')) unless poll.options.uniq(&:text).size == poll.options.map(&:text).size
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_long')) if poll.expires_at.nil? || poll.expires_at - current_time > MAX_EXPIRATION
poll.errors.add(:expires_at, I18n.t('polls.errors.duration_too_short')) if poll.expires_at.present? && (poll.expires_at - current_time).ceil < MIN_EXPIRATION
end
Only in truth-new/opensource/app/validators: status_group_validator.rb
diff -ru truth-old/opensource/app/validators/status_length_validator.rb truth-new/opensource/app/validators/status_length_validator.rb
--- truth-old/opensource/app/validators/status_length_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/status_length_validator.rb 2024-04-01 14:59:13
@@ -1,9 +1,7 @@
# frozen_string_literal: true
class StatusLengthValidator < ActiveModel::Validator
- MAX_CHARS = 500
- URL_PLACEHOLDER_CHARS = 23
- URL_PLACEHOLDER = "\1#{'x' * URL_PLACEHOLDER_CHARS}"
+ MAX_CHARS = 1000
def validate(status)
return unless status.local? && !status.reblog?
@@ -29,9 +27,8 @@
def countable_text
return '' if @status.text.nil?
- @status.text.dup.tap do |new_text|
- new_text.gsub!(FetchLinkCardService::URL_PATTERN, URL_PLACEHOLDER)
- new_text.gsub!(Account::MENTION_RE, '@\2')
- end
+ @status.text
+ .gsub(FetchLinkCardService::URL_PATTERN) { |url| URLPlaceholder.generate(url) }
+ .gsub(Account::MENTION_RE, '@\2')
end
end
diff -ru truth-old/opensource/app/validators/status_pin_validator.rb truth-new/opensource/app/validators/status_pin_validator.rb
--- truth-old/opensource/app/validators/status_pin_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/status_pin_validator.rb 2024-04-01 14:59:13
@@ -5,6 +5,8 @@
pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog?
pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id
pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility)
- pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.direct')) if pin.status.direct_visibility?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.group')) if pin.status.group_visibility?
+ pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.profile_pins.size > 4 && pin.account.local?
end
end
Only in truth-new/opensource/app/validators: unique_password_validator.rb
Only in truth-new/opensource/app/validators: valid_group_name_validator.rb
diff -ru truth-old/opensource/app/validators/vote_validator.rb truth-new/opensource/app/validators/vote_validator.rb
--- truth-old/opensource/app/validators/vote_validator.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/validators/vote_validator.rb 2024-04-01 14:59:13
@@ -6,7 +6,7 @@
vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote)
- if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, choice: vote.choice).exists?
+ if vote.poll.multiple? && vote.poll.votes.where(account: vote.account, option_number: vote.option_number).exists?
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
elsif !vote.poll.multiple? && vote.poll.votes.where(account: vote.account).exists?
vote.errors.add(:base, I18n.t('polls.errors.already_voted'))
@@ -16,6 +16,6 @@
private
def invalid_choice?(vote)
- vote.choice.negative? || vote.choice >= vote.poll.options.size
+ vote.option_number.negative? || vote.option_number >= vote.poll.options.size
end
end
diff -ru truth-old/opensource/app/views/admin/reports/_status.html.haml truth-new/opensource/app/views/admin/reports/_status.html.haml
--- truth-old/opensource/app/views/admin/reports/_status.html.haml 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/admin/reports/_status.html.haml 2024-04-01 14:59:13
@@ -1,3 +1,5 @@
+- show_author ||= false
+
.batch-table__row
%label.batch-table__row__select.batch-checkbox
= f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id
@@ -33,7 +35,14 @@
= t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account))
- else
= fa_visibility_icon(status)
- = t("statuses.visibilities.#{status.visibility}")
+ - if status.group_visibility?
+ - if status.group.present?
+ = link_to admin_group_path(status.group.id), class: 'detailed-status__group' do
+ = t("statuses.visibilities.#{status.visibility}")
+ - else
+ = t("statuses.visibilities.#{status.visibility}")
+ - else
+ = t("statuses.visibilities.#{status.visibility}")
- if status.proper.sensitive?
·
= fa_icon('eye-slash fw')
Only in truth-old/opensource/app/views/admin: trending_truths
Only in truth-new/opensource/app/views: api
Only in truth-new/opensource/app/views/application: _group_card.html.haml
diff -ru truth-old/opensource/app/views/layouts/application.html.haml truth-new/opensource/app/views/layouts/application.html.haml
--- truth-old/opensource/app/views/layouts/application.html.haml 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/layouts/application.html.haml 2024-04-01 14:59:13
@@ -21,7 +21,6 @@
%link{ rel: 'apple-touch-icon', sizes: '180x180', href: '/icons/icon-180x180.png' }/
%link{ rel: 'apple-touch-icon', sizes: '192x192', href: '/icons/icon-192x192.png' }/
%link{ rel: 'apple-touch-icon', sizes: '512x512', href: '/icons/icon-512x512.png' }/
- %link{ rel: 'mask-icon', href: '/mask-icon.svg', color: '#2B90D9' }/
%link{ rel: 'manifest', href: '/manifest.json' }/
%meta{ name: 'msapplication-config', content: '/browserconfig.xml' }/
%meta{ name: 'theme-color', content: '#282c37' }/
Only in truth-new/opensource/app/views/layouts: docs.html.haml
Only in truth-new/opensource/app/views: link
diff -ru truth-old/opensource/app/views/notification_mailer/user_approved.html.erb truth-new/opensource/app/views/notification_mailer/user_approved.html.erb
--- truth-old/opensource/app/views/notification_mailer/user_approved.html.erb 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/notification_mailer/user_approved.html.erb 2024-04-01 14:59:13
@@ -40,7 +40,24 @@
</p>
<p class="text-gray-600 mb-0">
- <%= t 'notification_mailer.user_approved.extra_step' %>
+ <%= t(
+ 'notification_mailer.user_approved.extra_step',
+ help_center_link: link_to(
+ t('notification_mailer.user_approved.help_center'),
+ 'https://help.truthsocial.com',
+ class: 'text-primary-600',
+ target: '_blank'
+ ),
+ mailto_support: link_to(
+ 'support@truthsocial.com',
+ 'mailto:support@truthsocial.com',
+ class: 'text-primary-600',
+ )
+ ).html_safe %>
+ </p>
+
+ <p class="text-gray-600 mb-0">
+ <%= t("notification_mailer.user_approved.signoff") %>
</p>
<table class="w-full" role="separator" cellpadding="0" cellspacing="0">
diff -ru truth-old/opensource/app/views/notification_mailer/user_approved.text.erb truth-new/opensource/app/views/notification_mailer/user_approved.text.erb
--- truth-old/opensource/app/views/notification_mailer/user_approved.text.erb 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/notification_mailer/user_approved.text.erb 2024-04-01 14:59:13
@@ -7,7 +7,15 @@
<%= t 'notification_mailer.user_approved.explanation' %>
-<%= t 'notification_mailer.user_approved.extra_step' %>
+<%= t(
+ 'notification_mailer.user_approved.extra_step',
+ help_center_link: t('notification_mailer.user_approved.help_center'),
+ mailto_support: 'support@truthsocial.com'
+) %>
+
+=> https://help.truthsocial.com
+
+<%= t("notification_mailer.user_approved.signoff") %>
---
diff -ru truth-old/opensource/app/views/statuses/_simple_status.html.haml truth-new/opensource/app/views/statuses/_simple_status.html.haml
--- truth-old/opensource/app/views/statuses/_simple_status.html.haml 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/statuses/_simple_status.html.haml 2024-04-01 14:59:13
@@ -58,6 +58,8 @@
= link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
- if status.distributable?
= fa_icon 'retweet fw'
+ - elsif status.group_visibility?
+ = fa_icon 'users fw'
- elsif status.private_visibility? || status.limited_visibility?
= fa_icon 'lock fw'
- else
diff -ru truth-old/opensource/app/views/user_mailer/status_removed.html.erb truth-new/opensource/app/views/user_mailer/status_removed.html.erb
--- truth-old/opensource/app/views/user_mailer/status_removed.html.erb 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/user_mailer/status_removed.html.erb 2024-04-01 14:59:13
@@ -54,7 +54,7 @@
<table cellpadding="0" cellspacing="0" border="0" role="presentation">
<tr>
<td class="leading-full">
- <a href="https://help.truthsocial.com/community/guidelines" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700">
+ <a href="https://help.truthsocial.com/community-guidelines-page" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700">
<!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%; mso-text-raise:30px;">&#8202;</i><![endif]-->
<span style="mso-text-raise: 16px;">
<%= t 'user_mailer.warning.review_server_policies' %>
diff -ru truth-old/opensource/app/views/user_mailer/warning.html.erb truth-new/opensource/app/views/user_mailer/warning.html.erb
--- truth-old/opensource/app/views/user_mailer/warning.html.erb 2022-06-08 09:15:38
+++ truth-new/opensource/app/views/user_mailer/warning.html.erb 2024-04-01 14:59:13
@@ -74,7 +74,7 @@
<table cellpadding="0" cellspacing="0" border="0" role="presentation">
<tr>
<td class="leading-full">
- <a href="https://help.truthsocial.com/community/guidelines" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700">
+ <a href="https://help.truthsocial.com/community-guidelines-page" class="relative inline-block py-16 px-24 rounded-full text-base font-medium text-center no-underline text-white bg-primary-600 hover:bg-primary-700">
<!--[if mso]><i style="letter-spacing: 24px; mso-font-width: -100%; mso-text-raise:30px;">&#8202;</i><![endif]-->
<span style="mso-text-raise: 16px;">
<%= t 'user_mailer.warning.review_server_policies' %>
diff -ru truth-old/opensource/app/workers/account_deletion_worker.rb truth-new/opensource/app/workers/account_deletion_worker.rb
--- truth-old/opensource/app/workers/account_deletion_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/account_deletion_worker.rb 2024-04-01 14:59:13
@@ -5,10 +5,18 @@
sidekiq_options queue: 'pull', lock: :until_executed
- def perform(account_id, options = {})
+ def perform(account_id, deleted_by_id, options = {})
reserve_username = options.with_indifferent_access.fetch(:reserve_username, true)
skip_activitypub = options.with_indifferent_access.fetch(:skip_activitypub, false)
- DeleteAccountService.new.call(Account.find(account_id), reserve_username: reserve_username, skip_activitypub: skip_activitypub, reserve_email: false)
+ deletion_type = options.with_indifferent_access.fetch(:deletion_type, 'unknown')
+ DeleteAccountService.new.call(
+ Account.find(account_id),
+ deleted_by_id,
+ deletion_type: deletion_type,
+ reserve_email: false,
+ reserve_username: reserve_username,
+ skip_activitypub: skip_activitypub,
+ )
rescue ActiveRecord::RecordNotFound
true
end
diff -ru truth-old/opensource/app/workers/activitypub/delivery_worker.rb truth-new/opensource/app/workers/activitypub/delivery_worker.rb
--- truth-old/opensource/app/workers/activitypub/delivery_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/activitypub/delivery_worker.rb 2024-04-01 14:59:13
@@ -39,12 +39,7 @@
Request.new(:post, @inbox_url, body: @json, http_client: http_client).tap do |request|
request.on_behalf_of(@source_account, :uri, sign_with: @options[:sign_with])
request.add_headers(HEADERS)
- request.add_headers({ 'Collection-Synchronization' => synchronization_header }) if ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] != 'true' && @options[:synchronize_followers]
end
- end
-
- def synchronization_header
- "collectionId=\"#{account_followers_url(@source_account)}\", digest=\"#{@source_account.remote_followers_hash(inbox_url_prefix)}\", url=\"#{account_followers_synchronization_url(@source_account)}\""
end
def inbox_url_prefix
Only in truth-old/opensource/app/workers/activitypub: processing_worker.rb
diff -ru truth-old/opensource/app/workers/admin/account_deletion_worker.rb truth-new/opensource/app/workers/admin/account_deletion_worker.rb
--- truth-old/opensource/app/workers/admin/account_deletion_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/admin/account_deletion_worker.rb 2024-04-01 14:59:13
@@ -5,8 +5,15 @@
sidekiq_options queue: 'pull'
- def perform(account_id)
- DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true)
+ def perform(account_id, deleted_by_id)
+ DeleteAccountService.new.call(
+ Account.find(account_id),
+ deleted_by_id,
+ deletion_type: 'worker_admin_account_deletion',
+ reserve_username: true,
+ reserve_email: true,
+ skip_activitypub: true,
+ )
rescue ActiveRecord::RecordNotFound
true
end
Only in truth-new/opensource/app/workers/admin: ad_worker.rb
Only in truth-new/opensource/app/workers/admin: group_deletion_worker.rb
Only in truth-new/opensource/app/workers/admin: media_privatization_worker.rb
Only in truth-new/opensource/app/workers/admin: media_publication_worker.rb
Only in truth-new/opensource/app/workers/concerns: image_migration.rb
Only in truth-new/opensource/app/workers/concerns: track_ad_impressions_concern.rb
Only in truth-new/opensource/app/workers: disabled_user_refollow_worker.rb
Only in truth-new/opensource/app/workers: disabled_user_unfollow_worker.rb
diff -ru truth-old/opensource/app/workers/distribution_worker.rb truth-new/opensource/app/workers/distribution_worker.rb
--- truth-old/opensource/app/workers/distribution_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/distribution_worker.rb 2024-04-05 09:07:34
@@ -25,13 +25,8 @@
end
def send_to_bailey(status_id)
- if @status.account.silenced? || !@status.public_visibility? || @status.reblog?
- rendered = nil
- else
- rendered = InlineRenderer.render(@status, nil, :status)
- rendered = Oj.dump(event: :update, payload: rendered)
- end
- Redis.current.lpush('elixir:distribution', Oj.dump(status_id: status_id, rendered: rendered))
- Rails.logger.debug("bailey_debug: sending #{rendered.nil? ? 'nil' : 'value'} for rendered for status #{status_id}")
+ QueueManager.enqueue_status_for_author_distribution(status_id)
+ QueueManager.enqueue_status_for_follower_distribution(status_id)
+ Rails.logger.debug("bailey_debug: enqueuing status #{status_id}")
end
end
Only in truth-new/opensource/app/workers: group_acceptance_notify_worker.rb
Only in truth-new/opensource/app/workers: group_deletion_notify_worker.rb
Only in truth-new/opensource/app/workers: group_deletion_worker.rb
Only in truth-new/opensource/app/workers: group_request_notify_worker.rb
Only in truth-new/opensource/app/workers: group_role_change_notify_worker.rb
Only in truth-new/opensource/app/workers: images
Only in truth-new/opensource/app/workers: inspect_link_worker.rb
Only in truth-new/opensource/app/workers: invalidate_account_statuses_worker.rb
Only in truth-new/opensource/app/workers: invalidate_ads_accounts_cache_worker.rb
Only in truth-new/opensource/app/workers: invalidate_ads_accounts_worker.rb
Only in truth-new/opensource/app/workers: invalidate_avatars_carousel_cache_worker.rb
Only in truth-new/opensource/app/workers: invalidate_descendants_cache_worker.rb
diff -ru truth-old/opensource/app/workers/invalidate_follow_cache_worker.rb truth-new/opensource/app/workers/invalidate_follow_cache_worker.rb
--- truth-old/opensource/app/workers/invalidate_follow_cache_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/invalidate_follow_cache_worker.rb 2024-04-01 14:59:13
@@ -5,8 +5,8 @@
include Redisable
def perform(source_account_id, target_account_id, whale)
- Rails.cache.delete("relationship:#{source_account_id}:#{target_account_id}")
- Rails.cache.delete("relationship:#{target_account_id}:#{source_account_id}")
+ Rails.cache.delete("relationship/#{source_account_id}/#{target_account_id}")
+ Rails.cache.delete("relationship/#{target_account_id}/#{source_account_id}")
redis.del("whale:following:#{source_account_id}") if whale
end
end
Only in truth-new/opensource/app/workers: invalidate_group_relationship_cache_worker.rb
Only in truth-new/opensource/app/workers: ios_device_check
diff -ru truth-old/opensource/app/workers/link_crawl_worker.rb truth-new/opensource/app/workers/link_crawl_worker.rb
--- truth-old/opensource/app/workers/link_crawl_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/link_crawl_worker.rb 2024-04-01 14:59:13
@@ -5,8 +5,8 @@
sidekiq_options queue: 'pull', retry: 5
- def perform(status_id, url = nil)
- FetchLinkCardService.new.call(Status.find(status_id), url)
+ def perform(status_id, url = nil, domain = nil)
+ FetchLinkCardService.new.call(Status.find(status_id), url, domain)
rescue ActiveRecord::RecordNotFound
true
end
diff -ru truth-old/opensource/app/workers/local_notification_worker.rb truth-new/opensource/app/workers/local_notification_worker.rb
--- truth-old/opensource/app/workers/local_notification_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/local_notification_worker.rb 2024-04-01 14:59:13
@@ -3,6 +3,8 @@
class LocalNotificationWorker
include Sidekiq::Worker
+ sidekiq_options retry: 1
+
def perform(receiver_account_id, activity_id = nil, activity_class_name = nil, type = nil)
if activity_id.nil? && activity_class_name.nil?
activity = Mention.find(receiver_account_id)
@@ -13,7 +15,5 @@
end
NotifyService.new.call(receiver, type || activity_class_name.underscore, activity)
- rescue ActiveRecord::RecordNotFound
- true
end
end
Only in truth-new/opensource/app/workers/mobile: channel_notification_queueing_worker.rb
Only in truth-new/opensource/app/workers/mobile: channel_notification_worker.rb
Only in truth-new/opensource/app/workers/mobile: marketing_notification_queueing_worker.rb
diff -ru truth-old/opensource/app/workers/mobile/marketing_notification_worker.rb truth-new/opensource/app/workers/mobile/marketing_notification_worker.rb
--- truth-old/opensource/app/workers/mobile/marketing_notification_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/mobile/marketing_notification_worker.rb 2024-04-01 14:59:13
@@ -7,40 +7,59 @@
TTL = 8.hours.to_s
URGENCY = 'normal'
+ IOS_PLATFORM = 1
+ ANDROID_PLATFORM = 2
- def self.queue_notifications(user_ids:, message:, url:)
- self.push_bulk(user_ids) do |user_id|
- [user_id, message, url]
+ def self.queue_notifications(message:, url:, mark_id: nil)
+ tester_platform = IOS_PLATFORM
+ tester_user_ids = if mark_id.present?
+ ENV.fetch('MARK_ID_PUSH_NOTIFICATIONS_USER_IDS', '')
+ .split(',')
+ .map(&:to_i)
+ .reject(&:zero?)
+ end
+
+ if tester_user_ids.present?
+ tester_tokens = MarketingPushSubscription
+ .select(:device_token)
+ .distinct
+ .joins('inner join users on users.id = web_push_subscriptions.user_id')
+ .where(users: { approved: true, disabled: false, id: tester_user_ids }, platform: tester_platform)
+ .where.not(device_token: nil)
+ .to_a
+ .pluck(:device_token)
+ perform_async(tester_tokens, message, url, tester_platform, mark_id)
end
+
+ [IOS_PLATFORM, ANDROID_PLATFORM].each do |platform|
+ tokens = MarketingPushSubscription
+ .select(:device_token)
+ .distinct
+ .joins('inner join users on users.id = web_push_subscriptions.user_id')
+ .where(users: { approved: true, disabled: false }, platform: platform)
+ .where.not(device_token: nil)
+ tokens = tokens.where.not(users: { id: tester_user_ids }) if platform == tester_platform && tester_user_ids.present?
+ tokens.find_in_batches(batch_size: 2_000) do |batch|
+ perform_async(batch.pluck(:device_token), message, url, platform, nil)
+ end
+ end
end
- def perform(user_id, message, url)
- subscriptions = Web::PushSubscription.where(user_id: user_id)
- endpoint = ENV.fetch('MOBILE_NOTIFICATION_ENDPOINT')
+ def perform(tokens, message, url, platform, mark_id)
+ endpoint = ENV.fetch('MOBILE_NOTIFICATION_ENDPOINT')
+ extend = [{ key: 'truthLink', val: url }, { key: 'category', val: 'marketing' }]
+ extend.push({ key: 'markId', val: mark_id }) if mark_id.present?
+ msg = { token: tokens, category: 'invite', platform: platform, message: message, extend: extend }
+ msg[:mutable_content] = true if platform == IOS_PLATFORM
+ body = { 'notifications' => [msg] }.to_json
- # return unless endpoint.present? && subscriptions.any? && message && url
+ return unless endpoint.present? && tokens.any? && message && url
- subscriptions.each do |subscription|
- payload = push_notification_json(subscription.device_token, message, url)
- request_pool.with(Addressable::URI.parse(endpoint).normalized_site) do |http_client|
- request = Request.new(:post, endpoint, body: payload, http_client: http_client)
-
- request.add_headers(
- 'Content-Type' => 'application/octet-stream'
- )
-
- request.perform do |response|
- # If the server responds with an error in the 4xx range
- # that isn't about rate-limiting or timeouts, we can
- # assume that the subscription is invalid or expired
- # and must be removed
-
- if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
- subscription.destroy!
- elsif !(200...300).cover?(response.code)
- raise Mastodon::UnexpectedResponseError, response
- end
- end
+ request_pool.with(Addressable::URI.parse(endpoint).normalized_site) do |http_client|
+ request = Request.new(:post, endpoint, body: body, http_client: http_client)
+ request.add_headers('Content-Type' => 'application/octet-stream')
+ request.perform do |response|
+ raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code)
end
end
rescue ActiveRecord::RecordNotFound
@@ -49,11 +68,12 @@
private
- def push_notification_json(token, message, url)
- { 'notifications' => [{token: [token], category: 'invite', platform: 1, message: message, extend: [{key: 'truthLink', val: url}]}] }.to_json
- end
-
def request_pool
RequestPool.current
end
+end
+
+class MarketingPushSubscription < ApplicationRecord
+ self.table_name = 'web_push_subscriptions'
+ self.primary_key = :device_token
end
diff -ru truth-old/opensource/app/workers/mobile/push_notification_worker.rb truth-new/opensource/app/workers/mobile/push_notification_worker.rb
--- truth-old/opensource/app/workers/mobile/push_notification_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/mobile/push_notification_worker.rb 2024-04-12 09:09:08
@@ -18,6 +18,7 @@
return unless @endpoint.present? && @notification.activity.present? && @subscription.pushable?(@notification)
payload = push_notification_json
+
request_pool.with(Addressable::URI.parse(@endpoint).normalized_site) do |http_client|
request = Request.new(:post, @endpoint, body: payload, http_client: http_client)
@@ -31,9 +32,7 @@
# assume that the subscription is invalid or expired
# and must be removed
- if (400..499).cover?(response.code) && ![408, 429].include?(response.code)
- @subscription.destroy!
- elsif !(200...300).cover?(response.code)
+ if !(200...300).cover?(response.code)
raise Mastodon::UnexpectedResponseError, response
end
end
diff -ru truth-old/opensource/app/workers/poll_expiration_notify_worker.rb truth-new/opensource/app/workers/poll_expiration_notify_worker.rb
--- truth-old/opensource/app/workers/poll_expiration_notify_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/poll_expiration_notify_worker.rb 2024-04-01 15:02:56
@@ -10,12 +10,11 @@
# Notify poll owner and remote voters
if poll.local?
- ActivityPub::DistributePollUpdateWorker.perform_async(poll.status.id)
NotifyService.new.call(poll.account, :poll, poll)
end
# Notify local voters
- poll.votes.includes(:account).group(:account_id).select(:account_id).map(&:account).select(&:local?).each do |account|
+ PollVote.where(poll_id: poll_id).includes(:account).group(:account_id).select(:account_id).map(&:account).select(&:local?).each do |account|
NotifyService.new.call(account, :poll, poll)
end
rescue ActiveRecord::RecordNotFound
Only in truth-new/opensource/app/workers: process_mention_notifications_worker.rb
Only in truth-new/opensource/app/workers: push_chat_message_worker.rb
Only in truth-new/opensource/app/workers: reblog_removal_worker.rb
Only in truth-new/opensource/app/workers/scheduler: device_verification_cleanup_worker.rb
diff -ru truth-old/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb truth-new/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb
--- truth-old/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb 2024-04-01 14:59:13
@@ -6,7 +6,7 @@
sidekiq_options retry: 0
def perform
- Doorkeeper::AccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
+ OauthAccessToken.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
Doorkeeper::AccessGrant.where('revoked_at IS NOT NULL').where('revoked_at < NOW()').delete_all
SystemKey.expired.delete_all
end
Only in truth-old/opensource/app/workers/scheduler: pghero_scheduler.rb
Only in truth-new/opensource/app/workers/scheduler: refresh_receipt_scheduler.rb
Only in truth-new/opensource/app/workers/scheduler: tv_create_program_records_scheduler.rb
Only in truth-new/opensource/app/workers/scheduler: tv_refetch_channels_list_scheduler.rb
diff -ru truth-old/opensource/app/workers/scheduler/user_cleanup_scheduler.rb truth-new/opensource/app/workers/scheduler/user_cleanup_scheduler.rb
--- truth-old/opensource/app/workers/scheduler/user_cleanup_scheduler.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/scheduler/user_cleanup_scheduler.rb 2024-04-01 14:59:13
@@ -8,6 +8,7 @@
def perform
clean_unconfirmed_accounts!
clean_suspended_accounts!
+ # clean_suspended_groups! # Revisit once we have a deletion strategy
end
private
@@ -20,8 +21,14 @@
end
def clean_suspended_accounts!
- AccountDeletionRequest.where('created_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).reorder(nil).find_each do |deletion_request|
- Admin::AccountDeletionWorker.perform_async(deletion_request.account_id)
+ AccountDeletionRequest.where('created_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).where(Account.where('account_deletion_requests.account_id = accounts.id and accounts.suspended_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).arel.exists).reorder(nil).find_each do |deletion_request|
+ Admin::AccountDeletionWorker.perform_async(deletion_request.account_id, -99)
end
end
+
+ # def clean_suspended_groups!
+ # GroupDeletionRequest.where('created_at <= ?', GroupDeletionRequest::DELAY_TO_DELETION.ago).reorder(nil).find_each do |deletion_request|
+ # Admin::GroupDeletionWorker.perform_async(deletion_request.group_id)
+ # end
+ # end
end
Only in truth-new/opensource/app/workers: status_moderated_event_worker.rb
Only in truth-new/opensource/app/workers: sync_avatars_seen_accounts_worker.rb
Only in truth-new/opensource/app/workers: sync_groups_seen_worker.rb
Only in truth-new/opensource/app/workers: sync_seen_feeds_worker.rb
Only in truth-new/opensource/app/workers: track_revcontent_ad_impressions_worker.rb
Only in truth-new/opensource/app/workers: track_rumble_ad_impressions_worker.rb
Only in truth-new/opensource/app/workers: tv_accounts_create_worker.rb
Only in truth-new/opensource/app/workers: tv_accounts_login_worker.rb
Only in truth-new/opensource/app/workers: tv_create_tv_program_status_worker.rb
Only in truth-new/opensource/app/workers: tv_program_reminder_notification_worker.rb
Only in truth-new/opensource/app/workers: upload_video_chat_worker.rb
Only in truth-new/opensource/app/workers: upload_video_status_worker.rb
Only in truth-new/opensource/app/workers: video_polling_worker.rb
Only in truth-new/opensource/app/workers: video_preview_worker.rb
Only in truth-old/opensource/app/workers: video_upload_worker.rb
diff -ru truth-old/opensource/app/workers/web/push_notification_worker.rb truth-new/opensource/app/workers/web/push_notification_worker.rb
--- truth-old/opensource/app/workers/web/push_notification_worker.rb 2022-06-08 09:15:38
+++ truth-new/opensource/app/workers/web/push_notification_worker.rb 2024-04-01 14:59:13
@@ -3,7 +3,7 @@
class Web::PushNotificationWorker
include Sidekiq::Worker
- sidekiq_options queue: 'push', retry: 5
+ sidekiq_options queue: 'push', retry: false
TTL = 48.hours.to_s
URGENCY = 'normal'
diff -ru truth-old/opensource/app.json truth-new/opensource/app.json
--- truth-old/opensource/app.json 2022-06-08 09:15:38
+++ truth-new/opensource/app.json 2024-04-01 14:59:13
@@ -93,10 +93,16 @@
}
],
"scripts": {
- "postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
+ "postdeploy": "bundle exec rails db:schema:load && bundle exec rails db:seed"
},
"addons": [
"heroku-postgresql",
"heroku-redis"
- ]
+ ],
+ "dokku": {
+ "plugins": [
+ "postgres",
+ "redis"
+ ]
+ }
}
Only in truth-new/opensource/bin: ci-pre-deploy
Only in truth-new/opensource/bin: rmq_status_moderated_event_worker
Only in truth-new/opensource/bin: sql-injection-check
diff -ru truth-old/opensource/brakeman-output.json truth-new/opensource/brakeman-output.json
--- truth-old/opensource/brakeman-output.json 2022-06-08 09:18:29
+++ truth-new/opensource/brakeman-output.json 2024-04-01 14:59:13
@@ -1,11 +1,11 @@
{
"scan_info": {
- "app_path": "/builds/tmediatech/social-v1",
+ "app_path": "/Users/markmorales/Code/social-v1_groups",
"rails_version": "6.1.3.2",
"security_warnings": 0,
- "start_time": "2022-06-08 13:18:11 +0000",
- "end_time": "2022-06-08 13:18:29 +0000",
- "duration": 18.243194352,
+ "start_time": "2023-07-17 16:03:50 -0500",
+ "end_time": "2023-07-17 16:04:07 -0500",
+ "duration": 17.111335,
"checks_performed": [
"BasicAuth",
"BasicAuthTimingAttack",
@@ -80,9 +80,9 @@
"XMLDoS",
"YAMLParsing"
],
- "number_of_controllers": 211,
- "number_of_models": 122,
- "number_of_templates": 202,
+ "number_of_controllers": 286,
+ "number_of_models": 173,
+ "number_of_templates": 205,
"ruby_version": "2.7.2",
"brakeman_version": "5.0.1"
},
@@ -97,7 +97,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/report.rb",
- "line": 118,
+ "line": 121,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")",
"render_path": null,
@@ -110,13 +110,32 @@
"confidence": "High"
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "0a8e1b95121cc27c9eccfa472297b9c738b55e8511a902dfb0cd6c0861cf4a29",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/groups_controller.rb",
+ "line": 186,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:role, :account_ids => ([]), :tags => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::GroupsController",
+ "method": "resource_params"
+ },
+ "user_input": ":role",
+ "confidence": "Medium"
+ },
+ {
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "19df3740b8d02a9fe0eb52c939b4b87d3a2a591162a6adfa8d64e9c26aeebe6d",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/status.rb",
- "line": 107,
+ "line": 131,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")",
"render_path": null,
@@ -148,6 +167,25 @@
"confidence": "High"
},
{
+ "warning_type": "SQL Injection",
+ "warning_code": 0,
+ "fingerprint": "3373360caca1c1ecad29d946fe605fb60d1ae163d68685c01faee2e86b7ef160",
+ "check_name": "SQL",
+ "message": "Possible SQL injection",
+ "file": "app/models/status.rb",
+ "line": 503,
+ "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
+ "code": "select(\"*\").from(\"(select s.* from statuses s\\n inner join conversations c on c.id = s.conversation_id\\n inner join conversation_mutes cm on cm.conversation_id = c.id\\n where cm.account_id = #{account_id} and in_reply_to_id is null) as statuses\")",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Status",
+ "method": "muted_conversations_for_account"
+ },
+ "user_input": "account_id",
+ "confidence": "Weak"
+ },
+ {
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7",
@@ -173,7 +211,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 542,
+ "line": 611,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" WITH first_degree AS (\\n SELECT target_account_id\\n FROM follows\\n WHERE account_id = ?\\n UNION ALL\\n SELECT ?\\n )\\n SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?)\\n WHERE accounts.id IN (SELECT * FROM first_degree)\\n AND #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, account.id, limit, offset])",
"render_path": null,
@@ -192,7 +230,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/status.rb",
- "line": 112,
+ "line": 136,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "result.joins(\"LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")",
"render_path": null,
@@ -230,7 +268,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 511,
+ "line": 580,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, limit, offset])",
"render_path": null,
@@ -262,6 +300,25 @@
"confidence": "Medium"
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/notifications_controller.rb",
+ "line": 82,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::NotificationsController",
+ "method": "browserable_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High"
+ },
+ {
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50",
@@ -300,13 +357,32 @@
"confidence": "High"
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "d6003f47b2ac2aa2a8c7e56d8a78d9490999ec6c6e0b82c353575084f8bd47cd",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/reports_controller.rb",
+ "line": 84,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::ReportsController",
+ "method": "report_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High"
+ },
+ {
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "e21d8fee7a5805761679877ca35ed1029c64c45ef3b4012a30262623e1ba8bb9",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 558,
+ "line": 627,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?)\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, limit, offset])",
"render_path": null,
@@ -317,31 +393,12 @@
},
"user_input": "textsearch",
"confidence": "Medium"
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "efa3a1b56f8c87aabd679156a2d5fd8185d469562361fb9ca8e5b478ed909906",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/reports_controller.rb",
- "line": 45,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]), :rule_ids => ([]))",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::ReportsController",
- "method": "report_params"
- },
- "user_input": ":account_id",
- "confidence": "High"
}
],
"errors": [
],
"obsolete": [
-
+ "a9c222f356d8b4b673445f266f86e6bfce87df4e97a25ac36407367ee1269bf4"
]
}
\ No newline at end of file
diff -ru truth-old/opensource/config/application.rb truth-new/opensource/config/application.rb
--- truth-old/opensource/config/application.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/application.rb 2024-04-01 14:59:13
@@ -33,6 +33,9 @@
require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches'
require_relative '../lib/s3/seahorse_extensions'
+require_relative '../lib/sidekiq_unique_jobs/manager_extensions'
+require_relative '../lib/http/request_extensions'
+
require 'prometheus_exporter/client'
Dotenv::Railtie.load
@@ -155,10 +158,12 @@
Doorkeeper::AuthorizationsController.layout 'modal'
Doorkeeper::AuthorizedApplicationsController.layout 'admin'
Doorkeeper::Application.send :include, ApplicationExtension
- Doorkeeper::AccessToken.send :include, AccessTokenExtension
+ OauthAccessToken.send :include, AccessTokenExtension
Devise::FailureApp.send :include, AbstractController::Callbacks
Devise::FailureApp.send :include, HttpAcceptLanguage::EasyAccess
Devise::FailureApp.send :include, Localized
end
+
+ config.active_record.schema_format = :sql
end
end
diff -ru truth-old/opensource/config/brakeman.ignore truth-new/opensource/config/brakeman.ignore
--- truth-old/opensource/config/brakeman.ignore 2022-06-08 09:15:38
+++ truth-new/opensource/config/brakeman.ignore 2024-04-01 14:59:13
@@ -7,7 +7,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/report.rb",
- "line": 118,
+ "line": 121,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "Admin::ActionLog.from(\"(#{[Admin::ActionLog.where(:target_type => \"Report\", :target_id => id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Account\", :target_id => target_account_id, :created_at => ((created_at..updated_at))).unscope(:order), Admin::ActionLog.where(:target_type => \"Status\", :target_id => status_ids, :created_at => ((created_at..updated_at))).unscope(:order)].map do\n \"(#{query.to_sql})\"\n end.join(\" UNION ALL \")}) AS admin_action_logs\")",
"render_path": null,
@@ -21,13 +21,33 @@
"note": ""
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "0a8e1b95121cc27c9eccfa472297b9c738b55e8511a902dfb0cd6c0861cf4a29",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/groups_controller.rb",
+ "line": 186,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:role, :account_ids => ([]), :tags => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::GroupsController",
+ "method": "resource_params"
+ },
+ "user_input": ":role",
+ "confidence": "Medium",
+ "note": ""
+ },
+ {
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "19df3740b8d02a9fe0eb52c939b4b87d3a2a591162a6adfa8d64e9c26aeebe6d",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/status.rb",
- "line": 103,
+ "line": 131,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "result.joins(\"INNER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")",
"render_path": null,
@@ -41,6 +61,46 @@
"note": ""
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "224a66f0b51e79901fae28810d09f30d6cd5a9b45eef4699ecb95dff9bd4aa95",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v2/search_controller.rb",
+ "line": 28,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :q, :resolve, :exclude_unreviewed, :limit)",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V2::SearchController",
+ "method": "search_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High",
+ "note": ""
+ },
+ {
+ "warning_type": "SQL Injection",
+ "warning_code": 0,
+ "fingerprint": "3373360caca1c1ecad29d946fe605fb60d1ae163d68685c01faee2e86b7ef160",
+ "check_name": "SQL",
+ "message": "Possible SQL injection",
+ "file": "app/models/status.rb",
+ "line": 503,
+ "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
+ "code": "select(\"*\").from(\"(select s.* from statuses s\\n inner join conversations c on c.id = s.conversation_id\\n inner join conversation_mutes cm on cm.conversation_id = c.id\\n where cm.account_id = #{account_id} and in_reply_to_id is null) as statuses\")",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Status",
+ "method": "muted_conversations_for_account"
+ },
+ "user_input": "account_id",
+ "confidence": "Weak",
+ "note": ""
+ },
+ {
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "5fad11cd67f905fab9b1d5739d01384a1748ebe78c5af5ac31518201925265a7",
@@ -67,7 +127,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 525,
+ "line": 611,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" WITH first_degree AS (\\n SELECT target_account_id\\n FROM follows\\n WHERE account_id = ?\\n UNION ALL\\n SELECT ?\\n )\\n SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?)\\n WHERE accounts.id IN (SELECT * FROM first_degree)\\n AND #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, account.id, limit, offset])",
"render_path": null,
@@ -87,7 +147,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/status.rb",
- "line": 108,
+ "line": 136,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "result.joins(\"LEFT OUTER JOIN statuses_tags t#{id} ON t#{id}.status_id = statuses.id AND t#{id}.tag_id = #{id}\")",
"render_path": null,
@@ -103,25 +163,6 @@
{
"warning_type": "Mass Assignment",
"warning_code": 105,
- "fingerprint": "224a66f0b51e79901fae28810d09f30d6cd5a9b45eef4699ecb95dff9bd4aa95",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v2/search_controller.rb",
- "line": 28,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :q, :resolve, :exclude_unreviewed, :limit)",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V2::SearchController",
- "method": "search_params"
- },
- "user_input": ":account_id",
- "confidence": "High"
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
"fingerprint": "874be88fedf4c680926845e9a588d3197765a6ccbfdd76466b44cc00151c612e",
"check_name": "PermitAttributes",
"message": "Potentially dangerous key allowed for mass assignment",
@@ -146,7 +187,7 @@
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 494,
+ "line": 580,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, limit, offset])",
"render_path": null,
@@ -180,6 +221,46 @@
"note": ""
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "a9c222f356d8b4b673445f266f86e6bfce87df4e97a25ac36407367ee1269bf4",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/reports_controller.rb",
+ "line": 70,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::ReportsController",
+ "method": "report_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High",
+ "note": ""
+ },
+ {
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/notifications_controller.rb",
+ "line": 82,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::NotificationsController",
+ "method": "browserable_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High",
+ "note": ""
+ },
+ {
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "ba568ac09683f98740f663f3d850c31785900215992e8c090497d359a2563d50",
@@ -220,13 +301,33 @@
"note": ""
},
{
+ "warning_type": "Mass Assignment",
+ "warning_code": 105,
+ "fingerprint": "d6003f47b2ac2aa2a8c7e56d8a78d9490999ec6c6e0b82c353575084f8bd47cd",
+ "check_name": "PermitAttributes",
+ "message": "Potentially dangerous key allowed for mass assignment",
+ "file": "app/controllers/api/v1/reports_controller.rb",
+ "line": 84,
+ "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
+ "code": "params.permit(:account_id, :comment, :forward, :group_id, :external_ad_url, :external_ad_media_url, :external_ad_description, :status_ids => ([]), :rule_ids => ([]), :message_ids => ([]))",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Api::V1::ReportsController",
+ "method": "report_params"
+ },
+ "user_input": ":account_id",
+ "confidence": "High",
+ "note": ""
+ },
+ {
"warning_type": "SQL Injection",
"warning_code": 0,
"fingerprint": "e21d8fee7a5805761679877ca35ed1029c64c45ef3b4012a30262623e1ba8bb9",
"check_name": "SQL",
"message": "Possible SQL injection",
"file": "app/models/account.rb",
- "line": 541,
+ "line": 627,
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
"code": "find_by_sql([\" SELECT\\n accounts.*,\\n (count(f.id) + 1) * ts_rank_cd(#{textsearch}, #{query}, 32) AS rank\\n FROM accounts\\n LEFT OUTER JOIN follows AS f ON (accounts.id = f.account_id AND f.target_account_id = ?) OR (accounts.id = f.target_account_id AND f.account_id = ?)\\n WHERE #{query} @@ #{textsearch}\\n AND accounts.suspended_at IS NULL\\n AND accounts.moved_to_account_id IS NULL\\n GROUP BY accounts.id\\n ORDER BY rank DESC\\n LIMIT ? OFFSET ?\\n\".squish, account.id, account.id, limit, offset])",
"render_path": null,
@@ -238,28 +339,8 @@
"user_input": "textsearch",
"confidence": "Medium",
"note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "efa3a1b56f8c87aabd679156a2d5fd8185d469562361fb9ca8e5b478ed909906",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/reports_controller.rb",
- "line": 45,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:account_id, :comment, :forward, :status_ids => ([]), :rule_ids => ([]))",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::ReportsController",
- "method": "report_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "note": ""
}
],
- "updated": "2022-02-03 12:54:45 -0500",
+ "updated": "2023-07-17 16:08:14 -0500",
"brakeman_version": "5.0.1"
}
diff -ru truth-old/opensource/config/database.yml truth-new/opensource/config/database.yml
--- truth-old/opensource/config/database.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/database.yml 2024-04-01 14:59:13
@@ -35,14 +35,22 @@
connections:
- role: master
blacklist_duration: 0
- url: postgresql://<%= ENV['DB_USER'] || 'truth' %>:<%= ENV['DB_PASS'] || '' %>@<%= ENV['DB_HOST'] || 'localhost' %>:<%= ENV['DB_PORT'] || 5432 %>/<%= ENV['DB_NAME'] || 'truth_production' %>
+ host: <%= ENV['DB_HOST'] || 'localhost' %>
+ port: <%= ENV['DB_PORT'] || 5432 %>
+ database: <%= ENV['DB_NAME'] || 'truth_production' %>
+ username: <%= ENV['DB_USER'] || 'truth' %>
+ password: <%= ENV['DB_PASS'] || '' %>
- role: slave
blacklist_duration: 0
- url: postgresql://<%= ENV['DB_USER'] || 'truth' %>:<%= ENV['DB_PASS'] || '' %>@<%= ENV['DB_HOST_REPLICA'] || 'localhost' %>:<%= ENV['DB_PORT_REPLICA'] || 5433 %>/<%= ENV['DB_NAME'] || 'truth_production' %>
+ host: <%= ENV['DB_HOST_REPLICA'] || 'localhost' %>
+ port: <%= ENV['DB_PORT_REPLICA'] || 5433 %>
+ database: <%= ENV['DB_NAME_REPLICA'] || 'truth_production_ro' %>
+ username: <%= ENV['DB_USER_REPLICA'] || 'truth' %>
+ password: <%= ENV['DB_PASS_REPLICA'] || '' %>
<% else %>
+ host: <%= ENV['DB_HOST'] || 'localhost' %>
+ port: <%= ENV['DB_PORT'] || 5432 %>
database: <%= ENV['DB_NAME'] || 'truth_production' %>
username: <%= ENV['DB_USER'] || 'truth' %>
password: <%= ENV['DB_PASS'] || '' %>
- host: <%= ENV['DB_HOST'] || 'localhost' %>
- port: <%= ENV['DB_PORT'] || 5432 %>
<% end %>
diff -ru truth-old/opensource/config/environment.rb truth-new/opensource/config/environment.rb
--- truth-old/opensource/config/environment.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/environment.rb 2024-04-01 14:59:13
@@ -4,4 +4,7 @@
# Initialize the Rails application.
Rails.application.initialize!
+# Require all proto events & schemas
+Dir[File.expand_path("./lib/proto/**/*.rb")].each { |f| require f }
+
ActiveRecord::SchemaDumper.ignore_tables = ['deprecated_preview_cards']
diff -ru truth-old/opensource/config/environments/development.rb truth-new/opensource/config/environments/development.rb
--- truth-old/opensource/config/environments/development.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/environments/development.rb 2024-04-01 14:59:13
@@ -105,6 +105,7 @@
end
config.x.otp_secret = ENV.fetch('OTP_SECRET', '1fc2b87989afa6351912abeebe31ffc5c476ead9bf8b3d74cbc4a302c7b69a45b40b1bbef3506ddad73e942e15ed5ca4b402bf9a66423626051104f4b5f05109')
+ config.hosts << "#{Rails.configuration.x.use_https ? 'https' : 'http' }://#{Rails.configuration.x.web_domain}"
end
ActiveRecordQueryTrace.enabled = ENV['QUERY_TRACE_ENABLED'] == 'true'
diff -ru truth-old/opensource/config/environments/production.rb truth-new/opensource/config/environments/production.rb
--- truth-old/opensource/config/environments/production.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/environments/production.rb 2024-04-01 14:59:13
@@ -47,7 +47,7 @@
config.force_ssl = true
config.ssl_options = {
redirect: {
- exclude: -> request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') }
+ exclude: -> request { request.path.start_with?('/health') || request.headers["Host"].end_with?('.onion') || request.remote_ip === '127.0.0.1'}
}
}
Only in truth-new/opensource/config: imagemagick
diff -ru truth-old/opensource/config/initializers/chewy.rb truth-new/opensource/config/initializers/chewy.rb
--- truth-old/opensource/config/initializers/chewy.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/chewy.rb 2024-04-01 14:59:13
@@ -1,17 +1,28 @@
-enabled = ENV['ES_ENABLED'] == 'true'
-host = ENV.fetch('ES_HOST') { 'localhost' }
-port = ENV.fetch('ES_PORT') { 9200 }
+enabled = ENV['ES_ENABLED'] == 'true'
+indexing_enabled = ENV.fetch('ES_INDEXING_ENABLED') { ENV['ES_ENABLED'] } == 'true'
+host = ENV.fetch('ES_HOST') { 'localhost' }
+port = ENV.fetch('ES_PORT') { 9200 }
fallback_prefix = ENV.fetch('REDIS_NAMESPACE') { nil }
-prefix = ENV.fetch('ES_PREFIX') { fallback_prefix }
+prefix = ENV.fetch('ES_PREFIX') { fallback_prefix }
+username = ENV.fetch('ES_USER') { nil }
+password = ENV.fetch('ES_PASSWORD') { nil }
Chewy.settings = {
- host: "#{host}:#{port}",
+ host: host,
+ port: port,
prefix: prefix,
enabled: enabled,
+ indexing_enabled: indexing_enabled,
journal: false,
sidekiq: { queue: 'chewy' },
}
+if username && password
+ Chewy.settings[:user] = username
+ Chewy.settings[:password] = password
+end
+
+
# We use our own async strategy even outside the request-response
# cycle, which takes care of checking if ElasticSearch is enabled
# or not. However, mind that for the Rails console, the :urgent
@@ -25,6 +36,10 @@
def enabled?
settings[:enabled]
end
+
+ def indexing_enabled?
+ settings[:indexing_enabled]
+ end
end
end
@@ -33,23 +48,3 @@
# Mastodon is run with hidden services enabled, because
# ElasticSearch is *not* supposed to be accessed through a proxy
Faraday.ignore_env_proxy = true
-
-# Elasticsearch 7.x workaround
-Elasticsearch::Transport::Client.prepend Module.new {
- def search(arguments = {})
- arguments[:rest_total_hits_as_int] = true
- super arguments
- end
-}
-
-Elasticsearch::API::Indices::IndicesClient.prepend Module.new {
- def create(arguments = {})
- arguments[:include_type_name] = true
- super arguments
- end
-
- def put_mapping(arguments = {})
- arguments[:include_type_name] = true
- super arguments
- end
-}
diff -ru truth-old/opensource/config/initializers/content_security_policy.rb truth-new/opensource/config/initializers/content_security_policy.rb
--- truth-old/opensource/config/initializers/content_security_policy.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/content_security_policy.rb 2024-04-01 14:59:13
@@ -19,8 +19,8 @@
#TODO: refactor the maintenance of these URLs
segment_base_url = 'https://cdn.segment.com'
segment_api_url = 'https://api.segment.io'
-segment_script_hashes = ["'sha256-Kru1cRFDRjvkSX3GJVOzPMlesOJPlwl8Yf/vyxi7wnc='",
- "'sha256-SkDGcKd1lxidykiwp0MQl3em4R4qTUyDCyVbFr52Qdo='",
+segment_script_hashes = ["'sha256-Kru1cRFDRjvkSX3GJVOzPMlesOJPlwl8Yf/vyxi7wnc='",
+ "'sha256-SkDGcKd1lxidykiwp0MQl3em4R4qTUyDCyVbFr52Qdo='",
"'sha256-CZKu4Ofm+PztnJbExQzfZGKk50F7ttkRpdQxduN4lCA='"
]
@@ -59,14 +59,3 @@
Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
Rails.application.config.content_security_policy_nonce_directives = %w(style-src)
-
-Rails.application.reloader.to_prepare do
- PgHero::HomeController.content_security_policy do |p|
- p.script_src :self, :unsafe_inline, assets_host
- p.style_src :self, :unsafe_inline, assets_host
- end
-
- PgHero::HomeController.after_action do
- request.content_security_policy_nonce_generator = nil
- end
-end
diff -ru truth-old/opensource/config/initializers/cors.rb truth-new/opensource/config/initializers/cors.rb
--- truth-old/opensource/config/initializers/cors.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/cors.rb 2024-04-01 14:59:13
@@ -25,7 +25,16 @@
headers: :any,
methods: [:post, :put, :delete, :get, :patch, :options],
credentials: false,
- expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id']
+ expose: [
+ 'Link',
+ 'X-RateLimit-Reset',
+ 'X-RateLimit-Limit',
+ 'X-RateLimit-Remaining',
+ 'X-Request-Id',
+ 'X-Unread-Messages-Count',
+ 'X-Total-Count',
+ 'X-Truth-Ad-Indexes'
+ ]
resource '/oauth/token',
headers: :any,
methods: [:post],
diff -ru truth-old/opensource/config/initializers/doorkeeper.rb truth-new/opensource/config/initializers/doorkeeper.rb
--- truth-old/opensource/config/initializers/doorkeeper.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/doorkeeper.rb 2024-04-01 14:59:13
@@ -2,6 +2,8 @@
# Change the ORM that doorkeeper will use (needs plugins)
orm :active_record
+ access_token_class "OauthAccessToken"
+
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
current_user || redirect_to(new_user_session_url)
@@ -11,22 +13,28 @@
username = request.params[:username]
if (username.present?)
- user = if username.include?("@")
+ username.strip!
+ user = if username =~ URI::MailTo::EMAIL_REGEXP
# Emails on the user table are all stored in lowercase, so this should allow us to login with case-insensitive email
User.find_by(email: username.downcase)
- else
+ elsif username.first == '@'
# Unlike emails usernames are stored as they are entered so we need to query case insensitive
+ Account.ci_find_by_username(username[1..-1])&.user
+ else
Account.ci_find_by_username(username)&.user
end
user = nil unless user&.valid_password?(request.params[:password])
elsif request.params[:mfa_token].present?
- user = User.get_user_from_token(request.params[:mfa_token])
+ mfa_token = params[:mfa_token].strip
+ code = params[:code].strip
+ user = User.get_user_from_token(mfa_token)
+
if user.present?
- user = nil unless user.validate_user_token(request.params[:mfa_token])
- user = nil unless (user.validate_and_consume_otp!(request.params[:code]) ||
- user.invalidate_otp_backup_code!(request.params[:code]))
+ user = nil unless user.validate_user_token(mfa_token)
+ user = nil unless (user.validate_and_consume_otp!(code) ||
+ user.invalidate_otp_backup_code!(code))
end
else
user = nil
@@ -123,7 +131,8 @@
:'admin:write',
:'admin:write:accounts',
:'admin:write:reports',
- :crypto
+ :crypto,
+ :ads
# Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
diff -ru truth-old/opensource/config/initializers/filter_parameter_logging.rb truth-new/opensource/config/initializers/filter_parameter_logging.rb
--- truth-old/opensource/config/initializers/filter_parameter_logging.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/filter_parameter_logging.rb 2024-04-01 14:59:13
@@ -1,4 +1,4 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
-Rails.application.config.filter_parameters += [:password, :sms, :private_key, :public_key, :otp_attempt]
+Rails.application.config.filter_parameters += [:password, :sms, :private_key, :public_key, :otp_attempt, :token]
diff -ru truth-old/opensource/config/initializers/makara.rb truth-new/opensource/config/initializers/makara.rb
--- truth-old/opensource/config/initializers/makara.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/makara.rb 2024-04-01 14:59:13
@@ -13,4 +13,4 @@
end
end
-Object.send :include, ReadFromReplica
\ No newline at end of file
+Object.send :include, ReadFromReplica
diff -ru truth-old/opensource/config/initializers/paperclip.rb truth-new/opensource/config/initializers/paperclip.rb
--- truth-old/opensource/config/initializers/paperclip.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/paperclip.rb 2024-04-01 14:59:13
@@ -11,7 +11,7 @@
end
end
-Paperclip.interpolates :prefix_path do |attachment, style|
+Paperclip.interpolates :prefix_path do |attachment, _style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache' + File::SEPARATOR
else
@@ -19,7 +19,7 @@
end
end
-Paperclip.interpolates :prefix_url do |attachment, style|
+Paperclip.interpolates :prefix_url do |attachment, _style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache/'
else
@@ -61,8 +61,8 @@
s3_options: {
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
- http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT'){ '5' }.to_i,
- http_read_timeout: ENV.fetch('S3_READ_TIMEOUT'){ '5' }.to_i,
+ http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT') { '5' }.to_i,
+ http_read_timeout: ENV.fetch('S3_READ_TIMEOUT') { '5' }.to_i,
http_idle_timeout: 5,
retry_limit: 0,
http_proxy: nil,
@@ -72,7 +72,7 @@
if ENV.has_key?('S3_ENDPOINT')
Paperclip::Attachment.default_options[:s3_options].merge!(
endpoint: ENV['S3_ENDPOINT'],
- force_path_style: ENV['S3_OVERRIDE_PATH_STYLE'] != 'true',
+ force_path_style: ENV['S3_OVERRIDE_PATH_STYLE'] != 'true'
)
Paperclip::Attachment.default_options[:url] = ':s3_path_url'
@@ -109,7 +109,7 @@
Paperclip::Attachment.default_options.merge!(
storage: :filesystem,
path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':prefix_path:class', ':attachment', ':id_partition', ':style', ':filename'),
- url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename',
+ url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename'
)
end
@@ -126,4 +126,11 @@
class NetworkingError < StandardError; end
end
end
+end
+
+# Set our ImageMagick security policy, but allow admins to override it
+ENV['MAGICK_CONFIGURE_PATH'] = begin
+ imagemagick_config_paths = ENV.fetch('MAGICK_CONFIGURE_PATH', '').split(File::PATH_SEPARATOR)
+ imagemagick_config_paths << Rails.root.join('config', 'imagemagick').expand_path.to_s
+ imagemagick_config_paths.join(File::PATH_SEPARATOR)
end
diff -ru truth-old/opensource/config/initializers/rack_attack.rb truth-new/opensource/config/initializers/rack_attack.rb
--- truth-old/opensource/config/initializers/rack_attack.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/rack_attack.rb 2024-04-01 14:59:13
@@ -7,6 +7,10 @@
def authenticated_token
return @token if defined?(@token)
+ if Rails.env.production? && path.start_with?('/api/v1/accounts/verify_credentials')
+ ActiveRecord::Base.connection.stick_to_master!(false)
+ end
+
@token = Doorkeeper::OAuth::Token.authenticate(
Doorkeeper::Grape::AuthorizationDecorator.new(self),
*Doorkeeper.configuration.access_token_methods
@@ -14,7 +18,7 @@
end
def remote_ip
- @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
+ @remote_ip ||= (@env['action_dispatch.remote_ip'] || ip).to_s
end
def authenticated_user_id
@@ -40,10 +44,9 @@
def private_address?(address)
PrivateAddressCheck.private_address?(IPAddr.new(address))
end
-
end
- unless ENV["SKIP_IP_RATE_LIMITING"] == "true"
+ unless ENV['SKIP_IP_RATE_LIMITING'] == 'true'
Rack::Attack.safelist('allow from private') do |req|
req.private_address?(req.remote_ip)
end
@@ -97,20 +100,19 @@
throttle('throttle_password_resets/email', limit: 5, period: 30.minutes) do |req|
(req.params.dig('user', 'email').presence if req.post? && req.path == '/auth/password') ||
- (req.params.dig('email').presence if req.post? && req.path == '/api/pleroma/change_password') ||
- (req.params.dig('email').presence if req.post? && req.path == '/api/v1/truth/password_reset/request')
-
+ (req.params.dig('email').presence if req.post? && req.path == '/api/pleroma/change_password') ||
+ (req.params.dig('email').presence if req.post? && req.path == '/api/v1/truth/password_reset/request')
end
throttle('throttle_email_confirmations/ip', limit: 25, period: 5.minutes) do |req|
- req.remote_ip if ((req.post? && %w(/auth/confirmation /api/v1/emails/confirmations).include?(req.path)) ||
- (req.get? && req.path.include?('api/v1/truth/email/confirm')))
+ req.remote_ip if (req.post? && %w(/auth/confirmation /api/v1/emails/confirmations).include?(req.path)) ||
+ (req.get? && req.path.include?('api/v1/truth/email/confirm'))
end
throttle('throttle_email_confirmations/email', limit: 5, period: 30.minutes) do |req|
if req.post? && req.path == '/auth/confirmation'
req.params.dig('user', 'email').presence
- elsif ((req.post? && req.path == '/api/v1/emails/confirmations') || (req.get? && req.path == '/api/v1/truth/email/confirm'))
+ elsif (req.post? && req.path == '/api/v1/emails/confirmations') || (req.get? && req.path == '/api/v1/truth/email/confirm')
req.authenticated_user_id
end
end
@@ -121,8 +123,23 @@
throttle('throttle_login_attempts/email', limit: 25, period: 1.hour) do |req|
(req.session[:attempt_user_id] || req.params.dig('user', 'email').presence if req.post? && req.path == '/auth/sign_in') ||
- (req.params.dig('username').presence if req.post? && req.path == '/oauth/token') ||
- (req.params.dig('mfa_token').presence if req.post? && req.path == '/oauth/mfa/challenge')
+ (req.params.dig('username').presence if req.post? && req.path == '/oauth/token') ||
+ (req.params.dig('mfa_token').presence if req.post? && req.path == '/oauth/mfa/challenge')
+ end
+
+ API_CHAT_MESSAGE_REGEX = /\A\/api\/v1\/pleroma\/chats\/[\d]+\/messages/.freeze
+ API_CHAT_MESSAGE_REACTION_REGEX = /\A\/api\/v1\/pleroma\/chats\/[\d]+\/messages\/[\d]+\/reactions/.freeze
+
+ throttle('throttle_chat_messages', limit: ChatMessage::MAX_MESSAGES_PER_MIN, period: 1.minute) do |req|
+ req.remote_ip if req.post? && req.path.match?(API_CHAT_MESSAGE_REGEX)
+ end
+
+ throttle('throttle_chat_message_reactions', limit: ChatMessageReaction::MAX_MESSAGES_PER_MIN, period: 1.minute) do |req|
+ req.remote_ip if req.post? && req.path.match?(API_CHAT_MESSAGE_REACTION_REGEX)
+ end
+
+ throttle('throttle_app_attest_attestations', limit: 11, period: 1.second) do |req|
+ req.remote_ip if req.path == '/api/v1/truth/ios_device_check/rate_limit'
end
self.throttled_response = lambda do |env|
@@ -139,4 +156,4 @@
[429, headers, [{ error: I18n.t('errors.429') }.to_json]]
end
end
-end
\ No newline at end of file
+end
diff -ru truth-old/opensource/config/initializers/rack_attack_logging.rb truth-new/opensource/config/initializers/rack_attack_logging.rb
--- truth-old/opensource/config/initializers/rack_attack_logging.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/rack_attack_logging.rb 2023-05-05 13:42:02
@@ -5,4 +5,7 @@
Rails.logger.info("Rate limit hit by rack-attack (#{req.env['rack.attack.match_type']} #{req.env['rack.attack.matched']}): #{req.ip} #{req.request_method} #{req.fullpath} #{req.authenticated_user_id}")
+ redis_key = "rate_limit:#{DateTime.current.to_date}"
+ redis_element_key = "#{req.authenticated_user_id}-#{req.ip}"
+ Redis.current.zincrby(redis_key, 1, redis_element_key)
end
Only in truth-new/opensource/config/initializers: rmq_consumer.rb
Only in truth-old/opensource/config/initializers: statsd.rb
diff -ru truth-old/opensource/config/initializers/webauthn.rb truth-new/opensource/config/initializers/webauthn.rb
--- truth-old/opensource/config/initializers/webauthn.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/initializers/webauthn.rb 2024-04-01 14:59:13
@@ -20,5 +20,22 @@
# In this case the default would be "auth.example.com", but you can set it to
# the suffix "example.com"
#
- # config.rp_id = "example.com"
+ config.rp_id = ENV.fetch('SERVER_RP_ID', "truth.social")
+ config.silent_authentication = true
+end
+
+module WebAuthn
+ module AttestationStatement
+ def self.from(format, statement)
+ new_format = format == "apple-appattest" ? "apple" : format
+
+ klass = FORMAT_TO_CLASS[new_format]
+
+ if klass
+ klass.new(statement)
+ else
+ raise(FormatNotSupportedError, "Unsupported attestation format '#{format}'")
+ end
+ end
+ end
end
diff -ru truth-old/opensource/config/locales/en.yml truth-new/opensource/config/locales/en.yml
--- truth-old/opensource/config/locales/en.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/en.yml 2024-04-05 09:17:47
@@ -55,6 +55,8 @@
other: users
user_count_before: Home to
what_is_mastodon: What is Truth?
+ ads:
+ why_copy: We show ads for products and services we think our users might like.
accounts:
choices_html: "%{name}'s choices:"
endorsements_hint: You can endorse people you follow from the web interface, and they will show up here.
@@ -126,7 +128,6 @@
disable_two_factor_authentication: Disable 2FA
disabled: Frozen
display_name: Display name
- location: Location
website: Website
domain: Domain
edit: Edit
@@ -256,6 +257,7 @@
enable_user: Enable User
memorialize_account: Memorialize Account
promote_user: Promote User
+ remove_avatar_group: Remove Group Avatar
remove_avatar_user: Remove Avatar
reopen_report: Reopen Report
reset_password_user: Reset Password
@@ -263,10 +265,12 @@
sensitive_account: Mark the media in your account as sensitive
silence_account: Silence Account
suspend_account: Suspend Account
+ suspend_group: Suspend Group
unassigned_report: Unassign Report
unsensitive_account: Unmark the media in your account as sensitive
unsilence_account: Unsilence Account
unsuspend_account: Unsuspend Account
+ unsuspend_group: Unsuspend Group
update_announcement: Update Announcement
update_custom_emoji: Update Custom Emoji
update_domain_block: Update Domain Block
@@ -299,6 +303,7 @@
enable_user_html: "%{name} enabled login for user %{target}"
memorialize_account_html: "%{name} turned %{target}'s account into a memoriam page"
promote_user_html: "%{name} promoted user %{target}"
+ remove_avatar_group_html: "%{name} removed the avatar for group %{target}"
remove_avatar_user_html: "%{name} removed %{target}'s avatar"
reopen_report_html: "%{name} reopened report %{target}"
reset_password_user_html: "%{name} reset password of user %{target}"
@@ -306,10 +311,12 @@
sensitive_account_html: "%{name} marked %{target}'s media as sensitive"
silence_account_html: "%{name} silenced %{target}'s account"
suspend_account_html: "%{name} suspended %{target}'s account"
+ suspend_group_html: "%{name} suspended the group %{target}"
unassigned_report_html: "%{name} unassigned report %{target}"
unsensitive_account_html: "%{name} unmarked %{target}'s media as sensitive"
unsilence_account_html: "%{name} unsilenced %{target}'s account"
unsuspend_account_html: "%{name} unsuspended %{target}'s account"
+ unsuspend_group_html: "%{name} unsuspended the group %{target}"
update_announcement_html: "%{name} updated announcement %{target}"
update_custom_emoji_html: "%{name} updated emoji %{target}"
update_domain_block_html: "%{name} updated domain block for %{target}"
@@ -463,12 +470,20 @@
suppressed: Suppressed
title: Follow recommendations
unsuppress: Restore follow recommendation
- trending_truths:
- description_html: "<strong>Follow recommendations help new users quickly find interesting content</strong>. When a user has not interacted with others enough to form personalized follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language."
- status: Status
- trending: Trending
- all: All
- title: Trending truths
+ group_memberships:
+ title: Group members
+ group_statuses:
+ back_to_group: Back to group page
+ title: Group posts
+ groups:
+ group: Group
+ removed_avatar_msg: Successfully removed this group's avatar image
+ removed_header_msg: Successfully removed this group's header image
+ suspended_msg: Successfully suspended this group
+ suspension_irreversible: The data of this group has been irreversibly deleted. You can unsuspend the group to make it usable but it will not recover any data it previously had.
+ suspension_reversible_hint_html: The group has been suspended, and the data will be fully removed on %{date}. Until then, the group can be restored without any ill effects. If you wish to remove all of the group's data immediately, you can do so below.
+ title: Groups
+ unsuspended_msg: Successfully unsuspended this group
instances:
back_to_all: All
back_to_limited: Limited
@@ -499,6 +514,87 @@
title: Moderation
private_comment: Private comment
public_comment: Public comment
+ rules:
+ content_illegal_activity:
+ text: Illegal activity and behavior
+ subtext: Content that depicts illegal or criminal acts, threats of violence.
+ content_intellectual_infringement:
+ text: Intellectual property infringement
+ subtext: Impersonating another account or business, infringing on intellectual property
+ rights.
+ content_sensitive_content:
+ text: Sensitive Content
+ subtext: Depictions of violence, gore, nudity.
+ content_underage_content:
+ text: Underage content
+ subtext: Sexually explicit content involving underage children.
+ content_prostitution:
+ text: Prostitution
+ subtext: Solicitation or advertising for illegal sexual activity or sex for hire.
+ content_privacy_violations:
+ text: Privacy violations
+ subtext: Violate or post content that violates a person's privacy rights.
+ content_illegal_sales:
+ text: Illegal sales
+ subtext: 'Sale of or promotion of illegal drugs, counterfeit services and goods,
+ or illegal products and services. '
+ content_doxxing:
+ text: Doxxing
+ subtext: Sharing or threatening to share the private information of an individual
+ without their consent or breach of privacy rights of others.
+ content_spam:
+ text: Spam
+ subtext: Fraudulent or malicious content or links, inauthentic engagement, repetitive
+ replies, ReTruths, or direct messages.
+ content_dislike:
+ text: I don't like this content
+ subtext: I dislike this content and/or this user is a troll.
+ account_illegal_activity:
+ text: Illegal activity and behavior
+ subtext: Content that depicts illegal or criminal acts, threats of violence.
+ account_sensitive_content:
+ text: Sensitive Content
+ subtext: Depictions of violence, gore, nudity.
+ account_spam:
+ text: SPAM
+ subtext: Fraudulent content, phishing, or content that is spam or a bot.
+ account_fake_account:
+ text: Fake Account
+ subtext: Improper use of username/account to masquerade as someone else or impersonate another person or entity that is not a parody.
+ account_intellectual_property:
+ text: Intellectual Property
+ subtext: Account name or entity infringes on intellectual property rights without
+ authorization.
+ account_underage_content:
+ text: Underage content
+ subtext: Sexually explicit content involving underage children.
+ account_prostitution:
+ text: Prostitution
+ subtext: Solicitation or advertising for illegal sexual activity or sex for hire.
+ account_privacy_violations:
+ text: Privacy violations
+ subtext: Violate or post content that violates a person's privacy rights.
+ account_troll:
+ text: Troll
+ subtext: This user is a troll and/or I dislike their content.
+ group_illegal_activity:
+ text: Illegal Activity and Behavior
+ subtext: 'This group is participating, encouraging or depicting illegal or criminal acts such as: threats, violence, incitement of violence, doxxing or harrassment.'
+ group_spam:
+ text: Spam
+ subtext: Fraudulent content, phishing or content that is spam or a bot.
+ group_impersonation:
+ text: Impersonation
+ subtext: This group is claiming to represent a person/entity that they aren't authorized to represent.
+ group_underage_content:
+ text: Underage Content
+ subtext: This group has posted sexually explicit content involving underage children.
+ group_prostitution:
+ text: Prostitution
+ subtext: This group is soliciting or advertising for illegal sexual activity or sex for hire.
+ group_privacy_violations:
+ text: Privacy Violations
+ subtext: This group has violated mine or someone else's privacy rights, including doxxing.
title: Federation
total_blocked_by_us: Blocked by us
total_followed_by_them: Followed by them
@@ -840,6 +936,14 @@
hint_html: "<strong>Tip:</strong> We won't ask you for your password again for the next hour."
invalid_password: Invalid password
prompt: Confirm password to continue
+ chats:
+ errors:
+ creator_started: Cannot accept this chat since you started it
+ blocked_constraints: Cannot perform request due to blocked constraints
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you
+ message_creation: An error occurred while creating message
+ channel_reaction: Reactions are not supported for channels
+ marketing_reply: Truth Social cannot receive direct messages. To contact us, email support@truthsocial.com!
crypto:
errors:
invalid_key: is not a valid Ed25519 or Curve25519 key
@@ -900,6 +1004,22 @@
title: This page is not correct
'503': The page could not be served due to a temporary server failure.
noscript_html: To use the Truth web application, please enable JavaScript. Alternatively, try one of the <a href="%{apps_path}">native apps</a> for Truth for your platform.
+ api:
+ '401': This method requires an authenticated user
+ '403': This action is not allowed
+ '404': Record not found
+ '503': There was a temporary problem serving your request, please try again
+ assertion: Unable to verify assertion
+ attestation: Unable to verify attestation
+ data_fetch: Remote data could not be fetched
+ duplicate: Duplicate record
+ login_disabled: Your login is currently disabled
+ login_pending: Your login is currently pending approval
+ sms_reverification_pending: Your account is currently pending SMS verification
+ missing_email: Your login is missing a confirmed e-mail address
+ ssl: Remote SSL certificate could not be verified
+ outside_scopes: This action is outside the authorized scopes
+ unauthorized: Not authorized
existing_username_validator:
not_found: could not find a local user with that username
not_found_multiple: could not find %{usernames}
@@ -923,6 +1043,10 @@
errors:
limit: You have already featured the maximum amount of hashtags
hint_html: "<strong>What are featured hashtags?</strong> They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects."
+ feeds:
+ errors:
+ feed_creation_limit: Feed Limit Reached. You've reached the maximum number of custom feeds. Delete a feed to add another.
+ too_many_pinned: Feed Pinned Limit Reached. You've reached the maximum number of pinned feeds. Unpin a feed to pin another.
filters:
contexts:
account: Profiles
@@ -957,6 +1081,22 @@
validation_errors:
one: Something isn't quite right yet! Please review the error below
other: Something isn't quite right yet! Please review %{count} errors below
+ groups:
+ errors:
+ unsupported_remote_role: Moderation roles are not supported for remote users yet
+ invalid_name: Please remove invalid characters
+ group_taken: This group name is taken
+ too_many_tags: 'Validation failed: Tags cannot exceed 3'
+ group_creation_limit: You've reached the group creation threshold
+ group_membership_limit: You've reached the group membership threshold
+ invalid_group_tag: 'Validation failed: Tag is invalid'
+ group_deleted: This group no longer exists
+ pending_request_conflict: Group owner or admin has already taken action on this request
+ too_many_admins: You can assign up to %{count} admins for the group at this time.
+ members:
+ one: Member
+ other: Members
+ members_of: Members of group %{name}
html_validator:
invalid_markup: 'contains invalid HTML markup: %{error}'
identity_proofs:
@@ -1023,6 +1163,31 @@
invite_pending: Invite pending
invite_redemed: Invite accepted
title: Invite people
+ links:
+ title:
+ default: "Are you sure you want to leave Truth Social?"
+ blocked: "The Site Ahead Contains Malware"
+ missing: "Page Not Found"
+ warning: "Warning: Potential Security Risk Ahead"
+ summary:
+ default: |
+ This link is taking you to a site outside of Truth Social
+ blocked: |
+ Attackers currently on %{url} might attempt to install dangerous programs on your device
+ that steal or delete your information (for example, photos, passwords, messages and
+ credit cards.)
+ missing: |
+ Sorry, the link you requested does not exist.
+ warning: |
+ Truth Social detected a potential security threat and did not continue to
+ %{url}. If you visit this site, attackers
+ could try to steal information like your passwords, emails, or
+ credit card details.
+ actions:
+ continue: Accept the risk and continue
+ go_back: Back to Truth Social
+ go_back_recommended: Back to Truth Social (recommended)
+ go_to_site: Go to Site
lists:
errors:
limit: You have reached the maximum amount of lists
@@ -1069,6 +1234,10 @@
carry_mutes_over_text: This user moved from %{acct}, which you had muted.
copy_account_note_text: 'This user moved from %{acct}, here were your previous notes about them:'
notification_mailer:
+ chat:
+ subject: "New message from %{name}"
+ subject_android: "sent you a message"
+ sent_message: "Sent you a message"
digest:
action: View all notifications
body: Here is a brief summary of the messages you missed since your last visit on %{since}
@@ -1079,46 +1248,112 @@
subject:
one: "1 new notification since your last visit \U0001F418"
other: "%{count} new notifications since your last visit \U0001F418"
+ subject_android:
+ one: "1 new notification since your last visit \U0001F418"
+ other: "%{count} new notifications since your last visit \U0001F418"
title: In your absence...
favourite:
body: 'Your post was liked by %{name}:'
- subject: "%{name} liked your post"
+ subject: "%{name} liked your Truth"
+ subject_android: "liked your Truth"
title: New favorite
favourite_group:
subject: "%{name} + %{count_others} %{actor} liked your Truth"
+ subject_android: "%{name} and %{count_others} %{actor} liked your Truth"
follow:
body: "%{name} is now following you!"
subject: "%{name} is now following you"
+ subject_android: "is now following you"
title: New follower
+ group_favourite:
+ body: "%{name} liked your Truth in %{group}"
+ subject: "%{name} liked your Truth in %{group}"
+ subject_android: "liked your Truth in %{group}"
+ title: New favorite
+ group_favourite_group:
+ subject: "%{name} + %{count_others} %{actor} liked your Truth in %{group}"
+ subject_android: "%{name} and %{count_others} %{actor} liked your Truth in %{group}"
follow_group:
subject: "%{name} + %{count_others} %{actor} followed you"
+ subject_android: "%{name} and %{count_others} %{actor} followed you"
follow_request:
action: Manage follow requests
body: "%{name} has requested to follow you"
subject: 'Pending follower: %{name}'
+ subject_android: 'requested to follow you'
title: New follow request
mention:
action: Reply
- body: 'You were mentioned by %{name} in:'
+ body: 'You were mentioned by %{name}'
subject: You were mentioned by %{name}
+ subject_android: "mentioned you in a Truth"
title: New mention
mention_group:
subject: "%{name} + %{count_others} %{actor} mentioned you"
+ subject_android: "%{name} and %{count_others} %{actor} mentioned you"
+ group_mention:
+ action: Reply
+ body: 'You were mentioned by %{name} in %{group}'
+ subject: 'You were mentioned by %{name} in %{group}'
+ subject_android: 'mentioned you in %{group}'
+ title: New mention
+ group_mention_group:
+ subject: "%{name} + %{count_others} %{actor} mentioned you"
+ subject_android: "%{name} and %{count_others} %{actor} mentioned you"
poll:
subject: A poll by %{name} has ended
+ subject_android: 'concluded a poll'
reblog:
body: 'Your post was ReTruthed by %{name}:'
- subject: "%{name} ReTruthed your post"
+ subject: "%{name} ReTruthed your Truth"
+ subject_android: "ReTruthed your Truth"
title: New ReTruth
reblog_group:
subject: "%{name} + %{count_others} %{actor} ReTruthed your Truth"
+ subject_android: "%{name} and %{count_others} %{actor} ReTruthed your Truth"
+ group_delete:
+ body: "%{group} has been deleted"
+ subject: "%{group} has been deleted"
+ subject_android: "has been deleted"
+ title: Group Deleted
+ group_approval:
+ body: "%{group} approved your request"
+ subject: "%{group} approved your request"
+ subject_android: "approved your request"
+ title: Accepted to group
+ group_reblog:
+ body: "%{name} ReTruthed your Truth from %{group}"
+ subject: "%{name} ReTruthed your Truth from %{group}"
+ subject_android: "ReTruthed your Truth from %{group}"
+ title: New ReTruth
+ group_reblog_group:
+ subject: "%{name} + %{count_others} %{actor} ReTruthed your group Truth"
+ subject_android: "%{name} and %{count_others} %{actor} ReTruthed your group Truth"
+ group_request:
+ body: "You have a new member request for %{group}"
+ subject: "You have a new member request for %{group}"
+ subject_android: "you have a new member request"
+ title: New member request
+ group_promoted:
+ body: You are now an admin for %{group}
+ subject: You are now an admin for %{group}
+ subject_android: you are now an admin
+ title: You're an admin
+ group_demoted:
+ body: You are no longer an admin for %{group}
+ subject: You are no longer an admin for %{group}
+ subject_android: you are no longer admin
+ title: You're no longer an admin
status:
subject: "%{name} just posted"
+ subject_android: "just posted"
user_approved:
edit_profile_action: Go to profile
edit_profile_step: Lastly, make sure to customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account.
- explanation: We are very excited to welcome you to our Truth Seeking community. We believe in free expression and encourage all viewpoints as we do not discriminate against political ideology.
- extra_step: Also, a friendly reminder, we are a new platform, and we are still fixing many bugs in our technology. We promise we are working as hard as we can to make things better as fast as possible! Thanks!
+ explanation: Let us be the first to welcome you to Truth Social! We're thrilled you've joined our growing free speech community and support our goal to keep the Internet open for everyone.
+ extra_step: If you ever have a question about how to use the app, please visit our %{help_center_link} or feel free to email our Support Team at %{mailto_support}. Happy Truthing!
+ signoff: The Truth Social Team
+ help_center: Help Center
final_action: Start posting
final_step: 'Start posting! Even without followers your public posts may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.'
full_handle: Your full handle
@@ -1126,16 +1361,16 @@
review_preferences_action: Change preferences
review_preferences_step: Make sure to set your preferences, such as which emails you'd like to receive, or what privacy level you’d like your posts to default to. If you don’t have motion sickness, you could choose to enable GIF autoplay.
subject: "Your wait is over! Tap here to start using Truth Social."
- web:
- subject: "Welcome to Truth"
+ subject_android: "Your wait is over! Tap here to start using Truth Social."
tip_federated_timeline: The federated timeline is a firehose view of the Truth network. But it only includes people your neighbours are subscribed to, so it's not complete.
tip_following: You follow your server's admin(s) by default. To find more interesting people, check the local and federated timelines.
tip_local_timeline: The local timeline is a firehose view of people on %{instance}. These are your immediate neighbours!
tip_mobile_webapp: If your mobile browser offers you to add Truth to your homescreen, you can receive push notifications. It acts like a native app in many ways!
tips: Tips
- title: Welcome to Truth Social, %{name}!
+ title: Welcome to Truth Social, @%{name}!
verify_sms_prompt:
subject: Your account is now active on Truth Social! Upgrade to the latest version and join us now!
+ subject_android: Your account is now active on Truth Social! Upgrade to the latest version and join us now!
notifications:
email_events: Events for e-mail notifications
email_events_hint: 'Select events that you want to receive notifications for:'
@@ -1159,6 +1394,7 @@
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
setup: Set up
wrong_code: The entered code was invalid!
+ invalid_code: "The code doesn't match. Please try again."
pagination:
newer: Newer
next: Next
@@ -1303,11 +1539,20 @@
one: 'contained a disallowed hashtag: %{tags}'
other: 'contained the disallowed hashtags: %{tags}'
errors:
- in_reply_not_found: The post you are trying to reply to does not appear to exist.
+ not_permitted_to_post: You cannot post to this group.
+ in_reply_not_found: The Truth you are trying to reply to does not appear to exist.
+ mention_mismatch: Unable to post your Truth at this time.
+ group_errors:
+ invalid_group_id: Invalid group identifier.
+ invalid_membership: Only members may post to this group.
+ invalid_reply: The post being replied to is not in this group.
+ invalid_visibility: You cannot post to this group.
language_detection: Automatically detect language
open_in_web: Open in web
over_character_limit: character limit of %{max} exceeded
pin_errors:
+ group: Group posts cannot be pinned
+ group_ownership: Only group owners can pin posts
limit: You have already pinned the maximum number of posts
ownership: Someone else's post cannot be pinned
private: Non-public posts cannot be pinned
@@ -1328,6 +1573,7 @@
title: '%{name}: "%{quote}"'
visibilities:
direct: Direct
+ group: Group
private: Followers-only
private_long: Only show to followers
public: Public
@@ -1458,12 +1704,12 @@
subject: Please confirm attempted sign in
title: Sign in attempt
status_removed:
- explanation: You have a Truth that has been removed for violating Truth Social community guidelines.
- title: Your Truth has been removed
- subject: Your Truth has been removed
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal.
+ title: Your Truth was flagged for review
+ subject: Your Truth was flagged for review
warning:
explanation:
- ban_html: After careful review we have decided to delete your account permanently due to Truth social community guidelines violations. If you feel we have made an error, you can file an appeal with %{email}.
+ ban_html: Your account has been banned for violating our Terms of Service. Indefinite bans are a rare and severe sanction, and are generally reserved for the most egregious violations of our Terms of Service. If you feel our decision to ban your account was unjust, immoral, or downright wrong, we encourage you to submit an appeal. Please send an email to %{email}. In the subject line, please write "@Appeal," along with your username. Our team will review your ban appeal at our earliest convenience. After careful review, we will reverse or uphold the ban.
disable_html: You can no longer login to your account or use it in any other way, but your profile and other data remains intact.
sensitive_html: Your uploaded media files and linked media will be treated as sensitive.
silence_html: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you.
@@ -1502,6 +1748,8 @@
invalid_otp_token: Invalid two-factor code
invalid_sign_in_token: Invalid security code
otp_lost_help_html: If you lost access to both, you may get in touch with %{email}
+ previously_used_password: Please use a password you have not previously used.
+ password_mismatch: Password and password confirmation do not match.
seamless_external_login: You are logged in via an external service, so password and e-mail settings are not available.
signed_in_as: 'Signed in as:'
suspicious_sign_in_confirmation: You appear to not have logged in from this device before, and you haven't logged in for a while, so we're sending a security code to your e-mail address to confirm that it's you.
@@ -1525,3 +1773,7 @@
not_supported: This browser doesn't support security keys
otp_required: To use security keys please enable two-factor authentication first.
registered_on: Registered on %{date}
+ activerecord:
+ attributes:
+ group:
+ slug: 'Display name'
diff -ru truth-old/opensource/config/locales/es-AR.yml truth-new/opensource/config/locales/es-AR.yml
--- truth-old/opensource/config/locales/es-AR.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/es-AR.yml 2024-04-01 14:59:13
@@ -942,6 +942,9 @@
validation_errors:
one: "¡Falta algo! Por favor, revisá el error abajo"
other: "¡Falta algo! Por favor, revisá los %{count} errores abajo"
+ groups:
+ errors:
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento.
html_validator:
invalid_markup: 'contiene markup HTML no válido: %{error}'
identity_proofs:
@@ -1457,6 +1460,8 @@
invalid_otp_token: Código de dos factores no válido
invalid_sign_in_token: Código de seguridad no válido
otp_lost_help_html: Si perdiste al acceso a ambos, podés ponerte en contacto con %{email}
+ previously_used_password: Por favor use una contraseña que no haya usado previamente.
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales.
seamless_external_login: Iniciaste sesión desde un servicio externo, así que la configuración de contraseña y correo electrónico no están disponibles.
signed_in_as: 'Iniciaste sesión como:'
suspicious_sign_in_confirmation: Parece que no iniciaste sesión desde este dispositivo antes, y no iniciaste sesión durante un tiempo, así que te estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que sos vos.
diff -ru truth-old/opensource/config/locales/es-MX.yml truth-new/opensource/config/locales/es-MX.yml
--- truth-old/opensource/config/locales/es-MX.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/es-MX.yml 2024-04-01 14:59:13
@@ -942,6 +942,9 @@
validation_errors:
one: "¡Algo no está bien! Por favor, revisa el error"
other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo"
+ groups:
+ errors:
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento.
html_validator:
invalid_markup: 'contiene código HTML no válido: %{error}'
identity_proofs:
@@ -1457,6 +1460,8 @@
invalid_otp_token: Código de dos factores incorrecto
invalid_sign_in_token: Código de seguridad no válido
otp_lost_help_html: Si perdiste al acceso a ambos, puedes ponerte en contancto con %{email}
+ previously_used_password: Por favor use una contraseña que no haya usado previamente.
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales.
seamless_external_login: Has iniciado sesión desde un servicio externo, así que los ajustes de contraseña y correo no están disponibles.
signed_in_as: 'Sesión iniciada como:'
suspicious_sign_in_confirmation: Parece que no has iniciado sesión desde este dispositivo antes, y no has iniciado sesión durante un tiempo, así que estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que eres tú.
diff -ru truth-old/opensource/config/locales/es.yml truth-new/opensource/config/locales/es.yml
--- truth-old/opensource/config/locales/es.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/es.yml 2024-04-01 14:59:13
@@ -942,6 +942,9 @@
validation_errors:
one: "¡Algo no está bien! Por favor, revisa el error"
other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo"
+ groups:
+ errors:
+ too_many_admins: Puede asignar hasta %{count} administradores para el grupo en este momento.
html_validator:
invalid_markup: 'contiene código HTML no válido: %{error}'
identity_proofs:
@@ -1457,6 +1460,8 @@
invalid_otp_token: Código de dos factores incorrecto
invalid_sign_in_token: Código de seguridad no válido
otp_lost_help_html: Si perdiste al acceso a ambos, puedes ponerte en contancto con %{email}
+ previously_used_password: Por favor use una contraseña que no haya usado previamente.
+ password_mismatch: La contraseña y la confirmación de contraseña no son iguales.
seamless_external_login: Has iniciado sesión desde un servicio externo, así que los ajustes de contraseña y correo no están disponibles.
signed_in_as: 'Sesión iniciada como:'
suspicious_sign_in_confirmation: Parece que no has iniciado sesión desde este dispositivo antes, y no has iniciado sesión durante un tiempo, así que estamos enviando un código de seguridad a tu dirección de correo electrónico para confirmar que eres tú.
diff -ru truth-old/opensource/config/locales/pt-BR.yml truth-new/opensource/config/locales/pt-BR.yml
--- truth-old/opensource/config/locales/pt-BR.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/pt-BR.yml 2024-04-05 09:17:47
@@ -53,6 +53,8 @@
other: usuários
user_count_before: Casa de
what_is_mastodon: O que é Mastodon?
+ ads:
+ why_copy: Mostramos anúncios de produtos e serviços que achamos que nossos usuários podem gostar.
accounts:
choices_html: 'Sugestões de %{name}:'
endorsements_hint: Você pode sugerir pessoas que você segue, elas aparecerão aqui.
@@ -641,6 +643,8 @@
edit_preset: Editar o aviso pré-definido
title: Gerenciar os avisos pré-definidos
admin_mailer:
+ account_invitation:
+ subject: Você foi convidado a entrar no Truth Social!
new_pending_account:
body: Os detalhes da nova conta estão abaixo. Você pode aprovar ou vetar.
subject: Nova conta para revisão em %{instance} (%{username})
@@ -698,7 +702,7 @@
didnt_get_confirmation: Não recebeu instruções de confirmação?
dont_have_your_security_key: Não está com a sua chave de segurança?
forgot_password: Esqueceu a sua senha?
- invalid_reset_password_token: Código de alteração de senha é inválido ou expirou. Por favor, solicite um novo.
+ invalid_reset_password_token: O token de redefinição de senha é inválido ou expirou. Solicite um novo token.
link_to_otp: Digite um código de duas etapas do seu telefone ou um código de recuperação
link_to_webauth: Use seu dispositivo de chave de segurança
login: Entrar
@@ -742,6 +746,11 @@
hint_html: "<strong>Dica:</strong> Não pediremos novamente sua senha pela próxima hora."
invalid_password: Senha inválida
prompt: Confirme sua senha para continuar
+ chats:
+ errors:
+ creator_started: Não foi possível aceitar esse chat já que você o iniciou
+ blocked_constraints: Não foi possível executar a solicitação devido a restrições bloqueadas
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you
crypto:
errors:
invalid_key: não é uma chave Ed25519 ou Curve25519 válida
@@ -762,11 +771,11 @@
x_months: "%{count}m"
x_seconds: "%{count}seg"
deletes:
- challenge_not_passed: As informações que você inseriu não estão corretas
+ challenge_not_passed: A informação que você inseriu não está correta
confirm_password: Digite a sua senha atual para verificar a sua identidade
confirm_username: Digite seu nome de usuário para confirmar o procedimento
proceed: Excluir conta
- success_msg: A sua conta foi excluída com sucesso
+ success_msg: Sua conta foi excluída com sucesso
warning:
before: 'Antes de prosseguir, por favor leia com cuidado:'
caches: Conteúdo que foi armazenado em cache por outras instâncias pode continuar a existir
@@ -793,12 +802,27 @@
'422':
content: Falha na verificação de segurança. Você está bloqueando cookies?
title: Falha na verificação de segurança
- '429': Muitas solicitações
+ '429': Excesso de solicitações
'500':
content: Desculpe, algo deu errado por aqui.
title: Esta página não está certa
'503': A página não pôde ser carregada devido a uma falha temporária do servidor.
noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="%{apps_path}">aplicativos nativos</a> para o Mastodon em sua plataforma.
+ api:
+ '401': Esse método requer um usuário autenticado
+ '403': Essa ação não é permitida
+ '404': Registro não encontrado
+ '503': Ocorreu um problema temporário ao atender sua solicitação. Tente novamente.
+ assertion: Não foi possível verificar sua afirmação
+ attestation: Não foi possível verificar seu atestado
+ data_fetch: Não foi possível resgatar os dados remotos
+ duplicate: Registro duplicado
+ login_disabled: No momento seu login está desativado
+ login_pending: No momento seu login está aguardando aprovação
+ missing_email: Está faltando um endereço de e-mail confirmado para o seu login
+ ssl: Não foi possível verificar o Certificado SSL remoto
+ outside_scopes: Essa ação está fora do escopo autorizado
+ unauthorized: Não autorizado
existing_username_validator:
not_found: não foi possível encontrar um usuário local com esse nome de usuário
not_found_multiple: não foi possível encontrar %{usernames}
@@ -846,7 +870,7 @@
trending_now: Em alta no momento
generic:
all: Tudo
- changes_saved_msg: Alterações foram salvas com sucesso!
+ changes_saved_msg: Alterações salvas com sucesso!
copy: Copiar
delete: Excluir
no_batch_actions_available: Nenhuma ação em lote disponível nesta página
@@ -855,6 +879,10 @@
validation_errors:
one: Algo errado não está certo! Por favor, analise o erro abaixo
other: Algo errado não está certo! Por favor, analise os %{count} erros abaixo
+ groups:
+ errors:
+ pending_request_conflict: O proprietário ou administrador do grupo já tomou medidas sobre esta solicitação.
+ too_many_admins: Você pode atribuir até %{count} administradores para o grupo.
html_validator:
invalid_markup: 'contém HTML inválido: %{error}'
identity_proofs:
@@ -920,9 +948,9 @@
limit: Você atingiu o máximo de listas
media_attachments:
validations:
- images_and_video: Não foi possível anexar um vídeo a um toot que já contém imagens
- not_ready: Não é possível anexar arquivos que não terminaram de ser processados. Tente novamente daqui a pouco!
- too_many: Não foi possível anexar mais de 4 imagens
+ images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens
+ not_ready: Não é possível anexar arquivos cujo processamento ainda não foi concluído. Tente novamente daqui a pouco!
+ too_many: Não é possível anexar mais de 4 arquivos
migrations:
acct: Mudou-se para
cancel: Cancelar redirecionamento
@@ -961,39 +989,133 @@
carry_mutes_over_text: Este usuário mudou de %{acct}, que você havia silenciado.
copy_account_note_text: 'Este usuário saiu de %{acct}, aqui estão suas notas anteriores sobre ele:'
notification_mailer:
+ chat:
+ subject: "Nova mensagem de %{name}"
+ subject_android: "lhe enviou uma mensagem"
+ sent_message: "Nova mensagem"
digest:
action: Ver todas as notificações
- body: Aqui está um breve resumo das mensagens que você perdeu desde o seu último acesso em %{since}
- mention: "%{name} te mencionou em:"
+ body: Aqui está um breve resumo das mensagens que você não viu desde seu último acesso em %{since}
+ mention: "%{name} mencionou você em:"
new_followers_summary:
- one: Você tem um novo seguidor! Uia!
- other: Você tem %{count} novos seguidores! AÊÊÊ!
+ one: Além disso, enquanto estava fora você ganhou um novo seguidor! Oba!
+ other: Além disso, enquanto estava fora você ganhou %{count} novos seguidores! Incrível!
subject:
- one: "Uma nova notificação desde o seu último acesso \U0001F418"
- other: "%{count} novas notificações desde o seu último acesso \U0001F418"
- title: Enquanto você estava ausente...
+ one: "1 nova notificação desde seu último acesso \U0001F418"
+ other: "%{count} novas notificações desde seu último acesso \U0001F418"
+ subject_android:
+ one: "1 nova notificação desde seu último acesso \U0001F418"
+ other: "%{count} novas notificações desde seu último acesso \U0001F418"
+ title: Na sua ausência...
favourite:
- body: "%{name} favoritou seu toot:"
- subject: "%{name} favoritou seu toot"
+ body: "Sua postagem foi curtida por %{name}:"
+ subject: "%{name} curtiu sua postagem"
+ subject_android: "gostou da sua Truth"
title: Novo favorito
+ favourite_group:
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth"
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth"
+ group_favourite:
+ body: "Sua postagem foi curtida por %{name}:"
+ subject: "%{name} curtiu sua postagem"
+ subject_android: "gostou da sua Truth em %{group}"
+ title: Novo favorito
+ group_favourite_group:
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth"
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth em %{group}"
follow:
- body: "%{name} te seguiu!"
- subject: "%{name} te seguiu"
+ body: "%{name} está seguindo você!"
+ subject: "%{name} está seguindo você"
+ subject_android: "está te seguindo agora"
title: Novo seguidor
+ follow_group:
+ subject: "%{name} + %{count_others} %{actor} seguiram você"
+ subject_android: "%{name} e %{count_others} outros seguiram você"
follow_request:
- action: Gerenciar seguidores pendentes
- body: "%{name} quer te seguir"
- subject: 'Seguidor pendente: %{name}'
- title: Novo seguidor pendente
+ action: Gerenciar solicitações de seguidores
+ body: "%{name} pediu para seguir você"
+ subject: 'Seguidor aguardando: %{name}'
+ subject_android: "pediu para te seguir"
+ title: Nova solicitação de seguidor
mention:
action: Responder
- body: "%{name} te mencionou em:"
- subject: "%{name} te mencionou"
+ body: "Você foi mencionado por %{name} em:"
+ subject: "Você foi mencionado por %{name}"
+ subject_android: "mencionou você em uma Truth"
title: Nova menção
+ mention_group:
+ subject: "%{name} + %{count_others} %{actor} mencionaram você"
+ subject_android: "%{name} e %{count_others} outros mencionaram você"
+ group_mention:
+ action: Responder
+ body: "Você foi mencionado por %{name} em:"
+ subject: "Você foi mencionado por %{name}"
+ subject_android: "mencionou você em %{group}"
+ title: Nova menção
+ group_mention_group:
+ subject: "%{name} + %{count_others} %{actor} mencionaram você"
+ subject_android: "%{name} e %{count_others} outros mencionaram você"
+ poll:
+ subject: Uma enquete postada por %{name} foi encerrada
+ subject_android: "concluiu uma enquete"
reblog:
- body: "%{name} deu boost no seu toot:"
- subject: "%{name} deu boost no seu toot"
- title: Novo boost
+ body: "Sua Truth foi RePostada por %{name}:"
+ subject: "%{name} RePostou sua Truth"
+ subject_android: "ReTruthed sua Truth"
+ title: Nova RePostagem
+ reblog_group:
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth"
+ subject_android: "%{name} e mais %{count_others} outros ReTruthed sua Truth"
+ group_reblog:
+ body: "Sua Truth foi RePostada por %{name}:"
+ subject: "%{name} RePostou sua Truth"
+ subject_android: "ReTruthed sua Truth de %{group}"
+ title: Nova RePostagem
+ group_reblog_group:
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth"
+ subject_android: "%{name} e %{count_others} outros ReTruthed a sua Truth de grupo"
+ group_request:
+ body: '%{name} wants to join your group:'
+ subject: "%{name} wants to join your group"
+ subject_android: "você tem uma nova solicitação de membro"
+ title: Group Join Request
+ status:
+ subject: "%{name} acabou de postar"
+ subject_android: "acabou de postar"
+ group_promoted:
+ body: You are now an admin for %{group}
+ subject: You are now an admin for %{group}
+ subject_android: "agora você é um administrador"
+ title: You're an admin
+ group_demoted:
+ body: You are no longer an admin for %{group}
+ subject: You are no longer an admin for %{group}
+ subject_android: "você não é mais administrador"
+ title: You're no longer an admin
+ user_approved:
+ edit_profile_action: Ir para o perfil
+ edit_profile_step: Para terminar, não deixe de personalizar seu perfil carregando um avatar, um título, mudando seu nome escolhido e muito mais. Se quiser avaliar novos seguidores antes de aprová-los, você pode bloquear sua conta.
+ explanation: Estamos super empolgados com sua chegada à nossa comunidade de Buscadores da Verdade. Acreditamos na liberdade de expressão e incentivamos todos os pontos de vista, já que não discriminamos ninguém devido a uma ideologia política.
+ extra_step: Além disso, gostaríamos de lembrar que somos uma plataforma nova e ainda estamos corrigindo vários bugs na nossa tecnologia. Podemos garantir que estamos trabalhando duro para aprimorar tudo o mais rápido possível! Obrigado!
+ final_action: Comece a postar
+ final_step: 'Comece a postar! Mesmo sem nenhum seguidor suas postagens públicas podem ser vistas por outras pessoas, por exemplo, na timeline local e nas hashtags. Você pode querer se apresentar com a hashtag #apresentações.'
+ full_handle: Seu identificador completo (handle)
+ full_handle_hint: É o que você quer dizer aos seus amigos para que possam seguir você ou lhe enviar mensagens de outro servidor.
+ review_preferences_action: Mudar preferências
+ review_preferences_step: Não deixe de configurar suas preferências, como os e-mails que quer receber ou o nível de privacidade padrão para as suas postagens. Se não costuma sentir enjoo você pode optar por reproduzir GIFs automaticamente.
+ subject: "Sua espera terminou! Toque aqui para começar a usar o Truth Social."
+ subject_android: "Sua espera terminou! Toque aqui para começar a usar o Truth Social."
+ web:
+ subject: "Bem-vindo(a) ao Truth, onde a Verdade impera"
+ tip_federated_timeline: A timeline federada é uma ampla visão ao vivo da rede do Truth. Mas só inclui as pessoas que seus vizinhos estão seguindo, então não é uma visão completa.
+ tip_following: Você segue os administradores do seu servidor por padrão Para encontrar mais gente interessante, verifique sua timeline local e federada.
+ tip_local_timeline: A timeline local é uma visão ao vivo das pessoas em %{instance}. São seus vizinhos de porta!
+ tip_mobile_webapp: Se o seu navegador móvel sugerir que você adicione o Truth à sua tela inicial, você poderá receber notificações por push. O Truth funciona de muitas maneiras como um aplicativo nativo!
+ tips: Dicas
+ title: Bem-vindo(a) ao Truth Social, %{name}!
+ verify_sms_prompt:
+ subject: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma!
+ subject_android: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma!
notifications:
email_events: Eventos para notificações por e-mail
email_events_hint: 'Selecione os eventos que deseja receber notificações:'
@@ -1014,7 +1136,7 @@
instructions_html: "<strong>Escaneie este código QR no Google Authenticator ou em um aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que digitar ao fazer login."
manual_instructions: 'Se você não pode escanear o código QR e precisa digitá-lo manualmente, aqui está o segredo em texto:'
setup: Configurar
- wrong_code: O código digitado é inválido! O horário do servidor e o horário do dispositivo estão corretos?
+ wrong_code: O código que você inseriu é inválido!
pagination:
newer: Mais novo
next: Próximo
@@ -1108,7 +1230,7 @@
windows_mobile: Windows Mobile
windows_phone: Windows Phone
revoke: Fechar
- revoke_success: Sessão fechada com sucesso
+ revoke_success: A sessão foi revogada com sucesso
title: Sessões
settings:
account: Conta
@@ -1150,7 +1272,7 @@
one: 'continha hashtag não permitida: %{tags}'
other: 'continha hashtags não permitidas: %{tags}'
errors:
- in_reply_not_found: O toot que você quer responder parece não existir.
+ in_reply_not_found: Parece que a postagem à qual você está tentando responder não existe.
language_detection: Detectar idioma automaticamente
open_in_web: Abrir no navegador
over_character_limit: limite de caracteres de %{max} excedido
@@ -1279,7 +1401,7 @@
two_factor_authentication:
add: Adicionar
disable: Desativar
- disabled_success: Autenticação de dois fatores desabilitada com sucesso
+ disabled_success: Autenticação de dois fatores desativada com sucesso
edit: Editar
enabled: Autenticação de dois fatores ativada
enabled_success: Autenticação de dois fatores ativada com sucesso
@@ -1288,28 +1410,34 @@
methods: Métodos de dois fatores
otp: Aplicativo autenticador
recovery_codes: Códigos de recuperação de reserva
- recovery_codes_regenerated: Códigos de recuperação gerados com sucesso
+ recovery_codes_regenerated: Códigos de recuperação regerados com sucesso
recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los junto com outros documentos importantes.
webauthn: Chaves de segurança
user_mailer:
backup_ready:
explanation: Você pediu um backup completo da sua conta no Mastodon. E agora está pronto para ser baixado!
- subject: Seu arquivo está pronto para ser baixado
+ subject: Seu arquivo está pronto para download
title: Baixar arquivo
sign_in_token:
details: 'Aqui estão os detalhes da tentativa:'
explanation: 'Detectamos uma tentativa de acessar sua conta a partir de um endereço IP não reconhecido. Se for você, insira o código de segurança abaixo na página de desafio:'
further_actions: 'Se não foi você, por favor mude sua senha e ative a autenticação de dois fatores em sua conta. Você pode fazê-lo aqui:'
- subject: Por favor, confirme a tentativa de acesso
+ subject: Confirme a tentativa de login
title: Tentativa de acesso
+ status_removed:
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal.
+ title: Your Truth was flagged for review
+ subject: Sua Truth foi sinalizada para revisão
warning:
explanation:
- disable: Enquanto sua conta está congelada, seus dados de conta permanecem intactos, mas você não pode realizar nenhuma ação até que esteja destrancada.
- sensitive: Seus arquivos de mídia carregados e mídias vinculadas serão tratados como sensíveis.
- silence: Enquanto sua conta está silenciada, somente pessoas que já estão seguindo você poderão ver seus toots nessa instância, e você pode ser excluído de várias listas públicas. No entanto, outros ainda podem te seguir manualmente.
- suspend: Sua conta foi banida e todos os seus toots e mídias foram irreversivelmente excluídos desta instância e das instâncias dos seus seguidores.
- verify: Você é um membro certificado da comunidade
- unverify: Você não é mais um membro certificado da comunidade.
+ ban_html: Sua conta foi banida por violar nossos Termos de Serviço. Banimentos sem prazo definido são um tipo de sanção raro e muito grave, geralmente aplicados somente às mais sérias violações de nossos Termos de Serviço. Se você acredita que nossa decisão de banir sua conta foi injusta, imoral ou simplesmente errada, recomendamos que entre com um recurso. Envie um e-mail para %{email}. Na linha de assunto, escreva "@Appeal" junto com seu nome de usuário. Aguarde 48 horas para que nossa equipe avalie seu recurso contra o banimento. Após uma avaliação cuidadosa, decidiremos reverter ou manter o banimento.
+ disable_html: Você não pode mais entrar na sua conta nem usá-la de qualquer forma que seja, mas seu perfil e seus outros dados permanecerão intactos.
+ sensitive_html: Os arquivos de mídia que você carregou e os links de mídia que publicou serão tratados como confidenciais.
+ silence_html: Você continua podendo usar sua conta, mas apenas as pessoas que já seguem você verão suas postagens nesse servidor. Além disso, você poderá ser excluído de várias listagens públicas. No entanto, as pessoas continuam podendo seguir você manualmente.
+ suspend_html: Sua conta foi suspensa e ficará inacessível por %{suspension_duration}.
+ suspend_indefinite_html: Sua conta foi suspensa e ficará inacessível.
+ verify_html: Você é um membro certificado da comunidade
+ unverify_html: Você não é mais um membro certificado da comunidade
get_in_touch: Você pode responder a este e-mail para entrar em contato com a equipe de %{instance}.
review_server_policies: Revisar as políticas da instância
statuses: 'Especificamente, para:'
@@ -1329,6 +1457,8 @@
suspend: Conta banida
verify: Conta verificada
unverify: Verificação de conta removida
+ waitlisted:
+ title: Sua conta foi criada com sucesso!
welcome:
edit_profile_action: Configurar perfil
edit_profile_step: Você pode personalizar o seu perfil enviando um avatar, uma capa, alterando seu nome de exibição e etc. Se você preferir aprovar seus novos seguidores antes de eles te seguirem, você pode trancar a sua conta.
@@ -1350,8 +1480,10 @@
follow_limit_reached: Você não pode seguir mais de %{limit} pessoas
generic_access_help_html: Problemas para acessar sua conta? Você pode entrar em contato com %{email} para obter ajuda
invalid_otp_token: Código de dois fatores inválido
- invalid_sign_in_token: Cógido de segurança inválido
+ invalid_sign_in_token: Código de segurança inválido
otp_lost_help_html: Se você perder o acesso à ambos, você pode entrar em contato com %{email}
+ previously_used_password: Por favor, use uma senha que você não usou anteriormente.
+ password_mismatch: A senha e a confirmação da senha não coincidem.
seamless_external_login: Você entrou usando um serviço externo, então configurações de e-mail e senha não estão disponíveis.
signed_in_as: 'Entrou como:'
suspicious_sign_in_confirmation: Parece que você não fez login deste dispositivo antes, e você não fez login por um tempo. Portanto, estamos enviando um código de segurança para o seu endereço de e-mail para confirmar que é você.
diff -ru truth-old/opensource/config/locales/pt-PT.yml truth-new/opensource/config/locales/pt-PT.yml
--- truth-old/opensource/config/locales/pt-PT.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/locales/pt-PT.yml 2024-04-05 09:17:47
@@ -1,87 +1,89 @@
---
-pt-PT:
+pt-BR:
about:
- about_hashtag_html: Estes são toots públicos marcados com <strong>#%{hashtag}</strong>. Podes interagir com eles se tiveres uma conta Mastodon.
- about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos da web e software livre e gratuito. É descentralizado como e-mail.
- about_this: Sobre esta instância
- active_count_after: activo
- active_footnote: Utilizadores activos mensais (UAM)
+ about_hashtag_html: Estes são toots públicos com a hashtag <strong>#%{hashtag}</strong>. Você pode interagir com eles se tiver uma conta em qualquer lugar no fediverso.
+ about_mastodon_html: 'A rede social do futuro: Sem anúncios, sem vigilância corporativa, com design ético e muita descentralização! Possua seus próprios dados com o Mastodon!'
+ about_this: Sobre
+ active_count_after: ativo
+ active_footnote: Usuários Ativos Mensalmente (UAM)
administered_by: 'Administrado por:'
api: API
- apps: Aplicações móveis
- apps_platforms: Usar o Mastodon a partir do iOS, Android e outras plataformas
- browse_directory: Navegue pelo directório de perfis e filtre por interesses
- browse_local_posts: Visualize as publicações públicas desta instância em tempo real
- browse_public_posts: Visualize as publicações públicas do Mastodon em tempo real
- contact: Contacto
- contact_missing: Não configurado
- contact_unavailable: n.d.
- discover_users: Descobrir utilizadores
+ apps: Aplicativos
+ apps_platforms: Use o Mastodon a partir do iOS, Android e outras plataformas
+ browse_directory: Navegue pelo diretório de perfis e filtre por interesses
+ browse_local_posts: Navegue pelos toots públicos locais em tempo real
+ browse_public_posts: Navegue pelos toots públicos globais em tempo real
+ contact: Contato
+ contact_missing: Não definido
+ contact_unavailable: Não disponível
+ discover_users: Descubra usuários
documentation: Documentação
- federation_hint_html: Ter uma conta em %{instance} permitirá seguir pessoas em qualquer instância Mastodon.
- get_apps: Experimente uma aplicação
- hosted_on: Mastodon em %{domain}
+ federation_hint_html: Com uma conta em %{instance} você vai poder seguir e interagir com pessoas de qualquer canto do fediverso.
+ get_apps: Experimente um aplicativo
+ hosted_on: Instância Mastodon em %{domain}
instance_actor_flash: |
- Esta conta é um actor virtual usado para representar a própria instância e não um utilizador individual.
- É usada para motivos de federação e não deve ser bloqueada a não ser que que queira bloquear a instância por completo. Se for esse o caso, deverá usar o bloqueio de domínio.
- learn_more: Saber mais
- privacy_policy: Política de privacidade
- rules: Regras da instância
- rules_html: 'Abaixo está um resumo das regras que precisa seguir se pretender ter uma conta nesta instância do Mastodon:'
- see_whats_happening: Veja o que está a acontecer
+ Esta conta é um ator virtual usado para representar o próprio servidor e não qualquer usuário individual.
+ É usado para propósitos de federação e não deve ser bloqueado a menos que queira bloquear toda a instância, o que no caso devia usar um bloqueio de domínio.
+ learn_more: Saiba mais
+ privacy_policy: Política de Privacidade
+ rules: Regras do servidor
+ rules_html: 'Abaixo está um resumo das regras que você precisa seguir se você quer ter uma conta neste servidor do Mastodon:'
+ see_whats_happening: Veja o que está acontecendo
server_stats: 'Estatísticas da instância:'
- source_code: Código fonte
+ source_code: Código-fonte
status_count_after:
- one: publicação
- other: publicações
- status_count_before: Que fizeram
- tagline: Siga os seus amigos e descubra novas amizades
+ one: toot
+ other: toots
+ status_count_before: Autores de
+ tagline: Siga seus amigos e faça novas amizades
terms: Termos de serviço
unavailable_content: Conteúdo indisponível
unavailable_content_description:
domain: Instância
- reason: Motivo
- rejecting_media: 'Arquivos de media destas instâncias não serão processados ou armazenados, e nenhuma miniatura será exibida, o que requer que o utilizador clique e abra o arquivo original manualmente:'
- rejecting_media_title: Media filtrada
- silenced: 'Publicações destas instâncias serão ocultas em linhas do tempo e conversas públicas, e nenhuma notificação será gerada a partir das interações dos seus utilizadores, a menos que você os esteja a seguir:'
+ reason: 'Motivo:'
+ rejecting_media: 'Arquivos de mídia destas instâncias não serão processados ou armazenados e nenhuma miniatura será exibida, exigindo que o usuário abra o arquivo original manualmente:'
+ rejecting_media_title: Mídia filtrada
+ silenced: 'Toots destas instâncias serão ocultos em linhas e conversas públicas, e nenhuma notificação será gerada a partir das interações dos seus usuários, a menos que esteja sendo seguido:'
silenced_title: Servidores silenciados
- suspended: 'Nenhum dado dessas instâncias será processado, armazenado ou trocado, tornando qualquer interação ou comunicação com os utilizadores dessas instâncias impossível:'
- suspended_title: Servidores suspensos
- unavailable_content_html: Mastodon geralmente permite que você veja o conteúdo e interaja com utilizadores de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico.
+ suspended: 'Você não será capaz de seguir ninguém destas instâncias, e nenhum dado delas será processado, armazenado ou trocado:'
+ suspended_title: Servidores banidos
+ unavailable_content_html: Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico.
user_count_after:
- one: utilizador
- other: utilizadores
- user_count_before: Casa para
- what_is_mastodon: O que é o Mastodon?
+ one: usuário
+ other: usuários
+ user_count_before: Casa de
+ what_is_mastodon: O que é Mastodon?
+ ads:
+ why_copy: Mostramos anúncios de produtos e serviços que achamos que nossos usuários podem gostar.
accounts:
- choices_html: 'escolhas de %{name}:'
- endorsements_hint: Você pode, através da interface web, escolher endossar pessoas que segue, e elas aparecerão aqui.
- featured_tags_hint: Você pode destacar hashtags específicas que serão exibidas aqui.
+ choices_html: 'Sugestões de %{name}:'
+ endorsements_hint: Você pode sugerir pessoas que você segue, elas aparecerão aqui.
+ featured_tags_hint: Você pode destacar hashtags específicas, elas aparecerão aqui.
follow: Seguir
followers:
one: Seguidor
other: Seguidores
- following: A seguir
- instance_actor_flash: Esta conta é um actor virtual usado para representar a própria instância e não um utilizador individual. É usada para motivos de federação e não deve ser suspenso.
- joined: Aderiu %{date}
- last_active: última vez activo
- link_verified_on: A posse deste link foi verificada em %{date}
- media: Media
- moved_html: "%{name} mudou-se para %{new_profile_link}:"
- network_hidden: Esta informação não está disponível
+ following: Seguindo
+ instance_actor_flash: Esta conta é um ator virtual usado para representar o próprio servidor e não um usuário individual. É utilizada para fins de federação e não deve ser suspensa.
+ joined: Entrou em %{date}
+ last_active: última atividade
+ link_verified_on: O link foi verificado em %{date}
+ media: Mídia
+ moved_html: "%{name} se mudou para %{new_profile_link}:"
+ network_hidden: Informação indisponível
never_active: Nunca
- nothing_here: Não há nada aqui!
- people_followed_by: Pessoas seguidas por %{name}
+ nothing_here: Nada aqui!
+ people_followed_by: Pessoas que %{name} segue
people_who_follow: Pessoas que seguem %{name}
pin_errors:
- following: Tu tens de estar a seguir a pessoa que pretendes apoiar
+ following: Você deve estar seguindo a pessoa que você deseja sugerir
posts:
- one: Publicação
- other: Publicações
- posts_tab_heading: Publicações
- posts_with_replies: Posts e Respostas
+ one: Toot
+ other: Toots
+ posts_tab_heading: Toots
+ posts_with_replies: Toots e respostas
roles:
- admin: Administrador(a)
+ admin: Admin
bot: Robô
group: Grupo
moderator: Moderador
@@ -89,235 +91,193 @@
unfollow: Deixar de seguir
admin:
account_actions:
- action: Executar acção
- title: Executar acção de moderação em %{acct}
+ action: Tomar uma atitude
+ title: Moderar %{acct}
account_moderation_notes:
- create: Criar nota
+ create: Deixar nota
created_msg: Nota de moderação criada com sucesso!
- delete: Eliminar
+ delete: Excluir
destroyed_msg: Nota de moderação excluída com sucesso!
accounts:
- add_email_domain_block: Adicionar o domínio de email à lista negra
+ add_email_domain_block: Adicionar o domínio de e-mail à lista negra
approve: Aprovar
- approve_all: Aprovar todos
- approved_msg: Inscrição de %{username} aprovada com sucesso
- are_you_sure: Tens a certeza?
- avatar: Imagem de Perfil
+ approve_all: Aprovar tudo
+ approved_msg: Aprovado com sucesso o pedido de registro de %{username}
+ are_you_sure: Você tem certeza?
+ avatar: Imagem de perfil
by_domain: Domínio
change_email:
changed_msg: E-mail da conta alterado com sucesso!
- current_email: E-mail actual
+ current_email: E-mail atual
label: Alterar e-mail
new_email: Novo e-mail
submit: Alterar e-mail
title: Alterar e-mail para %{username}
confirm: Confirmar
confirmed: Confirmado
- confirming: A confirmar
- delete: Eliminar dados
- deleted: Eliminada
- demote: Despromoveu
- destroyed_msg: Os dados de %{username} estão agora em fila de espera para serem eliminados de imediato
- disable: Desativar
- disable_two_factor_authentication: Desativar 2FA
- disabled: Congelada
- display_name: Nome a mostrar
+ confirming: Confirmando
+ delete: Excluir dados
+ deleted: Excluído
+ demote: Rebaixar
+ destroyed_msg: Os dados de %{username} estão na fila para serem excluídos em breve
+ disable: Congelar
+ disable_two_factor_authentication: Desativar autenticação de dois fatores
+ disabled: Desativada
+ display_name: Nome de exibição
location: localização
website: local na rede Internet
domain: Domínio
edit: Editar
email: E-mail
- email_status: Estado do e-mail
- enable: Ativar
- enabled: Ativado
- enabled_msg: Descongelou com sucesso a conta %{username}
+ email_status: Status do e-mail
+ enable: Descongelar
+ enabled: Ativada
+ enabled_msg: Descongelada com sucesso a conta de %{username}
followers: Seguidores
- follows: A seguir
- header: Cabeçalho
+ follows: Seguindo
+ header: Capa
inbox_url: URL da caixa de entrada
- invite_request_text: Razões para se juntar a nós
- invited_by: Convidado(a) por
+ invite_request_text: Motivos para entrar
+ invited_by: Convidado por
ip: IP
- joined: Aderiu
+ joined: Entrou
location:
all: Todos
- local: Local
remote: Remoto
- title: Local
- login_status: Estado de início de sessão
- media_attachments: Anexos de media
+ title: Localização
+ login_status: Situação da conta
+ media_attachments: Mídias anexadas
memorialize: Converter em memorial
- memorialized: Em memória
- memorialized_msg: Conta %{username} transformada com sucesso em memorial
+ memorialized: Convertidas em memorial
+ memorialized_msg: Transformou com sucesso %{username} em uma conta memorial
moderation:
- active: Activo
+ active: Ativo
all: Todos
pending: Pendente
silenced: Silenciados
- suspended: Supensos
+ suspended: Banidos
title: Moderação
moderation_notes: Notas de moderação
- most_recent_activity: Actividade mais recente
+ most_recent_activity: Atividade mais recente
most_recent_ip: IP mais recente
- no_account_selected: Nenhuma conta foi alterada porque nenhuma foi selecionada
- no_limits_imposed: Sem limites impostos
+ no_account_selected: Nenhuma conta foi alterada, pois nenhuma conta foi selecionada
+ no_limits_imposed: Nenhum limite imposto
not_subscribed: Não inscrito
- pending: Pendente de revisão
- perform_full_suspension: Fazer suspensão completa
+ pending: Revisão pendente
+ perform_full_suspension: Banir
promote: Promover
protocol: Protocolo
public: Público
- push_subscription_expires: A Inscrição PuSH expira
+ push_subscription_expires: Inscrição PuSH expira
redownload: Atualizar perfil
- redownloaded_msg: Atualizado com sucesso o perfil de %{username} da origem
- reject: Rejeitar
- reject_all: Rejeitar todas
- rejected_msg: Inscrição de %{username} rejeitada com sucesso
- remove_avatar: Remover a imagem de perfil
- remove_header: Remover o cabeçalho
- removed_avatar_msg: Imagem de perfil de %{username} removida com sucesso
- removed_header_msg: Imagem de cabeçalho de %{username} removida com sucesso
+ redownloaded_msg: Atualizado com sucesso o perfil de %{username} a partir da origem
+ reject: Vetar
+ reject_all: Vetar tudo
+ rejected_msg: Rejeitado com sucesso o pedido de registro de %{username}
+ remove_avatar: Remover imagem de perfil
+ remove_header: Remover capa
+ removed_avatar_msg: Removida com sucesso a imagem de avatar de %{username}
+ removed_header_msg: Removida com sucesso a imagem de capa de %{username}
resend_confirmation:
- already_confirmed: Este utilizador já está confirmado
- send: Reenviar um email de confirmação
- success: Email de confirmação enviado com sucesso!
- reset: Restaurar
- reset_password: Reset palavra-passe
- resubscribe: Reinscrever
+ already_confirmed: Este usuário já está confirmado
+ send: Reenviar o e-mail de confirmação
+ success: E-mail de confirmação enviado com sucesso!
+ reset: Redefinir
+ reset_password: Redefinir senha
+ resubscribe: Reinscrever-se
role: Permissões
roles:
- admin: Administrador(a)
+ admin: Administrador
moderator: Moderador
- staff: Equipa
- user: Utilizador
+ staff: Equipe
+ user: Usuário
search: Pesquisar
- search_same_email_domain: Outros utilizadores com o mesmo domínio de email
- search_same_ip: Outros utilizadores com o mesmo IP
- sensitive: Marcar como sensível
- sensitized: marcada como sensível
- shared_inbox_url: URL da caixa de entrada compartilhada
+ search_same_email_domain: Outros usuários com o mesmo domínio de e-mail
+ search_same_ip: Outros usuários com o mesmo IP
+ sensitive: Sensíveis
+ sensitized: marcadas como sensíveis
+ shared_inbox_url: Link da caixa de entrada compartilhada
show:
- created_reports: Relatórios gerados por esta conta
- targeted_reports: Relatórios feitos sobre esta conta
- silence: Silêncio
- silenced: Silenciada
- statuses: Status
+ created_reports: Denúncias desta conta
+ targeted_reports: Denúncias sobre esta conta
+ silence: Silenciar
+ silenced: Silenciado
+ statuses: Toots
subscribe: Inscrever-se
- suspended: Suspensa
- suspension_irreversible: Os dados desta conta foram eliminados irreversivelmente. Pode cancelar a suspensão da conta para torná-la utilizável, mas ela não irá recuperar os dados que possuía anteriormente.
- suspension_reversible_hint_html: A conta foi suspensa e os dados serão totalmente eliminados em %{date}. Até lá, a conta poderá ser recuperada sem quaisquer efeitos negativos. Se deseja eliminar todos os dados desta conta imediatamente, pode fazê-lo em baixo.
- time_in_queue: Aguardando na fila %{time}
+ suspended: Banido
+ suspension_irreversible: Os dados desta conta foram excluídos de forma irreversível. Você pode remover a suspensão da conta para torná-la utilizável, mas ela não irá recuperar nenhum dado que ela possuía anteriormente.
+ suspension_reversible_hint_html: A conta foi suspensa e os dados serão totalmente removidos em %{date}. Até lá, a conta pode ser restaurada sem nenhum efeito negativo. Se você deseja remover todos os dados da conta imediatamente, você pode fazer isso abaixo.
+ time_in_queue: Esperando na fila por %{time}
title: Contas
unconfirmed_email: E-mail não confirmado
- undo_sensitized: Desmarcar como sensível
- undo_silenced: Desfazer silenciar
- undo_suspension: Desfazer supensão
- unsilenced_msg: Removeu com sucesso as limitações da conta %{username}
+ undo_sensitized: Desfazer sensível
+ undo_silenced: Desfazer silêncio
+ undo_suspension: Desbanir
+ unsilenced_msg: Removidas com sucesso as limitações da conta de %{username}
unsubscribe: Cancelar inscrição
- unsuspended_msg: Removeu com sucesso a suspensão da conta %{username}
- username: Utilizador
- view_domain: Ver resumo do domínio
- warn: Aviso
+ unsuspended_msg: Removida com sucesso a suspensão da conta de %{username}
+ username: Nome de usuário
+ view_domain: Ver resumo para o domínio
+ warn: Notificar
web: Web
- whitelisted: Está na lista branca
+ whitelisted: Permitido
action_logs:
action_types:
- assigned_to_self_report: Atribuir Relatório
- change_email_user: Alterar E-mail do Utilizador
- confirm_user: Confirmar Utilizador
+ assigned_to_self_report: Adicionar relatório
+ change_email_user: Editar e-mail do usuário
+ confirm_user: Confirmar Usuário
create_account_warning: Criar Aviso
create_announcement: Criar Anúncio
create_custom_emoji: Criar Emoji Personalizado
- create_domain_allow: Criar Permissão de Domínio
+ create_domain_allow: Adicionar domínio permitido
create_domain_block: Criar Bloqueio de Domínio
create_email_domain_block: Criar Bloqueio de Domínio de E-mail
create_ip_block: Criar regra de IP
- create_unavailable_domain: Criar Domínio Indisponível
- demote_user: Despromover Utilizador
- destroy_announcement: Eliminar Anúncio
- destroy_custom_emoji: Eliminar Emoji Personalizado
- destroy_domain_allow: Eliminar Permissão de Domínio
- destroy_domain_block: Eliminar Bloqueio de Domínio
- destroy_email_domain_block: Eliminar Bloqueio de Domínio de E-mail
- destroy_ip_block: Eliminar regra de IP
- destroy_status: Eliminar Publicação
- destroy_unavailable_domain: Eliminar Domínio Indisponível
- disable_2fa_user: Desativar 2FA
+ demote_user: Rebaixar usuário
+ destroy_announcement: Excluir anúncio
+ destroy_custom_emoji: Excluir emoji personalizado
+ destroy_domain_allow: Excluir domínio permitido
+ destroy_domain_block: Excluir Bloqueio de Domínio
+ destroy_email_domain_block: Excluir bloqueio de domínio de e-mail
+ destroy_ip_block: Excluir regra de IP
+ destroy_status: Excluir Status
+ disable_2fa_user: Desativar autenticação de dois fatores
disable_custom_emoji: Desativar Emoji Personalizado
- disable_user: Desativar Utilizador
+ disable_user: Desativar usuário
enable_custom_emoji: Ativar Emoji Personalizado
- enable_user: Ativar Utilizador
- memorialize_account: Memorizar Conta
- promote_user: Promover Utilizador
- remove_avatar_user: Remover Imagem de Perfil
+ enable_user: Ativar usuário
+ memorialize_account: Converter conta em memorial
+ promote_user: Promover usuário
+ remove_avatar_user: Remover Avatar
reopen_report: Reabrir Relatório
- reset_password_user: Repor Password
+ reset_password_user: Redefinir a senha
resolve_report: Resolver Relatório
- sensitive_account: Marcar a media na sua conta como sensível
- silence_account: Silenciar Conta
+ sensitive_account: Marcar a mídia na sua conta como sensível
+ silence_account: Silenciar conta
suspend_account: Suspender Conta
- unassigned_report: Desatribuir Relatório
- unsensitive_account: Desmarcar a media na sua conta como sensível
- unsilence_account: Deixar de Silenciar Conta
- unsuspend_account: Retirar Suspensão à Conta
- update_announcement: Atualizar Anúncio
- update_custom_emoji: Atualizar Emoji Personalizado
- update_domain_block: Atualizar Bloqueio de Domínio
- update_status: Atualizar Estado
+ unassigned_report: Remover relatório
+ unsensitive_account: Desmarcar a mídia na sua conta como sensível
+ unsilence_account: Desfazer silenciar conta
+ unsuspend_account: Remover suspensão de conta
+ update_announcement: Editar anúncio
+ update_custom_emoji: Editar Emoji Personalizado
+ update_domain_block: Atualizar bloqueio de domínio
+ update_status: Editar Status
actions:
- assigned_to_self_report_html: "%{name} atribuiu o relatório %{target} a si próprio"
- change_email_user_html: "%{name} alterou o endereço de e-mail do utilizador %{target}"
- confirm_user_html: "%{name} confirmou o endereço de e-mail do utilizador %{target}"
create_account_warning_html: "%{name} enviou um aviso para %{target}"
- create_announcement_html: "%{name} criou o novo anúncio %{target}"
- create_custom_emoji_html: "%{name} carregou o novo emoji %{target}"
- create_domain_allow_html: "%{name} habilitou a federação com o domínio %{target}"
create_domain_block_html: "%{name} bloqueou o domínio %{target}"
- create_email_domain_block_html: "%{name} bloqueou o domínio de e-mail %{target}"
- create_ip_block_html: "%{name} criou regra para o IP %{target}"
- create_unavailable_domain_html: "%{name} parou a entrega ao domínio %{target}"
- demote_user_html: "%{name} despromoveu o utilizador %{target}"
- destroy_announcement_html: "%{name} eliminou o anúncio %{target}"
- destroy_custom_emoji_html: "%{name} destruiu o emoji %{target}"
- destroy_domain_allow_html: "%{name} desabilitou a federação com o domínio %{target}"
- destroy_domain_block_html: "%{name} desbloqueou o domínio %{target}"
- destroy_email_domain_block_html: "%{name} desbloqueou o domínio de e-mail %{target}"
- destroy_ip_block_html: "%{name} eliminou regra para o IP %{target}"
- destroy_status_html: "%{name} removeu a publicação de %{target}"
- destroy_unavailable_domain_html: "%{name} retomou a entrega ao domínio %{target}"
- disable_2fa_user_html: "%{name} desativou o requerimento de autenticação em dois passos para o utilizador %{target}"
- disable_custom_emoji_html: "%{name} desabilitou o emoji %{target}"
- disable_user_html: "%{name} desativou o acesso para o utilizador %{target}"
- enable_custom_emoji_html: "%{name} habilitou o emoji %{target}"
- enable_user_html: "%{name} ativou o acesso para o utilizador %{target}"
- memorialize_account_html: "%{name} transformou a conta de %{target} em um memorial"
- promote_user_html: "%{name} promoveu o utilizador %{target}"
- remove_avatar_user_html: "%{name} removeu a imagem de perfil de %{target}"
- reopen_report_html: "%{name} reabriu o relatório %{target}"
- reset_password_user_html: "%{name} restabeleceu a palavra-passe do utilizador %{target}"
- resolve_report_html: "%{name} resolveu o relatório %{target}"
- sensitive_account_html: "%{name} marcou a media de %{target} como sensível"
- silence_account_html: "%{name} silenciou a conta de %{target}"
- suspend_account_html: "%{name} suspendeu a conta de %{target}"
- unassigned_report_html: "%{name} desatribuiu o realtório %{target}"
- unsensitive_account_html: "%{name} desmarcou a media de %{target} como sensível"
- unsilence_account_html: "%{name} desativou o silêncio de %{target}"
- unsuspend_account_html: "%{name} desativou a suspensão de %{target}"
- update_announcement_html: "%{name} atualizou o anúncio %{target}"
- update_custom_emoji_html: "%{name} atualizou o emoji %{target}"
- update_domain_block_html: "%{name} atualizou o bloqueio de domínio para %{target}"
- update_status_html: "%{name} atualizou o estado de %{target}"
- deleted_status: "(publicação eliminada)"
- empty: Não foram encontrados registos.
+ create_email_domain_block_html: "%{name} bloqueou do domínio de e-mail %{target}"
+ deleted_status: "(status excluído)"
+ empty: Nenhum registro encontrado.
filter_by_action: Filtrar por ação
- filter_by_user: Filtrar por utilizador
- title: Registo de auditoria
+ filter_by_user: Filtrar por usuário
+ title: Auditar histórico
announcements:
- destroyed_msg: Anúncio eliminado com sucesso!
+ destroyed_msg: Anúncio excluído com sucesso!
edit:
title: Editar anúncio
- empty: Nenhum anúncio encontrado.
- live: Em exibição
+ empty: Sem anúncios.
+ live: Ao vivo
new:
create: Criar anúncio
title: Novo anúncio
@@ -326,184 +286,158 @@
scheduled_for: Agendado para %{time}
scheduled_msg: Anúncio agendado para publicação!
title: Anúncios
- unpublish: Anular publicação
- unpublished_msg: Anúncio retirado de exibição com sucesso!
+ unpublish: Cancelar publicação
+ unpublished_msg: Anúncio despublicado com sucesso!
updated_msg: Anúncio atualizado com sucesso!
custom_emojis:
assign_category: Atribuir categoria
by_domain: Domínio
copied_msg: Cópia local do emoji criada com sucesso
copy: Copiar
- copy_failed_msg: Não foi possível criar uma cópia local deste emoji
+ copy_failed_msg: Não foi possível criar cópia local do emoji
create_new_category: Criar nova categoria
created_msg: Emoji criado com sucesso!
- delete: Eliminar
- destroyed_msg: Emoji destruído com sucesso!
+ delete: Excluir
+ destroyed_msg: Emoji excluído com sucesso!
disable: Desativar
disabled: Desativado
- disabled_msg: Desativado com sucesso este emoji
- emoji: Emoji
+ disabled_msg: Emoji desativado com sucesso
enable: Ativar
enabled: Ativado
- enabled_msg: Ativado com sucesso este emoji
+ enabled_msg: Emoji ativado com sucesso
image_hint: PNG de até 50KB
- list: Lista
+ list: Listar
listed: Listado
new:
- title: Adicionar novo emoji customizado
- not_permitted: Não está autorizado a executar esta ação
+ title: Adicionar novo emoji personalizado
+ not_permitted: Você não tem permissão para executar esta ação
overwrite: Sobrescrever
- shortcode: Código de atalho
- shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underscores
- title: Emojis customizados
- uncategorized: Sem categoria
+ shortcode: Atalho
+ shortcode_hint: Pelo menos 2 caracteres, apenas caracteres alfanuméricos e underlines ("_")
+ title: Emojis personalizados
+ uncategorized: Não categorizado
unlist: Não listar
- unlisted: Não listado
+ unlisted: Não-listado
update_failed_msg: Não foi possível atualizar esse emoji
updated_msg: Emoji atualizado com sucesso!
upload: Enviar
dashboard:
authorized_fetch_mode: Modo seguro
- backlog: trabalhos atrasados
+ backlog: tarefas na fila
config: Configuração
- feature_deletions: Eliminações da conta
- feature_invites: Links de convites
+ feature_deletions: Exclusão de contas
+ feature_invites: Convites
feature_profile_directory: Diretório de perfis
- feature_registrations: Registos
+ feature_registrations: Novas contas
feature_relay: Repetidor da federação
- feature_timeline_preview: Pré-visualização da cronologia
- features: Componentes
- hidden_service: Federação com serviços escondidos
- open_reports: relatórios abertos
- pending_tags: hashtags a aguardar revisão
- pending_users: utilizadores a aguardar revisão
- recent_users: Utilizadores recentes
- search: Pesquisa com texto completo
- single_user_mode: Modo de utilizador único
+ feature_timeline_preview: Prévia da linha
+ features: Funcionalidades
+ hidden_service: Federação com serviços onion
+ open_reports: Denúncias em aberto
+ pending_tags: hashtags pendentes
+ pending_users: usuários pendentes
+ recent_users: Usuários recentes
+ search: Pesquisa em texto
+ single_user_mode: Modo de usuário único
software: Software
- space: Utilização do espaço
- title: Painel de controlo
- total_users: total de utilizadores
- trends: Tendências
- week_interactions: interacções desta semana
- week_users_active: activo esta semana
- week_users_new: utilizadores nesta semana
- whitelist_mode: Modo lista branca
+ space: Uso de espaço em disco
+ title: Painel de controle
+ total_users: usuários no total
+ trends: Em alta
+ week_interactions: interações essa semana
+ week_users_active: ativos essa semana
+ week_users_new: usuários essa semana
+ whitelist_mode: Modo lista de permitidos
domain_allows:
- add_new: Colocar domínio na lista branca
- created_msg: Domínio foi adicionado à lista branca com sucesso
- destroyed_msg: Domínio foi removido da lista branca
- undo: Remover da lista branca
+ add_new: Permitir domínio
+ created_msg: Domínio foi permitido
+ destroyed_msg: Domínio foi bloqueado
+ undo: Bloquear
domain_blocks:
- add_new: Adicionar novo
- created_msg: Bloqueio do domínio está a ser processado
- destroyed_msg: Bloqueio de domínio está a ser removido
+ add_new: Adicionar novo bloqueio de domínio
+ created_msg: Domínio está sendo bloqueado
+ destroyed_msg: Domínio desbloqueado
domain: Domínio
edit: Editar bloqueio de domínio
- existing_domain_block_html: Você já impôs limites mais restritivos a %{name}, é necessário primeiro <a href="%{unblock_url}">desbloqueá-lo</a>.
+ existing_domain_block_html: Você já impôs limites mais estritos em %{name}, você precisa <a href="%{unblock_url}">desbloqueá-lo</a> primeiro.
new:
create: Criar bloqueio
- hint: O bloqueio de dominio não vai previnir a criação de entradas na base de dados, mas irá retroativamente e automaticamente aplicar métodos de moderação específica nessas contas.
+ hint: O bloqueio de domínio não vai prevenir a criação de entradas de contas na base de dados, mas vai retroativamente e automaticamente aplicar métodos específicos de moderação nessas contas.
severity:
- desc_html: "<strong>Silenciar</strong> irá fazer com que as publicações dessa conta sejam invisíveis para quem não a segue. <strong>Supender</strong> irá eliminar todo o conteúdo guardado dessa conta, media e informação de perfil. Use <strong>Nenhum</strong> se apenas deseja rejeitar arquivos de media."
+ desc_html: "<strong>Silenciar</strong> vai fazer os posts da conta invisíveis para qualquer um que não os esteja seguindo. <strong>Suspender</strong> vai remover todo o conteúdo, mídia, e dados de perfil da conta. Use <strong>Nenhum</strong> se você só quer rejeitar arquivos de mídia."
noop: Nenhum
silence: Silenciar
- suspend: Suspender
+ suspend: Banir
title: Novo bloqueio de domínio
obfuscate: Ofuscar nome de domínio
- obfuscate_hint: Ofuscar parcialmente o nome de domínio na lista, se estiverem habilitadas as limitações na publicação da lista de domínios
private_comment: Comentário privado
- private_comment_hint: Comentário sobre essa limitação de domínio para uso interno pelos moderadores.
+ private_comment_hint: Comente sobre essa restrição ao domínio para uso interno dos moderadores.
public_comment: Comentário público
- public_comment_hint: Comentário sobre essa limitação de domínio para o público geral, se ativada a divulgação da lista de limitações de domínio.
- reject_media: Rejeitar ficheiros de media
- reject_media_hint: Remove arquivos de media armazenados localmente e rejeita descarregar novos arquivos no futuro. Irrelevante para suspensões
- reject_reports: Rejeitar relatórios
- reject_reports_hint: Ignorar todos os relatórios vindos deste domínio. Irrelevantes para efectuar suspensões
- rejecting_media: a rejeitar ficheiros de media
- rejecting_reports: a rejeitar relatórios
+ public_comment_hint: Comente sobre essa restrição ao domínio para o público geral, caso a divulgação da lista de bloqueio esteja ativada.
+ reject_media: Rejeitar arquivos de mídia
+ reject_media_hint: Remove arquivos de mídia armazenados localmente e recusa fazer download de qualquer um no futuro. Irrelevante para suspensões
+ reject_reports: Rejeitar denúncias
+ reject_reports_hint: Ignora todas as denúncias vindo deste domínio. Irrelevante para suspensões
+ rejecting_media: rejeitando arquivos de mídia
+ rejecting_reports: rejeitando denúncias
severity:
silence: silenciado
- suspend: suspenso
+ suspend: banido
show:
affected_accounts:
- one: Uma conta na base de dados afectada
- other: "%{count} contas na base de dados afectadas"
+ one: Uma conta no banco de dados foi afetada
+ other: "%{count} contas no banco de dados foram afetadas"
retroactive:
- silence: Não silenciar contas afetadas existentes deste domínio
- suspend: Não suspender todas as contas existentes nesse domínio
- title: Remover o bloqueio de domínio de %{domain}
- undo: Anular
- undo: Anular
+ silence: Dessilenciar contas existentes afetadas deste domínio
+ suspend: Remover a suspensão das contas afetadas deste domínio
+ title: Desfazer bloqueio de domínio para %{domain}
+ undo: Desfazer
+ undo: Desfazer bloqueio de domínio
view: Ver domínios bloqueados
email_domain_blocks:
add_new: Adicionar novo
- created_msg: Bloqueio de domínio de email criado com sucesso
- delete: Eliminar
- destroyed_msg: Bloqueio de domínio de email excluído com sucesso
+ created_msg: Domínio de e-mail adicionado à lista negra com sucesso
+ delete: Excluir
+ destroyed_msg: Domínio de e-mail excluído da lista negra com sucesso
domain: Domínio
- empty: Nenhum domínio de e-mail atualmente na lista negra.
+ empty: Nenhum domínio de e-mail atualmente bloqueado.
from_html: de %{domain}
new:
create: Adicionar domínio
- title: Novo bloqueio de domínio de email
- title: Bloqueio de Domínio de Email
- follow_recommendations:
- description_html: "<strong>Recomendações de quem seguir ajudam novos utilizadores a encontrar conteúdo interessante rapidamente.</strong>. Quando um utilizador não interage com outros o suficiente para formar recomendações personalizadas, estas contas são recomendadas. Elas são recalculadas diariamente a partir de uma mistura de contas com mais atividade recente e maior número de seguidores locais para um determinado idioma."
- language: Para o idioma
- status: Estado
- suppress: Suprimir recomendação de contas a seguir
- suppressed: Suprimida
- title: Seguir recomendações
- unsuppress: Restaurar recomendações de contas a seguir
+ title: Nova entrada de lista negra de e-mail
+ title: Lista de negra de e-mail
instances:
- back_to_all: Todas
- back_to_limited: Limitadas
back_to_warning: Aviso
by_domain: Domínio
- delivery:
- all: Todas
- clear: Limpar erros de entrega
- restart: Reiniciar entrega
- stop: Parar entrega
- title: Entrega
- unavailable: Indisponível
- unavailable_message: Entrega indisponível
- warning: Aviso
- warning_message:
- one: Falhou entrega %{count} dia
- other: Falhou entrega %{count} dias
- delivery_available: Entrega disponível
- delivery_error_days: Dias de erro de entrega
- delivery_error_hint: Se a entrega não for possível durante %{count} dias, será automaticamente marcada como não realizável.
- empty: Não foram encontrados domínios.
+ delivery_available: Envio disponível
+ empty: Nenhum domínio encontrado.
known_accounts:
one: "%{count} conta conhecida"
other: "%{count} contas conhecidas"
moderation:
- all: Todas
- limited: Limitadas
+ all: Todos
+ limited: Limitados
title: Moderação
- private_comment: Comentários privados
- public_comment: Comentários públicos
- title: Instâncias conhecidas
- total_blocked_by_us: Bloqueadas por nós
- total_followed_by_them: Seguidas por eles
- total_followed_by_us: Seguidas por nós
- total_reported: Relatórios sobre eles
- total_storage: Anexos de media
+ private_comment: Comentário privado
+ public_comment: Comentário público
+ title: Federação
+ total_blocked_by_us: Bloqueado por nós
+ total_followed_by_them: Seguidos por eles
+ total_followed_by_us: Seguidos por nós
+ total_reported: Denúncias sobre eles
+ total_storage: Mídias anexadas
invites:
- deactivate_all: Desactivar todos
+ deactivate_all: Desativar todos
filter:
all: Todos
- available: Disponíveis
- expired: Expirados
+ available: Disponível
+ expired: Expirado
title: Filtro
title: Convites
ip_blocks:
add_new: Criar regra
created_msg: Nova regra de IP adicionada com sucesso
- delete: Eliminar
+ delete: Excluir
expires_in:
'1209600': 2 semanas
'15778476': 6 meses
@@ -521,40 +455,39 @@
title: Relações de %{acct}
relays:
add_new: Adicionar novo repetidor
- delete: Eliminar
- description_html: Um <strong>repetidor de federação</strong> é um servidor intermediário que troca grandes volumes de publicações públicas entre instâncias que o subscrevem e publicam. <strong>Ele pode ajudar pequenas e medias instâncias a descobrir conteúdo do fediverso</strong> que, de outro modo, exigiria que os utilizadores locais seguissem manualmente outras pessoas em instâncias remotas.
- disable: Desactivar
- disabled: Desactivado
- enable: Activar
- enable_hint: Uma vez ativado, a tua instância irá subscrever a todas as publicações deste repetidor e irá começar a enviar as suas publicações públicas para ele.
+ delete: Excluir
+ description_html: Um <strong>repetidor de federação</strong> é um servidor intermediário que troca um grande volume de toots públicos entre instâncias que se inscrevem e publicam nele. <strong>O repetidor pode ser usado para ajudar instâncias pequenas e médias a descobrir conteúdo pelo fediverso</strong>, que normalmente precisariam que usuários locais manualmente seguissem outras pessoas em instâncias remotas.
+ disable: Desativar
+ disabled: Desativado
+ enable: Ativar
+ enable_hint: Uma vez ativado, sua instância se inscreverá para receber todos os toots públicos desse repetidor; E vai começar a enviar todos os toots públicos desta instância para o repetidor.
enabled: Ativado
- inbox_url: URL do repetidor
- pending: À espera da aprovação do repetidor
- save_and_enable: Guardar e ativar
- setup: Configurar uma ligação ao repetidor
- signatures_not_enabled: Relays não funcionarão corretamente enquanto o modo seguro ou o modo lista branca estiverem ativados
- status: Estado
- title: Retransmissores
+ inbox_url: Link do repetidor
+ pending: Esperando pela aprovação do repetidor
+ save_and_enable: Salvar e ativar
+ setup: Configurar uma conexão de repetidor
+ signatures_not_enabled: Repetidores não funcionarão adequadamente enquanto o modo seguro ou o modo lista de permitidos estiverem ativos
+ title: Repetidores
report_notes:
- created_msg: Relatório criado com sucesso!
- destroyed_msg: Nota de relatório eliminada com sucesso!
+ created_msg: Nota de denúncia criada com sucesso!
+ destroyed_msg: Nota de denúncia excluída com sucesso!
reports:
account:
notes:
one: "%{count} nota"
other: "%{count} notas"
reports:
- one: "%{count} relatório"
- other: "%{count} relatórios"
- action_taken_by: Ação tomada por
- are_you_sure: Tens a certeza?
- assign_to_self: Atribuí-me a mim
- assigned: Atribuído ao moderador
- by_target_domain: Domínio da conta reportada
+ one: "%{count} denúncia"
+ other: "%{count} denúncias"
+ action_taken_by: Atitude tomada por
+ are_you_sure: Você tem certeza?
+ assign_to_self: Pegar
+ assigned: Moderador responsável
+ by_target_domain: Domínio da conta denunciada
comment:
none: Nenhum
- created_at: Relatado
- forwarded: Encaminhado
+ created_at: Denunciado
+ forwarded: Encaminhados
forwarded_to: Encaminhado para %{domain}
mark_as_resolved: Marcar como resolvido
mark_as_unresolved: Marcar como não resolvido
@@ -562,423 +495,430 @@
create: Adicionar nota
create_and_resolve: Resolver com nota
create_and_unresolve: Reabrir com nota
- delete: Eliminar
- placeholder: Descreve as ações que foram tomadas ou quaisquer outras atualizações relacionadas...
- reopen: Reabrir relatório
+ delete: Excluir
+ placeholder: Descreva que ações foram tomadas, ou quaisquer outras atualizações relacionadas…
+ reopen: Reabrir denúncia
report: 'Denúncia #%{id}'
reported_account: Conta denunciada
- reported_by: Reportado por
+ reported_by: Denunciada por
resolved: Resolvido
- resolved_msg: Relatório resolvido com sucesso!
- status: Estado
- title: Relatórios
- unassign: Não atribuir
- unresolved: Por resolver
+ resolved_msg: Denúncia resolvida com sucesso!
+ title: Denúncias
+ unassign: Largar
+ unresolved: Não resolvido
updated_at: Atualizado
rules:
add_new: Adicionar regra
- delete: Eliminar
- description_html: Embora a maioria afirme ter lido e concordado com os termos de serviço, geralmente as pessoas só leem depois de surgir um problema. <strong>Dê uma olhada nas regras do seu servidor fornecendo-as em uma lista de marcadores planos.</strong> Tente manter as regras individuais curtas e simples, mas tente também não dividi-las em muitos itens separados.
+ delete: Deletar
+ description_html: Embora a maioria afirme ter lido e concordado com os termos de serviço, geralmente as pessoas só leem depois de surgir um problema. <strong>Faça com que seja mais fácil ver as regras do seu servidor rapidamente fornecendo-as em uma lista.</strong> Tente manter cada regra curta e simples, mas também tente não dividi-las em muitos itens separados.
edit: Editar regra
- empty: Nenhuma regra de instância foi ainda definida.
- title: Regras da instância
+ empty: Nenhuma regra do servidor foi definida.
+ title: Regras do servidor
settings:
activity_api_enabled:
- desc_html: Contagem semanais de publicações locais, utilizadores activos e novos registos
- title: Publicar estatísticas agregadas sobre atividade dos utilizadores
+ desc_html: Contagem de toots locais, usuários ativos e novos usuários semanalmente
+ title: Publicar estatísticas agregadas sobre atividade de usuários
bootstrap_timeline_accounts:
- desc_html: Separa os nomes de utilizadores por vírgulas. Funciona apenas com contas locais e desbloqueadas. O padrão quando vazio são todos os administradores locais.
- title: Seguidores predefinidos para novas contas
+ desc_html: Separe nomes de usuário através de vírgulas. Funciona apenas com contas locais e destrancadas. O padrão quando vazio são todos os administradores locais.
+ title: Usuários a serem seguidos por padrão por novas contas
contact_information:
- email: Inserir um endereço de email para tornar público
- username: Insira um nome de utilizador
+ email: E-mail
+ username: Usuário de contato
custom_css:
- desc_html: Modificar a aparência com CSS carregado em cada página
+ desc_html: Alterar o visual com CSS carregado em todas as páginas
title: CSS personalizado
default_noindex:
- desc_html: Afeta todos os utilizadores que não alteraram esta configuração
- title: Desactivar, por omissão, a indexação de utilizadores por parte dos motores de pesquisa
+ desc_html: Afeta qualquer usuário que não tenha alterado esta configuração manualmente
+ title: Optar por excluir usuários da indexação de mecanismos de pesquisa por padrão
domain_blocks:
- all: Para toda a gente
+ all: Para todos
disabled: Para ninguém
title: Mostrar domínios bloqueados
- users: Para utilizadores locais que se encontrem autenticados
+ users: Para usuários locais logados
domain_blocks_rationale:
title: Mostrar motivo
hero:
- desc_html: Apresentado na primeira página. Pelo menos 600x100px recomendados. Quando não é definido, é apresentada a miniatura da instância
- title: Imagem Hero
+ desc_html: Aparece na página inicial. Recomendado ao menos 600x100px. Se não estiver definido, a miniatura da instância é usada no lugar
+ title: Imagem de capa
mascot:
- desc_html: Apresentada em múltiplas páginas. Pelo menos 293x205px recomendados. Quando não é definida, é apresentada a mascote predefinida
- title: Imagem da mascote
+ desc_html: Mostrado em diversas páginas. Recomendado ao menos 293×205px. Quando não está definido, o mascote padrão é mostrado
+ title: Imagem do mascote
peers_api_enabled:
- desc_html: Nomes de domínio que esta instância encontrou no fediverso
+ desc_html: Nomes de domínio que essa instância encontrou no fediverso
title: Publicar lista de instâncias descobertas
preview_sensitive_media:
- desc_html: A pre-visualização de links noutros sites irá apresentar uma miniatura, mesmo que a media seja marcada como sensível
- title: Mostrar media sensível em pre-visualizações OpenGraph
+ desc_html: A prévia do link em outros sites vai incluir uma miniatura mesmo se a mídia estiver marcada como sensível
+ title: Mostrar mídia sensível em prévias OpenGraph
profile_directory:
- desc_html: Permite aos utilizadores serem descobertos
- title: Ativar directório do perfil
+ desc_html: Permitir que usuários possam ser descobertos
+ title: Ativar diretório de perfis
registrations:
closed_message:
- desc_html: Mostrar na página inicial quando registos estão encerrados<br/>Podes usar tags HTML
- title: Mensagem de registos encerrados
+ desc_html: Mostrado na página inicial quando a instância está fechada. Você pode usar tags HTML
+ title: Mensagem de instância fechada
deletion:
- desc_html: Permitir a qualquer utilizador eliminar a sua conta
- title: Permitir eliminar contas
+ desc_html: Permitir que qualquer um exclua a própria conta
+ title: Exclusão aberta de contas
min_invite_role:
disabled: Ninguém
title: Permitir convites de
require_invite_text:
- desc_html: Quando os registos exigirem aprovação manual, faça o texto "Porque se quer juntar a nós?" da solicitação de convite obrigatório, em vez de opcional
- title: Exigir que novos utilizadores preencham um texto de solicitação de convite
+ desc_html: Quando o cadastro de novas contas exigir aprovação manual, tornar obrigatório, ao invés de opcional, o texto de solicitação de convite em "Por que você deseja criar uma conta aqui?"
+ title: Exigir que novos usuários preencham um texto de solicitação de convite
registrations_mode:
modes:
- approved: Registo sujeito a aprovação
- none: Ninguém se pode registar
- open: Qualquer pessoa se pode registar
- title: Modo de registo
+ approved: Aprovação necessária para criar conta
+ none: Ninguém pode criar conta
+ open: Qualquer um pode criar conta
+ title: Modo de novos usuários
show_known_fediverse_at_about_page:
- desc_html: Quando comutado, irá mostrar a previsualização de publicações de todo o fediverse conhecido. De outro modo só mostrará publicações locais.
- title: Mostrar o fediverse conhecido na previsualização da cronologia
+ desc_html: Quando ativado, mostra toots globais na prévia da linha, se não, mostra somente toots locais
+ title: Mostrar toots globais na prévia da linha
show_staff_badge:
- desc_html: Mostrar um crachá da equipa na página de utilizador
- title: Mostrar crachá da equipa
+ desc_html: Mostrar uma insígnia de Equipe na página de usuário
+ title: Mostrar insígnia de equipe
site_description:
- desc_html: Mostrar como parágrafo na página inicial e usado como meta tag.Podes usar tags HTML, em particular <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
- title: Descrição do site
+ desc_html: Parágrafo introdutório na página inicial. Descreva o que faz esse servidor especial, e qualquer outra coisa de importante. Você pode usar tags HTML, em especial <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
+ title: Descrição da instância
site_description_extended:
- desc_html: Mostrar na página de mais informações<br/>Podes usar tags HTML
- title: Página de mais informações
+ desc_html: Um ótimo lugar para seu código de conduta, regras, diretrizes e outras coisas para diferenciar a sua instância. Você pode usar tags HTML
+ title: Informação estendida personalizada
site_short_description:
- desc_html: Mostrada na barra lateral e em etiquetas de metadados. Descreve o que o Mastodon é e o que torna esta instância especial num único parágrafo. Se deixada em branco, remete para a descrição da instância.
- title: Breve descrição da instância
+ desc_html: Mostrada na barra lateral e em etiquetas de metadados. Descreve o que é o Mastodon e o que torna esta instância especial num único parágrafo. Se deixada em branco, é substituído pela descrição da instância.
+ title: Descrição curta da instância
site_terms:
- desc_html: Podes escrever a sua própria política de privacidade, termos de serviço, entre outras coisas. Pode utilizar etiquetas HTML
+ desc_html: Você pode escrever a sua própria Política de Privacidade, Termos de Serviço, entre outras coisas. Você pode usar tags HTML
title: Termos de serviço personalizados
- site_title: Título do site
+ site_title: Nome da instância
thumbnail:
- desc_html: Usada para visualizações via OpenGraph e API. Recomenda-se 1200x630px
+ desc_html: Usada para prévias via OpenGraph e API. Recomenda-se 1200x630px
title: Miniatura da instância
timeline_preview:
- desc_html: Exibir a linha temporal pública na página inicial
- title: Visualização da linha temporal
+ desc_html: Mostra a linha do tempo pública na página inicial e permite acesso da API à mesma sem autenticação
+ title: Permitir acesso não autenticado à linha pública
title: Configurações do site
trendable_by_default:
- desc_html: Afecta as hashtags que ainda não tenham sido proibidas
- title: Permitir hashtags em tendência sem revisão prévia
+ desc_html: Afeta as hashtags que não foram reprovadas anteriormente
+ title: Permitir que hashtags fiquem em alta sem revisão prévia
trends:
- desc_html: Exibir publicamente hashtags atualmente em destaque que já tenham sido revistas anteriormente
- title: Hashtags em destaque
+ desc_html: Mostrar publicamente hashtags previamente revisadas que estão em alta
+ title: Hashtags em alta
site_uploads:
- delete: Eliminar arquivo carregado
- destroyed_msg: Upload do site eliminado com sucesso!
+ delete: Excluir arquivo enviado
+ destroyed_msg: Upload do site excluído com sucesso!
statuses:
back_to_account: Voltar para página da conta
batch:
- delete: Eliminar
- nsfw_off: NSFW OFF
- nsfw_on: NSFW ON
- deleted: Eliminado
- failed_to_execute: Falhou ao executar
+ delete: Excluir
+ nsfw_off: Desmarcar como sensível
+ nsfw_on: Marcar como sensível
+ deleted: Excluídos
+ failed_to_execute: Falha ao executar
media:
- title: Media
- no_media: Não há media
- no_status_selected: Nenhum estado foi alterado porque nenhum foi selecionado
- title: Estado das contas
- with_media: Com media
+ title: Mídia
+ no_media: Sem mídia
+ no_status_selected: Nenhum status foi modificado porque nenhum estava selecionado
+ title: Toots da conta
+ with_media: Com mídia
system_checks:
- database_schema_check:
- message_html: Existem migrações de base de dados pendentes. Por favor, execute-as para garantir que o aplicativo se comporte como esperado
rules_check:
- action: Gerir regras da instância
- message_html: Não definiu nenhuma regra para a instância.
- sidekiq_process_check:
- message_html: Nenhum processo Sidekiq em execução para a(s) fila(s) %{value}. Reveja a configuração do seu Sidekiq
+ message_html: Você não definiu nenhuma regra de servidor.
tags:
- accounts_today: Usos únicos hoje
+ accounts_today: Usos únicos de hoje
accounts_week: Usos únicos desta semana
breakdown: Descrição do consumo atual por fonte
- last_active: Última actividade
- most_popular: Mais popular
- most_recent: Mais recente
- name: Hashtag
- review: Estado da revisão
- reviewed: Revista
- title: Hashtags
- trending_right_now: Tendências agora
- unique_uses_today: "%{count} publicando hoje"
- unreviewed: Não revista
- updated_msg: Definições de hashtags actualizadas com sucesso
+ last_active: Última atividade
+ most_popular: Mais populares
+ most_recent: Mais recentes
+ review: Status da revisão
+ reviewed: Revisado
+ trending_right_now: Em alta no momento
+ unique_uses_today: "%{count} tootando hoje"
+ unreviewed: Não revisadas
+ updated_msg: Configurações de hashtag atualizadas com sucesso
title: Administração
warning_presets:
add_new: Adicionar novo
- delete: Eliminar
- edit_preset: Editar o aviso predefinido
- empty: Ainda não definiu nenhum aviso predefinido.
- title: Gerir os avisos predefinidos
+ delete: Excluir
+ edit_preset: Editar o aviso pré-definido
+ title: Gerenciar os avisos pré-definidos
admin_mailer:
+ account_invitation:
+ subject: Você foi convidado a entrar no Truth Social!
new_pending_account:
- body: Em baixo, estão os detalhes da nova conta. Pode aprovar ou rejeitar esta inscrição.
+ body: Os detalhes da nova conta estão abaixo. Você pode aprovar ou vetar.
subject: Nova conta para revisão em %{instance} (%{username})
new_report:
- body: "%{reporter} relatou %{target}"
- body_remote: Alguém de %{domain} relatou %{target}
- subject: Novo relatório sobre %{instance} (#%{id})
+ body: "%{reporter} denunciou %{target}"
+ body_remote: Alguém da instância %{domain} reportou %{target}
+ subject: Nova denúncia sobre %{instance} (#%{id})
new_trending_tag:
- body: 'A hashtag #%{name} está hoje a destacar-se, mas não foi anteriormente revista. Ela não será exibida publicamente a menos que você o permita, ou limite-se a salvar o formulário tal como está, para nunca mais ouvir falar dela.'
- subject: Nova hashtag para revisão em %{instance} (#%{name})
+ body: 'A hashtag #%{name} está em alta hoje, mas não foi previamente revisada. Ela não estará visível publicamente a menos que você aprove, ou salve o formulário do jeito que está para nunca mais ouvir falar dela.'
+ subject: Nova hashtag disponível para revisão em %{instance} (#%{name})
aliases:
- add_new: Criar pseudónimo
- created_msg: Criou com sucesso um novo pseudónimo. Pode agora iniciar a migração da conta antiga.
- deleted_msg: O pseudónimo foi eliminado com sucesso. Migrar dessa conta para esta não será mais possível.
- empty: Não tem pseudónimos.
- hint_html: Se quiser mudar de outra conta para esta, pode criar aqui um pseudónimo, que é necessário antes de poder prosseguir com a migração de seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada a partir da conta antiga</strong>.
- remove: Desvincular pseudónimo
+ add_new: Criar alias
+ created_msg: Um novo alias foi criado com sucesso. Agora você pode iniciar a mudança da conta antiga.
+ deleted_msg: Alias removido com sucesso. Não será mais possível se mudar daquela conta para esta conta.
+ empty: Você não tem alias.
+ hint_html: Se você quiser migrar de uma outra conta para esta, você pode criar um alias aqui, o que é necessário antes que você possa migrar os seguidores da conta antiga para esta. Esta ação por si só é <strong>inofensiva e reversível</strong>. <strong>A migração da conta é iniciada pela conta antiga</strong>.
+ remove: Desvincular alias
appearance:
- advanced_web_interface: Interface web avançada
- advanced_web_interface_hint: 'Se quiser utilizar toda a largura do seu ecrã, a interface web avançada permite-lhe configurar várias colunas diferentes para ver tanta informação ao mesmo tempo quanto quiser: Página inicial, notificações, cronologia federada, qualquer número de listas e hashtags.'
+ advanced_web_interface: Interface avançada de colunas
+ advanced_web_interface_hint: 'Se você deseja usar toda a sua largura de tela, a interface avançada permite que você configure muitas colunas diferentes para ver tantas informações ao mesmo tempo quanto você deseja: Página inicial, notificações, linha local, linha global, qualquer número de listas e hashtags.'
animations_and_accessibility: Animações e acessibilidade
- confirmation_dialogs: Caixas de confirmação
+ confirmation_dialogs: Diálogos de confirmação
discovery: Descobrir
localization:
body: Mastodon é traduzido por voluntários.
- guide_link: https://crowdin.com/project/mastodon/pt-PT
+ guide_link: https://br.crowdin.com/project/mastodon
guide_link_text: Todos podem contribuir.
sensitive_content: Conteúdo sensível
- toot_layout: Disposição do Toot
+ toot_layout: Layout do Toot
application_mailer:
notification_preferences: Alterar preferências de e-mail
- salutation: "%{name},"
- settings: 'Alterar preferências de email: %{link}'
+ settings: 'Alterar e-mail de preferência: %{link}'
view: 'Ver:'
view_profile: Ver perfil
- view_status: Ver publicação
+ view_status: Ver toot
applications:
- created: Aplicação criada com sucesso
- destroyed: Aplicação eliminada com sucesso
- invalid_url: O URL é inválido
- regenerate_token: Regenerar token de acesso
- token_regenerated: Token de acesso regenerado com sucesso
- warning: Cuidado com estes dados. Não partilhar com ninguém!
- your_token: O teu token de acesso
+ created: Aplicativo criado com sucesso
+ destroyed: Aplicativo excluído com sucesso
+ invalid_url: O link fornecido é inválido
+ regenerate_token: Gerar código de acesso
+ token_regenerated: Código de acesso gerado com sucesso
+ warning: Tenha cuidado com estes dados. Nunca compartilhe com alguém!
+ your_token: Seu código de acesso
auth:
- apply_for_account: Solicitar um convite
- change_password: Palavra-passe
- checkbox_agreement_html: Concordo com as <a href="%{rules_path}" target="_blank">regras da instância</a> e com os <a href="%{terms_path}" target="_blank">termos de serviço</a>
+ apply_for_account: Solicitar convite
+ change_password: Senha
+ checkbox_agreement_html: Concordo com <a href="%{rules_path}" target="_blank">as regras da instância</a> e com <a href="%{terms_path}" target="_blank">os termos de serviço</a>
checkbox_agreement_without_rules_html: Concordo com os <a href="%{terms_path}" target="_blank">termos do serviço</a>
- delete_account: Eliminar conta
- delete_account_html: Se deseja eliminar a sua conta, pode <a href="%{path}">continuar aqui</a>. Uma confirmação será solicitada.
+ delete_account: Excluir conta
+ delete_account_html: Se você deseja excluir sua conta, você pode <a href="%{path}">fazer isso aqui</a>. Uma confirmação será solicitada.
description:
- prefix_invited_by_user: "@%{name} convidou-o a juntar-se a esta instância do Mastodon!"
- prefix_sign_up: Inscreva-se hoje no Mastodon!
- suffix: Com uma conta, poderá seguir pessoas, publicar atualizações e trocar mensagens com utilizadores de qualquer instância Mastodon e muito mais!
- didnt_get_confirmation: Não recebeu o email de confirmação?
- dont_have_your_security_key: Não tem a sua chave de segurança?
- forgot_password: Esqueceste a palavra-passe?
- invalid_reset_password_token: Token de modificação da palavra-passe é inválido ou expirou. Por favor, solicita um novo.
- link_to_otp: Insere um código de duas etapas do teu telemóvel ou um código de recuperação
- link_to_webauth: Usa o teu dispositivo de chave de segurança
+ prefix_invited_by_user: "@%{name} convidou você para entrar nesta instância Mastodon!"
+ prefix_sign_up: Crie uma conta no Mastodon hoje!
+ suffix: Com uma conta, você poderá seguir pessoas, postar atualizações, trocar mensagens com usuários de qualquer instância Mastodon e muito mais!
+ didnt_get_confirmation: Não recebeu instruções de confirmação?
+ dont_have_your_security_key: Não está com a sua chave de segurança?
+ forgot_password: Esqueceu a sua senha?
+ invalid_reset_password_token: O token de redefinição de senha é inválido ou expirou. Solicite um novo token.
+ link_to_otp: Digite um código de duas etapas do seu telefone ou um código de recuperação
+ link_to_webauth: Use seu dispositivo de chave de segurança
login: Entrar
logout: Sair
- migrate_account: Mudar para uma conta diferente
- migrate_account_html: Se deseja redirecionar esta conta para uma outra pode <a href="%{path}">configurar isso aqui</a>.
- or_log_in_with: Ou iniciar sessão com
- providers:
- cas: CAS
- saml: SAML
- register: Registar
- registration_closed: "%{instance} não está a aceitar novos membros"
+ migrate_account: Mudar-se para outra conta
+ migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
+ or_log_in_with: Ou entre com
+ register: Criar conta
+ registration_closed: "%{instance} não está aceitando novos membros"
resend_confirmation: Reenviar instruções de confirmação
- reset_password: Criar nova palavra-passe
- security: Alterar palavra-passe
- set_new_password: Editar palavra-passe
+ reset_password: Redefinir senha
+ security: Segurança
+ set_new_password: Definir uma nova senha
setup:
- email_below_hint_html: Se o endereço de e-mail abaixo estiver incorreto, pode alterá-lo aqui e receber um novo e-mail de confirmação.
- email_settings_hint_html: O e-mail de confirmação foi enviado para %{email}. Se esse endereço de e-mail não estiver correcto, pode alterá-lo nas definições da conta.
- title: Configuração
+ email_below_hint_html: Se o endereço de e-mail abaixo não for seu, você pode alterá-lo aqui e receber um novo e-mail de confirmação.
+ email_settings_hint_html: O e-mail de confirmação foi enviado para %{email}. Se esse endereço de e-mail não estiver correto, você pode alterá-lo nas configurações da conta.
+ title: Configurações
status:
- account_status: Estado da conta
- confirming: A aguardar que conclua a confirmação do e-mail.
- functional: A sua conta está totalmente operacional.
- pending: A sua inscrição está pendente de revisão pela nossa equipa. Isso pode demorar algum tempo. Receberá um e-mail se a sua conta for aprovada.
- redirecting_to: A sua conta está inativa porque está atualmente a ser redirecionada para %{acct}.
- too_fast: Formulário enviado muito rapidamente, tente novamente.
- trouble_logging_in: Problemas em iniciar sessão?
+ account_status: Status da conta
+ confirming: Confirmação por e-mail pendente.
+ functional: Sua conta está totalmente operacional.
+ pending: Sua solicitação está com revisão pendente por parte de nossa equipe. Você receberá um e-mail se ela for aprovada.
+ redirecting_to: Sua conta está inativa porque atualmente está redirecionando para %{acct}.
+ too_fast: O formulário foi enviado muito rapidamente, tente novamente.
+ trouble_logging_in: Problemas para entrar?
use_security_key: Usar chave de segurança
authorize_follow:
- already_following: Tu já estás a seguir esta conta
- already_requested: Já enviou anteriormente um pedido para seguir esta conta
+ already_following: Você já segue
+ already_requested: Você já enviou uma solicitação para seguir esta conta
error: Infelizmente, ocorreu um erro ao buscar a conta remota
follow: Seguir
- follow_request: 'Enviaste uma solicitação de seguidor para:'
- following: 'Sucesso! Agora estás a seguir a:'
+ follow_request: 'Você mandou solicitação para seguir para:'
+ following: 'Sucesso! Você agora está seguindo:'
post_follow:
- close: Ou podes simplesmente fechar esta janela.
- return: Voltar ao perfil do utilizador
+ close: Ou você pode simplesmente fechar esta janela.
+ return: Mostrar o perfil do usuário
web: Voltar à página inicial
title: Seguir %{acct}
challenge:
confirm: Continuar
- hint_html: "<strong>Dica:</strong> Não vamos pedir novamente a sua senha durante a próxima hora."
+ hint_html: "<strong>Dica:</strong> Não pediremos novamente sua senha pela próxima hora."
invalid_password: Senha inválida
- prompt: Confirme a sua senha para continuar
+ prompt: Confirme sua senha para continuar
+ chats:
+ errors:
+ creator_started: Não foi possível aceitar esse chat já que você o iniciou
+ blocked_constraints: Não foi possível executar a solicitação devido a restrições bloqueadas
+ unfollowed_and_left_chat_by_user: This user has left the chat and no longer follows you
crypto:
errors:
invalid_key: não é uma chave Ed25519 ou Curve25519 válida
invalid_signature: não é uma assinatura Ed25519 válida
date:
formats:
- default: "%d %b %Y"
- with_month_name: "%d %B %Y"
+ default: "%d %b, %Y"
+ with_month_name: "%d de %b de %Y"
datetime:
distance_in_words:
- about_x_hours: "%{count}h"
- about_x_months: "%{count} meses"
- about_x_years: "%{count} anos"
- almost_x_years: "%{count} anos"
- half_a_minute: Justo agora
- less_than_x_minutes: "%{count} meses"
- less_than_x_seconds: Justo agora
- over_x_years: "%{count} anos"
- x_days: "%{count} dias"
- x_minutes: "%{count} minutos"
- x_months: "%{count} meses"
- x_seconds: "%{count} segundos"
+ about_x_months: "%{count}m"
+ about_x_years: "%{count}a"
+ almost_x_years: "%{count}a"
+ half_a_minute: Agora
+ less_than_x_seconds: Agora
+ over_x_years: "%{count}a"
+ x_minutes: "%{count}min"
+ x_months: "%{count}m"
+ x_seconds: "%{count}seg"
deletes:
- challenge_not_passed: A informação que introduziu não estava correta
- confirm_password: Introduza a sua palavra-passe atual para verificar a sua identidade
- confirm_username: Introduza o seu nome de utilizador para confirmar o procedimento
- proceed: Eliminar conta
- success_msg: A sua conta foi eliminada com sucesso
+ challenge_not_passed: A informação que você inseriu não está correta
+ confirm_password: Digite a sua senha atual para verificar a sua identidade
+ confirm_username: Digite seu nome de usuário para confirmar o procedimento
+ proceed: Excluir conta
+ success_msg: Sua conta foi excluída com sucesso
warning:
- before: 'Antes de continuar, por favor leia cuidadosamente estas notas:'
- caches: O conteúdo que foi armazenado em cache por outras instâncias pode persistir
- data_removal: As suas publicações e outros dados serão eliminados permanentemente
- email_change_html: Pode <a href="%{path}">alterar o seu endereço de e-mail</a> sem eliminar a sua conta
- email_contact_html: Se ainda não chegou, pode enviar um e-mail a <a href="mailto:%{email}">%{email}</a> para obter ajuda
- email_reconfirmation_html: Se não recebeu o e-mail de confirmação, pode <a href="%{path}">pedi-lo novamente</a>
- irreversible: Não será possível restaurar ou reativar sua conta
- more_details_html: Para mais detalhes, leia a <a href="%{terms_path}">política de privacidade</a>.
- username_available: O seu nome de utilizador ficará novamente disponível
- username_unavailable: O seu nome de utilizador permanecerá indisponível
+ before: 'Antes de prosseguir, por favor leia com cuidado:'
+ caches: Conteúdo que foi armazenado em cache por outras instâncias pode continuar a existir
+ data_removal: Seus toots e outros dados serão removidos permanentemente
+ email_change_html: Você pode <a href="%{path}">alterar seu endereço de e-mail</a> sem excluir sua conta
+ email_contact_html: Se você ainda não recebeu, você pode enviar um e-mail pedindo ajuda para <a href="mailto:%{email}">%{email}</a>
+ email_reconfirmation_html: Se você não está recebendo o e-mail de confirmação, você pode <a href="%{path}">solicitá-lo novamente</a>
+ irreversible: Você não conseguirá restaurar ou reativar a sua conta
+ more_details_html: Para mais detalhes, consulte a <a href="%{terms_path}">Política de Privacidade</a>.
+ username_available: Seu nome de usuário ficará disponível novamente
+ username_unavailable: Seu nome de usuário permanecerá indisponível
directories:
- directory: Dirétorio de perfil
- explanation: Descobre utilizadores com base nos seus interesses
- explore_mastodon: Explorar %{title}
+ directory: Diretório de perfis
+ explanation: Descobrir usuários baseado em seus interesses
+ explore_mastodon: Explore o %{title}
domain_validator:
invalid_domain: não é um nome de domínio válido
errors:
- '400': O pedido que submeteu foi inválido ou mal formulado.
- '403': Não tens a permissão necessária para ver esta página.
- '404': A página que estás a procurar não existe.
+ '400': A solicitação enviada é inválida ou incorreta.
+ '403': Você não tem permissão para ver esta página.
+ '404': A página pela qual você está procurando não existe.
'406': Esta página não está disponível no formato solicitado.
- '410': A página que estás a procurar não existe mais.
+ '410': A página que você procura não existe mais.
'422':
- content: "A verificação de segurança falhou. \nDesativaste o uso de cookies?"
- title: A verificação de segurança falhou
- '429': Desacelerado
+ content: Falha na verificação de segurança. Você está bloqueando cookies?
+ title: Falha na verificação de segurança
+ '429': Excesso de solicitações
'500':
- content: Desculpe, mas algo correu mal.
- title: Esta página não está correta
- '503': A página não pôde ser apresentada devido a uma falha temporária do servidor.
- noscript_html: Para usar o aplicativo web do Mastodon, por favor ativa o JavaScript. Alternativamente, experimenta um dos <a href="%{apps_path}">apps nativos</a> para o Mastodon na sua plataforma.
+ content: Desculpe, algo deu errado por aqui.
+ title: Esta página não está certa
+ '503': A página não pôde ser carregada devido a uma falha temporária do servidor.
+ noscript_html: Para usar o aplicativo web do Mastodon, por favor ative o JavaScript. Ou, se quiser, experimente um dos <a href="%{apps_path}">aplicativos nativos</a> para o Mastodon em sua plataforma.
+ api:
+ '401': Esse método requer um usuário autenticado
+ '403': Essa ação não é permitida
+ '404': Registro não encontrado
+ '503': Ocorreu um problema temporário ao atender sua solicitação. Tente novamente.
+ assertion: Não foi possível verificar sua afirmação
+ attestation: Não foi possível verificar seu atestado
+ data_fetch: Não foi possível resgatar os dados remotos
+ duplicate: Registro duplicado
+ login_disabled: No momento seu login está desativado
+ login_pending: No momento seu login está aguardando aprovação
+ missing_email: Está faltando um endereço de e-mail confirmado para o seu login
+ ssl: Não foi possível verificar o Certificado SSL remoto
+ outside_scopes: Essa ação está fora do escopo autorizado
+ unauthorized: Não autorizado
existing_username_validator:
- not_found: não foi possível encontrar um utilizador local com esse nome
+ not_found: não foi possível encontrar um usuário local com esse nome de usuário
not_found_multiple: não foi possível encontrar %{usernames}
exports:
archive_takeout:
date: Data
- download: Descarregar o teu arquivo
- hint_html: Pode pedir um arquivo das suas <strong>publicações e ficheiros de media carregados</strong>. Os dados no ficheiro exportado estarão no formato ActivityPub, que pode ser lido com qualquer software compatível. Pode solicitar um arquivo a cada 7 dias.
- in_progress: A compilar o seu arquivo...
- request: Pede o teu arquivo
+ download: Baixe o seu arquivo
+ hint_html: Você pode pedir um arquivo dos seus <strong>toots e mídias enviadas</strong>. Os dados exportados estarão no formato ActivityPub, que podem ser lidos por qualquer software compatível. Você pode pedir um arquivo a cada 7 dias.
+ in_progress: Preparando o seu arquivo...
+ request: Solicitar o seu arquivo
size: Tamanho
- blocks: Bloqueaste
- bookmarks: Itens Salvos
- csv: CSV
+ blocks: Você bloqueou
+ bookmarks: Marcadores
domain_blocks: Bloqueios de domínio
lists: Listas
- mutes: Tens em silêncio
- storage: Armazenamento de media
+ mutes: Você silenciou
+ storage: Armazenamento de mídia
featured_tags:
- add_new: Adicionar nova
+ add_new: Adicionar hashtag
errors:
- limit: Já atingiste o limite máximo de hashtags
- hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são exibidas de forma bem visível no seu perfil público e permitem que as pessoas consultem as suas publicações públicas especificamente sob essas hashtags. São uma ótima ferramenta para manter o controlo de trabalhos criativos ou projetos de longo prazo."
+ limit: Você atingiu o limite de hashtags em destaque
+ hint_html: "<strong>O que são hashtags em destaque?</strong> Elas são mostradas no seu perfil público e permitem que as pessoas acessem seus toots públicos que contenham especificamente essas hashtags. São uma excelente ferramenta para acompanhar os trabalhos criativos ou os projetos de longo prazo."
filters:
contexts:
account: Perfis
- home: Cronologia inicial
+ home: Página inicial
notifications: Notificações
- public: Cronologias públicas
- thread: Conversações
+ public: Linhas públicas
+ thread: Conversas
edit:
- title: Editar filtros
+ title: Editar filtro
errors:
- invalid_context: Inválido ou nenhum contexto fornecido
- invalid_irreversible: Filtragem irreversível só funciona no contexto das notificações ou do início
+ invalid_context: Contexto inválido ou nenhum contexto informado
+ invalid_irreversible: O filtro irreversível só funciona com os contextos página inicial e notificações
index:
- delete: Eliminar
- empty: Não tem filtros.
+ delete: Remover
+ empty: Sem filtros.
title: Filtros
new:
- title: Adicionar novo filtro
+ title: Adicionar filtro
footer:
- developers: Responsáveis pelo desenvolvimento
+ developers: Desenvolvedores
more: Mais…
resources: Recursos
- trending_now: Tendências atuais
+ trending_now: Em alta no momento
generic:
all: Tudo
- changes_saved_msg: Alterações guardadas!
+ changes_saved_msg: Alterações salvas com sucesso!
copy: Copiar
- delete: Eliminar
+ delete: Excluir
no_batch_actions_available: Nenhuma ação em lote disponível nesta página
order_by: Ordenar por
- save_changes: Guardar alterações
+ save_changes: Salvar alterações
validation_errors:
- one: Algo não está correcto. Por favor vê o erro abaixo
- other: Algo não está correto. Por favor vê os %{count} erros abaixo
+ one: Algo errado não está certo! Por favor, analise o erro abaixo
+ other: Algo errado não está certo! Por favor, analise os %{count} erros abaixo
+ groups:
+ errors:
+ pending_request_conflict: O proprietário ou administrador do grupo já tomou medidas sobre esta solicitação.
+ too_many_admins: Você pode atribuir até %{count} administradores para o grupo.
html_validator:
- invalid_markup: 'contém marcação HTML inválida: %{error}'
+ invalid_markup: 'contém HTML inválido: %{error}'
identity_proofs:
active: Ativo
authorize: Sim, autorizar
- authorize_connection_prompt: Autorizar esta conexão criptográfica?
+ authorize_connection_prompt: Autorizar essa conexão criptográfica?
errors:
- failed: A conexão criptográfica falhou. Por favor, tente novamente a partir de %{provider}.
+ failed: Falha na conexão criptográfica. Por favor, tente novamente a partir de %{provider}.
keybase:
- invalid_token: Os tokens Keybase são hashes de assinaturas e devem conter 66 caracteres hexadecimais
- verification_failed: O Keybase não reconhece este token como uma assinatura do utilizador do Keybase %{kb_username}. Por favor, tente novamente a partir do Keybase.
- wrong_user: Não é possível criar um comprovativo para %{proving} enquanto estiver conetado como %{current}. Inicie sessão como %{proving} e tente novamente.
- explanation_html: Aqui pode conetar criptograficamente as suas outras identidades, tais como um perfil Keybase. Isto permite que outras pessoas lhe enviem mensagens encriptadas e confiar em conteúdo que você lhes envia.
- i_am_html: Sou %{username} em %{service}.
+ invalid_token: Tokens keybase são hashes de assinatura e devem conter 66 caracteres hexa
+ verification_failed: Keybase não reconhece esse token como uma assinatura do usuário keybase %{kb_username}. Por favor, tente novamente a partir do Keybase.
+ wrong_user: Não foi possível criar uma prova para %{proving} como %{current}. Entre como %{proving} e tente novamente.
+ explanation_html: Você pode conectar criptograficamente suas outras identidades, tais quais seu perfil Keybase. Isso permite outras pessoas de lhe enviarem mensagens criptografadas e confiar no conteúdo que você as envia.
+ i_am_html: Eu sou %{username} em %{service}.
identity: Identidade
inactive: Inativo
- publicize_checkbox: 'E publique isso:'
- publicize_toot: 'Está comprovado! Eu sou %{username} em %{service}: %{url}'
- remove: Remover comprovatido da conta
- removed: Comprovativo removido da conta com sucesso
- status: Estado da verificação
+ publicize_checkbox: 'E toote isso:'
+ publicize_toot: 'Está provado! Eu sou %{username} no %{service}: %{url}'
+ remove: Remover prova da conta
+ removed: Prova removida da conta com sucesso
+ status: Status da verificação
view_proof: Ver prova
imports:
- errors:
- over_rows_processing_limit: contém mais de %{count} linhas
modes:
merge: Juntar
- merge_long: Manter os registos existentes e adicionar novos registos
- overwrite: Escrever por cima
- overwrite_long: Substituir os registos atuais pelos novos
- preface: Podes importar dados que tenhas exportado de outra instância, como a lista de pessoas que segues ou bloqueadas.
- success: Os teus dados foram enviados com sucesso e serão processados em breve
+ merge_long: Manter os registros existentes e adicionar novos
+ overwrite: Sobrescrever
+ overwrite_long: Substituir os registros atuais com os novos
+ preface: Você pode importar dados que você exportou de outra instância, como a lista de pessoas que você segue ou bloqueou.
+ success: Os seus dados foram enviados com sucesso e serão processados em instantes
types:
blocking: Lista de bloqueio
- bookmarks: Itens salvos
+ bookmarks: Marcadores
domain_blocking: Lista de domínios bloqueados
- following: Lista de pessoas que estás a seguir
- muting: Lista de utilizadores silenciados
+ following: Pessoas que você segue
+ muting: Lista de silenciados
upload: Enviar
in_memoriam_html: Em memória.
invites:
@@ -992,143 +932,231 @@
'604800': 1 semana
'86400': 1 dia
expires_in_prompt: Nunca
- generate: Gerar
- invited_by: 'Tu foste convidado por:'
+ generate: Gerar convite
+ invited_by: 'Você recebeu convite de:'
max_uses:
one: 1 uso
other: "%{count} usos"
max_uses_prompt: Sem limite
- prompt: Gerar e partilhar ligações com outras pessoas para permitir acesso a essa instância
+ prompt: Gere e compartilhe links para permitir acesso a essa instância
table:
- expires_at: Expira
+ expires_at: Expira em
uses: Usos
title: Convidar pessoas
lists:
errors:
- limit: Número máximo de listas alcançado
+ limit: Você atingiu o máximo de listas
media_attachments:
validations:
- images_and_video: Não é possível anexar um vídeo a uma publicação que já contém imagens
- not_ready: Não é possível anexar arquivos que não terminaram de ser processados. Tente novamente daqui a pouco!
+ images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens
+ not_ready: Não é possível anexar arquivos cujo processamento ainda não foi concluído. Tente novamente daqui a pouco!
too_many: Não é possível anexar mais de 4 arquivos
migrations:
- acct: username@domain da nova conta
+ acct: Mudou-se para
cancel: Cancelar redirecionamento
- cancel_explanation: Cancelar o redirecionamento irá reativar a sua conta atual, mas não trará de volta os seguidores que foram migrados para essa conta.
- cancelled_msg: Cancelou com sucesso o redireccionamento.
+ cancel_explanation: Cancelar o redirecionamento reativará a sua conta atual, mas não trará de volta os seguidores que não foram migrados para aquela conta.
+ cancelled_msg: Redirecionamento cancelado com sucesso.
errors:
- already_moved: é a mesma conta para a qual já migrou
- missing_also_known_as: não está a referenciar esta conta
- move_to_self: não pode ser conta atual
- not_found: não pode ser encontrado
+ already_moved: é a mesma conta que você migrou
+ missing_also_known_as: não está referenciando esta conta
+ move_to_self: não pode ser a conta atual
+ not_found: não pôde ser encontrado
on_cooldown: Você está no período de espera
- followers_count: Seguidores no momento da migração
- incoming_migrations: A migrar de uma conta diferente
- incoming_migrations_html: Para migrar de outra conta para esta, primeiro você precisa <a href="%{path}">criar um pseudónimo</a>.
- moved_msg: A sua conta está agora a ser redireccionada para %{acct} e os seus seguidores estão a ser transferidos.
- not_redirecting: A sua conta não está atualmente a ser redirecionada para nenhuma outra conta.
- on_cooldown: Migrou recentemente a sua conta. Esta função ficará disponível novamente em %{count} dias.
- past_migrations: Migrações anteriores
+ followers_count: Seguidores no momento da mudança
+ incoming_migrations: Migrando de outra conta
+ incoming_migrations_html: Para mover de outra conta para esta, você precisa <a href="%{path}">criar um alias</a>.
+ moved_msg: Agora sua conta está redirecionando para %{acct} e seus seguidores estão sendo movidos.
+ not_redirecting: Sua conta não está redirecionando para nenhuma outra conta atualmente.
+ on_cooldown: Você migrou recentemente sua conta. Esta função ficará disponível novamente em %{count} dias.
+ past_migrations: Migrações passadas
proceed_with_move: Migrar seguidores
- redirected_msg: A sua conta está agora a ser redireccionada para %{acct}.
- redirecting_to: A sua conta está a ser redireccionada para %{acct}.
+ redirected_msg: Agora sua conta está redirecionando para %{acct}.
+ redirecting_to: Sua conta está redirecionando para %{acct}.
set_redirect: Definir redirecionamento
warning:
backreference_required: A nova conta deve primeiro ser configurada para que esta seja referenciada
- before: 'Antes de continuar, leia cuidadosamente estas notas:'
- cooldown: Após a migração, há um período de tempo de espera durante o qual não poderá voltar a migrar
- disabled_account: Posteriormente, a sua conta atual não será totalmente utilizável. No entanto, continuará a ter acesso à exportação de dados, bem como à reativação.
- followers: Esta ação irá migrar todos os seguidores da conta atual para a nova conta
- only_redirect_html: Em alternativa, pode <a href="%{path}">apenas colocar um redireccionamento no seu perfil</a>.
- other_data: Nenhum outro dado será migrado automaticamente
- redirect: O perfil da sua conta atual será atualizado com um aviso de redirecionamento e será excluído das pesquisas
+ before: 'Antes de prosseguir, por favor leia com cuidado:'
+ cooldown: Depois de se mudar, há um período de espera para poder efetuar uma nova mudança
+ disabled_account: Sua conta não estará totalmente funcional ao término deste processo. Entretanto, você terá acesso à exportação de dados bem como à reativação.
+ followers: Esta ação moverá todos os seguidores da conta atual para a nova conta
+ only_redirect_html: Alternativamente, você pode <a href="%{path}">apenas colocar um redirecionamento no seu perfil</a>.
+ other_data: Nenhum outro dado será movido automaticamente
+ redirect: O perfil atual da sua conta será atualizado com um aviso de redirecionamento e também será excluído das pesquisas
moderation:
title: Moderação
move_handler:
- carry_blocks_over_text: Este utilizador migrou de %{acct}, que você tinha bloqueado.
- carry_mutes_over_text: Este utilizador migrou de %{acct}, que você tinha silenciado.
- copy_account_note_text: 'Este utilizador migrou de %{acct}, aqui estão as suas notas anteriores sobre ele:'
+ carry_blocks_over_text: Este usuário mudou de %{acct}, que você havia bloqueado.
+ carry_mutes_over_text: Este usuário mudou de %{acct}, que você havia silenciado.
+ copy_account_note_text: 'Este usuário saiu de %{acct}, aqui estão suas notas anteriores sobre ele:'
notification_mailer:
+ chat:
+ subject: "Nova mensagem de %{name}"
+ subject_android: "lhe enviou uma mensagem"
+ sent_message: "Nova mensagem"
digest:
action: Ver todas as notificações
- body: Aqui tens um breve resumo do que perdeste desde o último acesso a %{since}
- mention: "%{name} mencionou-te em:"
+ body: Aqui está um breve resumo das mensagens que você não viu desde seu último acesso em %{since}
+ mention: "%{name} mencionou você em:"
new_followers_summary:
- one: Tens um novo seguidor! Boa!
- other: Tens %{count} novos seguidores! Fantástico!
+ one: Além disso, enquanto estava fora você ganhou um novo seguidor! Oba!
+ other: Além disso, enquanto estava fora você ganhou %{count} novos seguidores! Incrível!
subject:
- one: "1 nova notificação desde o último acesso \U0001F418"
- other: "%{count} novas notificações desde o último acesso \U0001F418"
- title: Enquanto estiveste ausente…
+ one: "1 nova notificação desde seu último acesso \U0001F418"
+ other: "%{count} novas notificações desde seu último acesso \U0001F418"
+ subject_android:
+ one: "1 nova notificação desde seu último acesso \U0001F418"
+ other: "%{count} novas notificações desde seu último acesso \U0001F418"
+ title: Na sua ausência...
favourite:
- body: 'O teu post foi adicionado aos favoritos por %{name}:'
- subject: "%{name} adicionou o teu post aos favoritos"
+ body: "Sua postagem foi curtida por %{name}:"
+ subject: "%{name} curtiu sua postagem"
+ subject_android: "gostou da sua Truth"
title: Novo favorito
+ favourite_group:
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth"
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth"
+ group_favourite:
+ body: "Sua postagem foi curtida por %{name}:"
+ subject: "%{name} curtiu sua postagem"
+ subject_android: "gostou da sua Truth em %{group}"
+ title: Novo favorito
+ group_favourite_group:
+ subject: "%{name} + %{count_others} %{actor} curtiram sua Truth"
+ subject_android: "%{name} e mais %{count_others} pessoas gostaram da sua Truth em %{group}"
follow:
- body: "%{name} é teu seguidor!"
- subject: "%{name} começou a seguir-te"
+ body: "%{name} está seguindo você!"
+ subject: "%{name} está seguindo você"
+ subject_android: "está te seguindo agora"
title: Novo seguidor
+ follow_group:
+ subject: "%{name} + %{count_others} %{actor} seguiram você"
+ subject_android: "%{name} e %{count_others} outros seguiram você"
follow_request:
- action: Gerir pedidos de seguidores
- body: "%{name} solicitou autorização para te seguir"
- subject: 'Seguidor pendente: %{name}'
+ action: Gerenciar solicitações de seguidores
+ body: "%{name} pediu para seguir você"
+ subject: 'Seguidor aguardando: %{name}'
+ subject_android: 'pediu para te seguir'
title: Nova solicitação de seguidor
mention:
action: Responder
- body: 'Foste mencionado por %{name}:'
- subject: "%{name} mencionou-te"
+ body: "Você foi mencionado por %{name} em:"
+ subject: "Você foi mencionado por %{name}"
+ subject_android: "mencionou você em uma Truth"
title: Nova menção
+ mention_group:
+ subject: "%{name} + %{count_others} %{actor} mencionaram você"
+ subject_android: "%{name} e %{count_others} outros mencionaram você"
+ group_mention:
+ action: Responder
+ body: "Você foi mencionado por %{name} em:"
+ subject: "Você foi mencionado por %{name}"
+ subject_android: "mencionou você em %{group}"
+ title: Nova menção
+ group_mention_group:
+ subject: "%{name} + %{count_others} %{actor} mencionaram você"
+ subject_android: "%{name} e %{count_others} outros mencionaram você"
poll:
- subject: Uma votação realizada por %{name} terminou
+ subject: Uma enquete postada por %{name} foi encerrada
+ subject_android: "concluiu uma enquete"
reblog:
- body: 'O teu post foi partilhado por %{name}:'
- subject: "%{name} partilhou o teu post"
- title: Nova partilha
+ body: "Sua Truth foi RePostada por %{name}:"
+ subject: "%{name} RePostou sua Truth"
+ subject_android: "ReTruthed sua Truth"
+ title: Nova RePostagem
+ reblog_group:
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth"
+ subject_android: "%{name} e mais %{count_others} outros ReTruthed sua Truth"
+ group_reblog:
+ body: "Sua Truth foi RePostada por %{name}:"
+ subject: "%{name} RePostou sua Truth"
+ subject_android: "ReTruthed sua Truth de %{group}"
+ title: Nova RePostagem
+ group_reblog_group:
+ subject: "%{name} + %{count_others} %{actor} RePostaram sua Truth"
+ subject_android: "%{name} e %{count_others} outros ReTruthed a sua Truth de grupo"
+ group_request:
+ body: '%{name} wants to join your group:'
+ subject: "%{name} wants to join your group"
+ subject_android: "você tem uma nova solicitação de membro"
+ title: Group Join Request
status:
- subject: "%{name} acabou de publicar"
+ subject: "%{name} acabou de postar"
+ subject_android: "acabou de postar"
+ group_promoted:
+ body: You are now an admin for %{group}
+ subject: You are now an admin for %{group}
+ subject_android: "agora você é um administrador"
+ title: You're an admin
+ group_demoted:
+ body: You are no longer an admin for %{group}
+ subject: You are no longer an admin for %{group}
+ subject_android: "você não é mais administrador"
+ title: You're no longer an admin
+ user_approved:
+ edit_profile_action: Ir para o perfil
+ edit_profile_step: Para terminar, não deixe de personalizar seu perfil carregando um avatar, um título, mudando seu nome escolhido e muito mais. Se quiser avaliar novos seguidores antes de aprová-los, você pode bloquear sua conta.
+ explanation: Estamos super empolgados com sua chegada à nossa comunidade de Buscadores da Verdade. Acreditamos na liberdade de expressão e incentivamos todos os pontos de vista, já que não discriminamos ninguém devido a uma ideologia política.
+ extra_step: Além disso, gostaríamos de lembrar que somos uma plataforma nova e ainda estamos corrigindo vários bugs na nossa tecnologia. Podemos garantir que estamos trabalhando duro para aprimorar tudo o mais rápido possível! Obrigado!
+ final_action: Comece a postar
+ final_step: 'Comece a postar! Mesmo sem nenhum seguidor suas postagens públicas podem ser vistas por outras pessoas, por exemplo, na timeline local e nas hashtags. Você pode querer se apresentar com a hashtag #apresentações.'
+ full_handle: Seu identificador completo (handle)
+ full_handle_hint: É o que você quer dizer aos seus amigos para que possam seguir você ou lhe enviar mensagens de outro servidor.
+ review_preferences_action: Mudar preferências
+ review_preferences_step: Não deixe de configurar suas preferências, como os e-mails que quer receber ou o nível de privacidade padrão para as suas postagens. Se não costuma sentir enjoo você pode optar por reproduzir GIFs automaticamente.
+ subject: "Sua espera terminou! Toque aqui para começar a usar o Truth Social."
+ subject_android: "Sua espera terminou! Toque aqui para começar a usar o Truth Social."
+ web:
+ subject: "Bem-vindo(a) ao Truth, onde a Verdade impera"
+ tip_federated_timeline: A timeline federada é uma ampla visão ao vivo da rede do Truth. Mas só inclui as pessoas que seus vizinhos estão seguindo, então não é uma visão completa.
+ tip_following: Você segue os administradores do seu servidor por padrão Para encontrar mais gente interessante, verifique sua timeline local e federada.
+ tip_local_timeline: A timeline local é uma visão ao vivo das pessoas em %{instance}. São seus vizinhos de porta!
+ tip_mobile_webapp: Se o seu navegador móvel sugerir que você adicione o Truth à sua tela inicial, você poderá receber notificações por push. O Truth funciona de muitas maneiras como um aplicativo nativo!
+ tips: Dicas
+ title: Bem-vindo(a) ao Truth Social, %{name}!
+ verify_sms_prompt:
+ subject: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma!
+ subject_android: Sua conta já está ativa no Truth Social! Atualize para a versão mais recente e entre para a nossa turma!
notifications:
email_events: Eventos para notificações por e-mail
- email_events_hint: 'Selecione os eventos para os quais deseja receber notificações:'
- other_settings: Outras opções de notificações
+ email_events_hint: 'Selecione os eventos que deseja receber notificações:'
+ other_settings: Outras opções para notificações
number:
human:
decimal_units:
- format: "%n%u"
units:
- billion: MM
- million: M
- quadrillion: Q
- thousand: mil
- trillion: T
+ billion: BI
+ million: MI
+ quadrillion: QUA
+ thousand: MIL
+ trillion: TRI
otp_authentication:
- code_hint: Introduz o código gerado pela tua aplicação de autenticação para confirmar
- description_html: Se ativar a <strong>autenticação em duas etapas</strong>, para entrar na sua conta terá de ter consigo o seu telefone, que vai gerar os tokens necessários à validação do seu acesso.
- enable: Ativar
- instructions_html: "<strong>Digitalize este código QR no Google Authenticator ou num aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que introduzir para aceder à sua conta."
- manual_instructions: 'Se você não consegue digitalizar o código QR e precisa introduzi-lo manualmente, aqui está o código em texto:'
+ code_hint: Digite o código gerado pelo seu aplicativo autenticador para confirmar
+ description_html: Se você habilitar a <strong>autenticação de dois fatores</strong> usando um aplicativo autenticador, o login exigirá que você esteja com o seu telefone, que gerará tokens para você entrar.
+ enable: Habilitar
+ instructions_html: "<strong>Escaneie este código QR no Google Authenticator ou em um aplicativo TOTP similar no seu telefone</strong>. A partir de agora, esse aplicativo irá gerar tokens que você terá que digitar ao fazer login."
+ manual_instructions: 'Se você não pode escanear o código QR e precisa digitá-lo manualmente, aqui está o segredo em texto:'
setup: Configurar
- wrong_code: O código introduzido é inválido! A hora do servidor e a hora do dispositivo estão corretos?
+ wrong_code: O código que você inseriu é inválido!
pagination:
- newer: Mais nova
- next: Seguinte
- older: Mais velha
+ newer: Mais novo
+ next: Próximo
+ older: Mais antigo
prev: Anterior
- truncate: "&hellip;"
polls:
errors:
- already_voted: Tu já votaste nesta sondagem
+ already_voted: Enquete votada
duplicate_options: contém itens duplicados
- duration_too_long: está demasiado à frente no futuro
- duration_too_short: é demasiado cedo
- expired: A sondagem já terminou
- invalid_choice: A opção de voto escolhida não existe
- over_character_limit: não pode ter mais do que %{max} caracteres cada um
- too_few_options: tem de ter mais do que um item
- too_many_options: não pode conter mais do que %{max} itens
+ duration_too_long: é muito longe no futuro
+ duration_too_short: é curto demais
+ expired: A enquete já terminou
+ invalid_choice: Opção inválida
+ over_character_limit: não pode ter mais que %{max} caracteres em cada
+ too_few_options: deve ter mais que um item
+ too_many_options: não pode ter mais que %{max} itens
preferences:
other: Outro
posting_defaults: Padrões de publicação
- public_timelines: Cronologias públicas
+ public_timelines: Linhas públicas
reactions:
errors:
limit_reached: Limite de reações diferentes atingido
@@ -1136,9 +1164,9 @@
relationships:
activity: Atividade da conta
dormant: Inativo
- follow_selected_followers: Seguir seguidores selecionados
+ follow_selected_followers: Seguir os seguidores selecionados
followers: Seguidores
- following: A seguir
+ following: Seguindo
invited: Convidado
last_active: Última atividade
most_recent: Mais recente
@@ -1147,36 +1175,36 @@
primary: Primário
relationship: Relação
remove_selected_domains: Remover todos os seguidores dos domínios selecionados
- remove_selected_followers: Remover seguidores selecionados
- remove_selected_follows: Deixar de seguir os utilizadores selecionados
- status: Estado da conta
+ remove_selected_followers: Remover os seguidores selecionados
+ remove_selected_follows: Deixar de seguir usuários selecionados
+ status: Status da conta
remote_follow:
- acct: Introduza o seu utilizador@domínio do qual quer seguir
- missing_resource: Não foi possível achar a URL de redirecionamento para sua conta
- no_account_html: Não tens uma conta? Tu podes <a href='%{sign_up_path}' target='_blank'> aderir aqui</a>
- proceed: Prossiga para seguir
- prompt: 'Você vai seguir:'
- reason_html: "<strong> Porque é este passo necessário?</strong> <code>%{instance}</code> pode não ser a instância onde você está registado. Por isso, precisamos de o redirecionar para a sua instância de origem em primeiro lugar."
+ acct: Digite o seu usuário@domínio para continuar
+ missing_resource: Não foi possível encontrar o link de redirecionamento para sua conta
+ no_account_html: Não tem uma conta? Você pode <a href='%{sign_up_path}' target='_blank'>criar uma aqui</a>
+ proceed: Continue para seguir
+ prompt: 'Você seguirá:'
+ reason_html: "<strong>Por que esse passo é necessário?</strong> <code>%{instance}</code> pode não ser a instância onde você se hospedou, então precisamos redirecionar você para a sua instância primeiro."
remote_interaction:
favourite:
- proceed: Prosseguir para os favoritos
- prompt: 'Queres favoritar esta publicação:'
+ proceed: Continue para favoritar
+ prompt: 'Você favoritará este toot:'
reblog:
- proceed: Prosseguir com partilha
- prompt: 'Queres partilhar esta publicação:'
+ proceed: Continue para dar boost
+ prompt: 'Você dará boost neste toot:'
reply:
- proceed: Prosseguir com resposta
- prompt: 'Queres responder a esta publicação:'
+ proceed: Continue para responder
+ prompt: 'Você responderá este toot:'
scheduled_statuses:
- over_daily_limit: Excedeste o limite de %{limit} publicações agendadas para esse dia
- over_total_limit: Tu excedeste o limite de %{limit} publicações agendadas
- too_soon: A data de agendamento tem de ser futura
+ over_daily_limit: Você excedeu o limite de %{limit} toots agendados para esse dia
+ over_total_limit: Você excedeu o limite de %{limit} toots agendados
+ too_soon: A data agendada precisa ser no futuro
sessions:
activity: Última atividade
browser: Navegador
browsers:
alipay: Alipay
- blackberry: Blackberry
+ blackberry: BlackBerry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
@@ -1186,7 +1214,7 @@
micro_messenger: MicroMessenger
nokia: Navegador Nokia S40 Ovi
opera: Opera
- otter: Lontra
+ otter: Otter
phantom_js: PhantomJS
qq: QQ Browser
safari: Safari
@@ -1194,36 +1222,28 @@
weibo: Weibo
current_session: Sessão atual
description: "%{browser} em %{platform}"
- explanation: Estes são os navegadores que estão conectados com a tua conta do Mastodon.
- ip: IP
+ explanation: Estes são os navegadores que estão conectados com a sua conta Mastodon.
platforms:
- adobe_air: Adobe Air
- android: Android
- blackberry: Blackberry
- chrome_os: ChromeOS
- firefox_os: SO Firefox
- ios: iOS
- linux: Linux
- mac: Mac
+ blackberry: BlackBerry
+ mac: MacOS
other: Plataforma desconhecida
- windows: Windows
windows_mobile: Windows Mobile
windows_phone: Windows Phone
- revoke: Revogar
- revoke_success: Sessão revogada com sucesso
+ revoke: Fechar
+ revoke_success: A sessão foi revogada com sucesso
title: Sessões
settings:
account: Conta
- account_settings: Definições da conta
- aliases: Pseudónimos da conta
- appearance: Aspecto
+ account_settings: Configurações da conta
+ aliases: Alias da conta
+ appearance: Aparência
authorized_apps: Aplicativos autorizados
- back: Voltar ao Mastodon
- delete: Eliminação da conta
+ back: Voltar para o Mastodon
+ delete: Exclusão de conta
development: Desenvolvimento
edit_profile: Editar perfil
export: Exportar dados
- featured_tags: Hashtags destacadas
+ featured_tags: Hashtags em destaque
identity_proofs: Provas de identidade
import: Importar
import_and_export: Importar e exportar
@@ -1232,35 +1252,35 @@
preferences: Preferências
profile: Perfil
relationships: Seguindo e seguidores
- two_factor_authentication: Autenticação em dois passos
+ two_factor_authentication: Autenticação de dois fatores
webauthn_authentication: Chaves de segurança
statuses:
attached:
audio:
one: "%{count} áudio"
other: "%{count} áudios"
- description: 'Anexadas: %{attached}'
+ description: 'Anexado: %{attached}'
image:
one: "%{count} imagem"
other: "%{count} imagens"
video:
one: "%{count} vídeo"
other: "%{count} vídeos"
- boosted_from_html: Partilhadas de %{acct_link}
- content_warning: 'Aviso de conteúdo: %{warning}'
+ boosted_from_html: Boost de %{acct_link}
+ content_warning: 'Aviso de Conteúdo: %{warning}'
disallowed_hashtags:
- one: 'continha uma hashtag proibida: %{tags}'
- other: 'continha as hashtags proibidas: %{tags}'
+ one: 'continha hashtag não permitida: %{tags}'
+ other: 'continha hashtags não permitidas: %{tags}'
errors:
- in_reply_not_found: A publicação a que está a tentar responder parece não existir.
- language_detection: Detectar automaticamente a língua
- open_in_web: Abrir no browser
- over_character_limit: limite de caracter excedeu %{max}
+ in_reply_not_found: Parece que a postagem à qual você está tentando responder não existe.
+ language_detection: Detectar idioma automaticamente
+ open_in_web: Abrir no navegador
+ over_character_limit: limite de caracteres de %{max} excedido
pin_errors:
- limit: Já fixaste a quantidade máxima de publicações
- ownership: Posts de outras pessoas não podem ser fixados
- private: Post não-público não pode ser fixado
- reblog: Não podes fixar uma partilha
+ limit: Quantidade máxima de toots excedida
+ ownership: Toots dos outros não podem ser fixados
+ private: Toots não-públicos não podem ser fixados
+ reblog: Boosts não podem ser fixados
poll:
total_people:
one: "%{count} pessoa"
@@ -1273,109 +1293,107 @@
show_newer: Mostrar mais recentes
show_older: Mostrar mais antigos
show_thread: Mostrar conversa
- sign_in_to_participate: Inicie a sessão para participar na conversa
- title: '%{name}: "%{quote}"'
+ sign_in_to_participate: Entre para participar dessa conversa
visibilities:
- direct: Direto
- private: Mostrar apenas para seguidores
- private_long: Mostrar apenas para seguidores
+ private: Privado
+ private_long: Posta apenas para seguidores
public: Público
- public_long: Todos podem ver
- unlisted: Público, mas não mostre no timeline público
- unlisted_long: Todos podem ver, porém não será postado nas timelines públicas
+ public_long: Posta em linhas públicas
+ unlisted: Não-listado
+ unlisted_long: Não posta em linhas públicas
stream_entries:
pinned: Toot fixado
- reblogged: partilhado
+ reblogged: deu boost
sensitive_content: Conteúdo sensível
tags:
- does_not_match_previous_name: não coincide com o nome anterior
+ does_not_match_previous_name: não corresponde ao nome anterior
terms:
body_html: |
- <h2>Política de privacidade</h2>
- <h3 id="collect">Que informação nós recolhemos?</h3>
+ <h2>Política de Privacidade</h2>
+ <h3 id="collect">Quais dados nós coletamos?</h3>
<ul>
- <li><em>Informação básica da conta</em>: Se te registares neste servidor, pode-te ser pedido que indiques um nome de utilizador, um endereço de email e uma palavra-passe. Também podes introduzir informação adicional de perfil, tal como um nome a mostrar e dados biográficos, que carregues uma fotografia para o teu perfil e para o cabeçalho. O nome de utilizador, o nome a mostrar, a biografia, a imagem de perfil e a imagem de cabeçalho são sempre listados publicamente.</li>
- <li><em>Publicações, seguimento e outra informação pública</em>: A lista de pessoas que tu segues é pública, o mesmo é verdade para os teus seguidores. Quando tu publicas uma mensagem, a data e a hora são guardados, tal como a aplicação a partir da qual a mensagem foi enviada. As mensagens podem conter anexos média, tais como fotografias ou vídeos. Publicações públicas e não listadas são acessíveis publicamente. Quando expões uma publicação no teu perfil, isso é também informação disponível publicamente. As tuas publicações são enviadas aos teus seguidores. Em alguns casos isso significa que elas são enviadas para servidores diferentes onde são guardadas cópias. Quando tu apagas publicações, isso também é enviado para os teus seguidores. A ação de republicar ou favoritar outra publicação é sempre pública.</li>
- <li><em>Publicações diretas e exclusivas para seguidores</em>: Todas as publicações são guardadas e processadas no servidor. Publicações exclusivas para seguidores são enviadas para os teus seguidores e para utilizadores que são nelas mencionados. As publicações diretas são enviadas apenas para os utilizadores nelas mencionados. Em alguns casos isso significa que elas são enviadas para diferentes servidores onde são guardadas cópias das mesmas. Nós fazemos um grande esforço para limitar o acesso a estas publicações aos utilizadores autorizados, mas outros servidores podem falhar neste objetivo. Por isso, tu deves rever os servidores a que os teus seguidores pertencem. Tu podes ativar uma opção para aprovar e rejeitar manualmente novos seguidores nas configurações. <em>Por favor, tem em mente que os gestores do servidor e qualquer servidor que receba a publicação pode lê-la</em> e que os destinatários podem fazer uma captura de tela, copiar ou partilhar a publicação. <em>Não partilhes qualquer informação perigosa no Mastodon.</em></li>
- <li><em>IPs e outros metadados</em>: Quando inicias sessão, nós guardamos o endereço de IP a partir do qual iniciaste a sessão, tal como o nome do teu navegador. Todas as sessões estão disponíveis para verificação e revogação nas configurações. O último endereço de IP usado é guardado até 12 meses. Nós também podemos guardar registos de servidor, os quais incluem o endereço de IP de cada pedido dirigido ao nosso servidor.</li>
+ <li><em>Dados básicos de conta</em>: Se você criar conta nesta instância, um nome de usuário, um e-mail e uma senha serão exigidos. Você também pode adicionar dados extras como nome de exibição, biografia, imagem de perfil e capa. Com exceção do e-mail e da senha, os dados citados sempre são públicos.</li>
+ <li><em>Toots, seguindo e outros dados públicos</em>: A lista de pessoas que você segue e a sua lista de seguidores são públicas. Ao enviar um toot, a data, a hora e o aplicativo usado são armazenados. Toots podem conter mídias anexadas, como áudios, imagens e vídeos. Toots públicos e não-listados são visíveis publicamente. Os toots fixados no seu perfil são públicos. Seus toots são enviados aos seus seguidores, em alguns casos isso significa que os toots são enviados para instâncias diferentes e cópias são armazenadas lá. Quando você exclui toots, essa informação também é enviada aos seus seguidores. O ato de dar boost ou favoritar outro toot é sempre público.</li></li>
+ <li><em>Mensagens Diretas e toots privados</em>: Todos os toots são armazenados e processados na instância. Toots privados são enviados aos seus seguidores e aos usuários mencionados neles; Mensagens Diretas (ou toots diretos) são enviadas somente aos usuários mencionados nelas. Em alguns casos isso significa que os toots são enviados para instâncias diferentes e cópias são armazenadas lá. Nós trabalhamos constantemente para limitar o acesso a estes toots somente às pessoas autorizadas, porém outras instâncias podem não fazer o mesmo. Portanto, é importante analisar as instâncias dos seus seguidores. Você pode trancar a conta para aprovar ou vetar novos seguidores manualmente nas configurações. <em>Por favor, tenha em mente que os operadores da instância em que se está e das instâncias receptoras podem ver tais toots</em>, e que os destinatários podem fazer capturas de tela, copiar ou usar outra maneira para compartilhar os toots. <em>Não compartilhe informação confidencial pelo Mastodon.</em></li>
+ <li><em>IPs e outros metadados</em>: Ao entrar na sua conta, nós armazenamos o seu endereço de IP e o nome do navegador usado. Todas as sessões abertas estão disponíveis para serem analisadas e revogadas nas configurações. O último endereço de IP usado é armazenado por até 12 meses. Nós também podemos reter históricos da instância que incluem o endereço de IP de todas as conexões à nossa instância.</li>
</ul>
<hr class="spacer" />
- <h3 id="use">Para que usamos a tua informação?</h3>
+ <h3 id="use">Como usamos os seus dados?</h3>
- <p>Qualquer informação que recolhemos sobre ti pode ser usada dos seguintes modos:</p>
+ <p>Todo dado que coletamos pode ser usado das seguintes maneiras:</p>
<ul>
- <li>Para providenciar a funcionalidade central do Mastodon. Tu só podes interagir com o conteúdo de outras pessoas e publicar o teu próprio conteúdo depois de teres iniciado sessão. Por exemplo, tu podes seguir outras pessoas para veres as suas publicações na tua cronologia inicial personalizada. </li>
- <li>Para ajudar na moderação da comunidade para, por exemplo, comparar o teu endereço IP com outros conhecidos, para determinar a fuga ao banimento ou outras violações.</li>
- <li>O endereço de e-mail que tu forneces pode ser usado para te enviar informações e/ou notificações sobre outras pessoas que estão a interagir com o teu conteúdo ou a enviar-te mensagens, para responderes a inquéritos e/ou outros pedidos ou questões.</li>
+ <li>Para prover a funcionalidade básica do Mastodon. Você só pode interagir com o conteúdo de outras pessoas e postar seu próprio conteúdo usando uma conta. Por exemplo, você pode seguir outras pessoas para ver seus toots na sua própria linha do tempo personalizada.</li>
+ <li>Para auxiliar na moderação da comunidade, por exemplo ao comparar o seu endereço de IP com outros endereços de IP conhecidos para determinar evasão de banimento e outras violações.</li>
+ <li>O endereço de e-mail que você fornecer pode ser usado para te enviar informações, notificações sobre outras pessoas interagindo com o seu conteúdo ou contigo e para responder a questões ou outras solicitações.</li>
</ul>
<hr class="spacer" />
- <h3 id="protect">Como é que nós protegemos a tua informação?</h3>
+ <h3 id="protect">Como protegemos seus dados?</h3>
- <p>Nós implementamos uma variedade de medidas de segurança para garantir a segurança da tua informação pessoal quando tu introduzes, submetes ou acedes à mesma. Entre outras coisas, a tua sessão de navegação, tal como o tráfego entre as tuas aplicações e a API, estão seguras por SSL e a tua palavra-passe é codificada usando um forte algoritmo de sentido único. Tu podes activar a autenticação em dois passos para aumentares ainda mais a segurança do acesso à tua conta.</p>
+ <p>Nós implementamos diversas medidas de segurança para manter suas informações pessoais seguras quando você as acessa ou as envia. Entre outras coisas, sua sessão do navegador, bem como o tráfego entre os aplicativos e a API são asseguradas usando SSL e a sua senha é guardada usando um algoritmo forte de criptografia de mão única. Você pode ativar autenticação em dois fatores como forma de aumentar a segurança no acesso à sua conta.</p>
<hr class="spacer" />
<h3 id="data-retention">Qual é a nossa política de retenção de dados?</h3>
- <p>Nós envidaremos todos os esforços no sentido de:</p>
+ <p>Nós trabalhamos constantemente para:</p>
<ul>
- <li>Guardar registos do servidor contendo o endereço de IP de todos os pedidos feitos a este servidor, considerando que estes registos não serão guardados por mais de 90 dias.</li>
- <li>Guardar os endereços de IP associados aos utilizadores registados durante um período que não ultrapassará os 12 meses.</li>
+ <li>Reter o histórico da instância contendo os endereços de IP de todas as conexões a essa instância. O histórico é mantido por não mais que 90 dias.</li>
+ <li>Reter os endereços de IP associados à usuários da instância por não mais que 12 meses.</li>
</ul>
- <p>Tu podes pedir e descarregar um ficheiro com o teu conteúdo, incluindo as tuas publicações, os ficheiros multimédia, a imagem de perfil e a imagem de cabeçalho.</p>
+ <p>Você pode solicitar e baixar um arquivo de todo o conteúdo da sua conta, incluindo seus toots, suas mídias, imagem de perfil e capa.</p>
- <p>Tu podes apagar a tua conta de modo definitivo e a qualquer momento.</p>
+ <p>YVocê pode excluir a sua conta irreversivelmente a qualquer momento.</p>
<hr class="spacer"/>
- <h3 id="cookies">Usamos cookies?</h3>
+ <h3 id="cookies">Nós usamos cookies?</h3>
- <p>Sim. Cookies são pequenos ficheiros que um site ou o seu fornecedor de serviço transfere para o disco rígido do teu computador através do teu navegador (se tu permitires). Estes cookies permitem ao site reconhecer o teu navegador e, se tu tiveres uma conta registada, associá-lo a ela.</p>
+ <p>Sim. Cookies são pequenos arquivos que um site ou serviço baixa através do seu navegador (se você permitir). Esses cookies permitem ao site conhecer seu navegador e, se você tiver uma conta, associá-lo a ela.</p>
- <p>Nós usamos os cookies para compreender e guardar as tuas preferências para as visitas futuras.</p>
+ <p>Nós usamos cookies para salvar suas preferências para futuras visitas.</p>
<hr class="spacer" />
- <h3 id="disclose">Nós divulgamos alguma informação para entidades externas?</h3>
+ <h3 id="disclose">Nós compartilhamos algum dado para terceiros?</h3>
- <p>Nós não vendemos, trocamos ou transferimos de qualquer modo a tua informação pessoal que seja identificável para qualquer entidade externa. Isto não inclui terceiros de confiança que nos ajudam a manter o nosso site, conduzir o nosso negócio ou prestar-te este serviço, desde que esses terceiros concordem em manter essa informação confidencial. Poderemos também revelar a tua informação quando nós acreditamos que isso é apropriado para cumprir a lei, forçar a aplicação dos nossos termos de serviço ou proteger os direitos, propriedade e segurança, nossos e de outrem.</p>
+ <p>Nós não vendemos, trocamos ou compartilhamos de qualquer maneira dados que possam te identificar à terceiros. Isso não inclui terceiros confiáveis que nos auxiliam a operar o nosso site, realizar nosso serviço ou prestar assistência, contanto que esses terceiros se comprometam a manter essa informação confidencial. Nós podemos também divulgar informação quando acreditamos que é apropriado para obedecer a lei, para fazer cumprir nossas políticas ou proteger os nossos direitos, propriedade ou segurança, ou de outrém.</p>
- <p>O teu conteúdo público pode ser descarregado por outros servidores na rede. As tuas publicações públicas e exclusivas para os teus seguidores são enviadas para os servidores onde os teus seguidores residem e as mensagens directas são entregues aos servidores dos seus destinatários, no caso desses seguidores ou destinatários residirem num servidor diferente deste.</p>
+ <p>Seu conteúdo público pode ser acessado por outras instâncias na rede. Seus toots públicos e privados são enviados às instâncias dos seus seguidores e seus toots diretos são enviados às instâncias dos usuários mencionados neles, contanto que esses seguidores ou usuários estejam em uma instância diferente desta.</p>
- <p>Quando tu autorizas uma aplicação a usar a tua conta, dependendo da abrangência das permissões que tu aprovas, ela pode ter acesso à informação pública do teu perfil, à lista de quem segues, aos teus seguidores, às tuas listas, a todas as tuas publicações e aos teus favoritos. As aplicações nunca terão acesso ao teu endereço de e-mail ou à tua palavra-passe.</p>
+ <p>Quando você autoriza um aplicativo a usar sua conta, dependendo do nível de autorização das permissões que você aprovar, o aplicativo pode acessar seus dados públicos, a lista de usuários que você segue, seus seguidores, suas listas, suas Mensagens Diretas e seus toots favoritos. Aplicativos nunca podem acessar o seu endereço de e-mail ou senha.</p>
<hr class="spacer" />
- <h3 id="children">Utilização do site por crianças</h3>
+ <h3 id="children">Uso desse site por crianças</h3>
- <p>Se este servidor estiver na EU ou na EEA: O nosso site, produtos e serviços são todos dirigidos a pessoas que têm, pelo menos, 16 de idade. Se tu tens menos de 16 anos, devido aos requisitos da GDPR (<a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">General Data Protection Regulation</a>) não uses este site.</p>
+ <p>Se a instância está na UE ou no EEE: Nosso site, produto e serviço são direcionados às pessoas que tem ao menos 16 anos de idade. Se você tem menos de 16 anos, de acordo com os requisitos da RGPD (<a href="https://pt.wikipedia.org/wiki/Regulamento_Geral_sobre_a_Prote%C3%A7%C3%A3o_de_Dados">Regulamento Geral sobre Proteção de Dados</a>) não use este site.</p>
- <p>Se este servidor estiver nos EUA: O nosso site, produtos e serviços são todos dirigidos a pessoas que têm, pelo menos, 13 anos de idade. Se tu tens menos de 13 anos de idade, devido aos requisitos da COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) não uses este site.</p>
+ <p>Se esta instância está nos EUA: Nosso site, produto e serviço são direcionados às pessoas que tem ao menos 13 anos de idade. Se você tem menos de 13 anos, de acordo com os requerimentos da COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) não use este site</p>
- <p>Os requisitos legais poderão ser diferentes se este servidor estiver noutra jurisdição.</p>
+ <p>Os requisitos da lei podem ser diferentes em outra jurisdição.</p>
<hr class="spacer" />
- <h3 id="changes">Alterações à nossa Política de Privacidade</h3>
+ <h3 id="changes">Alterações na nossa Política de Privacidade</h3>
- <p>Se nós decidirmos alterar a nossa política de privacidade, nós iremos publicar essas alterações nesta página.</p>
+ <p>Se decidirmos mudar nossa Política de Privacidade, iremos disponibilizar as alterações nesta página.</p>
- <p>Este documento é CC-BY-SA. Ele foi actualizado pela última vez em 7 de Março 2018.</p>
+ <p>CC-BY-SA. Atualizado pela última vez em 7 de março de 2018.</p>
- <p>Originalmente adaptado de <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p>
- title: "%{instance} Termos de Serviço e Política de Privacidade"
+ <p>Adaptado originalmente de <a href="https://github.com/discourse/discourse">Política de Privacidade Discourse</a>.</p>
+ title: Termos de Serviço e Política de Privacidade de %{instance}
themes:
- contrast: Mastodon (Elevado contraste)
- default: Mastodon
- mastodon-light: Mastodon (Claro)
+ contrast: Mastodon (Alto contraste)
+ default: Mastodon (Noturno)
+ mastodon-light: Mastodon (Diurno)
time:
formats:
default: "%H:%M em %d de %b de %Y"
@@ -1383,99 +1401,109 @@
two_factor_authentication:
add: Adicionar
disable: Desativar
- disabled_success: Autenticação em duas etapas desativada com sucesso
+ disabled_success: Autenticação de dois fatores desativada com sucesso
edit: Editar
- enabled: A autenticação em dois passos está ativada
- enabled_success: Autenticação em dois passos ativada com sucesso
- generate_recovery_codes: Gerar códigos para recuperar conta
- lost_recovery_codes: Códigos de recuperação permite que você recupere o acesso a sua conta se você perder seu telefone. Se você perder os códigos de recuperação, você pode regera-los aqui. Seus códigos antigos serão invalidados.
- methods: Métodos de duas etapas
- otp: Aplicação de autenticação
- recovery_codes: Cópia de segurança dos códigos de recuperação
- recovery_codes_regenerated: Códigos de recuperação foram gerados com sucesso
- recovery_instructions_html: Se tu alguma vez perderes o teu smartphone, to poderás usar um dos códigos de recuperação para voltares a ter acesso à tua conta. <strong>Mantém os códigos de recuperação seguros</strong>. Por exemplo, tu podes imprimi-los e guardá-los junto a outros documentos importantes.
+ enabled: Autenticação de dois fatores ativada
+ enabled_success: Autenticação de dois fatores ativada com sucesso
+ generate_recovery_codes: Gerar códigos de recuperação
+ lost_recovery_codes: Códigos de recuperação permitem que você recupere o acesso à sua conta caso perca o seu celular. Se você perdeu seus códigos de recuperação, você pode gerá-los novamente aqui. Seus códigos de recuperação anteriores serão invalidados.
+ methods: Métodos de dois fatores
+ otp: Aplicativo autenticador
+ recovery_codes: Códigos de recuperação de reserva
+ recovery_codes_regenerated: Códigos de recuperação regerados com sucesso
+ recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. <strong>Mantenha os códigos de recuperação em um local seguro</strong>. Por exemplo, você pode imprimi-los e guardá-los junto com outros documentos importantes.
webauthn: Chaves de segurança
user_mailer:
backup_ready:
- explanation: Pediste uma cópia completa da tua conta Mastodon. Ela já está pronta para descarregares!
- subject: O teu arquivo está pronto para descarregar
- title: Arquivo de ficheiros
+ explanation: Você pediu um backup completo da sua conta no Mastodon. E agora está pronto para ser baixado!
+ subject: Seu arquivo está pronto para download
+ title: Baixar arquivo
sign_in_token:
details: 'Aqui estão os detalhes da tentativa:'
- explanation: 'Detectamos uma tentativa de entrar na sua conta a partir de um endereço IP não reconhecido. Se é você, por favor, insira o código de segurança abaixo na página de acesso:'
- further_actions: 'Se não foi você, por favor altere sua senha e ative a autenticação de dois fatores na sua conta. Pode fazê-lo aqui:'
- subject: Por favor, confirme a tentativa de acesso
+ explanation: 'Detectamos uma tentativa de acessar sua conta a partir de um endereço IP não reconhecido. Se for você, insira o código de segurança abaixo na página de desafio:'
+ further_actions: 'Se não foi você, por favor mude sua senha e ative a autenticação de dois fatores em sua conta. Você pode fazê-lo aqui:'
+ subject: Confirme a tentativa de login
title: Tentativa de acesso
+ status_removed:
+ explanation: We use artificial intelligence (AI) to assist our hardworking moderators, and some Truths are flagged for deletion or marked “sensitive” by AI. While the AI we use is very good, it is not error-proof. Assisted by technology, our moderators use their best judgment to ensure compliance with our Terms of Service. Please give our team time to review your Truth to determine whether it violates our Terms of Service. After a thorough review, we will reinstate the Truth or uphold its removal.
+ title: Your Truth was flagged for review
+ subject: Sua Truth foi sinalizada para revisão
warning:
explanation:
- disable: Enquanto a tua conta está congelada, os seus dados permanecem intactos, mas tu não podes executar quaisquer acções até que ela seja desbloqueada.
- sensitive: Os seus ficheiros de media carregados e os media ligados serão tratados como sensíveis.
- silence: Enquanto a sua conta estiver limitada, só pessoas que já estiver a seguir irão ver as suas publicações nesta instância e poderá ser excluído de várias listagens públicas. No entanto, outros ainda o poderão seguir de forma manual.
- suspend: A sua conta foi suspensa e todas as suas publicações e os seus ficheiros de media foram irreversivelmente removidos desta instância e das instâncias onde tinhas seguidores.
- verify: Você é um membro certificado da comunidade
- unverify: Você não é mais um membro certificado da comunidade.
- get_in_touch: Pode responder a este e-mail para entrar em contacto com a equipa de %{instance}.
- review_server_policies: Reveja a política da instância
+ ban_html: Sua conta foi banida por violar nossos Termos de Serviço. Banimentos sem prazo definido são um tipo de sanção raro e muito grave, geralmente aplicados somente às mais sérias violações de nossos Termos de Serviço. Se você acredita que nossa decisão de banir sua conta foi injusta, imoral ou simplesmente errada, recomendamos que entre com um recurso. Envie um e-mail para %{email}. Na linha de assunto, escreva "@Appeal" junto com seu nome de usuário. Aguarde 48 horas para que nossa equipe avalie seu recurso contra o banimento. Após uma avaliação cuidadosa, decidiremos reverter ou manter o banimento.
+ disable_html: Você não pode mais entrar na sua conta nem usá-la de qualquer forma que seja, mas seu perfil e seus outros dados permanecerão intactos.
+ sensitive_html: Os arquivos de mídia que você carregou e os links de mídia que publicou serão tratados como confidenciais.
+ silence_html: Você continua podendo usar sua conta, mas apenas as pessoas que já seguem você verão suas postagens nesse servidor. Além disso, você poderá ser excluído de várias listagens públicas. No entanto, as pessoas continuam podendo seguir você manualmente.
+ suspend_html: Sua conta foi suspensa e ficará inacessível por %{suspension_duration}.
+ suspend_indefinite_html: Sua conta foi suspensa e ficará inacessível.
+ verify_html: Você é um membro certificado da comunidade
+ unverify_html: Você não é mais um membro certificado da comunidade
+ get_in_touch: Você pode responder a este e-mail para entrar em contato com a equipe de %{instance}.
+ review_server_policies: Revisar as políticas da instância
statuses: 'Especificamente, para:'
subject:
- disable: A tua conta %{acct} foi congelada
+ disable: Sua conta %{acct} foi bloqueada
none: Aviso para %{acct}
- sensitive: As publicações de media da sua conta %{acct} foram marcadas como sensíveis
- silence: A tua conta %{acct} foi limitada
- suspend: A tua conta %{acct} foi suspensa
- verify: Sua conta %{acct} foi verificada
+ sensitive: Sua conta %{acct} de postagem de mídia foi marcada como sensível
+ silence: Sua conta %{acct} foi silenciada
+ suspend: Sua conta %{acct} foi banida
+ verify: Sua conta% {acct} foi verificada
unverify: A verificação% {acct} da sua conta foi removida
title:
- disable: Conta congelada
+ disable: Conta bloqueada
none: Aviso
- sensitive: A sua media foi marcada como sensível
- silence: Conta limitada
- suspend: Conta suspensa
+ sensitive: Sua mídia foi marcada como sensível
+ silence: Conta silenciada
+ suspend: Conta banida
verify: Conta verificada
unverify: Verificação de conta removida
+ waitlisted:
+ title: Sua conta foi criada com sucesso!
welcome:
- edit_profile_action: Configura o perfil
- edit_profile_step: Podes personalizar o teu perfil carregando uma imagem de perfil e de cabeçalho ou alterando o nome a exibir, entre outras opções. Se preferires rever os novos seguidores antes deles te poderem seguir, podes tornar a tua conta privada.
- explanation: Aqui estão algumas dicas para começares
- final_action: Começa a publicar
- final_step: 'Começa a publicar! Mesmo sem seguidores, as tuas mensagens públicas podem ser vistas por outros, por exemplo, na cronologia local e em hashtags. Tu podes querer apresentar-te na hashtag #introductions.'
- full_handle: O teu nome completo
- full_handle_hint: Isto é o que você diria aos seus amigos para que eles lhe possam enviar mensagens ou seguir a partir de outra instância.
+ edit_profile_action: Configurar perfil
+ edit_profile_step: Você pode personalizar o seu perfil enviando um avatar, uma capa, alterando seu nome de exibição e etc. Se você preferir aprovar seus novos seguidores antes de eles te seguirem, você pode trancar a sua conta.
+ explanation: Aqui estão algumas dicas para você começar
+ final_action: Comece a tootar
+ final_step: 'Comece a tootar! Mesmo sem seguidores, suas mensagens públicas podem ser vistas pelos outros, por exemplo, na linha local e nas hashtags. Você pode querer fazer uma introdução usando a hashtag #introdução, ou em inglês usando a hashtag #introductions.'
+ full_handle: Seu nome de usuário completo
+ full_handle_hint: Isso é o que você compartilha com aos seus amigos para que eles possam te mandar toots ou te seguir a partir de outra instância.
review_preferences_action: Alterar preferências
- review_preferences_step: Certifica-te de configurar as tuas preferências, tais como os e-mails que gostarias de receber ou o nível de privacidade que desejas que as tuas publicações tenham por defeito. Se não sofres de enjoo, podes ativar a opção de auto-iniciar GIFs.
- subject: Bem-vindo ao Mastodon
- tip_federated_timeline: A cronologia federativa é uma visão global da rede Mastodon. Mas só inclui pessoas que os teus vizinhos subscrevem, por isso não é uma visão completa.
- tip_following: Segues o(s) administrador(es) do teu servidor por defeito. Para encontrar mais pessoas interessantes, procura nas cronologias local e federada.
- tip_local_timeline: A cronologia local é uma visão global das pessoas em %{instance}. Estes são os seus vizinhos mais próximos!
- tip_mobile_webapp: Se o teu navegador móvel te oferecer a possibilidade de adicionar o Mastodon ao teu homescreen, tu podes receber notificações push. Ele age como uma aplicação nativa de vários modos!
+ review_preferences_step: Não se esqueça de configurar suas preferências, como quais e-mails você gostaria de receber, que nível de privacidade você gostaria que seus toots tenham por padrão. Se você não sofre de enjoo com movimento, você pode habilitar GIFs animado automaticamente.
+ subject: Boas-vindas ao Mastodon
+ tip_federated_timeline: A linha global é uma visão contínua da rede do Mastodon. Mas ela só inclui pessoas de instâncias que a sua instância conhece, então não é a rede completa.
+ tip_following: Você vai seguir administrador(es) da sua instância por padrão. Para encontrar mais gente interessante, confira as linhas local e global.
+ tip_local_timeline: A linha local é uma visão contínua das pessoas em %{instance}. Estes são seus vizinhos!
+ tip_mobile_webapp: Se o seu navegador móvel oferecer a opção de adicionar Mastodon à tela inicial, você pode receber notificações push. Será como um aplicativo nativo!
tips: Dicas
- title: Bem-vindo a bordo, %{name}!
+ title: Boas vindas, %{name}!
users:
- follow_limit_reached: Não podes seguir mais do que %{limit} pessoas
- generic_access_help_html: Problemas para aceder à sua conta? Pode entrar em contacto com %{email} para obter ajuda
- invalid_otp_token: Código de autenticação inválido
- invalid_sign_in_token: Cógido de segurança inválido
- otp_lost_help_html: Se tu perdeste acesso a ambos, tu podes entrar em contacto com %{email}
- seamless_external_login: Tu estás ligado via um serviço externo. Por isso, as configurações da palavra-passe e do e-mail não estão disponíveis.
- signed_in_as: 'Registado como:'
- suspicious_sign_in_confirmation: Parece que não iniciou sessão através deste dispositivo antes, e não acede à sua conta há algum tempo. Portanto, enviámos um código de segurança para o seu endereço de e-mail para confirmar que é você.
+ follow_limit_reached: Você não pode seguir mais de %{limit} pessoas
+ generic_access_help_html: Problemas para acessar sua conta? Você pode entrar em contato com %{email} para obter ajuda
+ invalid_otp_token: Código de dois fatores inválido
+ invalid_sign_in_token: Código de segurança inválido
+ otp_lost_help_html: Se você perder o acesso à ambos, você pode entrar em contato com %{email}
+ previously_used_password: Por favor, use uma senha que você não usou anteriormente.
+ password_mismatch: A senha e a confirmação da senha não coincidem.
+ seamless_external_login: Você entrou usando um serviço externo, então configurações de e-mail e senha não estão disponíveis.
+ signed_in_as: 'Entrou como:'
+ suspicious_sign_in_confirmation: Parece que você não fez login deste dispositivo antes, e você não fez login por um tempo. Portanto, estamos enviando um código de segurança para o seu endereço de e-mail para confirmar que é você.
verification:
- explanation_html: 'Pode <strong>comprovar que é o dono dos links nos metadados do seu perfil</strong>. Para isso, o website para o qual o link aponta tem de conter um link para o seu perfil do Mastodon. Este link <strong>tem</strong> de ter um atributo <code>rel="me"</code>. O conteúdo do texto não é relevante. Aqui está um exemplo:'
+ explanation_html: 'Você pode <strong>verificar os links nos metadados do seu perfil</strong>. Para isso, o site citado deve conter um link de volta para o seu perfil do Mastodon. O link de volta <strong>deve</strong> conter um atributo <code>rel="me"</code>. O conteúdo ou texto do link não importa. Aqui está um exemplo:'
verification: Verificação
webauthn_credentials:
add: Adicionar nova chave de segurança
create:
- error: Ocorreu um problema ao adicionar sua chave de segurança. Tente novamente.
+ error: Houve um problema ao adicionar sua chave de segurança. Tente novamente.
success: A sua chave de segurança foi adicionada com sucesso.
- delete: Eliminar
- delete_confirmation: Tem a certeza de que pretende eliminar esta chave de segurança?
- description_html: Se você ativar a <strong>autenticação com chave de segurança</strong>, para aceder à sua conta será necessário que utilize uma das suas chaves de segurança.
+ delete: Excluir
+ delete_confirmation: Você tem certeza de que deseja excluir esta chave de segurança?
+ description_html: Se você habilitar a <strong>autenticação por chave de segurança</strong>, o login exigirá que você use uma das suas chaves de segurança.
destroy:
- error: Ocorreu um problema ao remover a sua chave de segurança. Tente novamente.
- success: A sua chave de segurança foi eliminada com sucesso.
+ error: Houve um problema ao excluir sua chave de segurança. Tente novamente.
+ success: Sua chave de segurança foi excluída com sucesso.
invalid_credential: Chave de segurança inválida
- nickname_hint: Introduza o apelido da sua nova chave de segurança
- not_enabled: Ainda não ativou o WebAuthn
- not_supported: Este navegador não suporta chaves de segurança
- otp_required: Para usar chaves de segurança, por favor ative primeiro a autenticação de duas etapas.
- registered_on: Registado em %{date}
+ nickname_hint: Digite o apelido da sua nova chave de segurança
+ not_enabled: Você ainda não habilitou o WebAuthn
+ not_supported: Este navegador não tem suporte a chaves de segurança
+ otp_required: Para usar chaves de segurança, por favor habilite primeiro a autenticação de dois fatores.
+ registered_on: Registrado em %{date}
diff -ru truth-old/opensource/config/navigation.rb truth-new/opensource/config/navigation.rb
--- truth-old/opensource/config/navigation.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/navigation.rb 2024-04-01 14:59:13
@@ -26,7 +26,6 @@
n.item :moderation, safe_join([fa_icon('gavel fw'), t('moderation.title')]), admin_reports_url, if: proc { current_user.staff? } do |s|
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_url
s.item :reports, safe_join([fa_icon('flag fw'), t('admin.reports.title')]), admin_reports_url, highlights_on: %r{/admin/reports}
- s.item :reports, safe_join([fa_icon('bullhorn fw'), t('admin.trending_truths.title')]), admin_trending_truths_url, highlights_on: %r{/admin/trending_truths}
s.item :exports, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url, highlights_on: %r{/settings/export}
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_url, highlights_on: %r{/admin/accounts|/admin/pending_accounts}
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path
@@ -45,7 +44,6 @@
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis}
s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays}
s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? }
- s.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url, link_html: { target: 'pghero' }, if: -> { current_user.admin? }
end
n.item :logout, safe_join([fa_icon('sign-out fw'), t('auth.logout')]), destroy_user_session_url, link_html: { 'data-method' => 'delete' }
Only in truth-old/opensource/config: pghero.yml
diff -ru truth-old/opensource/config/routes.rb truth-new/opensource/config/routes.rb
--- truth-old/opensource/config/routes.rb 2022-06-08 09:15:38
+++ truth-new/opensource/config/routes.rb 2024-04-12 09:09:08
@@ -5,6 +5,7 @@
Rails.application.routes.draw do
root 'home#index'
+ resources :apidocs, only: [:index]
mount LetterOpenerWeb::Engine, at: 'letter_opener' if Rails.env.development?
@@ -12,7 +13,6 @@
authenticate :user, lambda { |u| u.admin? } do
mount Sidekiq::Web, at: 'sidekiq', as: :sidekiq
- mount PgHero::Engine, at: 'pghero', as: :pghero
end
namespace :oauth do
@@ -27,17 +27,17 @@
tokens: 'oauth/tokens'
end
+ get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' }
+ get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger
+ get '.well-known/change-password', to: redirect('/auth/edit')
+ post '.well-known/skadnetwork/report-attribution/', to: 'well_known/skadnetwork#create'
+
get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
get 'intent', to: 'intents#show'
get 'custom.css', to: 'custom_css#show', as: :custom_css
-
- resource :instance_actor, path: 'actor', only: [:show] do
- resource :inbox, only: [:create], module: :activitypub
- resource :outbox, only: [:show], module: :activitypub
- end
-
get '/unsubscribe', to: 'unsubscribe#unsubscribe'
+ get '/link/:id', to: 'link#show', as: :link
devise_scope :user do
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite
@@ -50,16 +50,16 @@
devise_for :users, path: 'auth', controllers: {
omniauth_callbacks: 'auth/omniauth_callbacks',
- sessions: 'auth/sessions',
- registrations: 'auth/registrations',
- passwords: 'auth/passwords',
- confirmations: 'auth/confirmations',
+ sessions: 'auth/sessions',
+ registrations: 'auth/registrations',
+ passwords: 'auth/passwords',
+ confirmations: 'auth/confirmations',
}
get '/users/:username', to: redirect('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
resources :accounts, path: 'users', only: [:show], param: :username do
- get :remote_follow, to: 'remote_follow#new'
+ get :remote_follow, to: 'remote_follow#new'
post :remote_follow, to: 'remote_follow#create'
resources :statuses, only: [:show] do
@@ -76,15 +76,11 @@
resource :follow, only: [:create], controller: :account_follow
resource :unfollow, only: [:create], controller: :account_unfollow
- resource :outbox, only: [:show], module: :activitypub
- resource :inbox, only: [:create], module: :activitypub
resource :claim, only: [:create], module: :activitypub
resources :collections, only: [:show], module: :activitypub
resource :followers_synchronization, only: [:show], module: :activitypub
end
- resource :inbox, only: [:create], module: :activitypub
-
get '/@:username', to: 'accounts#show', as: :short_account
get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies
get '/@:username/media', to: 'accounts#show', as: :short_account_media
@@ -92,7 +88,7 @@
get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status
get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status
- get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
+ get '/interact/:id', to: 'remote_interaction#new', as: :remote_interaction
post '/interact/:id', to: 'remote_interaction#create'
get '/explore', to: 'directories#index', as: :explore
@@ -135,7 +131,6 @@
resources :webauthn_credentials, only: [:index, :new, :create, :destroy],
path: 'security_keys',
controller: 'two_factor_authentication/webauthn_credentials' do
-
collection do
get :options
end
@@ -170,7 +165,7 @@
get :player
end
- resources :tags, only: [:show]
+ resources :tags, only: [:show]
resources :emojis, only: [:show]
resources :invites, only: [:index, :create, :destroy]
resources :filters, except: [:show, :index]
@@ -235,8 +230,6 @@
resources :reported_statuses, only: [:create]
end
- resources :trending_truths, only: [:index, :update, :destroy]
-
resources :report_notes, only: [:create, :destroy]
resources :accounts, only: [:index, :show, :destroy] do
@@ -331,6 +324,8 @@
end
namespace :api do
+ get '/docs', to: 'docs#index'
+
# OEmbed
get '/oembed', to: 'oembed#show', as: :oembed
@@ -340,7 +335,7 @@
post :change_email, controller: 'user_settings'
post :delete_account, controller: 'user_settings'
- scope :accounts, defaults: { format: 'json' } do
+ scope :accounts, defaults: { format: 'json' } do
get :mfa, to: 'accounts#mfa'
scope :mfa do
scope :setup do
@@ -377,6 +372,10 @@
post :unpin, to: 'pins#destroy'
end
+ collection do
+ resources :mutes, controller: 'statuses/mutes', only: :index
+ end
+
member do
get :context
get 'context/ancestors', to: 'statuses#ancestors'
@@ -386,21 +385,35 @@
namespace :timelines do
resource :home, only: :show, controller: :home
+ resource :following, only: :show, controller: :home
# resource :public, only: :show, controller: :public
resources :tag, only: :show
resources :list, only: :show
+
+ resources :group, only: :show do
+ resources :tags, only: :show, path: 'tags', controller: 'group_tag'
+ end
end
namespace :truth do
namespace :trending do
resources :truths, only: :index
+ resources :groups, only: :index
+ resources :group_tags, only: :show
end
namespace :admin do
- resources :accounts, only: [:index, :update]
+ resources :accounts, only: [:index, :update] do
+ post 'mfa/confirm/totp', to: 'accounts#confirm_totp'
+ end
scope :accounts do
+ get :blacklist, to: 'accounts#blacklist'
get :count, to: 'accounts#count'
+ get :email_domain_blocks, to: 'accounts#email_domain_blocks'
end
+ resources :email_domain_blocks, only: [:index, :create, :destroy]
+ resources :marketing_notifications, only: [:create]
+ resources :media_attachments, only: [:destroy]
end
scope :password_reset do
@@ -411,8 +424,96 @@
scope :email do
get :confirm, to: 'emails#email_confirm'
end
+
+ namespace :carousels do
+ resources :avatars, only: [:index] do
+ post :seen, on: :collection
+ end
+ resources :groups, only: [:index] do
+ post :seen, on: :collection
+ end
+ resources :suggestions, only: [:index]
+ get 'avatars/accounts/:account_id/statuses', to: '/api/v1/accounts/statuses#index'
+
+ get 'tv', to: '/api/v1/tv/carousel#index'
+ post 'tv/seen', to: '/api/v1/tv/carousel#seen'
+ end
+
+ get '/ads', to: 'ads#index', as: :ads
+ get '/ads/impression', to: 'ads#impression', as: :ads_impression
+
+ namespace :ios_device_check do
+ resources :challenge, only: [:index]
+ resources :rate_limit, only: [:index]
+ resources :attest, only: [:create] do
+ post :baseline, on: :collection
+ post :by_key_id, on: :collection
+ end
+ resources :assert, only: [:create] do
+ post :resolve, on: :collection
+ end
+ end
+
+ namespace :android_device_check do
+ resources :challenge, only: [:create]
+ end
+
+ resources :chats, only: [:index, :create] do
+ scope module: :chats do
+ resources :messages, only: [:index, :create, :destroy]
+ end
+ end
+
+ namespace :policies do
+ get :pending
+ patch :accept, path: '/:policy_id/accept', to: 'policies/accept'
+ end
+
+ resources :oauth_tokens, only: [:index, :destroy]
+
+ namespace :suggestions do
+ resources :groups, only: [:index, :destroy]
+
+ namespace :follows do
+ post :create, path: ':account_id', to: 'suggestions/follows'
+ end
+
+ namespace :statuses do
+ post :create, path: ':account_id', to: 'suggestions/statuses'
+ end
+ end
+
+ resources(:videos, only: :show)
end
+ namespace :pleroma do
+ namespace :chats do
+ post :by_account_id, path: 'by-account-id/:account_id'
+ get :by_account_id, path: 'by-account-id/:account_id', to: '/api/v1/pleroma/chats#get_by_account_id'
+ get 'silences', to: 'silences#index'
+ get 'sync'
+ get 'events', to: 'events#index'
+ get 'search', to: 'search#index'
+ get 'search/messages', to: 'search#search_messages'
+ get 'search/previews', to: 'search#search_previews'
+ end
+
+ resources :chats, only: [:index, :destroy, :show, :update] do
+ post 'read', to: 'chats#mark_read'
+ post 'accept', to: 'chats#accept'
+
+ scope module: :chats do
+ get 'sync', to: 'messages#sync'
+ resources :messages, only: [:index, :destroy, :create, :show] do
+ resources :reactions, only: [:show, :create, :destroy], param: :emoji
+ end
+ post 'silences', to: 'silences#create'
+ delete 'silences', to: 'silences#destroy'
+ get 'silences', to: 'silences#show'
+ end
+ end
+ end
+
resources :streaming, only: [:index]
resources :custom_emojis, only: [:index]
resources :suggestions, only: [:index, :destroy]
@@ -452,16 +553,16 @@
end
end
- resources :media, only: [:create, :update, :show]
- resources :blocks, only: [:index]
- resources :mutes, only: [:index]
- resources :favourites, only: [:index]
- resources :bookmarks, only: [:index]
- resources :reports, only: [:create]
- resources :trends, only: [:index]
- resources :filters, only: [:index, :create, :show, :update, :destroy]
+ resources :media, only: [:create, :update, :show]
+ resources :blocks, only: [:index]
+ resources :mutes, only: [:index]
+ resources :favourites, only: [:index]
+ resources :bookmarks, only: [:index]
+ resources :reports, only: [:create]
+ resources :trends, only: [:index]
+ resources :filters, only: [:index, :create, :show, :update, :destroy]
resources :endorsements, only: [:index]
- resources :markers, only: [:index, :create]
+ resources :markers, only: [:index, :create]
namespace :apps do
get :verify_credentials, to: 'credentials#show'
@@ -534,6 +635,54 @@
resource :accounts, only: [:show, :create, :destroy], controller: 'lists/accounts'
end
+ namespace :groups do
+ resources :relationships, only: [:index]
+ resources :tags, only: [:index]
+ end
+
+ get '/groups/mutes', to: 'groups/mutes#index'
+
+ resources :groups, only: [:index, :create, :show, :update, :destroy] do
+ post :mute, to: 'groups/mutes#create'
+ post :unmute, to: 'groups/mutes#destroy'
+
+ resources :memberships, only: [:index], controller: 'groups/memberships'
+
+ resources :membership_requests, only: [:index], controller: 'groups/membership_requests' do
+ member do
+ post :authorize, to: 'groups/membership_requests#accept'
+ post :reject
+ end
+ post 'resolve', on: :collection
+ end
+
+ resources :statuses, only: [:destroy], controller: 'groups/statuses' do
+ resource :pin, only: :create, controller: 'groups/statuses/pins'
+ post :unpin, to: 'groups/statuses/pins#destroy'
+ end
+
+ resource :blocks, only: [:show, :create, :destroy], controller: 'groups/blocks'
+
+ resources :tags, only: :update, controller: 'groups/tags'
+
+ member do
+ post :join
+ post :leave
+ post :promote
+ post :demote
+ end
+
+ collection do
+ get :search
+ get :lookup
+ get :validate
+ end
+ end
+
+ resources :tags, only: [:show] do
+ resources :groups, only: :index, controller: 'tags/groups'
+ end
+
namespace :featured_tags do
get :suggestions, to: 'suggestions#index'
end
@@ -548,20 +697,35 @@
resource :subscription, only: [:create, :show, :update, :destroy]
end
+ namespace :tv do
+ resources :channels, only: :index
+ get 'accounts/:id/status', to: 'accounts#show'
+ get 'epg/:name', to: 'programme_guides#show'
+ get 'channels/:id/guide', to: 'guide#show'
+ put 'channels/:id/remind', to: 'program_reminder#update'
+ delete 'channels/:id/remind', to: 'program_reminder#destroy'
+ end
+
get '/stats', to: 'admin#stats'
namespace :admin do
resources :statuses, only: [:index, :show] do
- post :sensitize
post :desensitize
- post :undiscard
post :discard
+ post :privatize
+ post :publicize
+ post :sensitize
+ post :undiscard
end
resources :accounts, only: [:index, :show, :create, :update, :destroy] do
resources :follows, only: [:show], param: :target_account_id, controller: 'accounts/follows'
+ resources :statuses, only: [:index], controller: 'accounts/statuses'
+ resources :webauthn_credentials, only: [:index], controller: 'accounts/webauthn_credentials'
+
collection do
post :bulk_approve
end
+
member do
post :enable
post :unsensitive
@@ -577,6 +741,13 @@
resource :action, only: [:create], controller: 'account_actions'
end
+ post '/accounts/bulk_action', to: 'bulk_account_actions#create'
+
+ resources :groups, only: [:index, :show, :update, :destroy] do
+ get :search, on: :collection
+ resources :statuses, only: [:index], controller: 'groups/statuses'
+ end
+
resources :reports, only: [:index, :show] do
member do
resources :moderation_records, only: [:index]
@@ -587,16 +758,119 @@
end
end
- resources :trending_statuses, only: [:index, :update, :destroy]
+ resources :trending_statuses, only: :index do
+ member do
+ put :include
+ put :exclude
+ end
+
+ collection do
+ resources :settings, param: :name, controller: 'trending_statuses/settings', only: [:index, :update]
+ end
+
+ collection do
+ resources :expressions, controller: 'trending_statuses/expressions', only: [:index, :create, :update, :destroy]
+ end
+ end
+
+ resources :trending_tags, only: [:index, :update]
+
+ resources :trending_groups, only: :index do
+ member do
+ put :include
+ put :exclude
+ end
+
+ get :excluded, on: :collection
+ end
+
+ resources :chat_messages, only: [:show, :destroy]
+
+ resources :policies, only: [:index, :create, :destroy]
+
+ namespace :truth do
+ resources :interactive_ads, only: :create
+ namespace :suggestions do
+ resources :groups, only: [:index, :show, :create, :destroy]
+ end
+
+ namespace :android_device_check do
+ resources :integrity, only: [:create]
+ end
+
+ namespace :ios_device_check do
+ resources :attest, only: [:create]
+ end
+ end
+
+ namespace :tv do
+ resources :sessions, only: :index
+ end
+
+ resources :tags, only: [:index, :update]
+ resources :registrations, only: [:create]
+ resources :links, only: [:update]
end
+
+ resources :feeds, only: [
+ :index,
+ # :create,
+ :show,
+ :update,
+ # :destroy
+ ] do
+ member do
+ post 'accounts/:account_id', to: 'feeds#add_account'
+ delete 'accounts/:account_id', to: 'feeds#remove_account'
+ patch :seen, to: 'feeds#seen'
+ end
+ end
+
+ namespace :recommendations do
+ namespace :accounts do
+ resources :suppressions, only: [:create, :destroy]
+ end
+ namespace :groups do
+ resources :suppressions, only: [:create, :destroy]
+ end
+ end
+
+ namespace :verify_sms do
+ resources :countries, only: :index
+ end
+
+ namespace :push_notifications do
+ post '/:mark_id/mark', to: 'analytics#mark', as: :analytics_mark
+ end
end
namespace :v2 do
resources :media, only: [:create]
get '/search', to: 'search#index', as: :search
resources :suggestions, only: [:index, :destroy]
+
+ namespace :pleroma do
+ namespace :chats do
+ get 'events', to: 'events#index'
+ end
+ end
+
+ resources :statuses, only: [:show] do
+ member do
+ get 'context/ancestors', to: 'statuses#ancestors', as: 'ancestors'
+ get 'context/descendants', to: 'statuses#descendants', as: 'descendants'
+ end
+ end
+
+ resources :feeds, only: [:index]
end
+ namespace :v4 do
+ namespace :truth do
+ get '/ads', to: 'ads#index'
+ end
+ end
+
namespace :web do
resource :settings, only: [:update]
resource :embed, only: [:create]
@@ -606,13 +880,35 @@
end
end
end
+
+ namespace :mock do
+ get '/feeds', to: 'feeds#index'
+ post '/feeds', to: 'feeds#create'
+ get '/feeds/:id', to: 'feeds#show'
+ patch '/feeds/:id', to: 'feeds#update'
+ delete '/feeds/:id', to: 'feeds#destroy'
+ put '/feeds/sort', to: 'feeds#sort'
+ post '/feeds/:id/accounts/:account_id', to: 'feeds#add_account'
+ delete '/feeds/:id/accounts/:account_id', to: 'feeds#remove_account'
+ post '/feeds/groups/:group_id/unmute', to: 'feeds#unmute_group'
+ post '/feeds/groups/:group_id/mute', to: 'feeds#mute_group'
+ end
end
+ get '/api/v2/pleroma/chats', to: 'api/v1/pleroma/chats#index'
+ get '/api/v1/truth/trends/groups', to: 'api/v1/truth/trending/groups#index'
+ get '/api/v1/truth/trends/groups/:id/tags', to: 'api/v1/truth/trending/group_tags#show', as: :truth_trends_groups
+
+ get '/api/oauth_tokens', to: 'api/v1/truth/oauth_tokens#index'
+ delete '/api/oauth_tokens/:id', to: 'api/v1/truth/oauth_tokens#destroy'
+
+ get '/api/v1/trends/statuses', to: 'api/v1/truth/trending/truths#index'
+
get '/web/(*any)', to: 'home#index', as: :web
- get '/about', to: 'about#show'
- get '/about/more', to: 'about#more'
- get '/terms', to: 'about#terms'
+ get '/about', to: 'about#show'
+ get '/about/more', to: 'about#more'
+ get '/terms', to: 'about#terms'
match '/', via: [:post, :put, :patch, :delete], to: 'application#raise_not_found', format: false
match '*unmatched_route', via: :all, to: 'application#raise_not_found', format: false
diff -ru truth-old/opensource/config/settings.yml truth-new/opensource/config/settings.yml
--- truth-old/opensource/config/settings.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/settings.yml 2024-04-01 14:59:13
@@ -61,6 +61,7 @@
- administrator
- mod
- moderator
+ - amtrak
disallowed_hashtags: # space separated string or list of hashtags without the hash
bootstrap_timeline_accounts: ''
activity_api_enabled: true
diff -ru truth-old/opensource/config/sidekiq.yml truth-new/opensource/config/sidekiq.yml
--- truth-old/opensource/config/sidekiq.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/sidekiq.yml 2024-04-12 09:09:08
@@ -7,6 +7,9 @@
- [mailers, 2]
- [pull]
- [chewy]
+ - [removal]
+ - [reblog-removal]
+ - [shared]
:scheduler:
:listened_queues_only: true
:schedule:
@@ -14,18 +17,10 @@
every: '5m'
class: Scheduler::ScheduledStatusesScheduler
queue: scheduler
- trending_tags_scheduler:
- every: '5m'
- class: Scheduler::TrendingTagsScheduler
- queue: scheduler
media_cleanup_scheduler:
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
class: Scheduler::MediaCleanupScheduler
queue: scheduler
- feed_cleanup_scheduler:
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(0..2) %> * * *'
- class: Scheduler::FeedCleanupScheduler
- queue: scheduler
follow_recommendations_scheduler:
cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *'
class: Scheduler::FollowRecommendationsScheduler
@@ -46,10 +41,6 @@
cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
class: Scheduler::BackupCleanupScheduler
queue: scheduler
- pghero_scheduler:
- cron: '0 0 * * *'
- class: Scheduler::PgheroScheduler
- queue: scheduler
instance_refresh_scheduler:
cron: '0 * * * *'
class: Scheduler::InstanceRefreshScheduler
@@ -57,4 +48,20 @@
users_approval_scheduler:
every: '5m'
class: Scheduler::UsersApprovalScheduler
+ queue: scheduler
+ refresh_receipt_scheduler:
+ cron: '0 0 * * *'
+ class: Scheduler::RefreshReceiptScheduler
+ queue: scheduler
+ tv_create_program_records_scheduler:
+ every: '10m'
+ class: Scheduler::TvCreateProgramRecordsScheduler
+ queue: scheduler
+ tv_refetch_channels_list_scheduler:
+ cron: '0 1 * * *'
+ class: Scheduler::TvRefetchChannelsListScheduler
+ queue: scheduler
+ device_verification_cleanup_scheduler:
+ cron: '0 2 * * *'
+ class: Scheduler::DeviceVerificationCleanupWorker
queue: scheduler
diff -ru truth-old/opensource/config/webpacker.yml truth-new/opensource/config/webpacker.yml
--- truth-old/opensource/config/webpacker.yml 2022-06-08 09:15:38
+++ truth-new/opensource/config/webpacker.yml 2024-04-01 14:59:13
@@ -50,7 +50,7 @@
development:
<<: *default
- compile: true
+ compile: false
# Reference: https://webpack.js.org/configuration/dev-server/
dev_server:
diff -ru truth-old/opensource/db/schema.rb truth-new/opensource/db/schema.rb
--- truth-old/opensource/db/schema.rb 2022-06-08 09:15:38
+++ truth-new/opensource/db/schema.rb 2024-04-01 14:59:13
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2022_05_25_140644) do
+ActiveRecord::Schema.define(version: 2022_06_10_102254) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -181,8 +181,10 @@
t.text "location", default: "", null: false
t.text "website", default: "", null: false
t.boolean "whale", default: false
+ t.integer "interactions_score"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
+ t.index ["interactions_score"], name: "index_accounts_on_interactions_score"
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
t.index ["uri"], name: "index_accounts_on_uri"
t.index ["url"], name: "index_accounts_on_url"
@@ -280,6 +282,32 @@
t.index ["reference_account_id"], name: "index_canonical_email_blocks_on_reference_account_id"
end
+ create_table "chat_accounts", force: :cascade do |t|
+ t.bigint "account_id"
+ t.bigint "chat_id"
+ t.index ["account_id"], name: "index_chat_accounts_on_account_id"
+ t.index ["chat_id"], name: "index_chat_accounts_on_chat_id"
+ end
+
+ create_table "chat_messages", force: :cascade do |t|
+ t.bigint "account_id"
+ t.bigint "chat_id"
+ t.text "content"
+ t.boolean "unread", default: true, null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["account_id"], name: "index_chat_messages_on_account_id"
+ t.index ["chat_id"], name: "index_chat_messages_on_chat_id"
+ end
+
+ create_table "chats", force: :cascade do |t|
+ t.integer "unread"
+ t.bigint "created_by_account"
+ t.datetime "last_message"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
+
create_table "conversation_mutes", force: :cascade do |t|
t.bigint "conversation_id", null: false
t.bigint "account_id", null: false
@@ -417,6 +445,14 @@
t.index ["tag_id"], name: "index_featured_tags_on_tag_id"
end
+ create_table "follow_deletes", force: :cascade do |t|
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.bigint "account_id", null: false
+ t.bigint "target_account_id", null: false
+ t.index ["account_id"], name: "index_follow_deletes_on_account_id"
+ end
+
create_table "follow_recommendation_suppressions", force: :cascade do |t|
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
@@ -447,6 +483,89 @@
t.index ["target_account_id"], name: "index_follows_on_target_account_id"
end
+ create_table "group_account_blocks", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "group_id", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["account_id", "group_id"], name: "index_group_account_blocks_on_account_id_and_group_id", unique: true
+ t.index ["group_id"], name: "index_group_account_blocks_on_group_id"
+ end
+
+ create_table "group_deletion_requests", force: :cascade do |t|
+ t.bigint "group_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["group_id"], name: "index_group_deletion_requests_on_group_id"
+ end
+
+ create_table "group_membership_requests", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "group_id", null: false
+ t.string "uri"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["account_id", "group_id"], name: "index_group_membership_requests_on_account_id_and_group_id", unique: true
+ t.index ["group_id"], name: "index_group_membership_requests_on_group_id"
+ t.index ["uri"], name: "index_group_membership_requests_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)"
+ end
+
+ create_table "group_memberships", force: :cascade do |t|
+ t.bigint "account_id", null: false
+ t.bigint "group_id", null: false
+ t.integer "role", default: 0, null: false
+ t.string "uri"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["account_id", "group_id"], name: "index_group_memberships_on_account_id_and_group_id", unique: true
+ t.index ["group_id", "role"], name: "index_group_memberships_on_group_id_and_role"
+ t.index ["uri"], name: "index_group_memberships_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)"
+ end
+
+ create_table "group_stats", force: :cascade do |t|
+ t.bigint "group_id", null: false
+ t.bigint "statuses_count", default: 0, null: false
+ t.bigint "members_count", default: 0, null: false
+ t.datetime "last_status_at"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["group_id"], name: "index_group_stats_on_group_id", unique: true
+ end
+
+ create_table "groups", id: :bigint, default: -> { "timestamp_id('groups'::text)" }, force: :cascade do |t|
+ t.string "domain"
+ t.string "url"
+ t.text "note", default: "", null: false
+ t.string "display_name", default: "", null: false
+ t.boolean "locked", default: false, null: false
+ t.boolean "hide_members", default: false, null: false
+ t.datetime "suspended_at"
+ t.integer "suspension_origin"
+ t.boolean "discoverable"
+ t.string "avatar_file_name"
+ t.string "avatar_content_type"
+ t.bigint "avatar_file_size"
+ t.datetime "avatar_updated_at"
+ t.string "avatar_remote_url", default: "", null: false
+ t.string "header_file_name"
+ t.string "header_content_type"
+ t.bigint "header_file_size"
+ t.datetime "header_updated_at"
+ t.string "header_remote_url", default: "", null: false
+ t.integer "image_storage_schema_version"
+ t.string "uri"
+ t.string "outbox_url", default: "", null: false
+ t.string "inbox_url", default: "", null: false
+ t.string "shared_inbox_url", default: "", null: false
+ t.string "members_url", default: "", null: false
+ t.string "wall_url", default: "", null: false
+ t.text "private_key"
+ t.text "public_key", default: "", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["uri"], name: "index_groups_on_uri", unique: true, opclass: :text_pattern_ops, where: "(uri IS NOT NULL)"
+ end
+
create_table "identities", force: :cascade do |t|
t.string "provider", default: "", null: false
t.string "uid", default: "", null: false
@@ -856,10 +975,12 @@
t.bigint "in_reply_to_account_id"
t.bigint "poll_id"
t.datetime "deleted_at"
- t.bigint "quote_id"
t.bigint "deleted_by_id"
+ t.bigint "quote_id"
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
t.index ["conversation_id"], name: "index_statuses_on_conversation_id"
+ t.bigint "group_id"
+ t.index ["group_id"], name: "index_statuses_on_group_id", where: "(group_id IS NOT NULL)"
t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
@@ -975,6 +1096,7 @@
t.boolean "unsubscribe_from_emails", default: false
t.integer "ready_to_approve", default: 0
t.boolean "unauth_visibility"
+ t.string "attestation_challenge"
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id"
@@ -1069,6 +1191,14 @@
add_foreign_key "follow_requests", "accounts", name: "fk_76d644b0e7", on_delete: :cascade
add_foreign_key "follows", "accounts", column: "target_account_id", name: "fk_745ca29eac", on_delete: :cascade
add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
+ add_foreign_key "group_account_blocks", "accounts", on_delete: :cascade
+ add_foreign_key "group_account_blocks", "groups", on_delete: :cascade
+ add_foreign_key "group_deletion_requests", "groups", on_delete: :cascade
+ add_foreign_key "group_membership_requests", "accounts", on_delete: :cascade
+ add_foreign_key "group_membership_requests", "groups", on_delete: :cascade
+ add_foreign_key "group_memberships", "accounts", on_delete: :cascade
+ add_foreign_key "group_memberships", "groups", on_delete: :cascade
+ add_foreign_key "group_stats", "groups", on_delete: :cascade
add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade
add_foreign_key "invites", "users", on_delete: :cascade
@@ -1110,6 +1240,7 @@
add_foreign_key "status_stats", "statuses", on_delete: :cascade
add_foreign_key "statuses", "accounts", column: "in_reply_to_account_id", name: "fk_c7fa917661", on_delete: :nullify
add_foreign_key "statuses", "accounts", name: "fk_9bda1543f7", on_delete: :cascade
+ add_foreign_key "statuses", "groups", on_delete: :cascade
add_foreign_key "statuses", "statuses", column: "in_reply_to_id", on_delete: :nullify
add_foreign_key "statuses", "statuses", column: "reblog_of_id", on_delete: :cascade
add_foreign_key "statuses_tags", "statuses", on_delete: :cascade
@@ -1168,30 +1299,30 @@
add_index "account_summaries", ["account_id"], name: "index_account_summaries_on_account_id", unique: true
create_view "follow_recommendations", materialized: true, sql_definition: <<-SQL
- SELECT t0.account_id,
+ SELECT t0.account_id,
sum(t0.rank) AS rank,
array_agg(t0.reason) AS reason
- FROM ( SELECT account_summaries.account_id,
+ FROM ( SELECT account_summaries.account_id,
((count(follows.id))::numeric / (1.0 + (count(follows.id))::numeric)) AS rank,
'most_followed'::text AS reason
- FROM (((follows
- JOIN account_summaries ON ((account_summaries.account_id = follows.target_account_id)))
- JOIN users ON ((users.account_id = follows.account_id)))
- LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = follows.target_account_id)))
+ FROM (((follows
+ JOIN account_summaries ON ((account_summaries.account_id = follows.target_account_id)))
+ JOIN users ON ((users.account_id = follows.account_id)))
+ LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = follows.target_account_id)))
WHERE ((users.current_sign_in_at >= (now() - 'P30D'::interval)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL))
GROUP BY account_summaries.account_id
- HAVING (count(follows.id) >= 5)
+ HAVING (count(follows.id) >= 5)
UNION ALL
- SELECT account_summaries.account_id,
+ SELECT account_summaries.account_id,
(sum((status_stats.reblogs_count + status_stats.favourites_count)) / (1.0 + sum((status_stats.reblogs_count + status_stats.favourites_count)))) AS rank,
'most_interactions'::text AS reason
- FROM (((status_stats
- JOIN statuses ON ((statuses.id = status_stats.status_id)))
- JOIN account_summaries ON ((account_summaries.account_id = statuses.account_id)))
- LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = statuses.account_id)))
+ FROM (((status_stats
+ JOIN statuses ON ((statuses.id = status_stats.status_id)))
+ JOIN account_summaries ON ((account_summaries.account_id = statuses.account_id)))
+ LEFT JOIN follow_recommendation_suppressions ON ((follow_recommendation_suppressions.account_id = statuses.account_id)))
WHERE ((statuses.id >= (((date_part('epoch'::text, (now() - 'P30D'::interval)) * (1000)::double precision))::bigint << 16)) AND (account_summaries.sensitive = false) AND (follow_recommendation_suppressions.id IS NULL))
GROUP BY account_summaries.account_id
- HAVING (sum((status_stats.reblogs_count + status_stats.favourites_count)) >= (5)::numeric)) t0
+ HAVING (sum((status_stats.reblogs_count + status_stats.favourites_count)) >= (5)::numeric)) t0
GROUP BY t0.account_id
ORDER BY (sum(t0.rank)) DESC;
SQL
Only in truth-new/opensource/db: seeds
diff -ru truth-old/opensource/db/seeds.rb truth-new/opensource/db/seeds.rb
--- truth-old/opensource/db/seeds.rb 2022-06-08 09:15:38
+++ truth-new/opensource/db/seeds.rb 2024-04-01 14:59:13
@@ -1,4 +1,4 @@
-Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
+app = Doorkeeper::Application.create!(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push')
# TODO: Replace these values with configuration values, but... its public information and doesn't really matter.
Doorkeeper::Application.where(uid: 'ZFgr4TjEEojuvyXsJWPh3PZgsN5NzmkwPlPTozNmT7U').first_or_create(name: 'Soapbox FE', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri, scopes: 'read write follow push', secret: 'OjvJska0Sa2Il-xxly4VB9YF8T2I0dwXu3mfkabpY_o')
@@ -6,9 +6,55 @@
account = Account.find_or_initialize_by(id: -99, actor_type: 'Application', locked: true, username: domain)
account.save!
+Dir[File.join(Rails.root, 'db', 'seeds/*', '*.sql')].sort.each do |seed|
+ ActiveRecord::Base.connection.execute File.read(File.expand_path(seed))
+end
-if Rails.env.development?
- admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
+if Rails.env.development? || ENV['REVIEW_APP'] == 'yes'
+ admin = Account.where(username: 'admin').first_or_initialize(username: 'admin')
admin.save(validate: false)
- User.where(email: "admin@#{domain}").first_or_initialize(email: "admin@#{domain}", password: 'mastodonadmin', password_confirmation: 'mastodonadmin', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true).save!
+ user = User.where(email: "admin@truthsocial.com").first_or_initialize(email: "admin@truthsocial.com", password: 'truthsocial', password_confirmation: 'truthsocial', confirmed_at: Time.now.utc, admin: true, account: admin, agreement: true, approved: true)
+ user.save!
+ OauthAccessToken.create!(application: app,
+ resource_owner_id: user.id,
+ scopes: Doorkeeper.configuration.optional_scopes,
+ expires_in: Doorkeeper.configuration.access_token_expires_in,
+ use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?)
end
+
+group_statuses_feature_id = Configuration::Feature.find_or_create_by(name: 'group_statuses').feature_id
+Configuration::FeatureSetting.find_or_create_by(feature_id: group_statuses_feature_id, name: 'rate_limit_duplicate_group_status_enabled', value_type: 'boolean', value: 'false')
+
+# Add seed data for review apps
+if ENV['REVIEW_APP'] == 'yes'
+ password = ENV['REVIEW_APP_PASSWORD'] || 'truthsocial'
+
+ # 10 regular accounts
+ 10.times do |i|
+ Fabricate.build(:user, email: "user#{i}@truthsocial.com", password: password) do
+ account { Fabricate(:account, username: "user#{i}") }
+ end.save
+ end
+
+ # A "whale" account
+ Fabricate.build(:user, email: "whale@truthsocial.com", password: password) do
+ account { Fabricate(:account, username: "whale", whale: true) }
+ end.save
+
+ # Waitlisted user
+ Fabricate.build(:user, email: "waitlisted@truthsocial.com", password: password, approved: false) do
+ account { Fabricate(:account, username: "waitlisted") }
+ end.save
+
+ # Suspended user
+ Fabricate.build(:user, email: "banned@truthsocial.com", password: password) do
+ account { Fabricate(:account, username: "banned", suspended: true) }
+ end.save
+
+ # User with 2FA enabled
+ Fabricate.build(:user, email: "2fa@truthsocial.com", password: password, otp_required_for_login: true) do
+ account { Fabricate(:account, username: "2fa", suspended: true) }
+ end.save
+end
+
+
Only in truth-new/opensource/db: structure.sql
diff -ru truth-old/opensource/lib/active_record/database_tasks_extensions.rb truth-new/opensource/lib/active_record/database_tasks_extensions.rb
--- truth-old/opensource/lib/active_record/database_tasks_extensions.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/active_record/database_tasks_extensions.rb 2023-05-05 13:42:02
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative '../mastodon/snowflake'
+require_relative '../mastodon/materialized_views'
module ActiveRecord
module Tasks
@@ -9,11 +9,10 @@
define_method(:load_schema) do |db_config, *args|
ActiveRecord::Base.establish_connection(db_config)
- Mastodon::Snowflake.define_timestamp_id
original_load_schema.bind(self).call(db_config, *args)
- Mastodon::Snowflake.ensure_id_sequences_exist
+ Mastodon::MaterializedViews.initialize
end
end
end
diff -ru truth-old/opensource/lib/exceptions.rb truth-new/opensource/lib/exceptions.rb
--- truth-old/opensource/lib/exceptions.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/exceptions.rb 2024-04-05 09:07:34
@@ -19,6 +19,18 @@
class RateLimitExceededError < Error; end
+ class HostileRateLimitExceededError < Error; end
+
+ class UnprocessableEntityError < Error; end
+
+ class UnprocessableAssertion < Error; end
+
+ class AttestationError < Error; end
+
+ class ReceiptVerificationError < Error; end
+
+ class RumbleVideoUploadError < Error; end
+
class UnexpectedResponseError < Error
attr_reader :response
@@ -32,4 +44,18 @@
end
end
end
+end
+
+module Tv
+ class Error < StandardError; end
+
+ class LoginError < Error; end
+
+ class SignUpError < Error; end
+
+ class MissingAccountError < Error; end
+
+ class MissingSessionError < Error; end
+
+ class GetProfilesError < Error; end
end
Only in truth-new/opensource/lib: http
diff -ru truth-old/opensource/lib/mastodon/accounts_cli.rb truth-new/opensource/lib/mastodon/accounts_cli.rb
--- truth-old/opensource/lib/mastodon/accounts_cli.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/mastodon/accounts_cli.rb 2024-04-01 14:59:13
@@ -77,7 +77,7 @@
def create(username)
account = Account.new(username: username)
password = SecureRandom.hex
- user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true)
+ user = User.new(email: options[:email], password: password, agreement: true, approved: true, admin: options[:role] == 'admin', moderator: options[:role] == 'moderator', confirmed_at: options[:confirmed] ? Time.now.utc : nil, bypass_invite_request_check: true, sign_up_city_id: 1, sign_up_country_id: 1)
if options[:reattach]
account = Account.find_local(username) || Account.new(username: username)
@@ -87,7 +87,13 @@
say('Use --force to reattach it anyway and delete the other user')
return
elsif account.user.present?
- DeleteAccountService.new.call(account, reserve_email: false)
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'mastodon_cli_create',
+ reserve_email: false,
+ skip_activitypub: true,
+ )
end
end
@@ -192,7 +198,13 @@
end
say("Deleting user with #{account.statuses_count} statuses, this might take a while...")
- DeleteAccountService.new.call(account, reserve_email: false)
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'mastodon_cli_delete',
+ reserve_email: false,
+ skip_activitypub: true,
+ )
say('OK', :green)
end
@@ -304,7 +316,13 @@
end
if [404, 410].include?(code)
- DeleteAccountService.new.call(account, reserve_username: false) unless options[:dry_run]
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'mastodon_cli_cull',
+ reserve_username: false,
+ skip_activitypub: true,
+ ) unless options[:dry_run]
1
else
# Touch account even during dry run to avoid getting the account into the window again
diff -ru truth-old/opensource/lib/mastodon/cache_cli.rb truth-new/opensource/lib/mastodon/cache_cli.rb
--- truth-old/opensource/lib/mastodon/cache_cli.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/mastodon/cache_cli.rb 2024-04-01 14:59:13
@@ -31,7 +31,7 @@
def recount(type)
case type
when 'accounts'
- processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account|
+ processed, = parallelize_with_progress(Account.local.includes(:account_follower, :account_following, :account_status)) do |account|
account_stat = account.account_stat
account_stat.following_count = account.active_relationships.count
account_stat.followers_count = account.passive_relationships.count
@@ -40,7 +40,7 @@
account_stat.save if account_stat.changed?
end
when 'statuses'
- processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status|
+ processed, = parallelize_with_progress(Status.includes(:status_favourite, :status_reply, :status_reblog)) do |status|
status_stat = status.status_stat
status_stat.replies_count = status.replies.where.not(visibility: :direct).count
status_stat.reblogs_count = status.reblogs.count
diff -ru truth-old/opensource/lib/mastodon/domains_cli.rb truth-new/opensource/lib/mastodon/domains_cli.rb
--- truth-old/opensource/lib/mastodon/domains_cli.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/mastodon/domains_cli.rb 2024-04-01 14:59:13
@@ -40,7 +40,16 @@
end
processed, = parallelize_with_progress(scope) do |account|
- DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run]
+ unless options[:dry_run]
+ DeleteAccountService.new.call(
+ account,
+ DeleteAccountService::DELETED_BY_SERVICE,
+ deletion_type: 'mastodon_cli_purge',
+ reserve_username: false,
+ skip_activitypub: true,
+ skip_side_effects: true,
+ )
+ end
end
DomainBlock.where(domain: domains).destroy_all unless options[:dry_run]
Only in truth-new/opensource/lib/mastodon: materialized_views.rb
diff -ru truth-old/opensource/lib/mastodon/redis_config.rb truth-new/opensource/lib/mastodon/redis_config.rb
--- truth-old/opensource/lib/mastodon/redis_config.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/mastodon/redis_config.rb 2024-04-01 14:59:13
@@ -35,9 +35,9 @@
expires_in: 10.minutes,
namespace: cache_namespace,
reconnect_attempts: 1,
- error_handler: -> (method:, returning:, exception:) {
+ error_handler: ->(method:, returning:, exception:) {
Rails.logger.error "redis_error | method: #{method}, returning: #{returning}, exception: #{exception}"
- }
+ },
}.freeze
REDIS_SIDEKIQ_PARAMS = {
diff -ru truth-old/opensource/lib/mastodon/search_cli.rb truth-new/opensource/lib/mastodon/search_cli.rb
--- truth-old/opensource/lib/mastodon/search_cli.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/mastodon/search_cli.rb 2023-05-05 13:42:02
@@ -62,11 +62,7 @@
progress.title = 'Estimating workload '
# Estimate the amount of data that has to be imported first
- indices.each do |index|
- index.types.each do |type|
- progress.total = (progress.total || 0) + type.adapter.default_scope.count
- end
- end
+ progress.total = indices.sum { |index| index.adapter.default_scope.count }
# Now import all the actual data. Mind that unlike chewy:sync, we don't
# fetch and compare all record IDs from the database and the index to
@@ -78,65 +74,66 @@
batch_size = 1_000
slice_size = (batch_size / options[:concurrency]).ceil
- index.types.each do |type|
- type.adapter.default_scope.reorder(nil).find_in_batches(batch_size: batch_size) do |batch|
- futures = []
+ index.adapter.default_scope.reorder(nil).find_in_batches(batch_size: batch_size) do |batch|
+ futures = []
- batch.each_slice(slice_size) do |records|
- futures << Concurrent::Future.execute(executor: pool) do
- if !progress.total.nil? && progress.progress + records.size > progress.total
- # The number of items has changed between start and now,
- # since there is no good way to predict the final count from
- # here, just change the progress bar to an indeterminate one
+ batch.each_slice(slice_size) do |records|
+ futures << Concurrent::Future.execute(executor: pool) do
+ if !progress.total.nil? && progress.progress + records.size > progress.total
+ # The number of items has changed between start and now,
+ # since there is no good way to predict the final count from
+ # here, just change the progress bar to an indeterminate one
- progress.total = nil
- end
+ progress.total = nil
+ end
- grouped_records = nil
- bulk_body = nil
- index_count = 0
- delete_count = 0
+ grouped_records = nil
+ bulk_body = nil
+ index_count = 0
+ delete_count = 0
- ActiveRecord::Base.connection_pool.with_connection do
- grouped_records = type.adapter.send(:grouped_objects, records)
- bulk_body = Chewy::Type::Import::BulkBuilder.new(type, **grouped_records).bulk_body
+ ActiveRecord::Base.connection_pool.with_connection do
+ grouped_records = records.to_a.group_by do |record|
+ index.adapter.send(:delete_from_index?, record) ? :delete : :to_index
end
- index_count = grouped_records[:index].size if grouped_records.key?(:index)
- delete_count = grouped_records[:delete].size if grouped_records.key?(:delete)
+ bulk_body = Chewy::Index::Import::BulkBuilder.new(index, **grouped_records).bulk_body
+ end
- # The following is an optimization for statuses specifically, since
- # we want to de-index statuses that cannot be searched by anybody,
- # but can't use Chewy's delete_if logic because it doesn't use
- # crutches and our searchable_by logic depends on them
- if type == StatusesIndex::Status
- bulk_body.map! do |entry|
- if entry[:index] && entry.dig(:index, :data, 'searchable_by').blank?
- index_count -= 1
- delete_count += 1
+ index_count = grouped_records[:to_index].size if grouped_records.key?
+ delete_count = grouped_records[:delete].size if grouped_records.key?(:delete)
- { delete: entry[:index].except(:data) }
- else
- entry
- end
+ # The following is an optimization for statuses specifically, since
+ # we want to de-index statuses that cannot be searched by anybody,
+ # but can't use Chewy's delete_if logic because it doesn't use
+ # crutches and our searchable_by logic depends on them
+ if index == StatusesIndex
+ bulk_body.map! do |entry|
+ if entry[:index] && entry.dig(:to_index, :data, 'searchable_by').blank?
+ index_count -= 1
+ delete_count += 1
+
+ { delete: entry[:index].except(:data) }
+ else
+ entry
end
end
+ end
- Chewy::Type::Import::BulkRequest.new(type).perform(bulk_body)
+ Chewy::Index::Import::BulkRequest.new(index).perform(bulk_body)
- progress.progress += records.size
+ progress.progress += records.size
- added.increment(index_count)
- removed.increment(delete_count)
+ added.increment(index_count)
+ removed.increment(delete_count)
- sleep 1
- rescue => e
- progress.log pastel.red("Error importing #{index}: #{e}")
- end
+ sleep 1
+ rescue => e
+ progress.log pastel.red("Error importing #{index}: #{e}")
end
-
- futures.map(&:value)
end
+
+ futures.map(&:value)
end
end
diff -ru truth-old/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb truth-new/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb
--- truth-old/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/paperclip/media_type_spoof_detector_extensions.rb 2024-04-01 14:59:13
@@ -27,6 +27,21 @@
def type_from_file_command
@type_from_file_command ||= FileCommandContentTypeDetector.new(@file.path).detect
end
+
+ def calculated_content_type
+ return @calculated_content_type if defined?(@calculated_content_type)
+
+ @calculated_content_type = type_from_file_command.chomp
+
+ # The `file` command fails to recognize some MP3 files as such
+ @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg'
+ @calculated_content_type
+ end
+
+ def type_from_marcel
+ @type_from_marcel ||= Marcel::MimeType.for Pathname.new(@file.path),
+ name: @file.path
+ end
end
end
diff -ru truth-old/opensource/lib/paperclip/transcoder.rb truth-new/opensource/lib/paperclip/transcoder.rb
--- truth-old/opensource/lib/paperclip/transcoder.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/paperclip/transcoder.rb 2024-04-01 14:59:13
@@ -18,36 +18,43 @@
def make
metadata = VideoMetadataExtractor.new(@file.path)
- unless metadata.valid?
- log("Unsupported file #{@file.path}")
- return File.open(@file.path)
- end
+ raise Paperclip::Error, "Error while transcoding #{@file.path}: unsupported file" unless metadata.valid?
- update_attachment_type(metadata)
+ # This method changes the attachment type to gifv
+ # if it is a video with no sound. Don't do that...
+ # it is stupid and it breaks unit tests.
+ # update_attachment_type(metadata)
update_options_from_metadata(metadata)
destination = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
destination.binmode
@output_options = @convert_options[:output]&.dup || {}
- @input_options = @convert_options[:input]&.dup || {}
+ @input_options = @convert_options[:input]&.dup || {}
case @format.to_s
when /jpg$/, /jpeg$/, /png$/, /gif$/
@input_options['ss'] = @time
- @output_options['f'] = 'image2'
+ @output_options['f'] = 'image2'
@output_options['vframes'] = 1
- when 'mp4'
- @output_options['acodec'] = 'aac'
- @output_options['strict'] = 'experimental'
+ when /mp4$/, /mov$/
+ if metadata.audio_codec != 'aac'
+ @output_options['acodec'] = 'aac'
+ end
end
+ @output_options['strict'] = 'experimental'
command_arguments, interpolations = prepare_command(destination)
begin
command = Terrapin::CommandLine.new('ffmpeg', command_arguments.join(' '), logger: Paperclip.logger)
+ timer_start = Time.zone.now
command.run(interpolations)
+ timer_end = Time.zone.now
+ if passthrough_encoding?
+ Prometheus::ApplicationExporter.observe_duration(:video_passthrough_encoding, (timer_end - timer_start).in_milliseconds)
+ end
rescue Terrapin::ExitStatusError => e
raise Paperclip::Error, "Error while transcoding #{@basename}: #{e}"
rescue Terrapin::CommandNotFoundError
@@ -59,9 +66,13 @@
private
+ def passthrough_encoding?
+ @output_options['c:v'] == 'copy'
+ end
+
def prepare_command(destination)
- command_arguments = ['-nostdin']
- interpolations = {}
+ command_arguments = ['-nostdin']
+ interpolations = {}
interpolation_keys = 0
@input_options.each_pair do |key, value|
@@ -87,11 +98,18 @@
[command_arguments, interpolations]
end
- def update_options_from_metadata(metadata)
- return unless @passthrough_options && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
+ def update_options_from_metadata(_metadata)
+ # always do passthrough encoding if we have the passthrough_options
+ # don't filter it by limiting it to quicktime/webm or a particular color palette
+ return unless @passthrough_options # && @passthrough_options[:video_codecs].include?(metadata.video_codec) && @passthrough_options[:audio_codecs].include?(metadata.audio_codec) && @passthrough_options[:colorspaces].include?(metadata.colorspace)
- @format = @passthrough_options[:options][:format] || @format
- @time = @passthrough_options[:options][:time] || @time
+ # When doing passthrough encoding, the output format should be the same as the current format
+ # don't set it it anything else or we will be doing a conversion
+ @format = @current_format # @passthrough_options[:options][:format] || @format
+ if @format == '.qt'
+ @format = 'mp4'
+ end
+ @time = @passthrough_options[:options][:time] || @time
@convert_options = @passthrough_options[:options][:convert_options].dup
end
Only in truth-old/opensource/lib/proto: account_updated_pb.rb
Only in truth-old/opensource/lib/proto: card_created_pb.rb
Only in truth-old/opensource/lib/proto: card_joined_pb.rb
Only in truth-old/opensource/lib/proto: report_created_pb.rb
Only in truth-new/opensource/lib/proto/serializers: account_created_event.rb
Only in truth-new/opensource/lib/proto/serializers: account_deleted_event.rb
diff -ru truth-old/opensource/lib/proto/serializers/account_updated_event.rb truth-new/opensource/lib/proto/serializers/account_updated_event.rb
--- truth-old/opensource/lib/proto/serializers/account_updated_event.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/proto/serializers/account_updated_event.rb 2024-04-01 14:59:13
@@ -1,10 +1,6 @@
# frozen_string_literal: true
-require "./lib/proto/account_updated_pb.rb"
-
class AccountUpdatedEvent
include RoutingHelper
-
- EVENT_KEY = "truth_events:v1:account:updated".freeze
def initialize(account, fields_changed)
@account = account
Only in truth-new/opensource/lib/proto/serializers: asset_created_event.rb
diff -ru truth-old/opensource/lib/proto/serializers/card_joined_event.rb truth-new/opensource/lib/proto/serializers/card_joined_event.rb
--- truth-old/opensource/lib/proto/serializers/card_joined_event.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/proto/serializers/card_joined_event.rb 2024-04-01 14:59:13
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require "./lib/proto/card_joined_pb.rb"
class CardJoinedEvent
include RoutingHelper
- EVENT_KEY = "truth_events:v1:card:joined".freeze
def initialize(card)
@card = card
Only in truth-new/opensource/lib/proto/serializers: chat_message_report_created_event.rb
Only in truth-new/opensource/lib/proto/serializers: feed_event.rb
Only in truth-new/opensource/lib/proto/serializers: group_event.rb
Only in truth-new/opensource/lib/proto/serializers: group_report_created_event.rb
diff -ru truth-old/opensource/lib/proto/serializers/report_created_event.rb truth-new/opensource/lib/proto/serializers/report_created_event.rb
--- truth-old/opensource/lib/proto/serializers/report_created_event.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/proto/serializers/report_created_event.rb 2024-04-01 14:59:13
@@ -1,9 +1,7 @@
# frozen_string_literal: true
-require "./lib/proto/report_created_pb.rb"
class ReportCreatedEvent
include RoutingHelper
- EVENT_KEY = "truth_events:v1:report:created".freeze
def initialize(report)
@report = report
@@ -22,16 +20,14 @@
ReportCreated.new(
id: report.id,
account_id: report.account_id,
- account_username: report.account_username,
target_account_id: report.target_account_id,
- target_account_username: report.target_account_username,
comment: report.comment[0..254],
- status_ids: report.status_ids,
rule_ids: report.rule_ids,
status_id: report.status_id,
image_ids: report.image_ids,
video_ids: report.video_ids,
- report_set_id: report.report_set_id
+ report_set_id: report.report_set_id,
+ group_id: report.group_id
)
end
end
Only in truth-new/opensource/lib/proto/serializers: session_updated_event.rb
diff -ru truth-old/opensource/lib/proto/serializers/status_created_event.rb truth-new/opensource/lib/proto/serializers/status_created_event.rb
--- truth-old/opensource/lib/proto/serializers/status_created_event.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/proto/serializers/status_created_event.rb 2024-04-01 14:59:13
@@ -1,12 +1,11 @@
# frozen_string_literal: true
-require "./lib/proto/status_created_pb.rb"
class StatusCreatedEvent
include RoutingHelper
- EVENT_KEY = "truth_events:v1:status:created".freeze
- def initialize(status)
+ def initialize(status, ip_address)
@status = status
+ @ip_address = ip_address
end
def serialize
@@ -15,14 +14,16 @@
private
- attr_reader :status
+ attr_reader :status, :ip_address
def protobuf
StatusCreated.new(
id: status.id,
account_id: status.account_id,
text: status.text,
- media_attachments: media_attachment_protobufs
+ media_attachments: media_attachment_protobufs,
+ ip_address: ip_address,
+ group_id: status.group_id
)
end
@@ -37,7 +38,7 @@
end
def url(media_attachment)
- if media_attachment.not_processed?
+ if media_attachment.type != "video" && media_attachment.not_processed?
nil
elsif media_attachment.needs_redownload?
media_proxy_url(media_attachment.id, :original)
Only in truth-new/opensource/lib/proto/serializers: status_removed_event.rb
Only in truth-old/opensource/lib/proto: status_created_pb.rb
Only in truth-new/opensource/lib: rmq_consumers
diff -ru truth-old/opensource/lib/sanitize_ext/sanitize_config.rb truth-new/opensource/lib/sanitize_ext/sanitize_config.rb
--- truth-old/opensource/lib/sanitize_ext/sanitize_config.rb 2022-06-08 09:15:38
+++ truth-new/opensource/lib/sanitize_ext/sanitize_config.rb 2024-04-01 14:59:13
@@ -93,26 +93,26 @@
]
)
- MASTODON_OEMBED ||= freeze_config merge(
- RELAXED,
- elements: RELAXED[:elements] + %w(audio embed iframe source video),
+ MASTODON_OEMBED ||= freeze_config(
+ elements: %w(audio embed iframe source video),
- attributes: merge(
- RELAXED[:attributes],
- 'audio' => %w(controls),
- 'embed' => %w(height src type width),
+ attributes: {
+ 'audio' => %w(controls),
+ 'embed' => %w(height src type width),
'iframe' => %w(allowfullscreen frameborder height scrolling src width),
'source' => %w(src type),
- 'video' => %w(controls height loop width),
- 'div' => [:data]
- ),
+ 'video' => %w(controls height loop width),
+ },
- protocols: merge(
- RELAXED[:protocols],
- 'embed' => { 'src' => HTTP_PROTOCOLS },
+ protocols: {
+ 'embed' => { 'src' => HTTP_PROTOCOLS },
'iframe' => { 'src' => HTTP_PROTOCOLS },
- 'source' => { 'src' => HTTP_PROTOCOLS }
- )
+ 'source' => { 'src' => HTTP_PROTOCOLS },
+ },
+
+ add_attributes: {
+ 'iframe' => { 'sandbox' => 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-forms' },
+ }
)
end
end
Only in truth-new/opensource/lib: sidekiq_unique_jobs
Only in truth-new/opensource/lib/tasks: accounts.rake
Only in truth-new/opensource/lib/tasks: app_attest.rake
Only in truth-new/opensource/lib/tasks: chats.rake
Only in truth-new/opensource/lib/tasks: feeds.rake
Only in truth-new/opensource/lib/tasks: groups.rake
Only in truth-new/opensource/lib/tasks: images.rake
Only in truth-new/opensource/lib/tasks: password_history.rake
Only in truth-new/opensource/lib/tasks: trending_statuses.rake
Only in truth-new/opensource: node_modules
diff -ru truth-old/opensource/package.json truth-new/opensource/package.json
--- truth-old/opensource/package.json 2022-06-08 09:15:38
+++ truth-new/opensource/package.json 2024-04-13 09:51:41
@@ -69,6 +69,13 @@
"@gamestdio/websocket": "^0.3.2",
"@github/webauthn-json": "^0.5.7",
"@rails/ujs": "^6.1.3",
+ "@types/cors": "^2.8.12",
+ "@types/express": "^4.17.14",
+ "@types/js-yaml": "^4.0.5",
+ "@types/lodash": "^4.14.190",
+ "@types/npmlog": "^4.1.4",
+ "@types/throng": "^5.0.4",
+ "@types/uuid": "^8.3.4",
"array-includes": "^3.1.3",
"arrow-key-navigation": "^1.2.0",
"autoprefixer": "^9.8.6",
@@ -83,6 +90,7 @@
"classnames": "^2.3.1",
"color-blend": "^3.0.1",
"compression-webpack-plugin": "^6.1.1",
+ "cors": "^2.8.5",
"cross-env": "^7.0.3",
"css-loader": "^5.2.6",
"cssnano": "^4.1.11",
@@ -107,10 +115,12 @@
"is-nan": "^1.3.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
+ "lossless-json": "^1.0.5",
"mark-loader": "^0.1.6",
"marky": "^1.2.2",
"mini-css-extract-plugin": "^1.6.0",
"mkdirp": "^1.0.4",
+ "newrelic": "^9.0.2",
"npmlog": "^4.1.2",
"object-assign": "^4.1.1",
"object-fit-images": "^3.2.3",
@@ -123,6 +133,8 @@
"promise.prototype.finally": "^3.1.2",
"prop-types": "^15.5.10",
"punycode": "^2.1.0",
+ "puppeteer": "^21.3.5",
+ "puppeteer-cluster": "^0.23.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-hotkeys": "^1.1.4",
@@ -160,16 +172,17 @@
"tesseract.js": "^2.1.1",
"throng": "^4.0.0",
"tiny-queue": "^0.2.1",
+ "ts-node": "^10.9.1",
"twitter-text": "3.1.0",
- "uuid": "^8.3.1",
+ "typescript": "^4.8.4",
+ "uuid": "^9.0.0",
"webpack": "^4.46.0",
"webpack-assets-manifest": "^4.0.6",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^3.3.12",
"webpack-merge": "^5.7.3",
"wicg-inert": "^3.1.1",
- "ws": "^7.4.6",
- "lossless-json": "^1.0.5"
+ "ws": "^7.4.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.13.0",
@@ -182,6 +195,7 @@
"eslint-plugin-promise": "~5.1.0",
"eslint-plugin-react": "~7.24.0",
"jest": "^26.6.3",
+ "nodemon": "^2.0.20",
"raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^16.14.0",
Only in truth-new/opensource/public: accounts
Only in truth-new/opensource/public: ads.txt
Binary files truth-old/opensource/public/android-chrome-192x192.png and truth-new/opensource/public/android-chrome-192x192.png differ
Binary files truth-old/opensource/public/apple-touch-icon.png and truth-new/opensource/public/apple-touch-icon.png differ
Only in truth-new/opensource/public: assets
Only in truth-new/opensource/public: docs.css
diff -ru truth-old/opensource/public/embed.js truth-new/opensource/public/embed.js
--- truth-old/opensource/public/embed.js 2022-06-08 09:15:38
+++ truth-new/opensource/public/embed.js 2024-04-01 14:59:13
@@ -1,6 +1,11 @@
+// @ts-check
+
(function() {
'use strict';
+ /**
+ * @param {() => void} loaded
+ */
var ready = function(loaded) {
if (['interactive', 'complete'].indexOf(document.readyState) !== -1) {
loaded();
@@ -10,25 +15,46 @@
};
ready(function() {
- var iframes = [];
+ /** @type {Map<number, HTMLIFrameElement>} */
+ var iframes = new Map();
window.addEventListener('message', function(e) {
var data = e.data || {};
- if (data.type !== 'setHeight' || !iframes[data.id]) {
+ if (typeof data !== 'object' || data.type !== 'setHeight' || !iframes.has(data.id)) {
return;
}
- iframes[data.id].height = data.height;
+ var iframe = iframes.get(data.id);
+
+ if ('source' in e && iframe.contentWindow !== e.source) {
+ return;
+ }
+
+ var hasVideo = iframe.classList.contains('truthsocial-video');
+
+ iframe.height = hasVideo ? data.height + 165 : data.height;
});
- [].forEach.call(document.querySelectorAll('iframe.mastodon-embed'), function(iframe) {
- iframe.scrolling = 'no';
- iframe.style.overflow = 'hidden';
+ [].forEach.call(document.querySelectorAll('iframe.truthsocial-embed'), function(iframe) {
+ // select unique id for each iframe
+ var id = 0, failCount = 0, idBuffer = new Uint32Array(1);
+ while (id === 0 || iframes.has(id)) {
+ id = crypto.getRandomValues(idBuffer)[0];
+ failCount++;
+ if (failCount > 100) {
+ // give up and assign (easily guessable) unique number if getRandomValues is broken or no luck
+ id = -(iframes.size + 1);
+ break;
+ }
+ }
- iframes.push(iframe);
+ iframes.set(id, iframe);
- var id = iframes.length - 1;
+ iframe.scrolling = 'no';
+ iframe.style.overflow = 'hidden';
+ iframe.style.borderRadius = '8px';
+ iframe.style.border = '1px solid rgb(237 237 239)';
iframe.onload = function() {
iframe.contentWindow.postMessage({
Binary files truth-old/opensource/public/favicon.ico and truth-new/opensource/public/favicon.ico differ
Binary files truth-old/opensource/public/favicon.png and truth-new/opensource/public/favicon.png differ
Only in truth-new/opensource/public: groups
Only in truth-new/opensource/public/headers: groups
Binary files truth-old/opensource/public/icons/icon-114x114.png and truth-new/opensource/public/icons/icon-114x114.png differ
Binary files truth-old/opensource/public/icons/icon-120x120.png and truth-new/opensource/public/icons/icon-120x120.png differ
Binary files truth-old/opensource/public/icons/icon-144x144.png and truth-new/opensource/public/icons/icon-144x144.png differ
Binary files truth-old/opensource/public/icons/icon-152x152.png and truth-new/opensource/public/icons/icon-152x152.png differ
Binary files truth-old/opensource/public/icons/icon-180x180.png and truth-new/opensource/public/icons/icon-180x180.png differ
Binary files truth-old/opensource/public/icons/icon-192x192.png and truth-new/opensource/public/icons/icon-192x192.png differ
Binary files truth-old/opensource/public/icons/icon-512x512.png and truth-new/opensource/public/icons/icon-512x512.png differ
Binary files truth-old/opensource/public/icons/icon-57x57.png and truth-new/opensource/public/icons/icon-57x57.png differ
Binary files truth-old/opensource/public/icons/icon-72x72.png and truth-new/opensource/public/icons/icon-72x72.png differ
Binary files truth-old/opensource/public/icons/icon-76x76.png and truth-new/opensource/public/icons/icon-76x76.png differ
Only in truth-new/opensource/public/icons: icon-maskable-128x128.png
Only in truth-new/opensource/public/icons: icon-maskable-192x192.png
Only in truth-new/opensource/public/icons: icon-maskable-384x384.png
Only in truth-new/opensource/public/icons: icon-maskable-48x48.png
Only in truth-new/opensource/public/icons: icon-maskable-512x512.png
Only in truth-new/opensource/public/icons: icon-maskable-72x72.png
Only in truth-new/opensource/public/icons: icon-maskable-96x96.png
Only in truth-new/opensource/public/icons: icon-maskable.png
Binary files truth-old/opensource/public/icons/icon.png and truth-new/opensource/public/icons/icon.png differ
Only in truth-old/opensource/public: mask-icon.svg
Binary files truth-old/opensource/public/mstile-150x150.png and truth-new/opensource/public/mstile-150x150.png differ
Only in truth-new/opensource/public: packs
Only in truth-new/opensource/public: packs-test
diff -ru truth-old/opensource/public/robots.txt truth-new/opensource/public/robots.txt
--- truth-old/opensource/public/robots.txt 2022-06-08 09:15:38
+++ truth-new/opensource/public/robots.txt 2024-04-01 14:59:14
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow: /
\ No newline at end of file
Only in truth-new/opensource/public: swagger-initializer.js
Only in truth-new/opensource/public: swagger-ui-bundle.js
Only in truth-new/opensource/public: swagger-ui-standalone-preset.js
Only in truth-new/opensource/public: swagger-ui.js
Only in truth-new/opensource/public: tv
Only in truth-new/opensource: renovate.json
Only in truth-old/opensource: rspec.xml
diff -ru truth-old/opensource/spec/controllers/accounts_controller_spec.rb truth-new/opensource/spec/controllers/accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -377,7 +377,7 @@
it 'renders account' do
json = body_as_json
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary)
end
context 'in authorized fetch mode' do
@@ -411,7 +411,7 @@
it 'renders account' do
json = body_as_json
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary)
end
end
@@ -435,7 +435,7 @@
it 'renders account' do
json = body_as_json
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary)
end
context 'in authorized fetch mode' do
@@ -459,7 +459,7 @@
it 'renders account' do
json = body_as_json
- expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary)
+ expect(json).to include(:id, :type, :preferredUsername, :publicKey, :name, :summary)
end
end
end
Only in truth-old/opensource/spec/controllers/activitypub: inboxes_controller_spec.rb
Only in truth-old/opensource/spec/controllers/activitypub: outboxes_controller_spec.rb
diff -ru truth-old/opensource/spec/controllers/activitypub/replies_controller_spec.rb truth-new/opensource/spec/controllers/activitypub/replies_controller_spec.rb
--- truth-old/opensource/spec/controllers/activitypub/replies_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/activitypub/replies_controller_spec.rb 2024-04-01 14:59:14
@@ -35,7 +35,7 @@
Fabricate(:status, account: remote_account, thread: status, visibility: :public, uri: remote_reply_id) if remote_reply_id
end
- describe 'GET #index' do
+ xdescribe 'GET #index' do
context 'with no signature' do
subject(:response) { get :index, params: { account_username: status.account.username, status_id: status.id } }
subject(:body) { body_as_json }
diff -ru truth-old/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb truth-new/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb
--- truth-old/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/admin/reported_statuses_controller_spec.rb 2024-04-01 14:59:14
@@ -47,7 +47,7 @@
it 'removes a status' do
allow(RemovalWorker).to receive(:perform_async)
subject.call
- expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
+ expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: false)
end
end
diff -ru truth-old/opensource/spec/controllers/admin/statuses_controller_spec.rb truth-new/opensource/spec/controllers/admin/statuses_controller_spec.rb
--- truth-old/opensource/spec/controllers/admin/statuses_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/admin/statuses_controller_spec.rb 2024-04-01 14:59:14
@@ -65,7 +65,7 @@
it 'removes a status' do
allow(RemovalWorker).to receive(:perform_async)
subject.call
- expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
+ expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: false)
end
end
diff -ru truth-old/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb truth-new/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb
--- truth-old/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/admin/two_factor_authentications_controller_spec.rb 2024-04-01 14:59:14
@@ -43,7 +43,6 @@
user.reload
expect(user.otp_enabled?).to eq false
- expect(user.webauthn_enabled?).to eq false
expect(response).to redirect_to(admin_accounts_path)
end
end
diff -ru truth-old/opensource/spec/controllers/api/oembed_controller_spec.rb truth-new/opensource/spec/controllers/api/oembed_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/oembed_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/oembed_controller_spec.rb 2024-04-01 14:59:14
@@ -5,15 +5,29 @@
let(:alice) { Fabricate(:account, username: 'alice') }
let(:status) { Fabricate(:status, text: 'Hello world', account: alice) }
+ let(:group) { Fabricate(:group, display_name: 'Lorem Ipsum', note: 'Note', statuses_visibility: 'everyone', owner_account: alice ) }
+ let!(:membership) { group.memberships.create!(account: alice, role: :owner) }
+ let!(:group_status) { Status.create!(account: alice, text: 'test', group: group, visibility: :group) }
describe 'GET #show' do
before do
request.host = Rails.configuration.x.local_domain
- get :show, params: { url: short_account_status_url(alice, status) }, format: :json
end
it 'returns http success' do
+ get :show, params: { url: short_account_status_url(alice, status) }, format: :json
expect(response).to have_http_status(200)
+ end
+
+ it 'returns http success for public group statuses' do
+ get :show, params: { url: short_account_status_url(alice, group_status) }, format: :json
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns 404 nout found for private group statuses' do
+ group.update(statuses_visibility: 'members_only')
+ get :show, params: { url: short_account_status_url(alice, group_status) }, format: :json
+ expect(response).to have_http_status(404)
end
end
-end
+end
\ No newline at end of file
diff -ru truth-old/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/pleroma/accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -14,12 +14,12 @@
describe 'GET #setup_totp' do
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
- let(:scopes) { 'write:security' }
+ let(:scopes) { 'write:security read' }
before do
get :setup_totp
end
-
+
it 'returns http success' do
expect(response).to have_http_status(200)
end
@@ -33,7 +33,7 @@
describe 'POST #confirm_totp' do
let(:code) { '123456' }
let(:new_otp_secret) { User.generate_otp_secret(32) }
- let(:scopes) { 'write:security' }
+ let(:scopes) { 'write:security write' }
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret) }
context 'with correct password and an incorrect code' do
@@ -60,7 +60,7 @@
describe 'GET #backup_codes' do
let(:code) { '123456' }
let(:new_otp_secret) { User.generate_otp_secret(32) }
- let(:scopes) { 'write:security' }
+ let(:scopes) { 'write:security read' }
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret) }
it 'returns http success' do
@@ -79,7 +79,7 @@
describe 'DELETE #delete_totp' do
let(:code) { '123456' }
let(:new_otp_secret) { User.generate_otp_secret(32) }
- let(:scopes) { 'write:security' }
+ let(:scopes) { 'write:security write' }
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), otp_secret: new_otp_secret, otp_required_for_login: true) }
it 'returns http success' do
diff -ru truth-old/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb truth-new/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/pleroma/user_settings_controller_spec.rb 2024-04-01 14:59:14
@@ -17,7 +17,7 @@
post :change_password, params: { password: '123456789', new_password: new_password, new_password_confirmation: new_password }
end
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
let(:new_password) { 'testfoobar' }
describe 'POST #change_password' do
@@ -29,6 +29,10 @@
user.reload
expect(user.valid_password?(new_password)).to be(true)
end
+
+ it 'keeps the user logged in to their current session' do
+ expect(controller.current_user_id).not_to be_nil
+ end
end
end
@@ -47,18 +51,39 @@
end
end
+ context 'with a previously used password' do
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
+ let(:new_password) { '123456789' }
+
+ before do
+ request.headers['Accept-Language'] = 'es'
+ post :change_password, params: { password: new_password, new_password: new_password, new_password_confirmation: new_password }
+ end
+
+ describe 'POST #change_password' do
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(400)
+ expect(body_as_json[:error]).to eq I18n.t('users.previously_used_password')
+ expect(body_as_json[:error_code]).to eq 'PASSWORD_INVALID'
+ expect(body_as_json[:error_message]).to eq I18n.t('users.previously_used_password', locale: :es)
+ end
+ end
+ end
+
context 'with a new passwords that don\'t match' do
before do
post :change_password, params: { password: '123456789', new_password: new_password, new_password_confirmation: 'bad_password' }
end
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
let(:new_password) { 'testfoobar' }
describe 'POST #change_password' do
it 'returns http forbidden' do
expect(response).to have_http_status(400)
expect(body_as_json[:error]).to eq('Password and password confirmation do not match.')
+ expect(body_as_json[:error_code]).to eq('PASSWORD_MISMATCH')
+ expect(body_as_json[:error_message]).to eq('Password and password confirmation do not match.')
end
end
end
@@ -72,7 +97,7 @@
allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil))
end
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
let(:new_email) { 'lets.go@brandon.com' }
context 'good email' do
@@ -108,7 +133,7 @@
describe 'POST #delete_account' do
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write') }
context 'with correct password' do
before do
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/accounts/credentials_controller_spec.rb 2024-04-12 16:28:08
@@ -3,11 +3,17 @@
describe Api::V1::Accounts::CredentialsController do
render_views
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'user')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
context 'with an oauth token' do
before do
+ _feature_1 = Fabricate(:feature_flag, name: 'feature_1', status: 'enabled')
+ _feature_2 = Fabricate(:feature_flag, name: 'feature_2', status: 'disabled')
+ feature_3 = Fabricate(:feature_flag, name: 'feature_3', status: 'account_based')
+ _feature_4 = Fabricate(:feature_flag, name: 'feature_4', status: 'account_based')
+ Fabricate(:account_feature, feature_flag: feature_3, account: user.account)
+
allow(controller).to receive(:doorkeeper_token) { token }
end
@@ -24,6 +30,11 @@
expect(body_as_json[:source][:email]).to eq(user.email)
end
+ it 'includes a user\'s unauth_visibility' do
+ get :show
+ expect(body_as_json[:source][:unauth_visibility]).to be true
+ end
+
it 'includes a user\'s approval status' do
get :show
expect(body_as_json[:source][:approved]).to eq(user.approved)
@@ -34,12 +45,53 @@
expect(body_as_json[:source][:sms_verified]).to eq(true)
end
+ it 'includes pleroma.accepts_chat_messages' do
+ get :show
+ expect(body_as_json[:pleroma][:accepts_chat_messages]).to be true
+ end
+
+ it 'includes a user\'s integrity score' do
+ get :show
+ expect(body_as_json[:source][:integrity]).to eq 1
+ end
+
+ it 'includes a user\'s integrity_status' do
+ get :show
+ expect(body_as_json[:source][:integrity_status]).to eq []
+ end
+
+ it 'includes feature flag config for a user' do
+ get :show
+ expect(body_as_json[:features]).to eq({ feature_1: true, feature_2: false, feature_3: true, feature_4: false })
+ end
+
+ it 'includes receive_only_follow_mentions' do
+ get :show
+ expect(body_as_json[:source][:receive_only_follow_mentions]).to eq false
+ end
+
+ it 'does not include a user\'s sms_last_four_digits' do
+ get :show
+ expect(body_as_json[:source][:sms_last_four_digits]).to be nil
+ end
+
context 'when a user has an sms saved' do
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), sms: '123-123-1222') }
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), sms: '+12312312222') }
+
it 'includes if a user has verified their sms' do
get :show
expect(body_as_json[:source][:sms_verified]).to eq(true)
end
+
+ it 'includes a user\'s sms_last_four_digits' do
+ get :show
+ expect(body_as_json[:source][:sms_last_four_digits]).to eq '2222'
+ end
+
+ it 'returns sms_country' do
+ get :show
+ expect(body_as_json[:source][:sms_country]).to eq("US")
+ end
end
context 'when a user does not have an sms saved and is not approved by sms' do
@@ -59,6 +111,114 @@
expect(body_as_json[:source][:sms_verified]).to eq(false)
end
end
+
+ context 'when user_sms_reverification_required is true' do
+ before do
+ request.user_agent = "TruthSocialAndroid/okhttp/5.0.0-alpha.7"
+ end
+
+ it 'returns "re_verify" status' do
+ user.create_user_sms_reverification_required
+ get :show
+ expect(body_as_json[:source][:integrity_status]).to eq %w(favourite status chat_message reblog)
+ end
+
+ it 'returns "re_verify" status if last_verified_at is outside of the allowable time' do
+ user.create_user_sms_reverification_required
+ verification = DeviceVerification.create!(remote_ip: '0.0.0.0', details: {}, platform_id: 2)
+ OauthAccessTokens::IntegrityCredential.create!(verification: verification, token: token, user_agent: 'UserAgent', last_verified_at: Time.now - 2.hours)
+
+ get :show
+
+ expect(body_as_json[:source][:integrity_status]).to eq %w(favourite status chat_message reblog)
+ end
+
+ it 'returns "verified"(empty array) for integrity_status' do
+ user.create_user_sms_reverification_required
+ verification = DeviceVerification.create!(remote_ip: '0.0.0.0', details: {}, platform_id: 2)
+ OauthAccessTokens::IntegrityCredential.create!(verification: verification, token: token, user_agent: 'UserAgent', last_verified_at: Time.now)
+
+ get :show
+
+ expect(body_as_json[:source][:integrity_status]).to eq []
+ end
+
+ it 'returns "verified" status if not android client' do
+ user.create_user_sms_reverification_required
+ request.user_agent = "TruthSocial/83 CFNetwork/1121.2.2 Darwin/19.3.0"
+
+ get :show
+
+ expect(body_as_json[:source][:integrity_status]).to eq []
+ end
+ end
+
+ describe '#TV' do
+ context 'when the request is made with the required user agent' do
+ before do
+ stub_const('Api::V1::Accounts::CredentialsController::TV_REQUIRED_IOS_VERSION', 200)
+ request.headers.merge!(HTTP_USER_AGENT: 'TruthSocial/201 CFNetwork/1410.0.3 Darwin/22.6.0')
+ allow(TvAccountsCreateWorker).to receive(:perform_async)
+ end
+
+ context 'when there isnt a TV account created' do
+ it 'it calls the worker for creating a new account' do
+ get :show
+ expect(TvAccountsCreateWorker).to have_received(:perform_async).with(user.account.id, token.id)
+ end
+ end
+
+ context 'when there is a TV account created with a missing pprofile_id' do
+ before do
+ tv_account = Fabricate(:tv_account, account: user.account, p_profile_id: nil)
+ end
+
+ it 'it calls the worker for creating a new account' do
+ get :show
+ expect(TvAccountsCreateWorker).to have_received(:perform_async).with(user.account.id, token.id)
+ end
+ end
+
+ context 'when there is a TV account created, but there isnt a tv session' do
+ before do
+ allow(TvAccountsLoginWorker).to receive(:perform_async)
+ tv_account = Fabricate(:tv_account, account: user.account)
+ end
+
+ it 'it calls the worker for login to an exsting account' do
+ get :show
+ expect(TvAccountsLoginWorker).to have_received(:perform_async).with(user.account.id, token.id)
+ end
+ end
+
+ context 'when there is a TV account created, and there is a tv session' do
+ before do
+ allow(TvAccountsLoginWorker).to receive(:perform_async)
+ allow(TvAccountsCreateWorker).to receive(:perform_async)
+ tv_account = Fabricate(:tv_account, account: user.account)
+ tv_device_session = Fabricate(:tv_device_session, doorkeeper_access_token: token)
+ end
+
+ it 'it doesnt call the workers for tv accounts' do
+ get :show
+ expect(TvAccountsLoginWorker).not_to have_received(:perform_async)
+ expect(TvAccountsCreateWorker).not_to have_received(:perform_async)
+ end
+ end
+ end
+
+ context 'when the request is made without the required user agent' do
+ before do
+ allow(TvAccountsLoginWorker).to receive(:perform_async)
+ allow(TvAccountsCreateWorker).to receive(:perform_async)
+ end
+ it 'it doesnt call the workers for tv accounts' do
+ get :show
+ expect(TvAccountsLoginWorker).not_to have_received(:perform_async)
+ expect(TvAccountsCreateWorker).not_to have_received(:perform_async)
+ end
+ end
+ end
end
describe 'PATCH #update' do
@@ -79,22 +239,40 @@
privacy: 'unlisted',
sensitive: true,
},
- pleroma_settings_store: { scott: "baio" }
+ pleroma_settings_store: { scott: 'baio' },
+ accepting_messages: true,
+ unauth_visibility: false,
+ feeds_onboarded: true,
+ tv_onboarded: true,
+ show_nonmember_group_statuses: false,
+ receive_only_follow_mentions: true
}
end
it 'leaves pleroma_settings_store alone if not provided' do
user.account.reload
- expect(user.account.settings_store).to eq({ "scott" => "baio" })
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' })
patch :update, params: {
- display_name: "Alice IS Dead",
+ display_name: 'Alice IS Dead',
}
user.account.reload
- expect(user.account.settings_store).to eq({ "scott" => "baio" })
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' })
end
+ it 'accepts "accepts_chat_messages" param' do
+ user.account.reload
+ expect(user.account.accepting_messages).to eq(true)
+
+ patch :update, params: {
+ accepts_chat_messages: false,
+ }
+
+ user.account.reload
+ expect(user.account.accepting_messages).to eq(false)
+ end
+
it 'returns http success' do
expect(response).to have_http_status(200)
end
@@ -107,15 +285,40 @@
expect(user.account.avatar).to exist
expect(user.account.header).to exist
expect(user.setting_default_privacy).to eq('unlisted')
- # TODO @features This setting is not user configurable
+ # TODO: @features This setting is not user configurable
# expect(user.setting_default_sensitive).to eq(true)
- expect(user.account.settings_store).to eq({ "scott" => "baio"})
+ expect(user.account.settings_store).to eq({ 'scott' => 'baio' })
expect(user.account.bot?).to eq(false)
end
it 'queues up an account update distribution' do
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id)
end
+
+ it 'updates unauth_visibility for user' do
+ expect(body_as_json[:source][:unauth_visibility]).to eq false
+ expect(user.reload.unauth_visibility).to eq false
+ end
+
+ it 'sets feeds_onboarded for account' do
+ expect(body_as_json[:source][:feeds_onboarded]).to eq true
+ expect(user.account.reload.feeds_onboarded).to eq true
+ end
+
+ it 'sets tv_onboarded for account' do
+ expect(body_as_json[:source][:tv_onboarded]).to eq true
+ expect(user.account.reload.tv_onboarded).to eq true
+ end
+
+ it 'sets show_nonmember_group_statuses for account' do
+ expect(body_as_json[:source][:show_nonmember_group_statuses]).to eq false
+ expect(user.account.reload.show_nonmember_group_statuses).to eq false
+ end
+
+ it 'sets receive_only_follow_mentions for account' do
+ expect(body_as_json[:source][:receive_only_follow_mentions]).to eq true
+ expect(user.account.reload.receive_only_follow_mentions).to eq true
+ end
end
describe 'with empty source list' do
@@ -129,7 +332,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
end
- end
+ end
describe 'with invalid data' do
before do
@@ -140,6 +343,20 @@
expect(response).to have_http_status(:unprocessable_entity)
end
end
+
+ describe 'with empty header' do
+ before do
+ patch :update, params: {
+ header: '',
+ }, as: :json
+ end
+
+ it 'returns http success' do
+ user.account.reload
+ expect(response).to have_http_status(200)
+ expect(user.account.header_file_name).to eq nil
+ end
+ end
end
describe 'GET #chat_token' do
@@ -157,14 +374,14 @@
it 'includes the token' do
token = body_as_json[:token]
decoded_token = JWT.decode token, ENV['MATRIX_SIGNING_KEY'], true, { algorithm: 'HS256' }
- sub = decoded_token.first["sub"]
- exp = Time.at(decoded_token.first["exp"])
- iat = Time.at(decoded_token.first["iat"])
- nbf = Time.at(decoded_token.first["nbf"])
- expect(sub).to eq('alice')
- expect(exp).to be >= Time.now().weeks_since(3)
- expect(iat).to be <= Time.now()
- expect(nbf).to be <= Time.now()
+ sub = decoded_token.first['sub']
+ exp = Time.at(decoded_token.first['exp'])
+ iat = Time.at(decoded_token.first['iat'])
+ nbf = Time.at(decoded_token.first['nbf'])
+ expect(sub).to eq('user')
+ expect(exp).to be >= Time.now.weeks_since(3)
+ expect(iat).to be <= Time.now
+ expect(nbf).to be <= Time.now
end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -29,34 +29,84 @@
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
- it 'paginates in ascending order' do
- follower = []
- 4.times do |i|
- follower[i] = Fabricate(:account)
- follower[i].follow!(account)
+ context 'for the current user' do
+ before do
+ current_user = Fabricate(:user, account: account)
+ current_user_token = Fabricate(:accessible_access_token, resource_owner_id: current_user.id, scopes: 'read:accounts')
+ allow(controller).to receive(:doorkeeper_token) { current_user_token }
end
- get :index, params: { account_id: account.id, limit: 2}
- expect(body_as_json.size).to eq 2
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
- min_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last
-
- get :index, params: { account_id: account.id, limit: 2, min_id: min_id}
- expect(body_as_json.size).to eq 2
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[0].id.to_s, follower[1].id.to_s])
- min_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last
+ it 'paginates in descending order' do
+ follower = []
+ 4.times do |i|
+ follower[i] = Fabricate(:account)
+ follower[i].follow!(account)
+ end
- get :index, params: { account_id: account.id, limit: 2, min_id: min_id}
- expect(body_as_json.size).to eq 2
- expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[2].id.to_s, follower[3].id.to_s])
+ get :index, params: { account_id: account.id, limit: 2 }
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[3].id.to_s, follower[2].id.to_s])
+ max_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last
+
+ get :index, params: { account_id: account.id, limit: 2, max_id: max_id}
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([follower[1].id.to_s, follower[0].id.to_s])
+ max_id = URI(response.headers['Link'].links.first.href).query.split('&').last.split('=').last
+
+ get :index, params: { account_id: account.id, limit: 2, max_id: max_id}
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([bob.id.to_s, alice.id.to_s])
+
+ end
end
+ context 'for a different user' do
+ before do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ @follower = []
+ 4.times do |i|
+ @follower[i] = Fabricate(:account)
+ @follower[i].follow!(account)
+ end
+ end
+ it 'gets the first page of results in ascending order' do
+ get :index, params: { account_id: account.id, limit: 2 }
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
+ end
+
+ it 'returns results from the first page, instead of the second' do
+ get :index, params: { account_id: account.id, limit: 2, min_id: bob.id}
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
+ end
+
+ it 'returns results from the first page with max_id as well' do
+ get :index, params: { account_id: account.id, limit: 2, max_id: @follower[2].id}
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
+ end
+
+ it 'does not recieve pagination headers' do
+ get :index, params: { account_id: account.id, limit: 2 }
+ expect(response.headers).not_to have_key('Link')
+ end
+ end
+
it 'does not return blocked users' do
user.account.block!(bob)
get :index, params: { account_id: account.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
+ end
+
+ it 'does return suspended users' do
+ bob.suspend!
+ get :index, params: { account_id: account.id, limit: 2 }
+
+ expect(body_as_json.size).to eq 2
+ expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
context 'when requesting user is blocked' do
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/accounts/relationships_controller_spec.rb 2024-04-05 09:07:34
@@ -11,8 +11,20 @@
end
describe 'GET #index' do
- let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account }
- let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account }
+ let(:simon) do
+ Fabricate(
+ :user,
+ email: 'simon@example.com',
+ account: Fabricate(:account, username: 'simon')
+ ).account
+ end
+ let(:lewis) do
+ Fabricate(
+ :user,
+ email: 'lewis@example.com',
+ account: Fabricate(:account, username: 'lewis')
+ ).account
+ end
before do
user.account.follow!(simon)
@@ -52,21 +64,27 @@
expect(json).to be_a Enumerable
expect(json.first[:id]).to eq simon.id.to_s
expect(json.first[:following]).to be true
- expect(json.first[:showing_reblogs]).to be true
+ expect(json.first[:showing_reblogs]).to be false
expect(json.first[:followed_by]).to be false
expect(json.first[:muting]).to be false
- expect(json.first[:requested]).to be false
- expect(json.first[:domain_blocking]).to be false
+ expect(json.first[:note]).to eq ''
expect(json.second[:id]).to eq lewis.id.to_s
expect(json.second[:following]).to be false
expect(json.second[:showing_reblogs]).to be false
expect(json.second[:followed_by]).to be true
expect(json.second[:muting]).to be false
- expect(json.second[:requested]).to be false
- expect(json.second[:domain_blocking]).to be false
+ expect(json.second[:note]).to eq ''
end
+ it 'returns hardcoded JSON with correct data' do
+ json = body_as_json
+ expect(json.second[:requested]).to eq false
+ expect(json.second[:domain_blocking]).to eq false
+ expect(json.second[:endorsed]).to eq false
+ expect(json.second[:showing_reblogs]).to eq false
+ end
+
it 'returns JSON with correct data on cached requests too' do
get :index, params: { id: [simon.id] }
@@ -74,7 +92,7 @@
expect(json).to be_a Enumerable
expect(json.first[:following]).to be true
- expect(json.first[:showing_reblogs]).to be true
+ expect(json.first[:showing_reblogs]).to be false
end
it 'returns JSON with correct data after change too' do
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/accounts/search_controller_spec.rb 2024-04-01 14:59:14
@@ -16,5 +16,19 @@
expect(response).to have_http_status(200)
end
+
+ it 'inserts next pagination link header' do
+ account1 = Fabricate(:account, username: 'query', accepting_messages: true)
+ account2 = Fabricate(:account, username: 'query1', accepting_messages: true)
+ account3 = Fabricate(:account, username: 'query2', accepting_messages: true)
+
+ account1.follow!(user.account)
+ account2.follow!(user.account)
+ account3.follow!(user.account)
+
+ get :show, params: { q: 'query', followers: true, limit: 2 }
+
+ expect(response.headers['Link'].links.first.href).to include api_v1_accounts_search_url(limit: 2, followers: true, q: 'query', offset: 2)
+ end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -30,6 +30,10 @@
expect(response).to have_http_status(200)
end
+ it 'returns pleroma.accepts_chat_messages' do
+ expect(body_as_json[:pleroma][:accepts_chat_messages]).to be true
+ end
+
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
end
@@ -60,6 +64,15 @@
expect(user.account.following?(other_account)).to be true
end
+ it 'applies HostileRateLimiter to hostile accounts' do
+ user.account.update!(trust_level: Account::TRUST_LEVELS[:hostile])
+ expect(Follow.where(account_id: user.account_id).count).to eq(1)
+
+ post :follow, params: { id: Fabricate(:account).id }
+ expect(response).to have_http_status(200)
+ expect(Follow.where(account_id: user.account_id).count).to eq(1)
+ end
+
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end
@@ -79,6 +92,14 @@
it 'creates a follow request relation between user and target user' do
expect(user.account.requested?(other_account)).to be true
+ end
+
+ it 'respects rate limit' do
+ 299.times do
+ post :follow, params: { id: Fabricate(:account).id }
+ end
+ post :follow, params: { id: Fabricate(:account).id }
+ expect(response).to have_http_status(429)
end
it_behaves_like 'forbidden for wrong scope', 'read:accounts'
Only in truth-new/opensource/spec/controllers/api/v1/admin/accounts: statuses_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin/accounts: webauthn_credentials_controller_spec.rb
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/admin/accounts_controller_spec.rb 2024-04-05 09:07:34
@@ -52,10 +52,57 @@
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
- expect(body_as_json[0][:id].to_i).to eq(user.account.id)
+ first_account = body_as_json.first
+ expect(first_account[:id].to_i).to eq(user.account.id)
+ expect(first_account[:advertiser]).to be false
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(first_account)
end
end
+
+ context 'with advertisers' do
+ let(:advertisers) do
+ [
+ { username: 'Mary' },
+ { username: 'Joe' },
+ ]
+ end
+ let(:advertisers_beyond_date_range) do
+ [
+ { username: 'Frank', travel_days_ago: 31 },
+ ]
+ end
+
+ before do
+ (advertisers + advertisers_beyond_date_range).each do |user_data|
+ travel_to Time.zone.now - (user_data[:travel_days_ago] || 0).days do
+ u = Fabricate(:user, role: role, sms: '234-555-2344', account: Fabricate(:account, username: user_data[:username]))
+ s = Fabricate(:status, account: u.account)
+ Fabricate(:ad, status: s)
+ end
+ end
+
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'write:statuses'
+ it_behaves_like 'forbidden for wrong role', 'user'
+
+ it 'has 2 records that are advertisers within 30 days' do
+ expect(response).to have_http_status(200)
+ body_as_json.each do |account|
+ expect_to_be_an_admin_account(account)
+ end
+ expect(body_as_json.count).to eq 4
+
+ #
+ # non-advertisers = Alice
+ # advertisers = Mary, Joe
+ # advertiser beyond date range = Frank
+ #
+ expect(body_as_json.select { |r| r[:advertiser] }.count).to eq 2
+ end
+ end
end
describe 'GET #show' do
@@ -68,11 +115,12 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
end
describe 'PATCH #update' do
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2 ) }
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2) }
let(:account1) { user1.account }
let(:sms) { '123123123' }
@@ -84,11 +132,12 @@
it_behaves_like 'forbidden for wrong role', 'user'
context 'with a user that is ready_to_approve' do
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2 ) }
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 2) }
let(:account1) { user1.account }
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'updates the users sms record and sets them to approved' do
@@ -99,7 +148,7 @@
end
context 'with a user that is not ready_to_approve' do
- let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 0 ) }
+ let(:user1) { Fabricate(:user, sms: nil, approved: false, ready_to_approve: 0) }
let(:account1) { user1.account }
it 'updates the users sms record' do
@@ -125,7 +174,7 @@
email: 'bob@example.com',
password: '12345678',
approved: 'true',
- role: 'moderator'
+ role: 'moderator',
}
)
end
@@ -136,7 +185,7 @@
it 'does not send Waitlisted email' do
expect(ActionMailer::Base.deliveries.count).to eq(1)
- expect(ActionMailer::Base.deliveries[0].subject).to eq(I18n.t('notification_mailer.user_approved.web.subject'))
+ expect(ActionMailer::Base.deliveries[0].subject).to eq(I18n.t('notification_mailer.user_approved.title', name: 'bob'))
end
it 'marks moderators as undiscoverable' do
@@ -156,7 +205,7 @@
verified: verified,
email: 'bob@example.com',
password: '12345678',
- role: 'user'
+ role: 'user',
}
)
end
@@ -178,7 +227,7 @@
end
context 'approved param included set to false' do
- let(:user_46) { Fabricate(:user, approved: false ) }
+ let(:user_46) { Fabricate(:user, approved: false) }
before do
user_46
post(
@@ -190,7 +239,7 @@
email: 'bob@example.com',
password: '12345678',
role: 'moderator',
- approved: 'false'
+ approved: 'false',
}
)
end
@@ -206,13 +255,27 @@
it 'sets the users waitlist position' do
user = User.find_by(email: 'bob@example.com')
- expect(user.waitlist_position).to eq(11343)
+ expect(user.waitlist_position).to eq(11_343)
end
end
context 'confirmed param included' do
+ let!(:policy) { Fabricate(:policy, version: '1.0.0') }
+
before do
- post :create, params: { username: 'bob', sms: '234-555-2344', approved: approved, verified: verified, email: 'bob@example.com', password: '12345678', confirmed: 'true', role: 'moderator' }
+ post(
+ :create,
+ params: {
+ username: 'bob',
+ sms: '234-555-2344',
+ approved: approved,
+ verified: verified,
+ email: 'bob@example.com',
+ password: '12345678',
+ confirmed: 'true',
+ role: 'moderator',
+ }
+ )
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
@@ -229,6 +292,7 @@
expect(account.user.functional?).to be true
expect(account.user.confirmed?).to be true
expect(account.verified?).to be false
+ expect(account.feeds_onboarded).to be true
expect(account.user.moderator).to be true
end
@@ -236,6 +300,7 @@
account = Account.find_by(username: 'bob')
expect(account.user.sms).to eq('234-555-2344')
expect(account.user.email).to eq('bob@example.com')
+ expect(account.user.policy.id).to eq(policy.id)
end
it 'approves the new user' do
@@ -262,13 +327,178 @@
context 'when confirmed param is missing' do
before do
- post :create, params: { username: 'bob', sms: '234-555-2344', approved: approved, verified: verified, email: 'bob@example.com', password: '12345678', role: 'moderator' }
+ post(
+ :create,
+ params: {
+ username: 'bob',
+ sms: '234-555-2344',
+ approved: approved,
+ verified: verified,
+ email: 'bob@example.com',
+ password: '12345678',
+ role: 'moderator',
+ }
+ )
end
it 'does not confirm the new user' do
account = Account.find_by(username: 'bob')
expect(account.user.confirmed?).to be false
end
end
+
+ context 'when geo params are included' do
+ let(:params) do
+ { username: 'steve',
+ sms: '234-555-2344',
+ verified: verified,
+ email: 'bob@example.com',
+ password: '12345678',
+ approved: 'true',
+ city_name: 'Timbuktu',
+ country_code: 'XX',
+ country_name: 'XX',
+ region_code: 'RR',
+ region_name: 'Region' }
+ end
+
+ it 'creates a new country if one does not exist' do
+ post :create, params: params
+
+ expect(Country.last.code).to eq 'XX'
+ end
+
+ it 'creates a new region if one does not exist' do
+ post :create, params: params
+
+ expect(Region.last.code).to eq 'RR'
+ end
+
+ it 'creates a new state if one does not exist' do
+ post :create, params: params
+
+ city = City.find_by(name: 'Timbuktu')
+ account = Account.find_by(username: 'steve')
+ expect(account.user.sign_up_city_id).to eq city.id
+ end
+ end
+
+ context 'when geo params are not included' do
+ it 'defaults to the city with id = 1' do
+ post(
+ :create,
+ params: {
+ username: 'steve',
+ sms: '234-555-2344',
+ verified: verified,
+ email: 'bob@example.com',
+ password: '12345678',
+ approved: 'true',
+ }
+ )
+
+ account = Account.find_by(username: 'steve')
+ expect(account.user.country.id).to eq 1
+ expect(account.user.sign_up_city_id).to eq 1
+ end
+ end
+
+ context 'when registration_token is present' do
+ let(:registration_token) { Base64.strict_encode64(SecureRandom.random_bytes(32)) }
+ let!(:verification) do
+ DeviceVerification.create!(platform_id: 2,
+ remote_ip: '0.0.0.0',
+ details: {
+ registration_token: registration_token,
+ })
+ end
+
+ context 'when android client' do
+ before do
+ @challenge = RegistrationService.new(token: registration_token, platform: 'android', new_otc: true).call[:one_time_challenge]
+ @registration = Registration.find_by(token: registration_token)
+ request.headers['registration_token'] = registration_token
+ end
+
+ it 'should delete registration records for android device registrant' do
+ post :create, params: {
+ username: 'bob',
+ sms: '234-555-2344',
+ verified: verified,
+ email: 'bob@example.com',
+ password: '12345678',
+ role: 'moderator',
+ approved: 'false',
+ geoip_country_code: 'US',
+ token: @registration.token,
+ }
+
+ verification = DeviceVerification.find_by("details ->> 'registration_token' = '#{@registration.token}'")
+ expect(response).to have_http_status(200)
+ expect { @registration.reload }.to raise_error ActiveRecord::RecordNotFound
+ expect(RegistrationOneTimeChallenge.find_by(registration_id: @registration.id)).to be_nil
+ expect(OneTimeChallenge.find_by(challenge: @challenge)).to be_nil
+ expect(verification.details['user_id']).to be_present
+ end
+ end
+
+ context 'when ios client' do
+ let(:credential) { Fabricate(:webauthn_credential, external_id: 'EXTERNAL ID') }
+
+ before do
+ @challenge = RegistrationService.new(token: registration_token, platform: 'ios', new_otc: true).call[:one_time_challenge]
+ @registration = Registration.find_by(token: registration_token)
+ @rwc = RegistrationWebauthnCredential.create!(registration: @registration, webauthn_credential: credential)
+ request.headers['registration_token'] = registration_token
+ end
+
+ it 'should transfer webauthn credentials to new user and delete registration records for ios device registrant' do
+ email = 'bob@example.com'
+
+ post :create, params: {
+ username: 'bob',
+ sms: '234-555-2344',
+ verified: verified,
+ email: email,
+ password: '12345678',
+ role: 'moderator',
+ approved: 'false',
+ geoip_country_code: 'US',
+ token: @registration.token,
+ }
+
+ expect(response).to have_http_status(200)
+ expect { @registration.reload }.to raise_error ActiveRecord::RecordNotFound
+ expect { @rwc.reload }.to raise_error ActiveRecord::RecordNotFound
+ user = User.find_by!(email: email)
+ expect(credential.reload.user_id).to eq user.id
+ expect(@registration.registration_webauthn_credential).to be_nil
+ expect(@registration.registration_one_time_challenge).to be_nil
+ end
+
+ context 'credential error' do
+ let(:credential) { Fabricate(:webauthn_credential, external_id: 'EXTERNAL ID', user: Fabricate(:user)) }
+
+ it 'should return a 422 is webauthn credential is already affiliated with an account' do
+ email = 'bob@example.com'
+
+ post :create, params: {
+ username: 'bob',
+ sms: '234-555-2344',
+ verified: verified,
+ email: email,
+ password: '12345678',
+ role: 'moderator',
+ approved: 'false',
+ geoip_country_code: 'US',
+ token: @registration.token,
+ }
+
+ expect(response).to have_http_status(422)
+ expect(body_as_json[:errors]).to eq 'Webauthn Credential is already associated with an account'
+ end
+ end
+ end
+ end
end
describe 'POST #approve' do
@@ -348,6 +578,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'removes user' do
@@ -366,6 +597,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'enables user' do
@@ -384,6 +616,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'unsuspends account' do
@@ -402,6 +635,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'unsuspends account' do
@@ -420,6 +654,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'unsensitives account' do
@@ -438,6 +673,7 @@
it 'returns http success' do
expect(response).to have_http_status(200)
+ expect_to_be_an_admin_account(body_as_json)
end
it 'unsilences account' do
Only in truth-new/opensource/spec/controllers/api/v1/admin: bulk_account_actions_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: chat_messages_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: groups
Only in truth-new/opensource/spec/controllers/api/v1/admin: groups_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: links_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: policies_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: registrations_controller_spec.rb
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/admin/statuses_controller_spec.rb 2024-04-01 14:59:14
@@ -3,11 +3,11 @@
RSpec.describe Api::V1::Admin::StatusesController, type: :controller do
render_views
- let(:role) { 'admin' }
- let(:user) { Fabricate(:user, role: role, sms: '234-555-2344', account: Fabricate(:account, username: 'alice')) }
+ let(:role) { 'admin' }
+ let(:account) { Fabricate(:account, username: 'alice') }
+ let(:user) { Fabricate(:user, role: role, sms: '234-555-2344', account: account) }
let(:scopes) { 'admin:read admin:write' }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
- let(:account) { Fabricate(:user).account }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:status) { Fabricate(:status, account: user.account) }
before do
@@ -19,6 +19,119 @@
it 'returns http success' do
get :index, params: { ids: [status.id] }
expect(response).to have_http_status(200)
+ end
+ end
+ end
+
+ describe 'POST #privatize' do
+ it 'returns http success and sets visibility to self' do
+ post :privatize, params: { status_id: status.id }
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:visibility]).to eq('self')
+ end
+ end
+
+ describe 'POST #publicize' do
+ context 'normal status' do
+ it 'returns http success and sets visibility to public' do
+ post :publicize, params: { status_id: status.id }
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:visibility]).to eq('public')
+ end
+ end
+
+ context 'group status' do
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) }
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) }
+
+ before do
+ group.memberships.create!(group: group, account: account, role: :owner)
+ end
+
+ it 'returns http success and sets visibility to group' do
+ post :publicize, params: { status_id: group_status.id }
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:visibility]).to eq('group')
+ end
+ end
+ end
+
+ describe 'POST #discard' do
+ it 'returns http success and updates statistics' do
+ status.reload
+ Procedure.process_account_status_statistics_queue
+ expect(status.deleted_at).to be_nil
+ expect(AccountStatusStatistic.find_by(account_id: user.account_id).statuses_count).to eq(1)
+ post :discard, params: { status_id: status.id }
+ Procedure.process_account_status_statistics_queue
+ expect(response).to have_http_status(200)
+ expect(AccountStatusStatistic.find_by(account_id: user.account_id)).to be_nil
+ end
+
+ context 'group status' do
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) }
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) }
+
+ before do
+ group.memberships.create!(group: group, account: account, role: :owner)
+ end
+
+ it 'returns http success and discards the group status' do
+ post :discard, params: { status_id: group_status.id }
+ expect(response).to have_http_status(200)
+ expect{ Status.find(group_status.id) }.to raise_exception ActiveRecord::RecordNotFound
+ end
+ end
+ end
+
+ describe 'POST #undiscard' do
+ before do
+ status.discard!
+ end
+ it 'returns http success and un-discards the status' do
+ post :undiscard, params: { status_id: status.id }
+ expect(response).to have_http_status(200)
+ expect(status.reload.discarded?).to eq(false)
+ end
+
+ context 'group status' do
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) }
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) }
+
+ before do
+ group.memberships.create!(group: group, account: account, role: :owner)
+ group_status.discard!
+ end
+
+ it 'returns http success and un-discards the group status' do
+ post :undiscard, params: { status_id: group_status.id }
+ expect(response).to have_http_status(200)
+ expect(group_status.reload.discarded?).to eq(false)
+ end
+ end
+ end
+
+ describe 'POST #sensitize' do
+ context 'normal status' do
+ it 'returns http success and sets sensitive to true' do
+ post :sensitize, params: { status_id: status.id }
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:sensitive]).to eq(true)
+ end
+ end
+
+ context 'group status' do
+ let(:group) { Fabricate(:group, display_name: 'Test group', note: 'Note', owner_account: account) }
+ let(:group_status) { Fabricate(:status, account: account, group_id: group.id, visibility: 'self', performed_by_admin: true) }
+
+ before do
+ group.memberships.create!(group: group, account: account, role: :owner)
+ end
+
+ it 'returns http success and sets sensitive to true' do
+ post :sensitize, params: { status_id: group_status.id }
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:sensitive]).to eq(true)
end
end
end
Only in truth-new/opensource/spec/controllers/api/v1/admin: tags_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_groups_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_statuses
diff -ru truth-old/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/admin/trending_statuses_controller_spec.rb 2024-04-01 14:59:14
@@ -5,69 +5,115 @@
let(:role) { 'admin' }
let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
+ let(:trending_account1) { Fabricate(:account, username: 'john') }
+ let(:trending_account2) { Fabricate(:account, username: 'bob') }
+ let(:trending_account3) { Fabricate(:account, username: 'gary') }
+ let(:trending_account4) { Fabricate(:account, username: 'greg') }
+ let(:trending_account5) { Fabricate(:account, username: 'steve') }
+ let(:trending_account6) { Fabricate(:account, username: 'phil') }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:status) { Fabricate(:status, account: user.account, text: "It is a great post") }
-
- before do
- allow(controller).to receive(:doorkeeper_token) { token }
+ let(:statuses) do
+ [
+ Fabricate(:status, account: trending_account1),
+ Fabricate(:status, account: trending_account1),
+ Fabricate(:status,account: trending_account2),
+ Fabricate(:status, account: trending_account2),
+ Fabricate(:status, account: trending_account3),
+ Fabricate(:status, account: trending_account3),
+ Fabricate(:status, account: trending_account4),
+ Fabricate(:status, account: trending_account4),
+ Fabricate(:status, account: trending_account5),
+ Fabricate(:status, account: trending_account5),
+ Fabricate(:status, account: trending_account6),
+ Fabricate(:status, account: trending_account6),
+ ]
end
context '#index' do
describe 'GET #index' do
- before do
- Fabricate(:favourite, account: user.account, status: status)
+ it 'should return 403 when not an admin' do
get :index
+ expect(response).to have_http_status(403)
end
-
- it 'returns http success' do
- expect(response).to have_http_status(200)
- end
-
- it 'returns the correct statuses' do
- expect(body_as_json.length).to eq(1)
- end
end
- describe 'GET #index trending=true' do
+ describe 'GET #index' do
before do
- Fabricate(:trending, status: status, user: user)
- get :index, params: { trending: true }
+ allow(controller).to receive(:doorkeeper_token) { token }
+ relation = Status.all
+ allow(relation).to receive(:[]).and_return(statuses)
+ allow(Status).to receive(:trending_statuses).and_return(relation)
+ allow(controller).to receive(:doorkeeper_token) { token }
end
- it 'returns http success' do
+ let(:trending_statuses) { Status.trending_statuses }
+
+ it 'returns http success and trending list' do
+ get :index
+
expect(response).to have_http_status(200)
+ expect(body_as_json.length).to eq(10)
end
- it 'returns the correct statuses' do
- expect(body_as_json.length).to eq(1)
+ it 'should return page two with appropriate headers' do
+ get :index, params: { page: 2 }
+
+ last_statuses = trending_statuses.last(2)
+ expect(response).to have_http_status(200)
+ expect(body_as_json.length).to eq(2)
+ expect(body_as_json.pluck(:id)).to eq([last_statuses.first.id.to_s, last_statuses.last.id.to_s])
+ expect(response.headers['x-page-size']).to eq(10)
+ expect(response.headers['x-page']).to eq("2")
+ expect(response.headers['x-total']).to eq(2)
+ expect(response.headers['x-total-pages']).to eq(2)
end
end
end
- context "#update" do
- it "creates a trending" do
- expect { put :update, params: { id: status.id } }.to change { Trending.count }.by(1)
+ describe 'PUT #include' do
+ it 'should return 403 when not an admin' do
+ get :index
+ expect(response).to have_http_status(403)
end
- it "returns http 204" do
- put :update, params: { id: status.id }
- expect(response).to have_http_status(204)
+ it 'should return a 404 if status id is non-existent' do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ put :include, params: { id: 'BAD' }
+
+ expect(response).to have_http_status(404)
end
+
+ it 'should make a status re-eligible for trending list' do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ TrendingStatusExcludedStatus.create(status_id: status.id)
+ put :include, params: { id: status.id }
+
+ expect(response).to have_http_status(200)
+ expect(TrendingStatusExcludedStatus.count).to eq(0)
+ end
end
- context '#destroy' do
- before do
- Fabricate(:trending, status: status, user: user)
+ describe 'PUT #exclude' do
+ it 'should return 403 when not an admin' do
+ get :index
+ expect(response).to have_http_status(403)
end
- it "destroys a trending" do
- expect { delete :destroy, params: { id: status.id } }.to change { Trending.count }.by(-1)
+ it 'should return a 404 if status id is non-existent' do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ put :include, params: { id: 'BAD' }
+
+ expect(response).to have_http_status(404)
end
- it "returns http 204" do
- delete :destroy, params: { id: status.id }
- expect(response).to have_http_status(204)
+ it 'should exclude a status from trending list' do
+ allow(controller).to receive(:doorkeeper_token) { token }
+ put :exclude, params: { id: status.id }
+
+ expect(response).to have_http_status(200)
+ expect(TrendingStatusExcludedStatus.first.status_id).to eq(status.id)
end
end
end
Only in truth-new/opensource/spec/controllers/api/v1/admin: trending_tags_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1/admin: truth
Only in truth-new/opensource/spec/controllers/api/v1/admin: tv
diff -ru truth-old/opensource/spec/controllers/api/v1/conversations_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/conversations_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/conversations_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/conversations_controller_spec.rb 2024-04-05 09:07:34
@@ -5,7 +5,7 @@
let!(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
- let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
+ let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob', created_at: Time.now - 10.days)) }
before do
acct = Fabricate(:account, username: "ModerationAI")
@@ -18,7 +18,8 @@
let(:scopes) { 'read:statuses' }
before do
- PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct', mentions: ['alice'])
+ status = PostStatusService.new.call(other.account, text: 'Hey @alice', visibility: 'direct', mentions: ['alice'])
+ ProcessMentionsService.create_notification(status, status.mentions.first)
end
it 'returns http success' do
Only in truth-new/opensource/spec/controllers/api/v1: feeds_controller_spec.rb
Only in truth-new/opensource/spec/controllers/api/v1: groups
Only in truth-new/opensource/spec/controllers/api/v1: groups_controller_spec.rb
diff -ru truth-old/opensource/spec/controllers/api/v1/instances_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/instances_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/instances_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/instances_controller_spec.rb 2024-04-01 14:59:14
@@ -17,12 +17,61 @@
get :show
expect(response).to have_http_status(200)
+ expect(body_as_json[:uri]).to be_an_instance_of String
+ expect(body_as_json[:title]).to be_an_instance_of String
+ expect(body_as_json[:short_description]).to be_an_instance_of String
+ expect(body_as_json[:description]).to be_an_instance_of String
+ expect(body_as_json[:email]).to be_an_instance_of String
+ expect(body_as_json[:version]).to be_an_instance_of String
+ expect(body_as_json[:urls]).to be_an_instance_of Hash
+ expect(body_as_json[:thumbnail]).to be_an_instance_of String
+ expect(body_as_json[:languages]).to be_an_instance_of Array
+ expect(body_as_json[:registrations]).to be_boolean
+ expect(body_as_json[:approval_required]).to be_boolean
+ expect(body_as_json[:invites_enabled]).to be_boolean
+ expect(body_as_json[:feature_quote]).to be_boolean
+ expect(body_as_json[:rules]).to be_an_instance_of Array
+ expect(body_as_json[:configuration]).to be_an_instance_of Hash
+ expect(body_as_json[:configuration][:statuses][:max_characters]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:statuses][:max_media_attachments]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:statuses][:characters_reserved_per_url]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:supported_mime_types]).to be_an_instance_of Array
+ expect(body_as_json[:configuration][:media_attachments][:image_size_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:image_matrix_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:video_size_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:video_frame_rate_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:video_matrix_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:media_attachments][:video_duration_limit]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:chats][:max_characters]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:chats][:max_messages_per_minute]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:chats][:max_media_attachments]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:polls][:max_options]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:polls][:max_characters_per_option]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:polls][:min_expiration]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:polls][:max_expiration]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:ads][:algorithm]).to have_key(:name)
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:frequency)
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:phase_min)
+ expect(body_as_json[:configuration][:ads][:algorithm][:configuration]).to have_key(:phase_max)
+ expect(body_as_json[:configuration][:groups][:max_characters_name]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:groups][:max_characters_description]).to be_an_instance_of Integer
+ expect(body_as_json[:configuration][:groups][:max_admins_allowed]).to be_an_instance_of Integer
end
it 'returns compat version string' do
get :show
- expect(response.body).to include('compatible; TruthSocial')
+ json = body_as_json
+ expect(json[:version]).to eq '3.4.1 (compatible; TruthSocial 1.0.0)'
+ end
+
+ it 'returns +unreleased on staging' do
+ stub_const('ENV', ENV.to_hash.merge('IS_STAGING' => 'true'))
+
+ get :show
+
+ json = body_as_json
+ expect(json[:version]).to eq '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)'
end
end
end
Only in truth-old/opensource/spec/controllers/api/v1: ma1sd
diff -ru truth-old/opensource/spec/controllers/api/v1/notifications_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/notifications_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/notifications_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/notifications_controller_spec.rb 2024-04-05 09:07:34
@@ -5,13 +5,13 @@
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
- let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
+ let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob', created_at: Time.now - 10.days)) }
let(:third) { Fabricate(:user, account: Fabricate(:account, username: 'carol')) }
before do
- acct = Fabricate(:account, username: "ModerationAI")
+ acct = Fabricate(:account, username: 'ModerationAI')
Fabricate(:user, admin: true, account: acct)
- stub_request(:post, ENV["MODERATION_TASK_API_URL"]).to_return(status: 200, body: request_fixture('moderation-response-0.txt'))
+ stub_request(:post, ENV['MODERATION_TASK_API_URL']).to_return(status: 200, body: request_fixture('moderation-response-0.txt'))
allow(controller).to receive(:doorkeeper_token) { token }
end
@@ -52,15 +52,31 @@
describe 'GET #index' do
let(:scopes) { 'read:notifications' }
+ let(:group) { Fabricate(:group, display_name: 'Group', note: 'Note', owner_account: user.account) }
before do
+ group.memberships.create!(account: user.account, role: :owner)
+ group.memberships.create!(account: other.account)
first_status = PostStatusService.new.call(user.account, text: 'Test')
@reblog_of_first_status = ReblogService.new.call(other.account, first_status)
mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice', mentions: ['alice'])
@mention_from_status = mentioning_status.mentions.first
+ group_mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice group_mention', mentions: ['alice'], group: group, visibility: 'group')
+ @group_mention_from_status = group_mentioning_status.mentions.first
+ group_status = PostStatusService.new.call(user.account, text: 'Hello other this is a group_mention', group: group, visibility: 'group')
+ mentioning_self_status = PostStatusService.new.call(other.account, text: 'Hello again @alice', mentions: ['alice'])
+ [first_status,mentioning_status, group_mentioning_status, group_status, mentioning_self_status].each { |status| PostDistributionService.new.distribute_to_author_and_followers(status) }
+ [
+ [mentioning_status, @mention_from_status],
+ [group_mentioning_status, @group_mention_from_status],
+ [mentioning_self_status, mentioning_self_status.mentions.first]
+ ].each { |status, mention| ProcessMentionsService.create_notification(status, mention) }
@favourite = FavouriteService.new.call(other.account, first_status)
@second_favourite = FavouriteService.new.call(third.account, first_status)
+ ReblogService.new.call(other.account, group_status)
+ FavouriteService.new.call(other.account, group_status)
@follow = FollowService.new.call(other.account, user.account)
+ mentioning_self_status.update(visibility: 'self')
end
describe 'with no options' do
@@ -73,53 +89,49 @@
end
it 'includes reblog' do
- expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
+ expect(body_as_json.map { |x| x[:type] }).to include 'reblog'
end
it 'includes mention' do
- expect(assigns(:notifications).map(&:activity)).to include(@mention_from_status)
+ expect(body_as_json.map { |x| x[:type] }).to include 'mention'
end
it 'includes favourite' do
- expect(assigns(:notifications).map(&:activity)).to include(@favourite)
+ expect(body_as_json.map { |x| x[:type] }).to include 'favourite'
end
it 'includes follow' do
- expect(assigns(:notifications).map(&:activity)).to include(@follow)
+ expect(body_as_json.map { |x| x[:type] }).to include 'follow'
end
- end
- describe 'from specified user' do
- before do
- get :index, params: { account_id: third.account.id }
+ it 'includes group_mention' do
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_mention'
end
- it 'returns http success' do
- expect(response).to have_http_status(200)
+ it 'includes group_favourite' do
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_favourite'
end
- it 'includes favourite' do
- expect(assigns(:notifications).map(&:activity)).to include(@second_favourite)
+ it 'includes group_reblog' do
+ expect(body_as_json.map { |x| x[:type] }).to include 'group_reblog'
end
+ end
- it 'excludes favourite' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@favourite)
+ describe 'with account_id param' do
+ before do
+ get :index, params: { account_id: third.account.id }
end
- it 'excludes mention' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status)
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
end
- it 'excludes reblog' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@reblog_of_first_status)
+ it 'returns only notifications from specified user' do
+ expect(body_as_json.map { |x| x[:account][:id] }.uniq).to eq [third.account.id.to_s]
end
-
- it 'excludes follow' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@follow)
- end
end
- describe 'from nonexistent user' do
+ describe 'with invalid account_id param' do
before do
get :index, params: { account_id: 'foo' }
end
@@ -128,54 +140,58 @@
expect(response).to have_http_status(200)
end
- it 'excludes favourite' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@favourite)
+ it 'returns nothing' do
+ expect(body_as_json.size).to eq 0
end
+ end
- it 'excludes second favourite' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@second_favourite)
+ describe 'with excluded_types param' do
+ before do
+ get :index, params: { exclude_types: %w(mention) }
end
- it 'excludes mention' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status)
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
end
- it 'excludes reblog' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@reblog_of_first_status)
+ it 'returns everything but excluded type' do
+ expect(body_as_json.size).to_not eq 0
+ expect(body_as_json.map { |x| x[:type] }.uniq).to_not include 'mention'
end
-
- it 'excludes follow' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@follow)
- end
end
- describe 'with excluded mentions' do
+ describe 'with types param' do
before do
- get :index, params: { exclude_types: ['mention'] }
+ get :index, params: { types: %w(mention) }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
- it 'includes reblog' do
- expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
+ it 'returns only requested type' do
+ expect(body_as_json.map { |x| x[:type] }.uniq).to eq ['mention']
end
+ end
- it 'excludes mention' do
- expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status)
+ describe 'with self status' do
+ before do
+ get :index, params: { types: %w(mention) }
end
- it 'includes favourite' do
- expect(assigns(:notifications).map(&:activity)).to include(@favourite)
+ it 'does not return the self status' do
+ expect(body_as_json.select {|n| n[:type] == 'mention' }.length).to eq 1
end
+ end
- it 'includes third favourite' do
- expect(assigns(:notifications).map(&:activity)).to include(@second_favourite)
+ describe 'pagination headers' do
+ before do
+ get :index, params: { limit: 4 }
end
- it 'includes follow' do
- expect(assigns(:notifications).map(&:activity)).to include(@follow)
+ it 'returns next and prev links' do
+ expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq "http://test.host/api/v1/notifications?limit=4&max_id=#{body_as_json.last[:id]}"
+ expect(response.headers['Link'].find_link(['rel', 'prev']).href).to eq "http://test.host/api/v1/notifications?limit=4&min_id=#{body_as_json.first[:id]}"
end
end
end
Only in truth-new/opensource/spec/controllers/api/v1: pleroma
diff -ru truth-old/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/polls/votes_controller_spec.rb 2024-04-01 14:59:14
@@ -13,7 +13,7 @@
let(:poll) { Fabricate(:poll) }
before do
- post :create, params: { poll_id: poll.id, choices: %w(1) }
+ post :create, params: { poll_id: poll.id, choices: [poll.options.last.option_number.to_s] }
end
it 'returns http success' do
@@ -21,14 +21,19 @@
end
it 'creates a vote' do
- vote = poll.votes.where(account: user.account).first
+ vote = PollVote.where(poll_id: poll.id, account: user.account).first
expect(vote).to_not be_nil
- expect(vote.choice).to eq 1
+ expect(vote.option_number).to eq 1
end
- it 'updates poll tallies' do
- expect(poll.reload.cached_tallies).to eq [0, 1]
+ it 'updates the stats' do
+ Procedure.process_poll_option_statistics_queue
+ expect(StatisticPollOption.where(poll_id: poll.id, option_number: 0).first).to eq nil
+ expect(StatisticPollOption.where(poll_id: poll.id, option_number: 1).first.votes).to eq 1
+
+ expect(StatisticPoll.where(poll_id: poll.id).first.votes).to eq 1
+ expect(StatisticPoll.where(poll_id: poll.id).first.voters).to eq 1
end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/push/subscriptions_controller_spec.rb 2024-04-01 14:59:14
@@ -5,10 +5,13 @@
describe Api::V1::Push::SubscriptionsController do
render_views
- let(:user) { Fabricate(:user) }
- let(:second_user) { Fabricate(:user) }
+ let(:user) { Fabricate(:user, id: 1) }
+ let!(:second_user) { Fabricate(:user) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'push') }
- let(:second_token) { Fabricate(:accessible_access_token, resource_owner_id: second_user.id, scopes: 'push') }
+ let(:first_token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'push') }
+ let!(:second_token) { Fabricate(:accessible_access_token, resource_owner_id: second_user.id, scopes: 'push') }
+ let(:mobile_device_token) { 'SgQsFj5JCkcoMrMt2WHoPTSTCkQCkQ/STCkSTCkQQSTCkQSTgQsFj5JCkcoMrMt2WHoPCkQSTSTCkQCkQ=' }
+ let(:endpoint) { 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
@@ -17,7 +20,7 @@
let(:create_payload) do
{
subscription: {
- endpoint: 'https://fcm.googleapis.com/fcm/send/fiuH06a27qE:APA91bHnSiGcLwdaxdyqVXNDR9w1NlztsHb6lyt5WDKOC_Z_Q8BlFxQoR8tWFSXUIDdkyw0EdvxTu63iqamSaqVSevW5LfoFwojws8XYDXv_NRRLH6vo2CdgiN4jgHv5VLt2A8ah6lUX',
+ endpoint: endpoint,
keys: {
p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
auth: 'eH_C8rq2raXqlcBVDa1gLg==',
@@ -29,7 +32,7 @@
let(:create_mobile_payload) do
{
subscription: {
- device_token: 'SgQsFj5JCkcoMrMt2WHoPTSTCkQCkQ/STCkSTCkQQSTCkQSTgQsFj5JCkcoMrMt2WHoPCkQSTSTCkQCkQ=',
+ device_token: mobile_device_token,
platform: 1,
environment: 1
}
@@ -54,6 +57,34 @@
}.with_indifferent_access
end
+ let(:update_payload) do
+ {
+ subscription: {
+ device_token: mobile_device_token,
+ platform: 1,
+ environment: 1,
+ endpoint: endpoint,
+ keys: {
+ p256dh: 'BEm_a0bdPDhf0SOsrnB2-ategf1hHoCnpXgQsFj5JCkcoMrMt2WHoPfEYOYPzOIs9mZE8ZUaD7VA5vouy0kEkr8=',
+ auth: 'eH_C8rq2raXqlcBVDa1gLg==',
+ },
+ },
+ data: {
+ policy: 'all',
+
+ alerts: {
+ follow: true,
+ follow_request: true,
+ favourite: false,
+ reblog: true,
+ mention: false,
+ poll: true,
+ status: false,
+ }
+ }
+ }.with_indifferent_access
+ end
+
describe 'POST #create' do
context 'with web notifications' do
before do
@@ -91,8 +122,19 @@
expect(push_subscription.access_token_id).to eq token.id
end
- it 'removes subscriptions with the same device_id' do
- allow(controller).to receive(:doorkeeper_token) { second_token }
+ context do
+ it 'does not remove subscriptions with the same device_id and different user_id' do
+ user2 = Fabricate(:user)
+ token2 = Fabricate(:accessible_access_token, resource_owner_id: user2.id, scopes: 'push')
+ allow(controller).to receive(:current_user) { user2 }
+ allow(controller).to receive(:doorkeeper_token) { token2 }
+ post :create, params: create_mobile_payload
+ expect(Web::PushSubscription.where(platform: create_mobile_payload[:subscription][:platform]).count).to eq 2
+ end
+ end
+
+ it 'removes subscriptions with the same device_id and user_id' do
+ allow(controller).to receive(:doorkeeper_token) { token }
post :create, params: create_mobile_payload
expect(Web::PushSubscription.where(platform: create_mobile_payload[:subscription][:platform]).count).to eq 1
end
@@ -122,18 +164,45 @@
end
describe 'PUT #update' do
- before do
- post :create, params: create_payload
- put :update, params: alerts_payload
+ context 'subscription record exists' do
+ before do
+ post :create, params: create_payload
+ put :update, params: alerts_payload.merge({subscription: {device_token: 'updated_device_token'}})
+ end
+
+ it 'changes alert settings, and updates the device token' do
+ push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:subscription][:endpoint])
+
+ expect(push_subscription.data['policy']).to eq(alerts_payload[:data][:policy])
+
+ expect(push_subscription.device_token).to eq('updated_device_token')
+
+ %w(follow follow_request favourite reblog mention poll status).each do |type|
+ expect(push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s)
+ end
+ end
end
- it 'changes alert settings' do
- push_subscription = Web::PushSubscription.find_by(endpoint: create_payload[:subscription][:endpoint])
+ context 'deleted subscription record' do
+ it 'creates a new subscription if one does not exist' do
+ put :update, params: update_payload
+ expect(response).to have_http_status(200)
+ end
- expect(push_subscription.data['policy']).to eq(alerts_payload[:data][:policy])
+ it 'if same device token and user but different access token' do
+ Web::PushSubscription.create!(
+ device_token: mobile_device_token,
+ platform: 1,
+ environment: 1,
+ user_id: user.id,
+ access_token_id: token.id
+ )
- %w(follow follow_request favourite reblog mention poll status).each do |type|
- expect(push_subscription.data['alerts'][type]).to eq(alerts_payload[:data][:alerts][type.to_sym].to_s)
+ allow(controller).to receive(:doorkeeper_token) { first_token }
+
+ put :update, params: update_payload
+ expect(response).to have_http_status(200)
+ expect(Web::PushSubscription.where(device_token: mobile_device_token).count).to eq 1
end
end
end
Only in truth-new/opensource/spec/controllers/api/v1: push_notifications
Only in truth-new/opensource/spec/controllers/api/v1: recommendations
diff -ru truth-old/opensource/spec/controllers/api/v1/reports_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/reports_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/reports_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/reports_controller_spec.rb 2024-04-01 14:59:14
@@ -5,8 +5,12 @@
RSpec.describe Api::V1::ReportsController, type: :controller do
render_views
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+ let(:account) { Fabricate(:account, username: 'alice') }
+ let(:user) { Fabricate(:user, account: account) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+ let(:group) { Fabricate(:group, display_name: 'test group', note: 'groupy group', owner_account: account) }
+ let!(:group_membership) { Fabricate(:group_membership, group: group, account: account, role: 'owner') }
+ let(:group_status) { Fabricate(:status, group: group, visibility: 'group', account: account) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
@@ -17,18 +21,120 @@
let!(:status) { Fabricate(:status) }
let!(:admin) { Fabricate(:user, admin: true) }
- before do
- allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil))
- post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ context 'normal account' do
+ before do
+ allow(AdminMailer).to receive(:new_report).and_return(double('email', deliver_later: nil))
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ end
+
+ it 'creates a report' do
+ expect(status.reload.account.targeted_reports).not_to be_empty
+ expect(response).to have_http_status(200)
+ end
+
+ it 'respects rate limit' do
+ 399.times do
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ end
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ expect(response).to have_http_status(422)
+ end
+
+ it 'doesn`t create a duplicate report' do
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ expect(response).to have_http_status(422)
+ end
end
- it 'creates a report' do
- expect(status.reload.account.targeted_reports).not_to be_empty
- expect(response).to have_http_status(200)
+ context 'hostile account' do
+ it 'subject to HostileRateLimiter' do
+ user.account.update!(trust_level: Account::TRUST_LEVELS[:hostile])
+ expect(user.account.trust_level).to eq(Account::TRUST_LEVELS[:hostile])
+ expect(Report.where(account_id: user.account.id).count).to eq(0)
+
+ post :create, params: { status_ids: [status.id], account_id: status.account.id, comment: 'reasons' }
+ expect(response).to have_http_status(200)
+ expect(Report.where(account_id: user.account_id).count).to eq(0)
+ end
end
- # it 'sends e-mails to admins' do
- # expect(AdminMailer).to have_received(:new_report).with(admin.account, Report)
- # end
+ context 'chat messages' do
+ let(:recipient) { Fabricate(:account, username: 'theirs') }
+ let(:recipient_user) { Fabricate(:user, account: recipient) }
+
+ it 'creates a report with chat messages' do
+ chat = Chat.create(owner_account_id: account.id, members: [recipient.id])
+ message = JSON.parse(ChatMessage.create_by_function!({
+ account_id: recipient.id,
+ token: nil,
+ idempotency_key: nil,
+ chat_id: chat.chat_id,
+ content: Faker::Lorem.characters(number: 15),
+ media_attachment_ids: nil
+ }))
+
+ post :create, params: { message_ids: [message['id']], account_id: account.id, comment: 'reasons' }
+
+ reports = Report.where(account_id: account.id)
+ expect(response).to have_http_status(200)
+ expect(reports.count).to eq(1)
+
+ expect(reports.last.message_ids).to eq([message['id'].to_i])
+ end
+ end
+
+ context 'group statuses' do
+ let(:non_member) { Fabricate(:user, account: Fabricate(:account, username: 'non_member')) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: non_member.id, scopes: scopes) }
+
+ it 'creates a report for statuses in a group' do
+ post :create, params: { status_ids: [group_status.id], account_id: group_status.account.id, comment: 'reasons' }
+
+ expect(group_status.account.targeted_reports).not_to be_empty
+ expect(group_status.account.targeted_reports.first.group_id).to eq group.id
+ expect(response).to have_http_status(200)
+ expect(body_as_json[:id]).to be_an_instance_of String
+ expect(body_as_json[:action_taken]).to be_boolean
+ end
+
+ it "should return http forbidden if it's a private group status and the user is not a member" do
+ group.members_only!
+
+ post :create, params: { status_ids: [group_status.id], account_id: group_status.account.id, comment: 'reasons' }
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'groups' do
+ before do
+ post :create, params: { group_id: group.id, comment: 'reasons' }
+ end
+
+ it 'reports a group' do
+ expect(account.targeted_reports.first.group_id).to eq group.id
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ context 'external ads' do
+ let(:ts_advertising_account) { Fabricate(:account, username: 'ts_advertising') }
+
+ before do
+ stub_const('ENV', ENV.to_hash.merge('TS_ADVERTISTING_ACCOUNT_ID' => ts_advertising_account.id))
+ post :create, params: { external_ad_url: 'https://example.com', external_ad_media_url: 'https://example.com', external_ad_description: 'Example ad' }
+ end
+
+ it 'reports an external ad' do
+ expect(ts_advertising_account.targeted_reports.first.external_ad_id).to_not be_nil
+ expect(response).to have_http_status(200)
+ end
+
+ it 'creates an external ad record' do
+ expect(ExternalAd.last.ad_url).to eq 'https://example.com'
+ expect(ExternalAd.last.media_url).to eq 'https://example.com'
+ expect(ExternalAd.last.description).to eq 'Example ad'
+ end
+ end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -25,7 +25,7 @@
it 'returns http success' do
get :index, params: { status_id: status.id, limit: 2 }
expect(response).to have_http_status(200)
- expect(response.headers['Link'].links.size).to eq(2)
+ expect(response.headers['Link'].links.size).to eq(1)
end
it 'returns accounts who favorited the status' do
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/statuses/favourites_controller_spec.rb 2024-04-01 14:59:14
@@ -5,7 +5,7 @@
describe Api::V1::Statuses::FavouritesController do
render_views
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: '0.0.0.0') }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:favourites', application: app) }
@@ -17,11 +17,11 @@
describe 'POST #create' do
let(:status) { Fabricate(:status, account: user.account) }
- before do
- post :create, params: { status_id: status.id }
- end
-
context 'with public status' do
+ before do
+ post :create, params: { status_id: status.id }
+ end
+
it 'returns http success' do
expect(response).to have_http_status(200)
end
@@ -47,9 +47,136 @@
let(:status) { Fabricate(:status, visibility: :private) }
it 'returns http not found' do
+ post :create, params: { status_id: status.id }
expect(response).to have_http_status(404)
end
end
+
+ context 'when current_sign_in_ip is nil' do
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: nil) }
+
+ it 'returns http success' do
+ post :create, params: { status_id: status.id }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json).to be_empty
+ end
+ end
+
+ context 'App Integrity' do
+ let(:date) { (Time.now.utc.to_f * 1000).to_i }
+ let(:alg_and_enc) { {alg: "A256KW", enc: "A256GCM"}.to_json }
+ let(:hashed_token) { OpenSSL::Digest.digest('SHA256', "INTEGRITY_TOKEN") }
+ let(:integrity_token) { Base64.encode64(alg_and_enc + hashed_token) }
+ let(:decoded_assertion) do
+ {
+ v: 0,
+ p: 2,
+ date: date,
+ integrity_token: integrity_token,
+ }
+ end
+ let(:x_tru_assertion) { Base64.strict_encode64(decoded_assertion.to_json) }
+ let(:nonce) { OpenSSL::Digest.digest('SHA256', "NONCE") }
+ let(:client_data) { { date: date, request: Base64.urlsafe_encode64(nonce) }.to_json }
+ let(:verdict_nonce) { Base64.urlsafe_encode64(OpenSSL::Digest.digest('SHA256', client_data)) }
+ let(:verdict) do
+ [
+ {
+ "requestDetails"=> {
+ "requestPackageName"=> "PACKAGE_NAME",
+ "timestampMillis"=> "TIMESTAMP",
+ "nonce"=> verdict_nonce
+ },
+ "appIntegrity"=> {
+ "appRecognitionVerdict"=> "UNRECOGNIZED_VERSION",
+ "packageName"=> "PACKAGE_NAME",
+ "certificateSha256Digest"=> ["DIGEST"],
+ "versionCode"=> "VERSION CODE"
+ },
+ "deviceIntegrity"=> {
+ "deviceRecognitionVerdict"=> %w[MEETS_BASIC_INTEGRITY MEETS_DEVICE_INTEGRITY MEETS_STRONG_INTEGRITY]
+ },
+ "accountDetails"=> {
+ "appLicensingVerdict"=> "LICENSED"
+ }
+ },
+ {
+ "alg"=> "ES256"
+ }
+ ]
+ end
+
+ before do
+ request.headers['x-tru-assertion'] = x_tru_assertion
+ request.headers['x-tru-date'] = date
+ request.user_agent = "TruthSocialAndroid/okhttp/5.0.0-alpha.7"
+
+ allow_any_instance_of(AndroidDeviceCheck::IntegrityService).to receive(:decrypt_token).and_return(verdict)
+ end
+
+ it 'should validate and store a device verification record' do
+ canonical_instance = instance_double(CanonicalRequestService, canonical_string: 'NONCE', canonical_headers: {})
+ allow(CanonicalRequestService).to receive(:new).and_return(canonical_instance)
+ allow(canonical_instance).to receive(:call).and_return(nonce)
+
+ post :create, params: { status_id: status.id }
+
+ expect(response).to have_http_status(200)
+ device_verification = DeviceVerification.find_by("details ->> 'integrity_token' = '#{integrity_token}'")
+ device_verification_user = DeviceVerificationUser.find_by(verification: device_verification)
+ expect(device_verification.details['integrity_errors']).to be_empty
+ expect(device_verification_user.user_id).to eq(user.id)
+ dvf = DeviceVerificationFavourite.find_by(verification_id: device_verification.id)
+ expect(dvf.favourite.status_id.to_s).to eq body_as_json[:id]
+ end
+ end
+
+ context 'App Attest' do
+ let(:assertion) { { 'signature' => "SIGNATURE", 'authenticatorData' => 'AUTHENTICATOR_DATA' } }
+ let(:credential) {
+ user.webauthn_credentials.create(
+ nickname: 'SecurityKeyNickname',
+ external_id: 'EXTERNAL_ID',
+ public_key: "PUBLIC_KEY",
+ sign_count: 0
+ )
+ }
+ let(:date) { (Time.now.utc.to_f * 1000).to_i }
+ let(:decoded_assertion) do
+ {
+ id: credential.external_id,
+ v: 0,
+ p: 1,
+ date: date,
+ assertion: Base64.strict_encode64(assertion.to_cbor),
+ }
+ end
+
+
+ let(:assertion_response) { OpenStruct.new(authenticator_data: { sign_count: 1 }) }
+ let(:x_tru_assertion) { Base64.strict_encode64(decoded_assertion.to_json) }
+
+ before do
+ allow(WebAuthn::AuthenticatorAssertionResponse).to receive(:new).and_return(assertion_response)
+ allow_any_instance_of(IosDeviceCheck::AssertionService).to receive(:valid_assertion?).and_return(true)
+
+ request.headers['x-tru-assertion'] = x_tru_assertion
+ request.headers['x-tru-date'] = date
+ request.user_agent = "TruthSocial/83 CFNetwork/1121.2.2 Darwin/19.3.0"
+ end
+
+ it 'should validate assertion and store a device verification record' do
+ post :create, params: { status_id: status.id }
+
+ expect(response).to have_http_status(200)
+ device_verification = DeviceVerification.find_by("details ->> 'external_id' = '#{credential.external_id}'")
+ device_verification_user = DeviceVerificationUser.find_by(verification: device_verification)
+ expect(device_verification_user.user_id).to eq(user.id)
+ dvf = DeviceVerificationFavourite.find_by(verification_id: device_verification.id)
+ expect(dvf.favourite.status_id.to_s).to eq body_as_json[:id]
+ end
+ end
end
describe 'POST #destroy' do
@@ -58,6 +185,7 @@
before do
FavouriteService.new.call(user.account, status)
+ Procedure.process_status_favourite_statistics_queue
post :destroy, params: { status_id: status.id }
end
@@ -87,6 +215,7 @@
before do
FavouriteService.new.call(user.account, status)
+ Procedure.process_status_favourite_statistics_queue
status.account.block!(user.account)
post :destroy, params: { status_id: status.id }
end
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/statuses/mutes_controller_spec.rb 2024-04-01 14:59:14
@@ -46,5 +46,62 @@
expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil
end
end
+
+ describe 'GET #index' do
+ let(:user2) { Fabricate(:user, account: Fabricate(:account, username: 'john')) }
+ let(:status) { Fabricate(:status, account: user.account) }
+ let(:status2) { Fabricate(:status, account: user.account) }
+ let(:status3) { Fabricate(:status, account: user.account) }
+ let(:status4) { Fabricate(:status, account: user2.account) }
+
+ before do
+ user.account.mute_conversation!(status.conversation)
+ user.account.mute_conversation!(status2.conversation)
+ user.account.mute_conversation!(status3.conversation)
+ user2.account.mute_conversation!(status4.conversation)
+ end
+
+ context 'with valid oauth scopes' do
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:mutes', application: app) }
+
+ it 'returns http success' do
+ get :index, params: { limit: 3 }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json.size).to eq 3
+ expect(body_as_json.pluck(:id)).to match_array [status.id.to_s, status2.id.to_s, status3.id.to_s]
+ expect(response.headers['Link']).to be_nil
+ end
+
+ it 'returns correct link header' do
+ get :index, params: { limit: 2 }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json.size).to eq 2
+ expect(body_as_json.pluck(:id)).to match_array [status3.id.to_s, status2.id.to_s]
+ expect(response.headers['Link'].find_link(['rel', 'next']).href).to eq api_v1_mutes_url(limit: 2, offset: 2)
+ end
+ end
+
+ context 'with invalid oauth scopes' do
+ it 'returns http forbidden' do
+ get :index
+
+ expect(response).to have_http_status(403)
+ expect(body_as_json[:error]).to eq "This action is outside the authorized scopes"
+ end
+ end
+
+ context 'without oauth token' do
+ it 'returns http forbidden' do
+ allow(controller).to receive(:doorkeeper_token) { nil }
+
+ get :index
+
+ expect(response).to have_http_status(403)
+ expect(body_as_json[:error]).to eq "Please log out and log back in."
+ end
+ end
+ end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb 2024-04-01 14:59:14
@@ -7,7 +7,7 @@
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
let(:alice) { Fabricate(:account) }
- let(:bob) { Fabricate(:account) }
+ let(:bob) { Fabricate(:account, user: Fabricate(:user)) }
context 'with an oauth token' do
before do
@@ -39,6 +39,39 @@
get :index, params: { status_id: status.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
+ end
+
+ context 'with a group status' do
+ let!(:group) { Fabricate(:group, display_name: Faker::Lorem.characters(number: 5), note: Faker::Lorem.characters(number: 5), statuses_visibility: 'members_only', owner_account: user.account) }
+
+ describe 'GET #index' do
+ before do
+ group.memberships.create!(account_id: user.account.id, role: :owner)
+ group.memberships.create!(account_id: bob.id, role: :user)
+ end
+
+ it 'returns http success if user is a member of a private group' do
+ status = Fabricate(:status, account: user.account, visibility: :group, group: group)
+ Fabricate(:status, reblog_of_id: status.id, group: group, visibility: :group, account: bob)
+
+ get :index, params: { status_id: status.id, visibility: :group }
+
+ expect(response).to have_http_status(200)
+ expect(body_as_json.first[:id]).to eq bob.id.to_s
+ end
+
+ it 'returns not found if user is not a member of the private group' do
+ status = Fabricate(:status, account: user.account, visibility: :group, group: group)
+ Fabricate(:status, reblog_of_id: status.id, group: group, visibility: :group, account: user.account)
+ group.memberships.find_by!(account_id: bob.id).destroy!
+ token = Fabricate(:accessible_access_token, resource_owner_id: bob.user.id, application: app, scopes: 'read:accounts')
+ allow(controller).to receive(:doorkeeper_token) { token }
+
+ get :index, params: { status_id: status.id, visibility: :group }
+
+ expect(response).to have_http_status(404)
+ end
+ end
end
end
end
diff -ru truth-old/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb truth-new/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb
--- truth-old/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb 2022-06-08 09:15:38
+++ truth-new/opensource/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb 2024-04-01 14:59:14
@@ -5,9 +5,18 @@
describe Api::V1::Statuses::ReblogsController do
render_views
- let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
+ let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice'), current_sign_in_ip: '0.0.0.0') }
+ let(:user2) { Fabricate(:user, account:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment