Merge tag 'v3.3.0' into instance_only_statuses
This commit is contained in:
		| @ -27,10 +27,10 @@ plugins: | ||||
|     enabled: true | ||||
|   eslint: | ||||
|     enabled: true | ||||
|     channel: eslint-6 | ||||
|     channel: eslint-7 | ||||
|   rubocop: | ||||
|     enabled: true | ||||
|     channel: rubocop-0-82 | ||||
|     channel: rubocop-0-92 | ||||
|   sass-lint: | ||||
|     enabled: true | ||||
| exclude_patterns: | ||||
|  | ||||
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| --- | ||||
| name: Bug Report | ||||
| about: If something isn't working as expected | ||||
|  | ||||
| labels: bug | ||||
| --- | ||||
|  | ||||
| <!-- Make sure that you are submitting a new bug that was not previously reported or already fixed --> | ||||
|  | ||||
							
								
								
									
										1
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							| @ -1,7 +1,6 @@ | ||||
| --- | ||||
| name: Feature Request | ||||
| about: I have a suggestion | ||||
|  | ||||
| --- | ||||
|  | ||||
| <!-- Please use a concise and distinct title for the issue --> | ||||
|  | ||||
							
								
								
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							| @ -11,7 +11,7 @@ updates: | ||||
|       interval: weekly | ||||
|     open-pull-requests-limit: 99 | ||||
|     allow: | ||||
|       - dependency-type: all | ||||
|       - dependency-type: direct | ||||
|  | ||||
|   - package-ecosystem: bundler | ||||
|     directory: "/" | ||||
| @ -19,4 +19,4 @@ updates: | ||||
|       interval: weekly | ||||
|     open-pull-requests-limit: 99 | ||||
|     allow: | ||||
|       - dependency-type: all | ||||
|       - dependency-type: direct | ||||
|  | ||||
							
								
								
									
										185
									
								
								.rubocop.yml
									
									
									
									
									
								
							
							
						
						
									
										185
									
								
								.rubocop.yml
									
									
									
									
									
								
							| @ -25,30 +25,68 @@ Layout/AccessModifierIndentation: | ||||
| Layout/EmptyLineAfterMagicComment: | ||||
|   Enabled: false | ||||
|  | ||||
| Layout/EmptyLineAfterGuardClause: | ||||
|   Enabled: false | ||||
|  | ||||
| Layout/EmptyLinesAroundAttributeAccessor: | ||||
|   Enabled: true | ||||
|  | ||||
| Layout/HashAlignment: | ||||
|   Enabled: false | ||||
|   # EnforcedHashRocketStyle: table | ||||
|   # EnforcedColonStyle: table | ||||
|  | ||||
| Layout/SpaceAroundMethodCallOperator: | ||||
|   Enabled: true | ||||
|  | ||||
| Layout/SpaceInsideHashLiteralBraces: | ||||
|   EnforcedStyle: space | ||||
|  | ||||
| Lint/DeprecatedOpenSSLConstant: | ||||
|   Enabled: true | ||||
|  | ||||
| Lint/DuplicateElsifCondition: | ||||
|   Enabled: true | ||||
|  | ||||
| Lint/MixedRegexpCaptureTypes: | ||||
|   Enabled: true | ||||
|  | ||||
| Lint/RaiseException: | ||||
|   Enabled: true | ||||
|  | ||||
| Lint/StructNewOverride: | ||||
|   Enabled: true | ||||
|  | ||||
| Lint/UselessAccessModifier: | ||||
|   ContextCreatingMethods: | ||||
|     - class_methods | ||||
|  | ||||
| Metrics/AbcSize: | ||||
|   Max: 100 | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Metrics/BlockLength: | ||||
|   Max: 35 | ||||
|   Max: 55 | ||||
|   Exclude: | ||||
|     - 'lib/tasks/**/*' | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Metrics/BlockNesting: | ||||
|   Max: 3 | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Metrics/ClassLength: | ||||
|   CountComments: false | ||||
|   Max: 300 | ||||
|   Max: 400 | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Metrics/CyclomaticComplexity: | ||||
|   Max: 25 | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Layout/LineLength: | ||||
|   AllowURI: true | ||||
| @ -56,7 +94,9 @@ Layout/LineLength: | ||||
|  | ||||
| Metrics/MethodLength: | ||||
|   CountComments: false | ||||
|   Max: 55 | ||||
|   Max: 65 | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*_cli.rb' | ||||
|  | ||||
| Metrics/ModuleLength: | ||||
|   CountComments: false | ||||
| @ -67,34 +107,90 @@ Metrics/ParameterLists: | ||||
|   CountKeywordArgs: true | ||||
|  | ||||
| Metrics/PerceivedComplexity: | ||||
|   Max: 20 | ||||
|   Max: 25 | ||||
|  | ||||
| Naming/MemoizedInstanceVariableName: | ||||
|   Enabled: false | ||||
|  | ||||
| Naming/MethodParameterName: | ||||
|   Enabled: true | ||||
|  | ||||
| Rails: | ||||
|   Enabled: true | ||||
|  | ||||
| Rails/ApplicationController: | ||||
|   Enabled: false | ||||
|   Exclude: | ||||
|     - 'app/controllers/well_known/**/*.rb' | ||||
|  | ||||
| Rails/BelongsTo: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/ContentTag: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/EnumHash: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HasAndBelongsToMany: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/SkipsModelValidations: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HttpStatus: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/Exit: | ||||
|   Exclude: | ||||
|     - 'lib/mastodon/*' | ||||
|     - 'lib/cli.rb' | ||||
|  | ||||
| Rails/FilePath: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HasAndBelongsToMany: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HasManyOrHasOneDependent: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HelperInstanceVariable: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/HttpStatus: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/IndexBy: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/InverseOf: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/LexicallyScopedActionFilter: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/OutputSafety: | ||||
|   Enabled: true | ||||
|  | ||||
| Rails/RakeEnvironment: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/RedundantForeignKey: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/SkipsModelValidations: | ||||
|   Enabled: false | ||||
|  | ||||
| Rails/UniqueValidationWithoutIndex: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/AccessorGrouping: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/AccessModifierDeclarations: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/ArrayCoercion: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/BisectedAttrAccessor: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/CaseLikeIf: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/ClassAndModuleChildren: | ||||
|   Enabled: false | ||||
|  | ||||
| @ -109,6 +205,15 @@ Style/Documentation: | ||||
| Style/DoubleNegation: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/ExpandPathArguments: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/ExponentialNotation: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/FormatString: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/FormatStringToken: | ||||
|   Enabled: false | ||||
|  | ||||
| @ -118,9 +223,33 @@ Style/FrozenStringLiteralComment: | ||||
| Style/GuardClause: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/HashAsLastArrayItem: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/HashEachMethods: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/HashLikeCase: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/HashTransformKeys: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/HashTransformValues: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/IfUnlessModifier: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/InverseMethods: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/Lambda: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/MutableConstant: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/PercentLiteralDelimiters: | ||||
|   PreferredDelimiters: | ||||
|     '%i': '()' | ||||
| @ -129,9 +258,36 @@ Style/PercentLiteralDelimiters: | ||||
| Style/PerlBackrefs: | ||||
|   AutoCorrect: false | ||||
|  | ||||
| Style/RedundantAssignment: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/RedundantFetchBlock: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/RedundantFileExtensionInRequire: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/RedundantRegexpCharacterClass: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/RedundantRegexpEscape: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/RedundantReturn: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/RegexpLiteral: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/RescueStandardError: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/SignalException: | ||||
|   Enabled: false | ||||
|  | ||||
| Style/SlicingWithRange: | ||||
|   Enabled: true | ||||
|  | ||||
| Style/SymbolArray: | ||||
|   Enabled: false | ||||
|  | ||||
| @ -140,3 +296,6 @@ Style/TrailingCommaInArrayLiteral: | ||||
|  | ||||
| Style/TrailingCommaInHashLiteral: | ||||
|   EnforcedStyleForMultiline: 'comma' | ||||
|  | ||||
| Style/UnpackFirst: | ||||
|   Enabled: false | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| 2.6.6 | ||||
| 2.7.2 | ||||
|  | ||||
							
								
								
									
										669
									
								
								AUTHORS.md
									
									
									
									
									
								
							
							
						
						
									
										669
									
								
								AUTHORS.md
									
									
									
									
									
								
							| @ -5,38 +5,39 @@ Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon) | ||||
| and provided thanks to the work of the following contributors: | ||||
|  | ||||
| * [Gargron](https://github.com/Gargron) | ||||
| * [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | ||||
| * [ThibG](https://github.com/ThibG) | ||||
| * [ykzts](https://github.com/ykzts) | ||||
| * [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | ||||
| * [dependabot[bot]](https://github.com/apps/dependabot) | ||||
| * [ykzts](https://github.com/ykzts) | ||||
| * [akihikodaki](https://github.com/akihikodaki) | ||||
| * [mjankowski](https://github.com/mjankowski) | ||||
| * [unarist](https://github.com/unarist) | ||||
| * [yiskah](https://github.com/yiskah) | ||||
| * [nolanlawson](https://github.com/nolanlawson) | ||||
| * [abcang](https://github.com/abcang) | ||||
| * [ysksn](https://github.com/ysksn) | ||||
| * [mayaeh](https://github.com/mayaeh) | ||||
| * [ysksn](https://github.com/ysksn) | ||||
| * [sorin-davidoi](https://github.com/sorin-davidoi) | ||||
| * [noellabo](https://github.com/noellabo) | ||||
| * [lynlynlynx](https://github.com/lynlynlynx) | ||||
| * [m4sk1n](mailto:me@m4sk.in) | ||||
| * [Marcin Mikołajczak](mailto:me@m4sk.in) | ||||
| * [Kjwon15](https://github.com/Kjwon15) | ||||
| * [noellabo](https://github.com/noellabo) | ||||
| * [renatolond](https://github.com/renatolond) | ||||
| * [alpaca-tc](https://github.com/alpaca-tc) | ||||
| * [jeroenpraat](https://github.com/jeroenpraat) | ||||
| * [nclm](https://github.com/nclm) | ||||
| * [ineffyble](https://github.com/ineffyble) | ||||
| * [shleeable](https://github.com/shleeable) | ||||
| * [zunda](https://github.com/zunda) | ||||
| * [shleeable](https://github.com/shleeable) | ||||
| * [Masoud Abkenar](mailto:ampbox@gmail.com) | ||||
| * [blackle](https://github.com/blackle) | ||||
| * [Quent-in](https://github.com/Quent-in) | ||||
| * [JantsoP](https://github.com/JantsoP) | ||||
| * [nullkal](https://github.com/nullkal) | ||||
| * [yookoala](https://github.com/yookoala) | ||||
| * [Sasha-Sorokin](https://github.com/Sasha-Sorokin) | ||||
| * [Brawaru](https://github.com/Brawaru) | ||||
| * [ariasuni](https://github.com/ariasuni) | ||||
| * [Aditoo17](https://github.com/Aditoo17) | ||||
| * [Quenty31](https://github.com/Quenty31) | ||||
| * [marek-lach](https://github.com/marek-lach) | ||||
| @ -45,9 +46,9 @@ and provided thanks to the work of the following contributors: | ||||
| * [danhunsaker](https://github.com/danhunsaker) | ||||
| * [eramdam](https://github.com/eramdam) | ||||
| * [takayamaki](https://github.com/takayamaki) | ||||
| * [ariasuni](https://github.com/ariasuni) | ||||
| * [masarakki](https://github.com/masarakki) | ||||
| * [ticky](https://github.com/ticky) | ||||
| * [trwnh](https://github.com/trwnh) | ||||
| * [ThisIsMissEm](https://github.com/ThisIsMissEm) | ||||
| * [hinaloe](https://github.com/hinaloe) | ||||
| * [hcmiya](https://github.com/hcmiya) | ||||
| @ -57,10 +58,10 @@ and provided thanks to the work of the following contributors: | ||||
| * [yukimochi](https://github.com/yukimochi) | ||||
| * [palindromordnilap](https://github.com/palindromordnilap) | ||||
| * [rkarabut](https://github.com/rkarabut) | ||||
| * [trwnh](https://github.com/trwnh) | ||||
| * [nightpool](https://github.com/nightpool) | ||||
| * [Artoria2e5](https://github.com/Artoria2e5) | ||||
| * [marrus-sh](https://github.com/marrus-sh) | ||||
| * [dunn](https://github.com/dunn) | ||||
| * [krainboltgreene](https://github.com/krainboltgreene) | ||||
| * [pfigel](https://github.com/pfigel) | ||||
| * [BoFFire](https://github.com/BoFFire) | ||||
| @ -84,25 +85,25 @@ and provided thanks to the work of the following contributors: | ||||
| * [ashleyhull-versent](https://github.com/ashleyhull-versent) | ||||
| * [yhirano55](https://github.com/yhirano55) | ||||
| * [rinsuki](https://github.com/rinsuki) | ||||
| * [dunn](https://github.com/dunn) | ||||
| * [devkral](https://github.com/devkral) | ||||
| * [camponez](https://github.com/camponez) | ||||
| * [hugogameiro](https://github.com/hugogameiro) | ||||
| * [SerCom_KC](mailto:szescxz@gmail.com) | ||||
| * [aschmitz](https://github.com/aschmitz) | ||||
| * [mfmfuyu](https://github.com/mfmfuyu) | ||||
| * [kedamaDQ](https://github.com/kedamaDQ) | ||||
| * [fpiesche](https://github.com/fpiesche) | ||||
| * [gandaro](https://github.com/gandaro) | ||||
| * [johnsudaar](https://github.com/johnsudaar) | ||||
| * [trebmuh](https://github.com/trebmuh) | ||||
| * [rmhasan](https://github.com/rmhasan) | ||||
| * [kedamaDQ](https://github.com/kedamaDQ) | ||||
| * [lindwurm](https://github.com/lindwurm) | ||||
| * [victorhck](mailto:victorhck@geeko.site) | ||||
| * [voidsatisfaction](https://github.com/voidsatisfaction) | ||||
| * [mkljczk](https://github.com/mkljczk) | ||||
| * [hikari-no-yume](https://github.com/hikari-no-yume) | ||||
| * [seefood](https://github.com/seefood) | ||||
| * [jackjennings](https://github.com/jackjennings) | ||||
| * [mfmfuyu](https://github.com/mfmfuyu) | ||||
| * [puckipedia](https://github.com/puckipedia) | ||||
| * [spla](mailto:spla@mastodont.cat) | ||||
| * [walf443](https://github.com/walf443) | ||||
| @ -111,14 +112,15 @@ and provided thanks to the work of the following contributors: | ||||
| * [Ashley](mailto:expenses@airmail.cc) | ||||
| * [xqus](https://github.com/xqus) | ||||
| * [pfm-eyesightjp](https://github.com/pfm-eyesightjp) | ||||
| * [Samy KACIMI](mailto:samy.kacimi@gmail.com) | ||||
| * [fakenine](https://github.com/fakenine) | ||||
| * [tsuwatch](https://github.com/tsuwatch) | ||||
| * [victorhck](https://github.com/victorhck) | ||||
| * [mkljczk](https://github.com/mkljczk) | ||||
| * [manuelviens](https://github.com/manuelviens) | ||||
| * [tateisu](https://github.com/tateisu) | ||||
| * [fvh-P](https://github.com/fvh-P) | ||||
| * [rtucker](https://github.com/rtucker) | ||||
| * [Anna e só](mailto:contraexemplos@gmail.com) | ||||
| * [dariusk](https://github.com/dariusk) | ||||
| * [kazu9su](https://github.com/kazu9su) | ||||
| * [Komic](https://github.com/Komic) | ||||
| * [lmorchard](https://github.com/lmorchard) | ||||
| @ -145,9 +147,9 @@ and provided thanks to the work of the following contributors: | ||||
| * [fhemberger](https://github.com/fhemberger) | ||||
| * [Gomasy](https://github.com/Gomasy) | ||||
| * [greysteil](https://github.com/greysteil) | ||||
| * [hencatsmith](https://github.com/hencatsmith) | ||||
| * [hendotcat](https://github.com/hendotcat) | ||||
| * [d6rkaiz](https://github.com/d6rkaiz) | ||||
| * [Reverite](https://github.com/Reverite) | ||||
| * [ladyisatis](https://github.com/ladyisatis) | ||||
| * [JohnD28](https://github.com/JohnD28) | ||||
| * [znz](https://github.com/znz) | ||||
| * [saper](https://github.com/saper) | ||||
| @ -160,14 +162,14 @@ and provided thanks to the work of the following contributors: | ||||
| * [leopku](https://github.com/leopku) | ||||
| * [SansPseudoFix](https://github.com/SansPseudoFix) | ||||
| * [spla](mailto:sp@mastodont.cat) | ||||
| * [tateisu](https://github.com/tateisu) | ||||
| * [tomfhowe](https://github.com/tomfhowe) | ||||
| * [noraworld](https://github.com/noraworld) | ||||
| * [lfuelling](https://github.com/lfuelling) | ||||
| * [theboss](https://github.com/theboss) | ||||
| * [aji-su](https://github.com/aji-su) | ||||
| * [nzws](https://github.com/nzws) | ||||
| * [duxovni](https://github.com/duxovni) | ||||
| * [smorimoto](https://github.com/smorimoto) | ||||
| * [mashirozx](https://github.com/mashirozx) | ||||
| * [178inaba](https://github.com/178inaba) | ||||
| * [acid-chicken](https://github.com/acid-chicken) | ||||
| * [xgess](https://github.com/xgess) | ||||
| @ -175,7 +177,6 @@ and provided thanks to the work of the following contributors: | ||||
| * [aablinov](https://github.com/aablinov) | ||||
| * [stalker314314](https://github.com/stalker314314) | ||||
| * [cutls](https://github.com/cutls) | ||||
| * [dariusk](https://github.com/dariusk) | ||||
| * [huertanix](https://github.com/huertanix) | ||||
| * [eleboucher](https://github.com/eleboucher) | ||||
| * [halkeye](https://github.com/halkeye) | ||||
| @ -183,7 +184,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [treby](https://github.com/treby) | ||||
| * [jpdevries](https://github.com/jpdevries) | ||||
| * [gdpelican](https://github.com/gdpelican) | ||||
| * [kmichl](https://github.com/kmichl) | ||||
| * [Korbinian](mailto:kontakt@korbinian-michl.de) | ||||
| * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name) | ||||
| * [panarom](https://github.com/panarom) | ||||
| * [Dar13](https://github.com/Dar13) | ||||
| @ -225,6 +226,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [aaribaud](https://github.com/aaribaud) | ||||
| * [pointlessone](https://github.com/pointlessone) | ||||
| * [Andrew](mailto:andrewlchronister@gmail.com) | ||||
| * [arielrodrigues](https://github.com/arielrodrigues) | ||||
| * [aurelien-reeves](https://github.com/aurelien-reeves) | ||||
| * [elegaanz](https://github.com/elegaanz) | ||||
| * [estuans](https://github.com/estuans) | ||||
| @ -238,6 +240,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [muffinista](https://github.com/muffinista) | ||||
| * [cdutson](https://github.com/cdutson) | ||||
| * [farlistener](https://github.com/farlistener) | ||||
| * [divergentdave](https://github.com/divergentdave) | ||||
| * [DavidLibeau](https://github.com/DavidLibeau) | ||||
| * [dmerejkowsky](https://github.com/dmerejkowsky) | ||||
| * [ddevault](https://github.com/ddevault) | ||||
| @ -276,7 +279,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [xPaw](https://github.com/xPaw) | ||||
| * [petzah](https://github.com/petzah) | ||||
| * [ignisf](https://github.com/ignisf) | ||||
| * [raymestalez](https://github.com/raymestalez) | ||||
| * [lumenwrites](https://github.com/lumenwrites) | ||||
| * [remram44](https://github.com/remram44) | ||||
| * [sts10](https://github.com/sts10) | ||||
| * [SuperSandro2000](https://github.com/SuperSandro2000) | ||||
| @ -286,8 +289,9 @@ and provided thanks to the work of the following contributors: | ||||
| * [Sir-Boops](https://github.com/Sir-Boops) | ||||
| * [stemid](https://github.com/stemid) | ||||
| * [sumdog](https://github.com/sumdog) | ||||
| * [OmmyZhang](https://github.com/OmmyZhang) | ||||
| * [ThomasLeister](https://github.com/ThomasLeister) | ||||
| * [mcat-ee](https://github.com/mcat-ee) | ||||
| * [Tom McAtee](mailto:a1608768@student.adelaide.edu.au) | ||||
| * [tototoshi](https://github.com/tototoshi) | ||||
| * [TrashMacNugget](https://github.com/TrashMacNugget) | ||||
| * [VirtuBox](https://github.com/VirtuBox) | ||||
| @ -314,11 +318,13 @@ and provided thanks to the work of the following contributors: | ||||
| * [matsurai25](https://github.com/matsurai25) | ||||
| * [mecab](https://github.com/mecab) | ||||
| * [nicobz25](https://github.com/nicobz25) | ||||
| * [niwatori24](https://github.com/niwatori24) | ||||
| * [oliverkeeble](https://github.com/oliverkeeble) | ||||
| * [partev](https://github.com/partev) | ||||
| * [pinfort](https://github.com/pinfort) | ||||
| * [rbaumert](https://github.com/rbaumert) | ||||
| * [rhoio](https://github.com/rhoio) | ||||
| * [santiagorodriguez96](https://github.com/santiagorodriguez96) | ||||
| * [sclaire-1](https://github.com/sclaire-1) | ||||
| * [umonaca](https://github.com/umonaca) | ||||
| * [usagi-f](https://github.com/usagi-f) | ||||
| @ -327,7 +333,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [wxcafe](https://github.com/wxcafe) | ||||
| * [Grawl](https://github.com/Grawl) | ||||
| * [新都心(Neet Shin)](mailto:nucx@dio-vox.com) | ||||
| * [clarfon](https://github.com/clarfon) | ||||
| * [clarfonthey](https://github.com/clarfonthey) | ||||
| * [cygnan](https://github.com/cygnan) | ||||
| * [Awea](https://github.com/Awea) | ||||
| * [eai04191](https://github.com/eai04191) | ||||
| @ -358,11 +364,11 @@ and provided thanks to the work of the following contributors: | ||||
| * [schas002](https://github.com/schas002) | ||||
| * [contraexemplo](https://github.com/contraexemplo) | ||||
| * [abackstrom](https://github.com/abackstrom) | ||||
| * [arielrodrigues](https://github.com/arielrodrigues) | ||||
| * [orlea](https://github.com/orlea) | ||||
| * [armandfardeau](https://github.com/armandfardeau) | ||||
| * [raboof](https://github.com/raboof) | ||||
| * [jumbosushi](https://github.com/jumbosushi) | ||||
| * [acuteaura](https://github.com/acuteaura) | ||||
| * [ayumin](https://github.com/ayumin) | ||||
| * [bzg](https://github.com/bzg) | ||||
| * [BastienDurel](https://github.com/BastienDurel) | ||||
| @ -389,7 +395,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [colindean](https://github.com/colindean) | ||||
| * [DeeUnderscore](https://github.com/DeeUnderscore) | ||||
| * [dachinat](https://github.com/dachinat) | ||||
| * [shapeshifter-system](https://github.com/shapeshifter-system) | ||||
| * [monsterpit-firedemon](https://github.com/monsterpit-firedemon) | ||||
| * [watilde](https://github.com/watilde) | ||||
| * [daprice](https://github.com/daprice) | ||||
| * [da2x](https://github.com/da2x) | ||||
| @ -400,14 +406,13 @@ and provided thanks to the work of the following contributors: | ||||
| * [singingwolfboy](https://github.com/singingwolfboy) | ||||
| * [caldwell](https://github.com/caldwell) | ||||
| * [davidcelis](https://github.com/davidcelis) | ||||
| * [divergentdave](https://github.com/divergentdave) | ||||
| * [davefp](https://github.com/davefp) | ||||
| * [yipdw](https://github.com/yipdw) | ||||
| * [debanshuk](https://github.com/debanshuk) | ||||
| * [mascali33](https://github.com/mascali33) | ||||
| * [DerekNonGeneric](https://github.com/DerekNonGeneric) | ||||
| * [dblandin](https://github.com/dblandin) | ||||
| * [Drew Gates](mailto:aranaur@users.noreply.github.com) | ||||
| * [Aranaur](https://github.com/Aranaur) | ||||
| * [dtschust](https://github.com/dtschust) | ||||
| * [Dryusdan](https://github.com/Dryusdan) | ||||
| * [d3vgru](https://github.com/d3vgru) | ||||
| @ -451,22 +456,25 @@ and provided thanks to the work of the following contributors: | ||||
| * [J Yeary](mailto:usbsnowcrash@users.noreply.github.com) | ||||
| * [jack-michaud](https://github.com/jack-michaud) | ||||
| * [Floppy](https://github.com/Floppy) | ||||
| * [loomchild](https://github.com/loomchild) | ||||
| * [jglauche](https://github.com/jglauche) | ||||
| * [jenkr55](https://github.com/jenkr55) | ||||
| * [hyenagirl64](https://github.com/hyenagirl64) | ||||
| * [press5](https://github.com/press5) | ||||
| * [TrollDecker](https://github.com/TrollDecker) | ||||
| * [jmontane](https://github.com/jmontane) | ||||
| * [Jarek Lipski](mailto:pub@loomchild.net) | ||||
| * [Jennifer Glauche](mailto:=^.^=@github19.jglauche.de) | ||||
| * [Jennifer Kruse](mailto:jenkr55@gmail.com) | ||||
| * [Jeremy Rose](mailto:nornagon@nornagon.net) | ||||
| * [Jessica](mailto:46502909+hyenagirl64@users.noreply.github.com) | ||||
| * [Jessica K. Litwin](mailto:jessica@litw.in) | ||||
| * [Jo Decker](mailto:trolldecker@users.noreply.github.com) | ||||
| * [Joan Montané](mailto:jmontane@users.noreply.github.com) | ||||
| * [Jonathan Klee](mailto:klee.jonathan@gmail.com) | ||||
| * [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net) | ||||
| * [Joseph Mingrone](mailto:jehops@users.noreply.github.com) | ||||
| * [Josh Leeb-du Toit](mailto:mail@joshleeb.com) | ||||
| * [Joshua Wood](mailto:josh@joshuawood.net) | ||||
| * [Julien](mailto:tiwy57@users.noreply.github.com) | ||||
| * [Julien Deswaef](mailto:juego@requiem4tv.com) | ||||
| * [June Sallou](mailto:jnsll@users.noreply.github.com) | ||||
| * [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com) | ||||
| * [KEINOS](mailto:github@keinos.com) | ||||
| * [Kairui Song | 宋恺睿](mailto:ryncsn@gmail.com) | ||||
| * [Keiji Matsuzaki](mailto:futoase@gmail.com) | ||||
| * [Kevin Liu](mailto:kevin@potatofrom.space) | ||||
| * [Kit Redgrave](mailto:qwertyitis@gmail.com) | ||||
| @ -482,7 +490,6 @@ and provided thanks to the work of the following contributors: | ||||
| * [Lukas Burk](mailto:jemus42@users.noreply.github.com) | ||||
| * [Manato Kameya](mailto:grabacr07+github@gmail.com) | ||||
| * [Mantas](mailto:mistermantas@users.noreply.github.com) | ||||
| * [Marcin Mikołajczak](mailto:me@mkljczk.pl) | ||||
| * [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com) | ||||
| * [Marek Lach](mailto:marek.brohatwack.lach@gmail.com) | ||||
| * [Markus R](mailto:wirehack7@users.noreply.github.com) | ||||
| @ -529,10 +536,12 @@ and provided thanks to the work of the following contributors: | ||||
| * [Norayr Chilingarian](mailto:norayr@arnet.am) | ||||
| * [Noëlle Anthony](mailto:noelle.d.anthony@gmail.com) | ||||
| * [N氏](mailto:uenok.htc@gmail.com) | ||||
| * [OSAMU SATO](mailto:satosamu@gmail.com) | ||||
| * [Olivier Nicole](mailto:olivierthnicole@gmail.com) | ||||
| * [Oskari Noppa](mailto:noppa@users.noreply.github.com) | ||||
| * [Otakan](mailto:otakan951@gmail.com) | ||||
| * [Padraig Fahy](mailto:tech@padraigfahy.com) | ||||
| * [Patrice Ferlet](mailto:metal3d@gmail.com) | ||||
| * [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com) | ||||
| * [Paul](mailto:naydex.mc+github@gmail.com) | ||||
| * [Pete Keen](mailto:pete@petekeen.net) | ||||
| @ -574,7 +583,6 @@ and provided thanks to the work of the following contributors: | ||||
| * [TakesxiSximada](mailto:takesxi.sximada@gmail.com) | ||||
| * [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com) | ||||
| * [Taras Gogol](mailto:taras2358@gmail.com) | ||||
| * [Tdxdxoz](mailto:tdxdxoz@gmail.com) | ||||
| * [TheInventrix](mailto:theinventrix@users.noreply.github.com) | ||||
| * [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com) | ||||
| * [Thomas Alberola](mailto:thomas@needacoffee.fr) | ||||
| @ -594,6 +602,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [Wesley Ellis](mailto:tahnok@gmail.com) | ||||
| * [Wiktor](mailto:wiktor@metacode.biz) | ||||
| * [Wonderfall](mailto:wonderfall@schrodinger.io) | ||||
| * [Y.Yamashiro](mailto:shukukei@mojizuri.jp) | ||||
| * [YDrogen](mailto:ydrogen45@gmail.com) | ||||
| * [YMHuang](mailto:ymhuang@fmbase.tw) | ||||
| * [YOSHIOKA Eiichiro](mailto:yoshioka.eiichiro@gmail.com) | ||||
| @ -638,6 +647,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [jumoru](mailto:jumoru@mailbox.org) | ||||
| * [kaiyou](mailto:pierre@jaury.eu) | ||||
| * [karlyeurl](mailto:karl.yeurl@gmail.com) | ||||
| * [kawaguchi](mailto:jiikko@users.noreply.github.com) | ||||
| * [kedama](mailto:32974885+kedamadq@users.noreply.github.com) | ||||
| * [kuro5hin](mailto:rusty@kuro5hin.org) | ||||
| * [leo60228](mailto:leo@60228.dev) | ||||
| @ -655,6 +665,7 @@ and provided thanks to the work of the following contributors: | ||||
| * [notozeki](mailto:notozeki@users.noreply.github.com) | ||||
| * [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com) | ||||
| * [nzws](mailto:git-yuzu@svk.jp) | ||||
| * [proxy](mailto:51172302+3n-k1@users.noreply.github.com) | ||||
| * [rch850](mailto:rich850@gmail.com) | ||||
| * [roikale](mailto:roikale@users.noreply.github.com) | ||||
| * [rysiekpl](mailto:rysiek@hackerspace.pl) | ||||
| @ -694,308 +705,414 @@ This document is provided for informational purposes only. Since it is only upda | ||||
|  | ||||
| Following people have contributed to translation of Mastodon: | ||||
|  | ||||
| - ᏦᏁᎢᎵᏫ 😷 (*Spanish, Argentina*) | ||||
| - Sveinn í Felli (*Icelandic*) | ||||
| - ᏦᏁᎢᎵᏫ 😷 (KNTRO) (*Spanish, Argentina*) | ||||
| - Sveinn í Felli (sveinki) (*Icelandic*) | ||||
| - qezwan (*Persian, Sorani (Kurdish)*) | ||||
| - Hồ Nhất Duy (kantcer) (*Vietnamese*) | ||||
| - taicv (*Vietnamese*) | ||||
| - ButterflyOfFire (*Arabic; French; Kabyle*) | ||||
| - Duy (*Vietnamese*) | ||||
| - Evert Prants (*Estonian*) | ||||
| - Zoltán Gera (*Hungarian*) | ||||
| - Daniele Lira Mereb (*Portuguese, Brazilian*) | ||||
| - Kristijan Tkalec (*Slovenian*) | ||||
| - stan ionut (*Romanian*) | ||||
| - Ramdziana F Y (*Indonesian*) | ||||
| - Michal Stanke (*Czech*) | ||||
| - Xosé M. (*Galician; Spanish*) | ||||
| - 奈卜拉 (*Chinese Simplified*) | ||||
| - borys_sh (*Ukrainian*) | ||||
| - Miguel Mayol (*Spanish; Catalan*) | ||||
| - Besnik_b (*Albanian*) | ||||
| - Thai Localization (*Thai*) | ||||
| - Emanuel Pina (*Portuguese*) | ||||
| - Jeong Arm (*Korean; Esperanto; Japanese*) | ||||
| - Imre Kristoffer Eilertsen (*Norwegian*) | ||||
| - Danial Behzadi (*Persian*) | ||||
| - Osoitz (*Basque*) | ||||
| - Peterandre (*Norwegian Nynorsk; Norwegian*) | ||||
| - Jeroen (*Dutch*) | ||||
| - spla (*Catalan; Spanish*) | ||||
| - Iváns (*Galician*) | ||||
| - koyu (*German*) | ||||
| - Sasha Sorokin (*Russian; Vietnamese; Swedish; Catalan; Greek; Hungarian; Armenian; Albanian; Galician; French; Danish; German; Korean; Ukrainian*) | ||||
| - enolp (*Asturian*) | ||||
| - Masoud Abkenar (*Persian*) | ||||
| - lamnatos (*Greek*) | ||||
| - Alix Rossi (*Corsican; French*) | ||||
| - arshat (*Kazakh*) | ||||
| - FédiQuébec (*French*) | ||||
| - Marek Ľach (*Slovak; Polish*) | ||||
| - Muha Aliss (*Turkish*) | ||||
| - tolstoevsky (*Russian*) | ||||
| - Emyn-Russell Nt Nefydd (*Welsh*) | ||||
| - Aditoo17 (*Czech*) | ||||
| - Maya Minatsuki (*Japanese*) | ||||
| - ariasuni (*French; Esperanto*) | ||||
| - Roboron (*Spanish*) | ||||
| - Alessandro Levati (*Italian*) | ||||
| - Diluns (*Occitan*) | ||||
| - regulartranslator (*Portuguese, Brazilian*) | ||||
| - vishnuvaratharajan (*Tamil*) | ||||
| - Marcin Mikołajczak (*Polish*) | ||||
| - Yi-Jyun Pan (*Chinese Traditional*) | ||||
| - Zoltán Gera (gerazo) (*Hungarian*) | ||||
| - ButterflyOfFire (BoFFire) (*French, Arabic, Kabyle*) | ||||
| - adrmzz (*Sardinian*) | ||||
| - Ramdziana F Y (rafeyu) (*Indonesian*) | ||||
| - Evert Prants (IcyDiamond) (*Estonian*) | ||||
| - Daniele Lira Mereb (danilmereb) (*Portuguese, Brazilian*) | ||||
| - Xosé M. (XoseM) (*Spanish, Galician*) | ||||
| - Kristijan Tkalec (lapor) (*Slovenian*) | ||||
| - stan ionut (stanionut12) (*Romanian*) | ||||
| - Besnik_b (*Albanian*) | ||||
| - Emanuel Pina (emanuelpina) (*Portuguese*) | ||||
| - Thai Localization (thl10n) (*Thai*) | ||||
| - 奈卜拉 (nebula_moe) (*Chinese Simplified*) | ||||
| - Jeong Arm (Kjwon15) (*Japanese, Korean, Esperanto*) | ||||
| - Michal Stanke (mstanke) (*Czech*) | ||||
| - Alix Rossi (palindromordnilap) (*French, Corsican*) | ||||
| - spla (*Spanish, Catalan*) | ||||
| - Imre Kristoffer Eilertsen (DandelionSprout) (*Norwegian*) | ||||
| - Jeroen (jeroenpraat) (*Dutch*) | ||||
| - borys_sh (*Ukrainian*) | ||||
| - Miguel Mayol (mitcoes) (*Spanish, Catalan*) | ||||
| - Danial Behzadi (danialbehzadi) (*Persian*) | ||||
| - yeft (*Chinese Traditional, Chinese Traditional, Hong Kong*) | ||||
| - koyu (*German*) | ||||
| - Koala Yeung (yookoala) (*Chinese Traditional, Hong Kong*) | ||||
| - Osoitz (*Basque*) | ||||
| - Peterandre (*Norwegian, Norwegian Nynorsk*) | ||||
| - tzium (*Sardinian*) | ||||
| - Iváns (Ivans_translator) (*Galician*) | ||||
| - Sasha Sorokin (Sasha-Sorokin) (*French, Catalan, Danish, German, Greek, Hungarian, Armenian, Korean, Russian, Albanian, Swedish, Ukrainian, Vietnamese, Galician*) | ||||
| - kamee (*Armenian*) | ||||
| - tolstoevsky (*Russian*) | ||||
| - enolp (*Asturian*) | ||||
| - FédiQuébec (manuelviens) (*French*) | ||||
| - lamnatos (*Greek*) | ||||
| - Maya Minatsuki (mayaeh) (*Japanese*) | ||||
| - Masoud Abkenar (mabkenar) (*Persian*) | ||||
| - Alessandro Levati (Oct326) (*Italian*) | ||||
| - arshat (*Kazakh*) | ||||
| - Roboron (*Spanish*) | ||||
| - ariasuni (*French, Arabic, Czech, German, Greek, Hungarian, Slovenian, Ukrainian, Chinese Simplified, Portuguese, Brazilian, Persian, Norwegian Nynorsk, Esperanto, Breton, Corsican, Sardinian, Kabyle*) | ||||
| - Ali Demirtaş (alidemirtas) (*Turkish*) | ||||
| - Em St Cenydd (cancennau) (*Welsh*) | ||||
| - Marek Ľach (mareklach) (*Polish, Slovak*) | ||||
| - Muha Aliss (muhaaliss) (*Turkish*) | ||||
| - Jurica (ahjk) (*Croatian*) | ||||
| - Aditoo17 (*Czech*) | ||||
| - Diluns (*Occitan*) | ||||
| - gagik_ (*Armenian*) | ||||
| - vishnuvaratharajan (*Tamil*) | ||||
| - Marcin Mikołajczak (mkljczkk) (*Czech, Polish, Russian*) | ||||
| - regulartranslator (*Portuguese, Brazilian*) | ||||
| - Akarshan Biswas (biswasab) (*Bengali, Sanskrit*) | ||||
| - Yi-Jyun Pan (pan93412) (*Chinese Traditional*) | ||||
| - d5Ziif3K (*Ukrainian*) | ||||
| - GiorgioHerbie (*Italian*) | ||||
| - Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*) | ||||
| - Saederup92 (*Danish*) | ||||
| - christalleras (*Norwegian Nynorsk*) | ||||
| - cybergene (cyber-gene) (*Japanese*) | ||||
| - Taloran (*Norwegian Nynorsk*) | ||||
| - ThibG (*French; Icelandic*) | ||||
| - Akarshan Biswas (*Bengali*) | ||||
| - ThibG (*French, Icelandic*) | ||||
| - xatier (*Chinese Traditional*) | ||||
| - otrapersona (*Spanish, Spanish, Mexico*) | ||||
| - atarashiako (*Chinese Simplified*) | ||||
| - 101010 (*Polish*) | ||||
| - 101010 (101010pl) (*Polish*) | ||||
| - silkevicious (*Italian*) | ||||
| - Bertil Hedkvist (*Swedish*) | ||||
| - cybergene (*Japanese*) | ||||
| - Floxu (fredrikdim1) (*Norwegian Nynorsk*) | ||||
| - Bertil Hedkvist (Berrahed) (*Swedish*) | ||||
| - William(ѕ)ⁿ (wmlgr) (*Spanish*) | ||||
| - norayr (*Armenian*) | ||||
| - William(ѕ)ⁿ (*Spanish*) | ||||
| - Tiago Epifânio (*Portuguese*) | ||||
| - Mentor Gashi (*Albanian*) | ||||
| - Jaz-Michael King (*Welsh*) | ||||
| - Tiago Epifânio (tfve) (*Portuguese*) | ||||
| - Ryo (DrRyo) (*Korean*) | ||||
| - Mentor Gashi (mentorgashi.com) (*Albanian*) | ||||
| - Jaz-Michael King (jazmichaelking) (*Welsh*) | ||||
| - carolinagiorno (*Portuguese, Brazilian*) | ||||
| - Roby Thomas (*Malayalam*) | ||||
| - Bharat Kumar (*Hindi*) | ||||
| - Roby Thomas (roby.thomas) (*Malayalam*) | ||||
| - Bharat Kumar (Marwari) (*Hindi*) | ||||
| - ThonyVezbe (*Breton*) | ||||
| - dkdarshan760 (*Sanskrit*) | ||||
| - Tagomago (tagomago) (*French, Spanish*) | ||||
| - tykayn (*French*) | ||||
| - axi (*Finnish*) | ||||
| - Selyan Slimane AMIRI (*Kabyle*) | ||||
| - Selyan Slimane AMIRI (slimane_AMIRI) (*Kabyle*) | ||||
| - Balázs Meskó (mesko.balazs) (*Hungarian*) | ||||
| - taoxvx (*Danish*) | ||||
| - Hrach Mkrtchyan (*Armenian*) | ||||
| - sabri (*Spanish; Spanish, Argentina*) | ||||
| - Dewi (*Breton; French*) | ||||
| - Hrach Mkrtchyan (mhrach87) (*Armenian*) | ||||
| - sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*) | ||||
| - Dewi (Unkorneg) (*French, Breton*) | ||||
| - Coelacanthus (*Chinese Simplified*) | ||||
| - syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*) | ||||
| - SteinarK (*Norwegian Nynorsk*) | ||||
| - Mathias B. Vagnes (*Norwegian*) | ||||
| - dashersyed (*Urdu*) | ||||
| - ThonyVezbe (*Breton*) | ||||
| - Acolyte (*Ukrainian*) | ||||
| - Conight Wang (*Chinese Simplified*) | ||||
| - Damjan Dimitrioski (*Macedonian*) | ||||
| - PPNplus (*Thai*) | ||||
| - Tagomago (*Spanish; French*) | ||||
| - shioko (*Chinese Simplified*) | ||||
| - Balázs Meskó (*Hungarian*) | ||||
| - Evgeny Petrov (*Russian*) | ||||
| - Gwenn (*Breton*) | ||||
| - Ryo (*Korean*) | ||||
| - Rafael H L Moretti (*Portuguese, Brazilian*) | ||||
| - jaranta (*Finnish*) | ||||
| - gagik_ (*Armenian*) | ||||
| - Felicia (*Swedish*) | ||||
| - Jess Rafn (*Danish*) | ||||
| - Stasiek Michalski (*Polish*) | ||||
| - Sokratis Alichanidis (alichani) (*Greek*) | ||||
| - Mathias B. Vagnes (vagnes) (*Norwegian*) | ||||
| - dashersyed (*Urdu (Pakistan)*) | ||||
| - Acolyte (666noob404) (*Ukrainian*) | ||||
| - Conight Wang (xfddwhh) (*Chinese Simplified*) | ||||
| - liffon (*Swedish*) | ||||
| - dxwc (*Bengali*) | ||||
| - Saederup92 (*Danish*) | ||||
| - Vanege (*Esperanto*) | ||||
| - jmontane (*Catalan*) | ||||
| - Johan Schiff (*Swedish*) | ||||
| - Arunmozhi (*Tamil*) | ||||
| - kat (*Ukrainian; Russian*) | ||||
| - Laura (*Polish*) | ||||
| - oti4500 (*Hungarian; Ukrainian*) | ||||
| - diazepan (*Spanish; Spanish, Argentina*) | ||||
| - Sokratis Alichanidis (*Greek*) | ||||
| - Rikard Linde (*Swedish*) | ||||
| - Juan José Salvador Piedra (*Spanish*) | ||||
| - marzuquccen (*Kabyle*) | ||||
| - BurekzFinezt (*Serbian*) | ||||
| - SHeija (*Finnish*) | ||||
| - Jack R (*Spanish*) | ||||
| - andruhov (*Ukrainian; Russian*) | ||||
| - 森の子リスのミーコの大冒険 (*Japanese*) | ||||
| - るいーね (*Japanese*) | ||||
| - Sam Tux (*Bengali*) | ||||
| - Unmual (*Spanish*) | ||||
| - AW Unad (*Indonesian*) | ||||
| - Cutls (*Japanese*) | ||||
| - Ray (*Spanish*) | ||||
| - Falling Snowdin (*Vietnamese*) | ||||
| - Andrea Lo Iacono (*Italian*) | ||||
| - EPEMA (*German*) | ||||
| - Kinshuk Sunil (*Hindi*) | ||||
| - Ullas Joseph (*Malayalam*) | ||||
| - Yu-Pai Liu (*Chinese Traditional*) | ||||
| - Amarin Cemthong (*Thai*) | ||||
| - juanda097 (*Spanish*) | ||||
| - Anunnakey (*Macedonian*) | ||||
| - Damjan Dimitrioski (gnud) (*Macedonian*) | ||||
| - PPNplus (*Thai*) | ||||
| - shioko (*Chinese Simplified*) | ||||
| - v4vachan (*Malayalam*) | ||||
| - Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*) | ||||
| - Evgeny Petrov (kondra007) (*Russian*) | ||||
| - Gwenn (Belvar) (*Breton*) | ||||
| - StanleyFrew (*French*) | ||||
| - Hayk Khachatryan (brutusromanus123) (*Armenian*) | ||||
| - jaranta (*Finnish*) | ||||
| - Felicia (midsommar) (*Swedish*) | ||||
| - Denys (dector) (*Ukrainian*) | ||||
| - Pukima (pukimaaa) (*German*) | ||||
| - Vanege (*Esperanto*) | ||||
| - Jess Rafn (therealyez) (*Danish*) | ||||
| - strubbl (*German*) | ||||
| - Stasiek Michalski (hellcp) (*Polish*) | ||||
| - dxwc (*Bengali*) | ||||
| - jmontane (*Catalan*) | ||||
| - Liboide (*Spanish*) | ||||
| - Johan Schiff (schyffel) (*Swedish*) | ||||
| - Arunmozhi (tecoholic) (*Tamil*) | ||||
| - kat (katktv) (*Russian, Ukrainian*) | ||||
| - Rikard Linde (rikardlinde) (*Swedish*) | ||||
| - oti4500 (*Hungarian, Ukrainian*) | ||||
| - Laura (selfisekai) (*Polish*) | ||||
| - Rachida S. (ZiriSut) (*Kabyle*) | ||||
| - diazepan (*Spanish, Spanish, Argentina*) | ||||
| - marzuquccen (*Kabyle*) | ||||
| - Juan José Salvador Piedra (JuanjoSalvador) (*Spanish*) | ||||
| - Tigran (tigransimonyan) (*Armenian*) | ||||
| - BurekzFinezt (*Serbian (Cyrillic)*) | ||||
| - SHeija (*Finnish*) | ||||
| - atriix (*Swedish*) | ||||
| - Jack R (isaac.97_WT) (*Spanish*) | ||||
| - antonyho (*Chinese Traditional, Hong Kong*) | ||||
| - andruhov (*Russian, Ukrainian*) | ||||
| - Aryamik Sharma (Aryamik) (*Swedish, Hindi*) | ||||
| - phena109 (*Chinese Traditional, Hong Kong*) | ||||
| - 森の子リスのミーコの大冒険 (Phroneris) (*Japanese*) | ||||
| - るいーね (ruine) (*Japanese*) | ||||
| - ahangarha (*Persian*) | ||||
| - Sam Tux (imahbub) (*Bengali*) | ||||
| - igordrozniak (*Polish*) | ||||
| - Unmual (*Spanish*) | ||||
| - Isaac Huang (caasih) (*Chinese Traditional*) | ||||
| - AW Unad (awcodify) (*Indonesian*) | ||||
| - Allen Zhong (AstroProfundis) (*Chinese Simplified*) | ||||
| - Cutls (cutls) (*Japanese*) | ||||
| - Ray (Ipsumry) (*Spanish*) | ||||
| - Falling Snowdin (tghgg) (*Vietnamese*) | ||||
| - coxde (*Chinese Simplified*) | ||||
| - Rasmus Lindroth (RasmusLindroth) (*Swedish*) | ||||
| - Andrea Lo Iacono (niels0n) (*Italian*) | ||||
| - Kinshuk Sunil (kinshuksunil) (*Hindi*) | ||||
| - Ullas Joseph (ullasjoseph) (*Malayalam*) | ||||
| - Goudarz Jafari (Goudarz) (*Persian*) | ||||
| - Yu-Pai Liu (tedliou) (*Chinese Traditional*) | ||||
| - Amarin Cemthong (acitmaster) (*Thai*) | ||||
| - juanda097 (juanda-097) (*Spanish*) | ||||
| - Anunnakey (*Macedonian*) | ||||
| - fragola (*Italian*) | ||||
| - erikstl (*Esperanto*) | ||||
| - twpenguin (*Chinese Traditional*) | ||||
| - bobchao (*Chinese Traditional*) | ||||
| - Esther (esthermations) (*Portuguese*) | ||||
| - MadeInSteak (*Finnish*) | ||||
| - Heimen Stoffels (*Dutch*) | ||||
| - Rajarshi Guha (*Bengali*) | ||||
| - Andrew (*Romanian*) | ||||
| - Goudarz Jafari (*Persian*) | ||||
| - Heimen Stoffels (vistausss) (*Dutch*) | ||||
| - Rajarshi Guha (rajarshiguha) (*Bengali*) | ||||
| - Andrew (iAndrew3) (*Romanian*) | ||||
| - Gopal Sharma (gopalvirat) (*Hindi*) | ||||
| - arethsu (*Swedish*) | ||||
| - Carlos Solís (*Esperanto*) | ||||
| - Parthan S Ramanujam (*Tamil*) | ||||
| - Ali Demirtaş (*Turkish*) | ||||
| - Kasper Nymand (*Danish*) | ||||
| - TS (*Finnish*) | ||||
| - Tofiq Abdula (Xwla) (*Sorani (Kurdish)*) | ||||
| - Carlos Solís (csolisr) (*Esperanto*) | ||||
| - Parthan S Ramanujam (parthan) (*Tamil*) | ||||
| - Kasper Nymand (KasperNymand) (*Danish*) | ||||
| - TS (morte) (*Finnish*) | ||||
| - subram (*Turkish*) | ||||
| - SensDeViata (*Ukrainian*) | ||||
| - Ptrcmd (ptrcmd) (*Chinese Traditional*) | ||||
| - SergioFMiranda (*Portuguese, Brazilian*) | ||||
| - OctolinGamer (*Portuguese, Brazilian*) | ||||
| - Scvoet (scvoet) (*Chinese Simplified*) | ||||
| - hiroTS (*Chinese Traditional*) | ||||
| - johne32rus23 (*Russian*) | ||||
| - AzureNya (*Chinese Simplified*) | ||||
| - Ram varma (*Tamil*) | ||||
| - 北䑓如法 (*Japanese*) | ||||
| - OctolinGamer (octolingamer) (*Portuguese, Brazilian*) | ||||
| - Ram varma (ram4varma) (*Tamil*) | ||||
| - Hexandcube (hexandcube) (*Polish*) | ||||
| - 北䑓如法 (Nyoho) (*Japanese*) | ||||
| - frumble (*German*) | ||||
| - kekkepikkuni (*Tamil*) | ||||
| - Neo_Chen (NeoChen1024) (*Chinese Traditional*) | ||||
| - oorsutri (*Tamil*) | ||||
| - Nithin V (*Tamil*) | ||||
| - Miro Rauhala (*Finnish*) | ||||
| - Rhys Harrison (rhedders) (*Esperanto*) | ||||
| - Nithin V (Nithin896) (*Tamil*) | ||||
| - Miro Rauhala (mirorauhala) (*Finnish*) | ||||
| - diorama (*Italian*) | ||||
| - Rhys Harrison (*Esperanto*) | ||||
| - Guillaume Turchini (*French*) | ||||
| - Ganesh D (*Marathi*) | ||||
| - AlexKoala (alexkoala) (*Korean*) | ||||
| - Aswin C (officialcjunior) (*Malayalam*) | ||||
| - Guillaume Turchini (orion78fr) (*French*) | ||||
| - Ganesh D (auntgd) (*Marathi*) | ||||
| - dragnucs2 (*Arabic*) | ||||
| - Pedro Henrique (*Portuguese, Brazilian*) | ||||
| - Tejas Harad (*Marathi*) | ||||
| - Vasanthan (*Tamil*) | ||||
| - 硫酸鶏 (*Japanese*) | ||||
| - Ryan Ho (koungho) (*Chinese Traditional*) | ||||
| - Pedro Henrique (exploronauta) (*Portuguese, Brazilian*) | ||||
| - Tejas Harad (h_tejas) (*Marathi*) | ||||
| - Vasanthan (vasanthan) (*Tamil*) | ||||
| - 硫酸鶏 (acid_chicken) (*Japanese*) | ||||
| - clarmin b8 (clarminb8) (*Sorani (Kurdish)*) | ||||
| - manukp (*Malayalam*) | ||||
| - psymyn (*Hebrew*) | ||||
| - earth dweller (*Marathi*) | ||||
| - meijerivoi (*Finnish*) | ||||
| - earth dweller (sanethoughtyt) (*Marathi*) | ||||
| - meijerivoi (toilet) (*Finnish*) | ||||
| - essaar (*Tamil*) | ||||
| - serubeena (*Swedish*) | ||||
| - Karol Kosek (krkkPL) (*Polish*) | ||||
| - Rintan (*Japanese*) | ||||
| - Karol Kosek (*Polish*) | ||||
| - valarivan (*Tamil*) | ||||
| - Sebastián Andil (*Slovak*) | ||||
| - v4vachan (*Malayalam*) | ||||
| - KEINOS (*Japanese*) | ||||
| - Ivan T. (*Chinese Traditional, Hong Kong*) | ||||
| - Hernik (hernik27) (*Czech*) | ||||
| - Sebastián Andil (Selrond) (*Slovak*) | ||||
| - Hinaloe (hinaloe) (*Japanese*) | ||||
| - filippodb (*Italian*) | ||||
| - Balázs Meskó (*Hungarian*) | ||||
| - KEINOS (*Japanese*) | ||||
| - Balázs Meskó (meskobalazs) (*Hungarian*) | ||||
| - Bottle (suryasalem2010) (*Tamil*) | ||||
| - JzshAC (*Chinese Simplified*) | ||||
| - Bottle (*Tamil*) | ||||
| - Khóo (*Chinese Traditional*) | ||||
| - Steven Tappert (*German*) | ||||
| - Antillion (*Spanish*) | ||||
| - ZiriSut (*Kabyle*) | ||||
| - gowthamanb (*Tamil*) | ||||
| - hiphipvargas (*Portuguese*) | ||||
| - Arttu Ylhävuori (*Finnish*) | ||||
| - Ch. (*Korean*) | ||||
| - tctovsli (*Norwegian Nynorsk*) | ||||
| - Hinaloe (*Japanese*) | ||||
| - strubbl (*German*) | ||||
| - vjasiegd (*Polish*) | ||||
| - SamitiMed (*Thai*) | ||||
| - Wrya ali (John12) (*Sorani (Kurdish)*) | ||||
| - Khóo (khootiatling) (*Chinese Traditional*) | ||||
| - Steven Tappert (sammy8806) (*German*) | ||||
| - Antillion (antillion99) (*Spanish*) | ||||
| - Pukima (Pukimaa) (*German*) | ||||
| - Reg3xp (*Persian*) | ||||
| - AlexKoala (*Korean*) | ||||
| - hiphipvargas (*Portuguese*) | ||||
| - gowthamanb (*Tamil*) | ||||
| - Ch. (sftblw) (*Korean*) | ||||
| - Jeff Huang (s8321414) (*Chinese Traditional*) | ||||
| - Arttu Ylhävuori (arttu.ylhavuori) (*Finnish*) | ||||
| - tctovsli (*Norwegian Nynorsk*) | ||||
| - Timo Tijhof (Krinkle) (*Dutch*) | ||||
| - Yamagishi Kazutoshi (ykzts) (*Japanese, Icelandic, Sorani (Kurdish)*) | ||||
| - vjasiegd (*Polish*) | ||||
| - SamitiMed (samiti3d) (*Thai*) | ||||
| - Rekan Adl (rekan-adl1) (*Sorani (Kurdish)*) | ||||
| - umelard (*Hebrew*) | ||||
| - Antara2Cinta (Se7enTime) (*Indonesian*) | ||||
| - VSx86 (*Russian*) | ||||
| - Daniel Dimitrov (*Bulgarian*) | ||||
| - mynameismonkey (*Welsh*) | ||||
| - Daniel Dimitrov (danny-dimitrov) (*Bulgarian*) | ||||
| - parnikkapore (*Thai*) | ||||
| - Mo_der Steven (*Chinese Simplified*) | ||||
| - mynameismonkey (*Welsh*) | ||||
| - Sherwan Othman (sherwanothman11) (*Sorani (Kurdish)*) | ||||
| - Yassine Aït-El-Mouden (yaitelmouden) (*Standard Moroccan Tamazight*) | ||||
| - SKELET (*Danish*) | ||||
| - Renato "Lond" Cerqueira (*Portuguese, Brazilian*) | ||||
| - Mo_der Steven (SakuraPuare) (*Chinese Simplified*) | ||||
| - Fei Yang (Fei1Yang) (*Chinese Traditional*) | ||||
| - ALEM FARID (faridatcemlulaqbayli) (*Kabyle*) | ||||
| - enipra (*Armenian*) | ||||
| - musix (*Persian*) | ||||
| - ギャラ (*Chinese Simplified; Japanese*) | ||||
| - ALEM FARID (*Kabyle*) | ||||
| - Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*) | ||||
| - ギャラ (gyara) (*Japanese, Chinese Simplified*) | ||||
| - Hougo (hougo) (*French*) | ||||
| - ybardapurkar (*Marathi*) | ||||
| - Adrián Lattes (*Spanish*) | ||||
| - Adrián Lattes (haztecaso) (*Spanish*) | ||||
| - TracyJacks (*Chinese Simplified*) | ||||
| - rasheedgm (*Kannada*) | ||||
| - omquylzu (*Latvian*) | ||||
| - Belkacem Mohammed (*Kabyle*) | ||||
| - Navjot Singh (*Hindi*) | ||||
| - Ozai (*German*) | ||||
| - Sahak Petrosyan (*Armenian*) | ||||
| - siamano (*Thai; Esperanto*) | ||||
| - se7entime (*Indonesian*) | ||||
| - Viorel-Cătălin Răpițeanu (*Romanian*) | ||||
| - Siddhartha Sarathi Basu (*Bengali*) | ||||
| - Pachara Chantawong (*Thai*) | ||||
| - Skew (*French*) | ||||
| - Zijian Zhao (*Chinese Simplified*) | ||||
| - Guru Prasath Anandapadmanaban (*Tamil*) | ||||
| - turtle836 (*German*) | ||||
| - GatoOscuro (*Spanish*) | ||||
| - Lamin (*Japanese*) | ||||
| - Marcepanek_ (*Polish*) | ||||
| - Yann Aguettaz (*French*) | ||||
| - Feruz Oripov (*Russian*) | ||||
| - Mick Onio (*Asturian*) | ||||
| - hg6 (*Hindi*) | ||||
| - Malik Mann (*German*) | ||||
| - padulafacundo (*Spanish*) | ||||
| - mecqor labi (mecqorlabi) (*Persian*) | ||||
| - Belkacem Mohammed (belkacem77) (*Kabyle*) | ||||
| - Navjot Singh (nspeaks) (*Hindi*) | ||||
| - omquylzu (*Latvian*) | ||||
| - Ozai (*German*) | ||||
| - Sahak Petrosyan (petrosyan) (*Armenian*) | ||||
| - siamano (*Thai, Esperanto*) | ||||
| - Viorel-Cătălin Răpițeanu (rapiteanu) (*Romanian*) | ||||
| - Siddhartha Sarathi Basu (quinoa_biryani) (*Bengali*) | ||||
| - Pachara Chantawong (pachara2202) (*Thai*) | ||||
| - mkljczk (*Polish*) | ||||
| - Skew (noan.perrot) (*French*) | ||||
| - Zijian Zhao (jobs2512821228) (*Chinese Simplified*) | ||||
| - turtle836 (*German*) | ||||
| - Guru Prasath Anandapadmanaban (guruprasath) (*Tamil*) | ||||
| - Lamin (laminne) (*Japanese*) | ||||
| - Marcepanek_ (thekingmarcepan) (*Polish*) | ||||
| - Feruz Oripov (FeruzOripov) (*Russian*) | ||||
| - Yann Aguettaz (yann-a) (*French*) | ||||
| - Mick Onio (xgc.redes) (*Asturian*) | ||||
| - Tianqi Zhang (tina.zhang040609) (*Chinese Simplified*) | ||||
| - Malik Mann (dermalikmann) (*German*) | ||||
| - dadosch (*German*) | ||||
| - r3dsp1 (*Chinese Traditional, Hong Kong*) | ||||
| - Tianqi Zhang (*Chinese Simplified*) | ||||
| - Padraic Calpin (*Slovenian*) | ||||
| - cenegd (*Chinese Simplified*) | ||||
| - padulafacundo (*Spanish*) | ||||
| - hg6 (*Hindi*) | ||||
| - Orlando Murcio (Atos20) (*Spanish, Mexico*) | ||||
| - piupiupiudiu (*Chinese Simplified*) | ||||
| - Hugh Liu (*Chinese Simplified*) | ||||
| - Rakino (*Chinese Simplified*) | ||||
| - Jothipazhani Nagarajan (*Tamil*) | ||||
| - Miquel Sabaté Solà (*Catalan*) | ||||
| - shdy (*German*) | ||||
| - Padraic Calpin (padraic-padraic) (*Slovenian*) | ||||
| - Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*) | ||||
| - cenegd (*Chinese Simplified*) | ||||
| - Hugh Liu (youloveonlymeh) (*Chinese Simplified*) | ||||
| - Pixelcode (realpixelcode) (*German*) | ||||
| - Yogesh K S (yogi) (*Kannada*) | ||||
| - Rakino (rakino) (*Chinese Simplified*) | ||||
| - Miquel Sabaté Solà (mssola) (*Catalan*) | ||||
| - AmazighNM (*Kabyle*) | ||||
| - Solid Rhino (*Dutch*) | ||||
| - Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*) | ||||
| - Clash Clans (KURD12345) (*Sorani (Kurdish)*) | ||||
| - hallomaurits (*Dutch*) | ||||
| - alnd hezh (alndhezh) (*Sorani (Kurdish)*) | ||||
| - Solid Rhino (SolidRhino) (*Dutch*) | ||||
| - k_taka (peaceroad) (*Japanese*) | ||||
| - Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*) | ||||
| - hussama (*Portuguese, Brazilian*) | ||||
| - shafouz (*Portuguese, Brazilian*) | ||||
| - Tagada (*French*) | ||||
| - Tom_ (*Czech*) | ||||
| - SnDer (*Dutch*) | ||||
| - Sébastien Feugère (smonff) (*French*) | ||||
| - 林水溶 (shuiRong) (*Chinese Simplified*) | ||||
| - eichkat3r (*German*) | ||||
| - PifyZ (*French*) | ||||
| - OminousCry (*Russian*) | ||||
| - Shrinivasan T (*Tamil*) | ||||
| - Nathaël Noguès (*French*) | ||||
| - Daniel M. (*Catalan*) | ||||
| - Swati Sani (*Urdu*) | ||||
| - Kk (*Kannada*) | ||||
| - SusVersiva (*Catalan*) | ||||
| - Robin van der Vliet (*Esperanto*) | ||||
| - Zinkokooo (*Basque*) | ||||
| - Tradjincal (*French*) | ||||
| - SnDer (*Dutch*) | ||||
| - PifyZ (*French*) | ||||
| - Tom_ (*Czech*) | ||||
| - Tagada (Tagadda) (*French*) | ||||
| - shafouz (*Portuguese, Brazilian*) | ||||
| - Kahina Mess (K_hina) (*Kabyle*) | ||||
| - Nathaël Noguès (NatNgs) (*French*) | ||||
| - Kk (kishorkumara3) (*Kannada*) | ||||
| - Swati Sani (swatisani) (*Urdu (Pakistan)*) | ||||
| - Shrinivasan T (tshrinivasan) (*Tamil*) | ||||
| - さっかりんにーさん (saccharin23) (*Japanese*) | ||||
| - 夜楓Yoka (Yoka2627) (*Chinese Simplified*) | ||||
| - Daniel M. (daniconil) (*Catalan*) | ||||
| - Vikatakavi (*Kannada*) | ||||
| - prabhjot (*Hindi*) | ||||
| - twpenguin (*Chinese Traditional*) | ||||
| - SusVersiva (*Catalan*) | ||||
| - Tradjincal (tradjincal) (*French*) | ||||
| - pullopen (*Chinese Simplified*) | ||||
| - Robin van der Vliet (RobinvanderVliet) (*Esperanto*) | ||||
| - Zinkokooo (*Basque*) | ||||
| - mmokhi (*Persian*) | ||||
| - Livingston Samuel (livingston) (*Tamil*) | ||||
| - prabhjot (*Hindi*) | ||||
| - sergioaraujo1 (*Portuguese, Brazilian*) | ||||
| - Livingston Samuel (*Tamil*) | ||||
| - CyberAmoeba (pseudoobscura) (*Chinese Simplified*) | ||||
| - tsundoker (*Malayalam*) | ||||
| - skaaarrr (*German*) | ||||
| - 夜楓Yoka (*Chinese Simplified*) | ||||
| - kiwi0 (*Italian*) | ||||
| - Ricardo Colin (rysard) (*Spanish*) | ||||
| - mkljczk (mykylyjczyk) (*Polish*) | ||||
| - Philipp Fischbeck (PFischbeck) (*German*) | ||||
| - fedot (*Russian*) | ||||
| - mkljczk (*Polish*) | ||||
| - igordrozniak (*Polish*) | ||||
| - Ricardo Colin (*Spanish*) | ||||
| - Esther (*Portuguese*) | ||||
| - Paz Galindo (*Spanish*) | ||||
| - Philipp Fischbeck (*German*) | ||||
| - Paz Galindo (paz.almendra.g) (*Spanish*) | ||||
| - GaggiX (*Italian*) | ||||
| - ralozkolya (*Georgian*) | ||||
| - JackXu (*Chinese Simplified*) | ||||
| - Allen Zhong (*Chinese Simplified*) | ||||
| - Zoé Bőle (*German*) | ||||
| - Lukas Fülling (*German*) | ||||
| - Albatroz Jeremias (*Portuguese*) | ||||
| - Samir Tighzert (*Kabyle*) | ||||
| - Nocta (*French*) | ||||
| - Anoop (*Malayalam*) | ||||
| - Zoé Bőle (zoe1337) (*German*) | ||||
| - Lukas Fülling (lfuelling) (*German*) | ||||
| - JackXu (Merman-Jack) (*Chinese Simplified*) | ||||
| - Aymeric (AymBroussier) (*French*) | ||||
| - Anoop (anoopp) (*Malayalam*) | ||||
| - pezcurrel (*Italian*) | ||||
| - Dremski (*Bulgarian*) | ||||
| - Aymeric (*French*) | ||||
| - tamaina (*Japanese*) | ||||
| - Doug (*Portuguese, Brazilian*) | ||||
| - Matias Lavik (*Norwegian Nynorsk*) | ||||
| - Fleva (*Sardinian*) | ||||
| - Xurxo Guerra (xguerrap) (*Galician*) | ||||
| - mashirozx (*Chinese Simplified*) | ||||
| - Albatroz Jeremias (albjeremias) (*Portuguese*) | ||||
| - Samir Tighzert (samir_t7) (*Kabyle*) | ||||
| - Apple (blackteaovo) (*Chinese Simplified*) | ||||
| - Nocta (*French*) | ||||
| - OpenAlgeria (*Arabic*) | ||||
| - koppe-pan (*Japanese*) | ||||
| - Amith Raj Shetty (*Kannada*) | ||||
| - tamaina (*Japanese*) | ||||
| - abidin toumi (Zet24) (*Arabic*) | ||||
| - xpac1985 (xpac) (*German*) | ||||
| - Kaede (kaedech) (*Japanese*) | ||||
| - ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*) | ||||
| - Matias Lavik (matiaslavik) (*Norwegian Nynorsk*) | ||||
| - smedvedev (*Russian*) | ||||
| - Trond Boksasp (*Norwegian*) | ||||
| - mikel (mikelalas) (*Spanish*) | ||||
| - Doug (douglasalvespe) (*Portuguese, Brazilian*) | ||||
| - Trond Boksasp (boksasp) (*Norwegian*) | ||||
| - Fleva (*Sardinian*) | ||||
| - Mohammad Adnan Mahmood (adnanmig) (*Arabic*) | ||||
| - Sais Lakshmanan (Saislakshmanan) (*Tamil*) | ||||
| - Amith Raj Shetty (amithraj1989) (*Kannada*) | ||||
| - random_person (*Spanish*) | ||||
| - Sais Lakshmanan (*Tamil*) | ||||
| - mikel (*Spanish*) | ||||
| - Mohammad Adnan Mahmood (*Arabic*) | ||||
| - djoerd (*Dutch*) | ||||
| - Baban Abdulrahman (baban.abdulrehman) (*Sorani (Kurdish)*) | ||||
| - ebrezhoneg (*Breton*) | ||||
| - dashty (*Sorani (Kurdish)*) | ||||
| - Salh_haji6 (*Sorani (Kurdish)*) | ||||
| - Amir Kurdo (kuraking202) (*Sorani (Kurdish)*) | ||||
| - おさ (osapon) (*Japanese*) | ||||
| - Ranj A Abdulqadir (RanjAhmed) (*Sorani (Kurdish)*) | ||||
| - umonaca (*Chinese Simplified*) | ||||
| - Bartek Fijałkowski (brateq) (*Polish*) | ||||
| - tateisu (*Japanese*) | ||||
| - centumix (*Japanese*) | ||||
| - Jari Ronkainen (ronchaine) (*Finnish*) | ||||
| - Savarín Electrográfico Marmota Intergalactica (herrero.maty) (*Spanish*) | ||||
| - Torsten Högel (torstenhoegel) (*German*) | ||||
| - Abijeet Patro (Abijeet) (*Basque*) | ||||
| - Ács Zoltán (acszoltan111) (*Hungarian*) | ||||
| - Benjamin Cobb (benjamincobb) (*German*) | ||||
| - waweic (*German*) | ||||
| - Aries (orlea) (*Japanese*) | ||||
| - silverscat_3 (SilversCat) (*Japanese*) | ||||
| - kavitha129 (*Tamil*) | ||||
| - dcapillae (*Spanish*) | ||||
| - SamOak (*Portuguese, Brazilian*) | ||||
| - capiscuas (*Spanish*) | ||||
| - NeverMine17 (*Russian*) | ||||
| - Nithya Mary (nithyamary25) (*Tamil*) | ||||
| - t_aus_m (*German*) | ||||
| - dobrado (*Portuguese, Brazilian*) | ||||
| - Hannah (Aniqueper1) (*Chinese Simplified*) | ||||
| - Jiniux (*Italian*) | ||||
| - 于晚霞 (xissshawww) (*Chinese Simplified*) | ||||
|  | ||||
							
								
								
									
										1
									
								
								Aptfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Aptfile
									
									
									
									
									
								
							| @ -5,7 +5,6 @@ libidn11 | ||||
| libidn11-dev | ||||
| libpq-dev | ||||
| libprotobuf-dev | ||||
| libssl-dev | ||||
| libxdamage1 | ||||
| libxfixes3 | ||||
| protobuf-compiler | ||||
|  | ||||
							
								
								
									
										208
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										208
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -3,6 +3,208 @@ Changelog | ||||
|  | ||||
| All notable changes to this project will be documented in this file. | ||||
|  | ||||
| ## [3.3.0] - 2020-12-27 | ||||
| ### Added | ||||
|  | ||||
| - **Add hotkeys for audio/video control in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/15158), [Gargron](https://github.com/tootsuite/mastodon/pull/15198)) | ||||
|   - `Space` and `k` to toggle playback | ||||
|   - `m` to toggle mute | ||||
|   - `f` to toggle fullscreen | ||||
|   - `j` and `l` to go back and forward by 10 seconds | ||||
|   - `.` and `,` to go back and forward by a frame (video only) | ||||
| - Add expand/compress button on media modal in web UI ([mashirozx](https://github.com/tootsuite/mastodon/pull/15068), [mashirozx](https://github.com/tootsuite/mastodon/pull/15088), [mashirozx](https://github.com/tootsuite/mastodon/pull/15094)) | ||||
| - Add border around 🕺 emoji in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14769)) | ||||
| - Add border around 🐞 emoji in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14712)) | ||||
| - Add home link to the getting started column when home isn't mounted ([ThibG](https://github.com/tootsuite/mastodon/pull/14707)) | ||||
| - Add option to disable swiping motions across the web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/13885)) | ||||
| - **Add pop-out player for audio/video in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/14870), [Gargron](https://github.com/tootsuite/mastodon/pull/15157), [Gargron](https://github.com/tootsuite/mastodon/pull/14915), [noellabo](https://github.com/tootsuite/mastodon/pull/15309)) | ||||
|   - Continue watching/listening when you scroll away | ||||
|   - Action bar to interact with/open toot from the pop-out player | ||||
| - Add unread notification markers in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14818), [ThibG](https://github.com/tootsuite/mastodon/pull/14960), [ThibG](https://github.com/tootsuite/mastodon/pull/14954), [noellabo](https://github.com/tootsuite/mastodon/pull/14897), [noellabo](https://github.com/tootsuite/mastodon/pull/14907)) | ||||
| - Add paragraph about browser add-ons when encountering errors in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14801)) | ||||
| - Add import and export for bookmarks ([ThibG](https://github.com/tootsuite/mastodon/pull/14956)) | ||||
| - Add cache buster feature for media files ([Gargron](https://github.com/tootsuite/mastodon/pull/15155)) | ||||
|   - If you have a proxy cache in front of object storage, deleted files will persist until the cache expires | ||||
|   - If enabled, cache buster will make a special request to the proxy to signal a cache reset | ||||
| - Add duration option to the mute function ([aquarla](https://github.com/tootsuite/mastodon/pull/13831)) | ||||
| - Add replies policy option to the list function ([ThibG](https://github.com/tootsuite/mastodon/pull/9205), [trwnh](https://github.com/tootsuite/mastodon/pull/15304)) | ||||
| - Add `og:published_time` OpenGraph tags on toots ([nornagon](https://github.com/tootsuite/mastodon/pull/14865)) | ||||
| - **Add option to be notified when a followed user posts** ([Gargron](https://github.com/tootsuite/mastodon/pull/13546), [ThibG](https://github.com/tootsuite/mastodon/pull/14896), [Gargron](https://github.com/tootsuite/mastodon/pull/14822)) | ||||
|   - If you don't want to miss a toot, click the bell button! | ||||
| - Add client-side validation in password change forms ([ThibG](https://github.com/tootsuite/mastodon/pull/14564)) | ||||
| - Add client-side validation in the registration form ([ThibG](https://github.com/tootsuite/mastodon/pull/14560), [ThibG](https://github.com/tootsuite/mastodon/pull/14599)) | ||||
| - Add support for Gemini URLs ([joshleeb](https://github.com/tootsuite/mastodon/pull/15013)) | ||||
| - Add app shortcuts to web app manifest ([mkljczk](https://github.com/tootsuite/mastodon/pull/15234)) | ||||
| - Add WebAuthn as an alternative 2FA method ([santiagorodriguez96](https://github.com/tootsuite/mastodon/pull/14466), [jiikko](https://github.com/tootsuite/mastodon/pull/14806)) | ||||
| - Add honeypot fields and minimum fill-out time for sign-up form ([ThibG](https://github.com/tootsuite/mastodon/pull/15276)) | ||||
| - Add icon for mutual relationships in relationship manager ([noellabo](https://github.com/tootsuite/mastodon/pull/15149)) | ||||
| - Add follow selected followers button in relationship manager ([noellabo](https://github.com/tootsuite/mastodon/pull/15148)) | ||||
| - **Add subresource integrity for JS and CSS assets** ([Gargron](https://github.com/tootsuite/mastodon/pull/15096)) | ||||
|   - If you use a CDN for static assets (JavaScript, CSS, and so on), you have to trust that the CDN does not modify the assets maliciously | ||||
|   - Subresource integrity compares server-generated asset digests with what's actually served from the CDN and prevents such attacks | ||||
| - Add `ku`, `sa`, `sc`, `zgh` to available locales ([ykzts](https://github.com/tootsuite/mastodon/pull/15138)) | ||||
| - Add ability to force an account to mark media as sensitive ([noellabo](https://github.com/tootsuite/mastodon/pull/14361)) | ||||
| - **Add ability to block access or limit sign-ups from chosen IPs** ([Gargron](https://github.com/tootsuite/mastodon/pull/14963), [ThibG](https://github.com/tootsuite/mastodon/pull/15263)) | ||||
|   - Add rules for IPs or CIDR ranges that automatically expire after a configurable amount of time | ||||
|   - Choose the severity of the rule, either blocking all access or merely limiting sign-ups | ||||
| - **Add support for reversible suspensions through ActivityPub** ([Gargron](https://github.com/tootsuite/mastodon/pull/14989)) | ||||
|   - Servers can signal that one of their accounts has been suspended | ||||
|   - During suspension, the account can only delete its own content | ||||
|   - A reversal of the suspension can be signalled the same way | ||||
|   - A local suspension always overrides a remote one | ||||
| - Add indication to admin UI of whether a report has been forwarded ([ThibG](https://github.com/tootsuite/mastodon/pull/13237)) | ||||
| - Add display of reasons for joining of an account in admin UI ([mashirozx](https://github.com/tootsuite/mastodon/pull/15265)) | ||||
| - Add option to obfuscate domain name in public list of domain blocks ([Gargron](https://github.com/tootsuite/mastodon/pull/15355)) | ||||
| - Add option to make reasons for joining required on sign-up ([ThibG](https://github.com/tootsuite/mastodon/pull/15326), [ThibG](https://github.com/tootsuite/mastodon/pull/15358), [ThibG](https://github.com/tootsuite/mastodon/pull/15385), [ThibG](https://github.com/tootsuite/mastodon/pull/15405)) | ||||
| - Add ActivityPub follower synchronization mechanism ([ThibG](https://github.com/tootsuite/mastodon/pull/14510), [ThibG](https://github.com/tootsuite/mastodon/pull/15026)) | ||||
| - Add outbox attribute to instance actor ([ThibG](https://github.com/tootsuite/mastodon/pull/14721)) | ||||
| - Add featured hashtags as an ActivityPub collection ([Gargron](https://github.com/tootsuite/mastodon/pull/11595), [noellabo](https://github.com/tootsuite/mastodon/pull/15277)) | ||||
| - Add support for dereferencing objects through bearcaps ([Gargron](https://github.com/tootsuite/mastodon/pull/14683), [noellabo](https://github.com/tootsuite/mastodon/pull/14981)) | ||||
| - Add `S3_READ_TIMEOUT` environment variable ([tateisu](https://github.com/tootsuite/mastodon/pull/14952)) | ||||
| - Add `ALLOWED_PRIVATE_ADDRESSES` environment variable ([ThibG](https://github.com/tootsuite/mastodon/pull/14722)) | ||||
| - Add `--fix-permissions` option to `tootctl media remove-orphans` ([Gargron](https://github.com/tootsuite/mastodon/pull/14383), [uist1idrju3i](https://github.com/tootsuite/mastodon/pull/14715)) | ||||
| - Add `tootctl accounts merge` ([Gargron](https://github.com/tootsuite/mastodon/pull/15201), [ThibG](https://github.com/tootsuite/mastodon/pull/15264), [ThibG](https://github.com/tootsuite/mastodon/pull/15256)) | ||||
|   - Has someone changed their domain or subdomain thereby creating two accounts where there should be one? | ||||
|   - This command will fix it on your end | ||||
| - Add `tootctl maintenance fix-duplicates` ([ThibG](https://github.com/tootsuite/mastodon/pull/14860), [Gargron](https://github.com/tootsuite/mastodon/pull/15223), [ThibG](https://github.com/tootsuite/mastodon/pull/15373)) | ||||
|   - Index corruption in the database? | ||||
|   - This command is for you | ||||
| - **Add support for managing multiple stream subscriptions in a single connection** ([Gargron](https://github.com/tootsuite/mastodon/pull/14524), [Gargron](https://github.com/tootsuite/mastodon/pull/14566), [mfmfuyu](https://github.com/tootsuite/mastodon/pull/14859), [zunda](https://github.com/tootsuite/mastodon/pull/14608)) | ||||
|   - Previously, getting live updates for multiple timelines required opening a HTTP or WebSocket connection for each | ||||
|   - More connections means more resource consumption on both ends, not to mention the (ever so slight) delay when establishing a new connection | ||||
|   - Now, with just a single WebSocket connection you can subscribe and unsubscribe to and from multiple streams | ||||
| - Add support for limiting results by both `min_id` and `max_id` at the same time in REST API ([tateisu](https://github.com/tootsuite/mastodon/pull/14776)) | ||||
| - Add `GET /api/v1/accounts/:id/featured_tags` to REST API ([noellabo](https://github.com/tootsuite/mastodon/pull/11817), [noellabo](https://github.com/tootsuite/mastodon/pull/15270)) | ||||
| - Add stoplight for object storage failures, return HTTP 503 in REST API ([Gargron](https://github.com/tootsuite/mastodon/pull/13043)) | ||||
| - Add optional `tootctl remove media` cronjob in Helm chart ([dunn](https://github.com/tootsuite/mastodon/pull/14396)) | ||||
| - Add clean error message when `RAILS_ENV` is unset ([ThibG](https://github.com/tootsuite/mastodon/pull/15381)) | ||||
|  | ||||
| ### Changed | ||||
|  | ||||
| - **Change media modals look in web UI** ([Gargron](https://github.com/tootsuite/mastodon/pull/15217), [Gargron](https://github.com/tootsuite/mastodon/pull/15221), [Gargron](https://github.com/tootsuite/mastodon/pull/15284), [Gargron](https://github.com/tootsuite/mastodon/pull/15283), [Kjwon15](https://github.com/tootsuite/mastodon/pull/15308), [noellabo](https://github.com/tootsuite/mastodon/pull/15305), [ThibG](https://github.com/tootsuite/mastodon/pull/15417)) | ||||
|   - Background of the overlay matches the color of the image | ||||
|   - Action bar to interact with or open the toot from the modal | ||||
| - Change order of announcements in admin UI to be newest-first ([ThibG](https://github.com/tootsuite/mastodon/pull/15091)) | ||||
| - **Change account suspensions to be reversible by default** ([Gargron](https://github.com/tootsuite/mastodon/pull/14726), [ThibG](https://github.com/tootsuite/mastodon/pull/15152), [ThibG](https://github.com/tootsuite/mastodon/pull/15106), [ThibG](https://github.com/tootsuite/mastodon/pull/15100), [ThibG](https://github.com/tootsuite/mastodon/pull/15099), [noellabo](https://github.com/tootsuite/mastodon/pull/14855), [ThibG](https://github.com/tootsuite/mastodon/pull/15380), [Gargron](https://github.com/tootsuite/mastodon/pull/15420), [Gargron](https://github.com/tootsuite/mastodon/pull/15414)) | ||||
|   - Suspensions no longer equal deletions | ||||
|   - A suspended account can be unsuspended with minimal consequences for 30 days | ||||
|   - Immediate deletion of data is still available as an explicit option | ||||
|   - Suspended accounts can request an archive of their data through the UI | ||||
| - Change REST API to return empty data for suspended accounts (14765) | ||||
| - Change web UI to show empty profile for suspended accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/14766), [Gargron](https://github.com/tootsuite/mastodon/pull/15345)) | ||||
| - Change featured hashtag suggestions to be recently used instead of most used ([abcang](https://github.com/tootsuite/mastodon/pull/14760)) | ||||
| - Change direct toots to appear in the home feed again ([Gargron](https://github.com/tootsuite/mastodon/pull/14711), [ThibG](https://github.com/tootsuite/mastodon/pull/15182), [noellabo](https://github.com/tootsuite/mastodon/pull/14727)) | ||||
|   - Return to treating all toots the same instead of trying to retrofit direct visibility into an instant messaging model | ||||
| - Change email address validation to return more specific errors ([ThibG](https://github.com/tootsuite/mastodon/pull/14565)) | ||||
| - Change HTTP signature requirements to include `Digest` header on `POST` requests ([ThibG](https://github.com/tootsuite/mastodon/pull/15069)) | ||||
| - Change click area of video/audio player buttons to be bigger in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15049)) | ||||
| - Change order of filters by alphabetic by "keyword or phrase" ([ariasuni](https://github.com/tootsuite/mastodon/pull/15050)) | ||||
| - Change suspension of remote accounts to also undo outgoing follows ([ThibG](https://github.com/tootsuite/mastodon/pull/15188)) | ||||
| - Change string "Home" to "Home and lists" in the filter creation screen ([ariasuni](https://github.com/tootsuite/mastodon/pull/15139)) | ||||
| - Change string "Boost to original audience" to "Boost with original visibility" in web UI ([3n-k1](https://github.com/tootsuite/mastodon/pull/14598)) | ||||
| - Change string "Show more" to "Show newer" and "Show older" on public pages ([ariasuni](https://github.com/tootsuite/mastodon/pull/15052)) | ||||
| - Change order of announcements to be reverse chronological in web UI ([dariusk](https://github.com/tootsuite/mastodon/pull/15065), [dariusk](https://github.com/tootsuite/mastodon/pull/15070)) | ||||
| - Change RTL detection to rely on unicode-bidi paragraph by paragraph in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14573)) | ||||
| - Change visibility icon next to timestamp to be clickable in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15053), [mayaeh](https://github.com/tootsuite/mastodon/pull/15055)) | ||||
| - Change public thread view to hide "Show thread" link ([ThibG](https://github.com/tootsuite/mastodon/pull/15266)) | ||||
| - Change number format on about page from full to shortened ([Gargron](https://github.com/tootsuite/mastodon/pull/15327)) | ||||
| - Change how scheduled tasks run in multi-process environments ([noellabo](https://github.com/tootsuite/mastodon/pull/15314)) | ||||
|   - New dedicated queue `scheduler` | ||||
|   - Runs by default when Sidekiq is executed with no options | ||||
|   - Has to be added manually in a multi-process environment | ||||
|  | ||||
| ### Removed | ||||
|  | ||||
| - Remove fade-in animation from modals in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15199)) | ||||
| - Remove auto-redirect to direct messages in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15142)) | ||||
| - Remove obsolete IndexedDB operations from web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14730)) | ||||
| - Remove dependency on unused and unmaintained http_parser.rb gem ([ThibG](https://github.com/tootsuite/mastodon/pull/14574)) | ||||
|  | ||||
| ### Fixed | ||||
|  | ||||
| - Fix layout on about page when contact account has a long username ([ThibG](https://github.com/tootsuite/mastodon/pull/15357)) | ||||
| - Fix follow limit preventing re-following of a moved account ([Gargron](https://github.com/tootsuite/mastodon/pull/14207), [ThibG](https://github.com/tootsuite/mastodon/pull/15384)) | ||||
| - **Fix deletes not reaching every server that interacted with toot** ([Gargron](https://github.com/tootsuite/mastodon/pull/15200)) | ||||
|   - Previously, delete of a toot would be primarily sent to the followers of its author, people mentioned in the toot, and people who reblogged the toot | ||||
|   - Now, additionally, it is ensured that it is sent to people who replied to it, favourited it, and to the person it replies to even if that person is not mentioned | ||||
| - Fix resolving an account through its non-canonical form (i.e. alternate domain) ([ThibG](https://github.com/tootsuite/mastodon/pull/15187)) | ||||
| - Fix sending redundant ActivityPub events when processing remote account deletion ([ThibG](https://github.com/tootsuite/mastodon/pull/15104)) | ||||
| - Fix Move handler not being triggered when failing to fetch target account ([ThibG](https://github.com/tootsuite/mastodon/pull/15107)) | ||||
| - Fix downloading remote media files when server returns empty filename ([ThibG](https://github.com/tootsuite/mastodon/pull/14867)) | ||||
| - Fix account processing failing because of large collections ([ThibG](https://github.com/tootsuite/mastodon/pull/15027)) | ||||
| - Fix not being able to unfavorite toots one has lost access to ([ThibG](https://github.com/tootsuite/mastodon/pull/15192)) | ||||
| - Fix not being able to unbookmark toots one has lost access to ([ThibG](https://github.com/tootsuite/mastodon/pull/14604)) | ||||
| - Fix possible casing inconsistencies in hashtag search ([ThibG](https://github.com/tootsuite/mastodon/pull/14906)) | ||||
| - Fix updating account counters when association is not yet created ([Gargron](https://github.com/tootsuite/mastodon/pull/15108)) | ||||
| - Fix cookies not having a SameSite attribute ([Gargron](https://github.com/tootsuite/mastodon/pull/15098)) | ||||
| - Fix poll ending notifications being created for each vote ([ThibG](https://github.com/tootsuite/mastodon/pull/15071)) | ||||
| - Fix multiple boosts of a same toot erroneously appearing in TL ([ThibG](https://github.com/tootsuite/mastodon/pull/14759)) | ||||
| - Fix asset builds not picking up `CDN_HOST` change ([ThibG](https://github.com/tootsuite/mastodon/pull/14381)) | ||||
| - Fix desktop notifications permission prompt in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14985), [Gargron](https://github.com/tootsuite/mastodon/pull/15141), [ThibG](https://github.com/tootsuite/mastodon/pull/13543), [ThibG](https://github.com/tootsuite/mastodon/pull/15176)) | ||||
|   - Some time ago, browsers added a requirement that desktop notification prompts could only be displayed in response to a user-generated event (such as a click) | ||||
|   - This means that for some time, users who haven't already given the permission before were not getting a prompt and as such were not receiving desktop notifications | ||||
| - Fix "Mark media as sensitive" string not supporting pluralizations in other languages in web UI ([ariasuni](https://github.com/tootsuite/mastodon/pull/15051)) | ||||
| - Fix glitched image uploads when canvas read access is blocked in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15180)) | ||||
| - Fix some account gallery items having empty labels in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15073)) | ||||
| - Fix alt-key hotkeys activating while typing in a text field in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14942)) | ||||
| - Fix wrong seek bar width on media player in web UI ([mfmfuyu](https://github.com/tootsuite/mastodon/pull/15060)) | ||||
| - Fix logging out on mobile in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14901)) | ||||
| - Fix wrong click area for GIFVs in media modal in web UI ([noellabo](https://github.com/tootsuite/mastodon/pull/14615)) | ||||
| - Fix unreadable placeholder text color in high contrast theme in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/14803)) | ||||
| - Fix scrolling issues when closing some dropdown menus in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14606)) | ||||
| - Fix notification filter bar incorrectly filtering gaps in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14808)) | ||||
| - Fix disabled boost icon being replaced by private boost icon on hover in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14456)) | ||||
| - Fix hashtag detection in compose form being different to server-side in web UI ([kedamaDQ](https://github.com/tootsuite/mastodon/pull/14484), [ThibG](https://github.com/tootsuite/mastodon/pull/14513)) | ||||
| - Fix home last read marker mishandling gaps in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14809)) | ||||
| - Fix unnecessary re-rendering of various components when typing in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15286)) | ||||
| - Fix notifications being unnecessarily re-rendered in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15312)) | ||||
| - Fix column swiping animation logic in web UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15301)) | ||||
| - Fix inefficiency when fetching hashtag timeline ([noellabo](https://github.com/tootsuite/mastodon/pull/14861), [akihikodaki](https://github.com/tootsuite/mastodon/pull/14662)) | ||||
| - Fix inefficiency when fetching bookmarks ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14674)) | ||||
| - Fix inefficiency when fetching favourites ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14673)) | ||||
| - Fix inefficiency when fetching media-only account timeline ([akihikodaki](https://github.com/tootsuite/mastodon/pull/14675)) | ||||
| - Fix inefficieny when deleting accounts ([Gargron](https://github.com/tootsuite/mastodon/pull/15387), [ThibG](https://github.com/tootsuite/mastodon/pull/15409), [ThibG](https://github.com/tootsuite/mastodon/pull/15407), [ThibG](https://github.com/tootsuite/mastodon/pull/15408), [ThibG](https://github.com/tootsuite/mastodon/pull/15402), [ThibG](https://github.com/tootsuite/mastodon/pull/15416), [Gargron](https://github.com/tootsuite/mastodon/pull/15421)) | ||||
| - Fix redundant query when processing batch actions on custom emojis ([niwatori24](https://github.com/tootsuite/mastodon/pull/14534)) | ||||
| - Fix slow distinct queries where grouped queries are faster ([Gargron](https://github.com/tootsuite/mastodon/pull/15287)) | ||||
| - Fix performance on instances list in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/15282)) | ||||
| - Fix server actor appearing in list of accounts in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/14567)) | ||||
| - Fix "bootstrap timeline accounts" toggle in site settings in admin UI ([ThibG](https://github.com/tootsuite/mastodon/pull/15325)) | ||||
| - Fix PostgreSQL secret name for cronjob in Helm chart ([metal3d](https://github.com/tootsuite/mastodon/pull/15072)) | ||||
| - Fix Procfile not being compatible with herokuish ([acuteaura](https://github.com/tootsuite/mastodon/pull/12685)) | ||||
| - Fix installation of tini being split into multiple steps in Dockerfile ([ryncsn](https://github.com/tootsuite/mastodon/pull/14686)) | ||||
|  | ||||
| ### Security | ||||
|  | ||||
| - Fix streaming API allowing connections to persist after access token invalidation ([Gargron](https://github.com/tootsuite/mastodon/pull/15111)) | ||||
| - Fix 2FA/sign-in token sessions being valid after password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14802)) | ||||
| - Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ThibG](https://github.com/tootsuite/mastodon/pull/15364)) | ||||
|  | ||||
| ## [3.2.2] - 2020-12-19 | ||||
| ### Added | ||||
|  | ||||
| - Add `tootctl maintenance fix-duplicates` ([ThibG](https://github.com/tootsuite/mastodon/pull/14860), [Gargron](https://github.com/tootsuite/mastodon/pull/15223)) | ||||
|   - Index corruption in the database? | ||||
|   - This command is for you | ||||
|  | ||||
| ### Removed | ||||
|  | ||||
| - Remove dependency on unused and unmaintained http_parser.rb gem ([ThibG](https://github.com/tootsuite/mastodon/pull/14574)) | ||||
|  | ||||
| ### Fixed | ||||
|  | ||||
| - Fix Move handler not being triggered when failing to fetch target account ([ThibG](https://github.com/tootsuite/mastodon/pull/15107)) | ||||
| - Fix downloading remote media files when server returns empty filename ([ThibG](https://github.com/tootsuite/mastodon/pull/14867)) | ||||
| - Fix possible casing inconsistencies in hashtag search ([ThibG](https://github.com/tootsuite/mastodon/pull/14906)) | ||||
| - Fix updating account counters when association is not yet created ([Gargron](https://github.com/tootsuite/mastodon/pull/15108)) | ||||
| - Fix account processing failing because of large collections ([ThibG](https://github.com/tootsuite/mastodon/pull/15027)) | ||||
| - Fix resolving an account through its non-canonical form (i.e. alternate domain) ([ThibG](https://github.com/tootsuite/mastodon/pull/15187)) | ||||
| - Fix slow distinct queries where grouped queries are faster ([Gargron](https://github.com/tootsuite/mastodon/pull/15287)) | ||||
|  | ||||
| ### Security | ||||
|  | ||||
| - Fix 2FA/sign-in token sessions being valid after password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14802)) | ||||
| - Fix resolving accounts sometimes creating duplicate records for a given ActivityPub identifier ([ThibG](https://github.com/tootsuite/mastodon/pull/15364)) | ||||
|  | ||||
| ## [3.2.1] - 2020-10-19 | ||||
| ### Added | ||||
|  | ||||
| @ -185,14 +387,14 @@ All notable changes to this project will be documented in this file. | ||||
|   - Only then proceed to start removing their data (slow) | ||||
|   - Clear out media attachments in a separate worker (slow) | ||||
|  | ||||
| ## [v3.1.5] - 2020-07-07 | ||||
| ## [3.1.5] - 2020-07-07 | ||||
| ### Security | ||||
|  | ||||
| - Fix media attachment enumeration ([ThibG](https://github.com/tootsuite/mastodon/pull/14254)) | ||||
| - Change rate limits for various paths ([Gargron](https://github.com/tootsuite/mastodon/pull/14253)) | ||||
| - Fix other sessions not being logged out on password change ([Gargron](https://github.com/tootsuite/mastodon/pull/14252)) | ||||
|  | ||||
| ## [v3.1.4] - 2020-05-14 | ||||
| ## [3.1.4] - 2020-05-14 | ||||
| ### Added | ||||
|  | ||||
| - Add `vi` to available locales ([taicv](https://github.com/tootsuite/mastodon/pull/13542)) | ||||
| @ -259,7 +461,7 @@ All notable changes to this project will be documented in this file. | ||||
|   - For apps that self-register on behalf of every individual user (such as most mobile apps), this is a non-issue | ||||
|   - The issue only affects developers of apps who are shared between multiple users, such as server-side apps like cross-posters | ||||
|  | ||||
| ## [v3.1.3] - 2020-04-05 | ||||
| ## [3.1.3] - 2020-04-05 | ||||
| ### Added | ||||
|  | ||||
| - Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381)) | ||||
|  | ||||
							
								
								
									
										23
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -4,7 +4,7 @@ FROM ubuntu:20.04 as build-dep | ||||
| SHELL ["bash", "-c"] | ||||
|  | ||||
| # Install Node v12 (LTS) | ||||
| ENV NODE_VER="12.16.3" | ||||
| ENV NODE_VER="12.20.0" | ||||
| RUN ARCH= && \ | ||||
|     dpkgArch="$(dpkg --print-architecture)" && \ | ||||
|   case "${dpkgArch##*-}" in \ | ||||
| @ -36,10 +36,11 @@ RUN apt update && \ | ||||
| 	./autogen.sh && \ | ||||
| 	./configure --prefix=/opt/jemalloc && \ | ||||
| 	make -j$(nproc) > /dev/null && \ | ||||
| 	make install_bin install_include install_lib | ||||
| 	make install_bin install_include install_lib && \ | ||||
| 	cd .. && rm -rf jemalloc-$JE_VER $JE_VER.tar.gz | ||||
|  | ||||
| # Install Ruby | ||||
| ENV RUBY_VER="2.6.6" | ||||
| ENV RUBY_VER="2.7.2" | ||||
| ENV CPPFLAGS="-I/opt/jemalloc/include" | ||||
| ENV LDFLAGS="-L/opt/jemalloc/lib/" | ||||
| RUN apt update && \ | ||||
| @ -56,7 +57,8 @@ RUN apt update && \ | ||||
| 	  --disable-install-doc && \ | ||||
| 	ln -s /opt/jemalloc/lib/* /usr/lib/ && \ | ||||
| 	make -j$(nproc) > /dev/null && \ | ||||
| 	make install | ||||
| 	make install && \ | ||||
| 	cd .. && rm -rf ruby-$RUBY_VER.tar.gz ruby-$RUBY_VER | ||||
|  | ||||
| ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin" | ||||
|  | ||||
| @ -107,11 +109,14 @@ RUN apt -y --no-install-recommends install \ | ||||
| 	rm -rf /var/lib/apt/lists/* | ||||
|  | ||||
| # Add tini | ||||
| ENV TINI_VERSION="0.18.0" | ||||
| ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855" | ||||
| ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini | ||||
| RUN echo "$TINI_SUM tini" | sha256sum -c - | ||||
| RUN chmod +x /tini | ||||
| ENV TINI_VERSION="0.19.0" | ||||
| RUN dpkgArch="$(dpkg --print-architecture)" && \ | ||||
| 	ARCH=$dpkgArch && \ | ||||
| 	wget https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH \ | ||||
| 	https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH.sha256sum && \ | ||||
| 	cat tini-$ARCH.sha256sum | sha256sum -c - && \ | ||||
| 	mv tini-$ARCH /tini && rm tini-$ARCH.sha256sum && \ | ||||
| 	chmod +x /tini | ||||
|  | ||||
| # Copy over mastodon source, and dependencies from building, and set permissions | ||||
| COPY --chown=mastodon:mastodon . /opt/mastodon | ||||
|  | ||||
							
								
								
									
										62
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								Gemfile
									
									
									
									
									
								
							| @ -5,22 +5,19 @@ ruby '>= 2.5.0', '< 3.0.0' | ||||
|  | ||||
| gem 'pkg-config', '~> 1.4' | ||||
|  | ||||
| gem 'puma', '~> 4.3' | ||||
| gem 'rails', '~> 5.2.4.3' | ||||
| gem 'puma', '~> 5.0' | ||||
| gem 'rails', '~> 5.2.4.4' | ||||
| gem 'sprockets', '~> 3.7.2' | ||||
| gem 'thor', '~> 0.20' | ||||
| gem 'thor', '~> 1.0' | ||||
| gem 'rack', '~> 2.2.3' | ||||
|  | ||||
| gem 'thwait', '~> 0.1.0' | ||||
| gem 'e2mmap', '~> 0.1.0' | ||||
|  | ||||
| gem 'hamlit-rails', '~> 0.2' | ||||
| gem 'pg', '~> 1.2' | ||||
| gem 'makara', '~> 0.4' | ||||
| gem 'pghero', '~> 2.5' | ||||
| gem 'pghero', '~> 2.7' | ||||
| gem 'dotenv-rails', '~> 2.7' | ||||
|  | ||||
| gem 'aws-sdk-s3', '~> 1.73', require: false | ||||
| gem 'aws-sdk-s3', '~> 1.85', require: false | ||||
| gem 'fog-core', '<= 2.1.0' | ||||
| gem 'fog-openstack', '~> 0.3', require: false | ||||
| gem 'paperclip', '~> 6.0' | ||||
| @ -30,7 +27,7 @@ gem 'blurhash', '~> 0.1' | ||||
|  | ||||
| gem 'active_model_serializers', '~> 0.10' | ||||
| gem 'addressable', '~> 2.7' | ||||
| gem 'bootsnap', '~> 1.4', require: false | ||||
| gem 'bootsnap', '~> 1.5', require: false | ||||
| gem 'browser' | ||||
| gem 'charlock_holmes', '~> 0.7.7' | ||||
| gem 'iso-639' | ||||
| @ -44,9 +41,10 @@ group :pam_authentication, optional: true do | ||||
| end | ||||
|  | ||||
| gem 'net-ldap', '~> 0.16' | ||||
| gem 'omniauth-cas', '~> 1.1' | ||||
| gem 'omniauth-cas', '~> 2.0' | ||||
| gem 'omniauth-saml', '~> 1.10' | ||||
| gem 'omniauth', '~> 1.9' | ||||
| gem 'omniauth-rails_csrf_protection', '~> 0.1' | ||||
|  | ||||
| gem 'color_diff', '~> 0.1' | ||||
| gem 'discard', '~> 1.2' | ||||
| @ -55,12 +53,11 @@ gem 'ed25519', '~> 1.2' | ||||
| gem 'fast_blank', '~> 1.0' | ||||
| gem 'fastimage' | ||||
| gem 'hiredis', '~> 0.6' | ||||
| gem 'redis-namespace', '~> 1.7' | ||||
| gem 'redis-namespace', '~> 1.8' | ||||
| gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b' | ||||
| gem 'htmlentities', '~> 4.3' | ||||
| gem 'http', '~> 4.4' | ||||
| gem 'http_accept_language', '~> 2.1' | ||||
| gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', ref: '54b17ba8c7d8d20a16dfc65d1775241833219cf2', submodules: true | ||||
| gem 'httplog', '~> 1.4.3' | ||||
| gem 'idn-ruby', require: 'idn' | ||||
| gem 'kaminari', '~> 1.2' | ||||
| @ -72,8 +69,8 @@ gem 'nsa', '~> 0.2' | ||||
| gem 'oj', '~> 3.10' | ||||
| gem 'ox', '~> 2.13' | ||||
| gem 'parslet' | ||||
| gem 'parallel', '~> 1.19' | ||||
| gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' | ||||
| gem 'parallel', '~> 1.20' | ||||
| gem 'posix-spawn' | ||||
| gem 'pundit', '~> 2.1' | ||||
| gem 'premailer-rails' | ||||
| gem 'rack-attack', '~> 6.3' | ||||
| @ -85,20 +82,22 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' | ||||
| gem 'rqrcode', '~> 1.1' | ||||
| gem 'ruby-progressbar', '~> 1.10' | ||||
| gem 'sanitize', '~> 5.2' | ||||
| gem 'sidekiq', '~> 6.0' | ||||
| gem 'scenic', '~> 1.5' | ||||
| gem 'sidekiq', '~> 6.1' | ||||
| gem 'sidekiq-scheduler', '~> 3.0' | ||||
| gem 'sidekiq-unique-jobs', '~> 6.0' | ||||
| gem 'sidekiq-bulk', '~>0.2.0' | ||||
| gem 'simple-navigation', '~> 4.1' | ||||
| gem 'simple_form', '~> 5.0' | ||||
| gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' | ||||
| gem 'stoplight', '~> 2.2.0' | ||||
| gem 'strong_migrations', '~> 0.6' | ||||
| gem 'tty-prompt', '~> 0.21', require: false | ||||
| gem 'stoplight', '~> 2.2.1' | ||||
| gem 'strong_migrations', '~> 0.7' | ||||
| gem 'tty-prompt', '~> 0.22', require: false | ||||
| gem 'twitter-text', '~> 1.14' | ||||
| gem 'tzinfo-data', '~> 1.2020' | ||||
| gem 'webpacker', '~> 5.1' | ||||
| gem 'webpacker', '~> 5.2' | ||||
| gem 'webpush' | ||||
| gem 'webauthn', '~> 3.0.0.alpha1' | ||||
|  | ||||
| gem 'json-ld' | ||||
| gem 'json-ld-preloaded', '~> 3.1' | ||||
| @ -120,33 +119,33 @@ end | ||||
| group :test do | ||||
|   gem 'capybara', '~> 3.33' | ||||
|   gem 'climate_control', '~> 0.2' | ||||
|   gem 'faker', '~> 2.13' | ||||
|   gem 'faker', '~> 2.14' | ||||
|   gem 'microformats', '~> 4.2' | ||||
|   gem 'rails-controller-testing', '~> 1.0' | ||||
|   gem 'rspec-sidekiq', '~> 3.1' | ||||
|   gem 'simplecov', '~> 0.18', require: false | ||||
|   gem 'webmock', '~> 3.8' | ||||
|   gem 'parallel_tests', '~> 3.0' | ||||
|   gem 'simplecov', '~> 0.19', require: false | ||||
|   gem 'webmock', '~> 3.10' | ||||
|   gem 'parallel_tests', '~> 3.4' | ||||
|   gem 'rspec_junit_formatter', '~> 0.4' | ||||
| end | ||||
|  | ||||
| group :development do | ||||
|   gem 'active_record_query_trace', '~> 1.7' | ||||
|   gem 'active_record_query_trace', '~> 1.8' | ||||
|   gem 'annotate', '~> 3.1' | ||||
|   gem 'better_errors', '~> 2.7' | ||||
|   gem 'better_errors', '~> 2.9' | ||||
|   gem 'binding_of_caller', '~> 0.7' | ||||
|   gem 'bullet', '~> 6.1' | ||||
|   gem 'letter_opener', '~> 1.7' | ||||
|   gem 'letter_opener_web', '~> 1.4' | ||||
|   gem 'memory_profiler' | ||||
|   gem 'rubocop', '~> 0.86', require: false | ||||
|   gem 'rubocop-rails', '~> 2.6', require: false | ||||
|   gem 'brakeman', '~> 4.8', require: false | ||||
|   gem 'rubocop', '~> 1.3', require: false | ||||
|   gem 'rubocop-rails', '~> 2.8', require: false | ||||
|   gem 'brakeman', '~> 4.10', require: false | ||||
|   gem 'bundler-audit', '~> 0.7', require: false | ||||
|  | ||||
|   gem 'capistrano', '~> 3.14' | ||||
|   gem 'capistrano-rails', '~> 1.5' | ||||
|   gem 'capistrano-rbenv', '~> 2.1' | ||||
|   gem 'capistrano-rails', '~> 1.6' | ||||
|   gem 'capistrano-rbenv', '~> 2.2' | ||||
|   gem 'capistrano-yarn', '~> 2.0' | ||||
|  | ||||
|   gem 'stackprof' | ||||
| @ -159,3 +158,6 @@ end | ||||
|  | ||||
| gem 'concurrent-ruby', require: false | ||||
| gem 'connection_pool', require: false | ||||
|  | ||||
| gem 'xorcist', '~> 1.1' | ||||
| gem 'pluck_each', '~> 0.1.3' | ||||
|  | ||||
							
								
								
									
										386
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										386
									
								
								Gemfile.lock
									
									
									
									
									
								
							| @ -6,21 +6,6 @@ GIT | ||||
|     health_check (4.0.0.pre) | ||||
|       rails (>= 4.0) | ||||
|  | ||||
| GIT | ||||
|   remote: https://github.com/rtomayko/posix-spawn | ||||
|   revision: 58465d2e213991f8afb13b984854a49fcdcc980c | ||||
|   ref: 58465d2e213991f8afb13b984854a49fcdcc980c | ||||
|   specs: | ||||
|     posix-spawn (0.3.13) | ||||
|  | ||||
| GIT | ||||
|   remote: https://github.com/tmm1/http_parser.rb | ||||
|   revision: 54b17ba8c7d8d20a16dfc65d1775241833219cf2 | ||||
|   ref: 54b17ba8c7d8d20a16dfc65d1775241833219cf2 | ||||
|   submodules: true | ||||
|   specs: | ||||
|     http_parser.rb (0.6.1) | ||||
|  | ||||
| GIT | ||||
|   remote: https://github.com/witgo/nilsimsa | ||||
|   revision: fd184883048b922b176939f851338d0a4971a532 | ||||
| @ -31,25 +16,25 @@ GIT | ||||
| GEM | ||||
|   remote: https://rubygems.org/ | ||||
|   specs: | ||||
|     actioncable (5.2.4.3) | ||||
|       actionpack (= 5.2.4.3) | ||||
|     actioncable (5.2.4.4) | ||||
|       actionpack (= 5.2.4.4) | ||||
|       nio4r (~> 2.0) | ||||
|       websocket-driver (>= 0.6.1) | ||||
|     actionmailer (5.2.4.3) | ||||
|       actionpack (= 5.2.4.3) | ||||
|       actionview (= 5.2.4.3) | ||||
|       activejob (= 5.2.4.3) | ||||
|     actionmailer (5.2.4.4) | ||||
|       actionpack (= 5.2.4.4) | ||||
|       actionview (= 5.2.4.4) | ||||
|       activejob (= 5.2.4.4) | ||||
|       mail (~> 2.5, >= 2.5.4) | ||||
|       rails-dom-testing (~> 2.0) | ||||
|     actionpack (5.2.4.3) | ||||
|       actionview (= 5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     actionpack (5.2.4.4) | ||||
|       actionview (= 5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       rack (~> 2.0, >= 2.0.8) | ||||
|       rack-test (>= 0.6.3) | ||||
|       rails-dom-testing (~> 2.0) | ||||
|       rails-html-sanitizer (~> 1.0, >= 1.0.2) | ||||
|     actionview (5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     actionview (5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       builder (~> 3.1) | ||||
|       erubi (~> 1.4) | ||||
|       rails-dom-testing (~> 2.0) | ||||
| @ -59,21 +44,21 @@ GEM | ||||
|       activemodel (>= 4.1, < 6.1) | ||||
|       case_transform (>= 0.2) | ||||
|       jsonapi-renderer (>= 0.1.1.beta1, < 0.3) | ||||
|     active_record_query_trace (1.7) | ||||
|     activejob (5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     active_record_query_trace (1.8) | ||||
|     activejob (5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       globalid (>= 0.3.6) | ||||
|     activemodel (5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     activerecord (5.2.4.3) | ||||
|       activemodel (= 5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     activemodel (5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|     activerecord (5.2.4.4) | ||||
|       activemodel (= 5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       arel (>= 9.0) | ||||
|     activestorage (5.2.4.3) | ||||
|       actionpack (= 5.2.4.3) | ||||
|       activerecord (= 5.2.4.3) | ||||
|     activestorage (5.2.4.4) | ||||
|       actionpack (= 5.2.4.4) | ||||
|       activerecord (= 5.2.4.4) | ||||
|       marcel (~> 0.3.1) | ||||
|     activesupport (5.2.4.3) | ||||
|     activesupport (5.2.4.4) | ||||
|       concurrent-ruby (~> 1.0, >= 1.0.2) | ||||
|       i18n (>= 0.7, < 2) | ||||
|       minitest (~> 5.1) | ||||
| @ -82,6 +67,7 @@ GEM | ||||
|       public_suffix (>= 2.0.2, < 5.0) | ||||
|     airbrussh (1.4.0) | ||||
|       sshkit (>= 1.6.1, != 1.7.0) | ||||
|     android_key_attestation (0.3.0) | ||||
|     annotate (3.1.1) | ||||
|       activerecord (>= 3.2, < 7.0) | ||||
|       rake (>= 10.4, < 14.0) | ||||
| @ -91,34 +77,36 @@ GEM | ||||
|       encryptor (~> 3.0.0) | ||||
|     av (0.9.0) | ||||
|       cocaine (~> 0.5.3) | ||||
|     awrence (1.1.1) | ||||
|     aws-eventstream (1.1.0) | ||||
|     aws-partitions (1.338.0) | ||||
|     aws-sdk-core (3.103.0) | ||||
|     aws-partitions (1.397.0) | ||||
|     aws-sdk-core (3.109.3) | ||||
|       aws-eventstream (~> 1, >= 1.0.2) | ||||
|       aws-partitions (~> 1, >= 1.239.0) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|       jmespath (~> 1.0) | ||||
|     aws-sdk-kms (1.36.0) | ||||
|       aws-sdk-core (~> 3, >= 3.99.0) | ||||
|     aws-sdk-kms (1.39.0) | ||||
|       aws-sdk-core (~> 3, >= 3.109.0) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|     aws-sdk-s3 (1.73.0) | ||||
|       aws-sdk-core (~> 3, >= 3.102.1) | ||||
|     aws-sdk-s3 (1.85.0) | ||||
|       aws-sdk-core (~> 3, >= 3.109.0) | ||||
|       aws-sdk-kms (~> 1) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|     aws-sigv4 (1.2.1) | ||||
|     aws-sigv4 (1.2.2) | ||||
|       aws-eventstream (~> 1, >= 1.0.2) | ||||
|     bcrypt (3.1.13) | ||||
|     better_errors (2.7.1) | ||||
|     bcrypt (3.1.16) | ||||
|     better_errors (2.9.1) | ||||
|       coderay (>= 1.0.0) | ||||
|       erubi (>= 1.0.0) | ||||
|       rack (>= 0.9.0) | ||||
|     bindata (2.4.8) | ||||
|     binding_of_caller (0.8.0) | ||||
|       debug_inspector (>= 0.0.1) | ||||
|     blurhash (0.1.4) | ||||
|       ffi (~> 1.10.0) | ||||
|     bootsnap (1.4.6) | ||||
|     bootsnap (1.5.1) | ||||
|       msgpack (~> 1.0) | ||||
|     brakeman (4.8.2) | ||||
|     brakeman (4.10.0) | ||||
|     browser (4.2.0) | ||||
|     builder (3.2.4) | ||||
|     bullet (6.1.0) | ||||
| @ -133,12 +121,12 @@ GEM | ||||
|       i18n | ||||
|       rake (>= 10.0.0) | ||||
|       sshkit (>= 1.9.0) | ||||
|     capistrano-bundler (1.6.0) | ||||
|     capistrano-bundler (2.0.1) | ||||
|       capistrano (~> 3.1) | ||||
|     capistrano-rails (1.5.0) | ||||
|     capistrano-rails (1.6.1) | ||||
|       capistrano (~> 3.1) | ||||
|       capistrano-bundler (~> 1.1) | ||||
|     capistrano-rbenv (2.1.6) | ||||
|       capistrano-bundler (>= 1.1, < 3) | ||||
|     capistrano-rbenv (2.2.0) | ||||
|       capistrano (~> 3.1) | ||||
|       sshkit (~> 1.3) | ||||
|     capistrano-yarn (2.0.2) | ||||
| @ -153,12 +141,13 @@ GEM | ||||
|       xpath (~> 3.2) | ||||
|     case_transform (0.2) | ||||
|       activesupport | ||||
|     cbor (0.5.9.6) | ||||
|     charlock_holmes (0.7.7) | ||||
|     chewy (5.1.0) | ||||
|       activesupport (>= 4.0) | ||||
|       elasticsearch (>= 2.0.0) | ||||
|       elasticsearch-dsl | ||||
|     chunky_png (1.3.11) | ||||
|     chunky_png (1.3.12) | ||||
|     cld3 (3.3.0) | ||||
|       ffi (>= 1.1.0, < 1.12.0) | ||||
|     climate_control (0.2.0) | ||||
| @ -166,15 +155,17 @@ GEM | ||||
|       climate_control (>= 0.0.3, < 1.0) | ||||
|     coderay (1.1.3) | ||||
|     color_diff (0.1) | ||||
|     concurrent-ruby (1.1.6) | ||||
|     concurrent-ruby (1.1.7) | ||||
|     connection_pool (2.2.3) | ||||
|     crack (0.4.3) | ||||
|       safe_yaml (~> 1.0.0) | ||||
|     cose (1.0.0) | ||||
|       cbor (~> 0.5.9) | ||||
|       openssl-signature_algorithm (~> 0.4.0) | ||||
|     crack (0.4.4) | ||||
|     crass (1.0.6) | ||||
|     css_parser (1.7.1) | ||||
|       addressable | ||||
|     debug_inspector (0.0.3) | ||||
|     devise (4.7.2) | ||||
|     devise (4.7.3) | ||||
|       bcrypt (~> 3.0) | ||||
|       orm_adapter (~> 0.1) | ||||
|       railties (>= 4.1.0) | ||||
| @ -197,34 +188,33 @@ GEM | ||||
|       unf (>= 0.0.5, < 1.0.0) | ||||
|     doorkeeper (5.4.0) | ||||
|       railties (>= 5) | ||||
|     dotenv (2.7.5) | ||||
|     dotenv-rails (2.7.5) | ||||
|       dotenv (= 2.7.5) | ||||
|       railties (>= 3.2, < 6.1) | ||||
|     dotenv (2.7.6) | ||||
|     dotenv-rails (2.7.6) | ||||
|       dotenv (= 2.7.6) | ||||
|       railties (>= 3.2) | ||||
|     e2mmap (0.1.0) | ||||
|     ed25519 (1.2.4) | ||||
|     elasticsearch (7.8.0) | ||||
|       elasticsearch-api (= 7.8.0) | ||||
|       elasticsearch-transport (= 7.8.0) | ||||
|     elasticsearch-api (7.8.0) | ||||
|     elasticsearch (7.9.0) | ||||
|       elasticsearch-api (= 7.9.0) | ||||
|       elasticsearch-transport (= 7.9.0) | ||||
|     elasticsearch-api (7.9.0) | ||||
|       multi_json | ||||
|     elasticsearch-dsl (0.1.9) | ||||
|     elasticsearch-transport (7.8.0) | ||||
|     elasticsearch-transport (7.9.0) | ||||
|       faraday (~> 1) | ||||
|       multi_json | ||||
|     encryptor (3.0.0) | ||||
|     equatable (0.6.1) | ||||
|     erubi (1.9.0) | ||||
|     et-orbi (1.2.4) | ||||
|       tzinfo | ||||
|     excon (0.75.0) | ||||
|     excon (0.76.0) | ||||
|     fabrication (2.21.1) | ||||
|     faker (2.13.0) | ||||
|     faker (2.14.0) | ||||
|       i18n (>= 1.6, < 2) | ||||
|     faraday (1.0.1) | ||||
|       multipart-post (>= 1.2, < 3) | ||||
|     fast_blank (1.0.0) | ||||
|     fastimage (2.1.7) | ||||
|     fastimage (2.2.0) | ||||
|     ffi (1.10.0) | ||||
|     ffi-compiler (1.0.1) | ||||
|       ffi (>= 1.0.0) | ||||
| @ -242,7 +232,7 @@ GEM | ||||
|       fog-json (>= 1.0) | ||||
|       ipaddress (>= 0.8) | ||||
|     formatador (0.2.5) | ||||
|     fugit (1.3.6) | ||||
|     fugit (1.3.9) | ||||
|       et-orbi (~> 1.1, >= 1.1.8) | ||||
|       raabro (~> 1.3) | ||||
|     fuubar (2.5.0) | ||||
| @ -250,7 +240,7 @@ GEM | ||||
|       ruby-progressbar (~> 1.4) | ||||
|     globalid (0.4.2) | ||||
|       activesupport (>= 4.2.0) | ||||
|     hamlit (2.11.0) | ||||
|     hamlit (2.13.0) | ||||
|       temple (>= 0.8.2) | ||||
|       thor | ||||
|       tilt | ||||
| @ -281,7 +271,7 @@ GEM | ||||
|     httplog (1.4.3) | ||||
|       rack (>= 1.0) | ||||
|       rainbow (>= 2.0.0) | ||||
|     i18n (1.8.3) | ||||
|     i18n (1.8.5) | ||||
|       concurrent-ruby (~> 1.0) | ||||
|     i18n-tasks (0.9.31) | ||||
|       activesupport (>= 4.0.2) | ||||
| @ -299,7 +289,7 @@ GEM | ||||
|     jmespath (1.4.0) | ||||
|     json (2.3.1) | ||||
|     json-canonicalization (0.2.0) | ||||
|     json-ld (3.1.4) | ||||
|     json-ld (3.1.5) | ||||
|       htmlentities (~> 4.3) | ||||
|       json-canonicalization (~> 0.2) | ||||
|       link_header (~> 0.0, >= 0.0.8) | ||||
| @ -310,7 +300,7 @@ GEM | ||||
|       json-ld (~> 3.1) | ||||
|       rdf (~> 3.1) | ||||
|     jsonapi-renderer (0.2.2) | ||||
|     jwt (2.2.1) | ||||
|     jwt (2.2.2) | ||||
|     kaminari (1.2.1) | ||||
|       activesupport (>= 4.1.0) | ||||
|       kaminari-actionview (= 1.2.1) | ||||
| @ -337,7 +327,7 @@ GEM | ||||
|       activesupport (>= 4) | ||||
|       railties (>= 4) | ||||
|       request_store (~> 1.0) | ||||
|     loofah (2.6.0) | ||||
|     loofah (2.7.0) | ||||
|       crass (~> 1.0.2) | ||||
|       nokogiri (>= 1.5.9) | ||||
|     mail (2.7.1) | ||||
| @ -350,7 +340,7 @@ GEM | ||||
|       redis (>= 3.0.5) | ||||
|     memory_profiler (0.9.14) | ||||
|     method_source (1.0.0) | ||||
|     microformats (4.2.0) | ||||
|     microformats (4.2.1) | ||||
|       json (~> 2.2) | ||||
|       nokogiri (~> 1.10) | ||||
|     mime-types (3.3.1) | ||||
| @ -359,17 +349,16 @@ GEM | ||||
|     mimemagic (0.3.5) | ||||
|     mini_mime (1.0.2) | ||||
|     mini_portile2 (2.4.0) | ||||
|     minitest (5.14.1) | ||||
|     minitest (5.14.2) | ||||
|     msgpack (1.3.3) | ||||
|     multi_json (1.14.1) | ||||
|     multi_json (1.15.0) | ||||
|     multipart-post (2.1.1) | ||||
|     necromancer (0.5.1) | ||||
|     net-ldap (0.16.2) | ||||
|     net-ldap (0.16.3) | ||||
|     net-scp (3.0.0) | ||||
|       net-ssh (>= 2.6.5, < 7.0.0) | ||||
|     net-ssh (6.1.0) | ||||
|     nio4r (2.5.2) | ||||
|     nokogiri (1.10.9) | ||||
|     nio4r (2.5.4) | ||||
|     nokogiri (1.10.10) | ||||
|       mini_portile2 (~> 2.4.0) | ||||
|     nokogumbo (2.0.2) | ||||
|       nokogiri (~> 1.8, >= 1.8.4) | ||||
| @ -378,19 +367,24 @@ GEM | ||||
|       concurrent-ruby (~> 1.0, >= 1.0.2) | ||||
|       sidekiq (>= 3.5) | ||||
|       statsd-ruby (~> 1.4, >= 1.4.0) | ||||
|     oj (3.10.6) | ||||
|     oj (3.10.16) | ||||
|     omniauth (1.9.1) | ||||
|       hashie (>= 3.4.6) | ||||
|       rack (>= 1.6.2, < 3) | ||||
|     omniauth-cas (1.1.1) | ||||
|     omniauth-cas (2.0.0) | ||||
|       addressable (~> 2.3) | ||||
|       nokogiri (~> 1.5) | ||||
|       omniauth (~> 1.2) | ||||
|     omniauth-saml (1.10.2) | ||||
|     omniauth-rails_csrf_protection (0.1.2) | ||||
|       actionpack (>= 4.2) | ||||
|       omniauth (>= 1.3.1) | ||||
|     omniauth-saml (1.10.3) | ||||
|       omniauth (~> 1.3, >= 1.3.2) | ||||
|       ruby-saml (~> 1.9) | ||||
|     openssl (2.2.0) | ||||
|     openssl-signature_algorithm (0.4.0) | ||||
|     orm_adapter (0.5.0) | ||||
|     ox (2.13.2) | ||||
|     ox (2.13.4) | ||||
|     paperclip (6.0.0) | ||||
|       activemodel (>= 4.2.0) | ||||
|       activesupport (>= 4.2.0) | ||||
| @ -400,20 +394,23 @@ GEM | ||||
|     paperclip-av-transcoder (0.6.4) | ||||
|       av (~> 0.9.0) | ||||
|       paperclip (>= 2.5.2) | ||||
|     parallel (1.19.2) | ||||
|     parallel_tests (3.0.0) | ||||
|     parallel (1.20.1) | ||||
|     parallel_tests (3.4.0) | ||||
|       parallel | ||||
|     parser (2.7.1.4) | ||||
|     parser (2.7.2.0) | ||||
|       ast (~> 2.4.1) | ||||
|     parslet (2.0.0) | ||||
|     pastel (0.7.4) | ||||
|       equatable (~> 0.6) | ||||
|     pastel (0.8.0) | ||||
|       tty-color (~> 0.5) | ||||
|     pg (1.2.3) | ||||
|     pghero (2.5.1) | ||||
|     pghero (2.7.2) | ||||
|       activerecord (>= 5) | ||||
|     pkg-config (1.4.1) | ||||
|     premailer (1.11.1) | ||||
|     pkg-config (1.4.4) | ||||
|     pluck_each (0.1.3) | ||||
|       activerecord (> 3.2.0) | ||||
|       activesupport (> 3.0.0) | ||||
|     posix-spawn (0.3.15) | ||||
|     premailer (1.14.2) | ||||
|       addressable | ||||
|       css_parser (>= 1.6.0) | ||||
|       htmlentities (>= 4.0.0) | ||||
| @ -429,12 +426,12 @@ GEM | ||||
|       pry (~> 0.13.0) | ||||
|     pry-rails (0.3.9) | ||||
|       pry (>= 0.10.4) | ||||
|     public_suffix (4.0.5) | ||||
|     puma (4.3.5) | ||||
|     public_suffix (4.0.6) | ||||
|     puma (5.0.4) | ||||
|       nio4r (~> 2.0) | ||||
|     pundit (2.1.0) | ||||
|       activesupport (>= 3.0.0) | ||||
|     raabro (1.3.1) | ||||
|     raabro (1.3.3) | ||||
|     rack (2.2.3) | ||||
|     rack-attack (6.3.1) | ||||
|       rack (>= 1.0, < 3) | ||||
| @ -444,18 +441,18 @@ GEM | ||||
|       rack | ||||
|     rack-test (1.1.0) | ||||
|       rack (>= 1.0, < 3) | ||||
|     rails (5.2.4.3) | ||||
|       actioncable (= 5.2.4.3) | ||||
|       actionmailer (= 5.2.4.3) | ||||
|       actionpack (= 5.2.4.3) | ||||
|       actionview (= 5.2.4.3) | ||||
|       activejob (= 5.2.4.3) | ||||
|       activemodel (= 5.2.4.3) | ||||
|       activerecord (= 5.2.4.3) | ||||
|       activestorage (= 5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     rails (5.2.4.4) | ||||
|       actioncable (= 5.2.4.4) | ||||
|       actionmailer (= 5.2.4.4) | ||||
|       actionpack (= 5.2.4.4) | ||||
|       actionview (= 5.2.4.4) | ||||
|       activejob (= 5.2.4.4) | ||||
|       activemodel (= 5.2.4.4) | ||||
|       activerecord (= 5.2.4.4) | ||||
|       activestorage (= 5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       bundler (>= 1.3.0) | ||||
|       railties (= 5.2.4.3) | ||||
|       railties (= 5.2.4.4) | ||||
|       sprockets-rails (>= 2.0.0) | ||||
|     rails-controller-testing (1.0.5) | ||||
|       actionpack (>= 5.0.1.rc1) | ||||
| @ -471,20 +468,20 @@ GEM | ||||
|       railties (>= 5.0, < 6) | ||||
|     rails-settings-cached (0.6.6) | ||||
|       rails (>= 4.2.0) | ||||
|     railties (5.2.4.3) | ||||
|       actionpack (= 5.2.4.3) | ||||
|       activesupport (= 5.2.4.3) | ||||
|     railties (5.2.4.4) | ||||
|       actionpack (= 5.2.4.4) | ||||
|       activesupport (= 5.2.4.4) | ||||
|       method_source | ||||
|       rake (>= 0.8.7) | ||||
|       thor (>= 0.19.0, < 2.0) | ||||
|     rainbow (3.0.0) | ||||
|     rake (13.0.1) | ||||
|     rdf (3.1.4) | ||||
|     rdf (3.1.7) | ||||
|       hamster (~> 3.0) | ||||
|       link_header (~> 0.0, >= 0.0.8) | ||||
|     rdf-normalize (0.4.0) | ||||
|       rdf (~> 3.1) | ||||
|     redis (4.2.1) | ||||
|     redis (4.2.5) | ||||
|     redis-actionpack (5.2.0) | ||||
|       actionpack (>= 5, < 7) | ||||
|       redis-rack (>= 2.1.0, < 3) | ||||
| @ -492,9 +489,9 @@ GEM | ||||
|     redis-activesupport (5.2.0) | ||||
|       activesupport (>= 3, < 7) | ||||
|       redis-store (>= 1.3, < 2) | ||||
|     redis-namespace (1.7.0) | ||||
|     redis-namespace (1.8.0) | ||||
|       redis (>= 3.0.4) | ||||
|     redis-rack (2.1.2) | ||||
|     redis-rack (2.1.3) | ||||
|       rack (>= 2.0.8, < 3) | ||||
|       redis-store (>= 1.2, < 2) | ||||
|     redis-rails (5.0.2) | ||||
| @ -503,7 +500,7 @@ GEM | ||||
|       redis-store (>= 1.2, < 2) | ||||
|     redis-store (1.9.0) | ||||
|       redis (>= 4, < 5) | ||||
|     regexp_parser (1.7.1) | ||||
|     regexp_parser (1.8.2) | ||||
|     request_store (1.5.0) | ||||
|       rack (>= 1.4) | ||||
|     responders (3.0.1) | ||||
| @ -516,7 +513,7 @@ GEM | ||||
|       chunky_png (~> 1.0) | ||||
|       rqrcode_core (~> 0.1) | ||||
|     rqrcode_core (0.1.2) | ||||
|     rspec-core (3.9.2) | ||||
|     rspec-core (3.9.3) | ||||
|       rspec-support (~> 3.9.3) | ||||
|     rspec-expectations (3.9.2) | ||||
|       diff-lcs (>= 1.2.0, < 2.0) | ||||
| @ -538,33 +535,38 @@ GEM | ||||
|     rspec-support (3.9.3) | ||||
|     rspec_junit_formatter (0.4.1) | ||||
|       rspec-core (>= 2, < 4, != 2.12.0) | ||||
|     rubocop (0.86.0) | ||||
|     rubocop (1.3.1) | ||||
|       parallel (~> 1.10) | ||||
|       parser (>= 2.7.0.1) | ||||
|       parser (>= 2.7.1.5) | ||||
|       rainbow (>= 2.2.2, < 4.0) | ||||
|       regexp_parser (>= 1.7) | ||||
|       regexp_parser (>= 1.8) | ||||
|       rexml | ||||
|       rubocop-ast (>= 0.0.3, < 1.0) | ||||
|       rubocop-ast (>= 1.1.1) | ||||
|       ruby-progressbar (~> 1.7) | ||||
|       unicode-display_width (>= 1.4.0, < 2.0) | ||||
|     rubocop-ast (0.1.0) | ||||
|       parser (>= 2.7.0.1) | ||||
|     rubocop-rails (2.6.0) | ||||
|     rubocop-ast (1.1.1) | ||||
|       parser (>= 2.7.1.5) | ||||
|     rubocop-rails (2.8.1) | ||||
|       activesupport (>= 4.2.0) | ||||
|       rack (>= 1.1) | ||||
|       rubocop (>= 0.82.0) | ||||
|       rubocop (>= 0.87.0) | ||||
|     ruby-progressbar (1.10.1) | ||||
|     ruby-saml (1.11.0) | ||||
|       nokogiri (>= 1.5.10) | ||||
|     rufus-scheduler (3.6.0) | ||||
|       fugit (~> 1.1, >= 1.1.6) | ||||
|     safe_yaml (1.0.5) | ||||
|     safety_net_attestation (0.4.0) | ||||
|       jwt (~> 2.0) | ||||
|     sanitize (5.2.1) | ||||
|       crass (~> 1.0.2) | ||||
|       nokogiri (>= 1.8.0) | ||||
|       nokogumbo (~> 2.0) | ||||
|     scenic (1.5.4) | ||||
|       activerecord (>= 4.0.0) | ||||
|       railties (>= 4.0.0) | ||||
|     securecompare (1.0.0) | ||||
|     semantic_range (2.3.0) | ||||
|     sidekiq (6.1.0) | ||||
|     sidekiq (6.1.2) | ||||
|       connection_pool (>= 2.2.2) | ||||
|       rack (~> 2.0) | ||||
|       redis (>= 4.2.0) | ||||
| @ -577,74 +579,87 @@ GEM | ||||
|       sidekiq (>= 3) | ||||
|       thwait | ||||
|       tilt (>= 1.4.0) | ||||
|     sidekiq-unique-jobs (6.0.22) | ||||
|     sidekiq-unique-jobs (6.0.25) | ||||
|       concurrent-ruby (~> 1.0, >= 1.0.5) | ||||
|       sidekiq (>= 4.0, < 7.0) | ||||
|       thor (~> 0) | ||||
|       thor (>= 0.20, < 2.0) | ||||
|     simple-navigation (4.1.0) | ||||
|       activesupport (>= 2.3.2) | ||||
|     simple_form (5.0.2) | ||||
|     simple_form (5.0.3) | ||||
|       actionpack (>= 5.0) | ||||
|       activemodel (>= 5.0) | ||||
|     simplecov (0.18.5) | ||||
|     simplecov (0.19.1) | ||||
|       docile (~> 1.1) | ||||
|       simplecov-html (~> 0.11) | ||||
|     simplecov-html (0.12.2) | ||||
|     simplecov-html (0.12.3) | ||||
|     sprockets (3.7.2) | ||||
|       concurrent-ruby (~> 1.0) | ||||
|       rack (> 1, < 3) | ||||
|     sprockets-rails (3.2.1) | ||||
|     sprockets-rails (3.2.2) | ||||
|       actionpack (>= 4.0) | ||||
|       activesupport (>= 4.0) | ||||
|       sprockets (>= 3.0.0) | ||||
|     sshkit (1.21.0) | ||||
|       net-scp (>= 1.1.2) | ||||
|       net-ssh (>= 2.8.0) | ||||
|     stackprof (0.2.15) | ||||
|     stackprof (0.2.16) | ||||
|     statsd-ruby (1.4.0) | ||||
|     stoplight (2.2.0) | ||||
|     stoplight (2.2.1) | ||||
|     streamio-ffmpeg (3.0.2) | ||||
|       multi_json (~> 1.8) | ||||
|     strong_migrations (0.6.8) | ||||
|     strong_migrations (0.7.2) | ||||
|       activerecord (>= 5) | ||||
|     temple (0.8.2) | ||||
|     terminal-table (1.8.0) | ||||
|       unicode-display_width (~> 1.1, >= 1.1.1) | ||||
|     terrapin (0.6.0) | ||||
|       climate_control (>= 0.0.3, < 1.0) | ||||
|     thor (0.20.3) | ||||
|     thor (1.0.1) | ||||
|     thread_safe (0.3.6) | ||||
|     thwait (0.1.0) | ||||
|     thwait (0.2.0) | ||||
|       e2mmap | ||||
|     tilt (2.0.10) | ||||
|     tty-color (0.5.1) | ||||
|     tpm-key_attestation (0.9.0) | ||||
|       bindata (~> 2.4) | ||||
|       openssl-signature_algorithm (~> 0.4.0) | ||||
|     tty-color (0.5.2) | ||||
|     tty-cursor (0.7.1) | ||||
|     tty-prompt (0.21.0) | ||||
|       necromancer (~> 0.5.0) | ||||
|       pastel (~> 0.7.0) | ||||
|       tty-reader (~> 0.7.0) | ||||
|     tty-reader (0.7.0) | ||||
|     tty-prompt (0.22.0) | ||||
|       pastel (~> 0.8) | ||||
|       tty-reader (~> 0.8) | ||||
|     tty-reader (0.8.0) | ||||
|       tty-cursor (~> 0.7) | ||||
|       tty-screen (~> 0.7) | ||||
|       wisper (~> 2.0.0) | ||||
|     tty-screen (0.8.0) | ||||
|       tty-screen (~> 0.8) | ||||
|       wisper (~> 2.0) | ||||
|     tty-screen (0.8.1) | ||||
|     twitter-text (1.14.7) | ||||
|       unf (~> 0.1.0) | ||||
|     tzinfo (1.2.7) | ||||
|       thread_safe (~> 0.1) | ||||
|     tzinfo-data (1.2020.1) | ||||
|     tzinfo-data (1.2020.4) | ||||
|       tzinfo (>= 1.0.0) | ||||
|     unf (0.1.4) | ||||
|       unf_ext | ||||
|     unf_ext (0.0.7.7) | ||||
|     unicode-display_width (1.7.0) | ||||
|     uniform_notifier (1.13.0) | ||||
|     warden (1.2.8) | ||||
|       rack (>= 2.0.6) | ||||
|     webmock (3.8.3) | ||||
|     warden (1.2.9) | ||||
|       rack (>= 2.0.9) | ||||
|     webauthn (3.0.0.alpha1) | ||||
|       android_key_attestation (~> 0.3.0) | ||||
|       awrence (~> 1.1) | ||||
|       bindata (~> 2.4) | ||||
|       cbor (~> 0.5.9) | ||||
|       cose (~> 1.0) | ||||
|       openssl (~> 2.0) | ||||
|       safety_net_attestation (~> 0.4.0) | ||||
|       securecompare (~> 1.0) | ||||
|       tpm-key_attestation (~> 0.9.0) | ||||
|     webmock (3.10.0) | ||||
|       addressable (>= 2.3.6) | ||||
|       crack (>= 0.3.2) | ||||
|       hashdiff (>= 0.4.0, < 2.0.0) | ||||
|     webpacker (5.1.1) | ||||
|     webpacker (5.2.1) | ||||
|       activesupport (>= 5.2) | ||||
|       rack-proxy (>= 0.6.1) | ||||
|       railties (>= 5.2) | ||||
| @ -652,10 +667,11 @@ GEM | ||||
|     webpush (0.3.8) | ||||
|       hkdf (~> 0.2) | ||||
|       jwt (~> 2.0) | ||||
|     websocket-driver (0.7.2) | ||||
|     websocket-driver (0.7.3) | ||||
|       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) | ||||
|  | ||||
| @ -664,21 +680,21 @@ PLATFORMS | ||||
|  | ||||
| DEPENDENCIES | ||||
|   active_model_serializers (~> 0.10) | ||||
|   active_record_query_trace (~> 1.7) | ||||
|   active_record_query_trace (~> 1.8) | ||||
|   addressable (~> 2.7) | ||||
|   annotate (~> 3.1) | ||||
|   aws-sdk-s3 (~> 1.73) | ||||
|   better_errors (~> 2.7) | ||||
|   aws-sdk-s3 (~> 1.85) | ||||
|   better_errors (~> 2.9) | ||||
|   binding_of_caller (~> 0.7) | ||||
|   blurhash (~> 0.1) | ||||
|   bootsnap (~> 1.4) | ||||
|   brakeman (~> 4.8) | ||||
|   bootsnap (~> 1.5) | ||||
|   brakeman (~> 4.10) | ||||
|   browser | ||||
|   bullet (~> 6.1) | ||||
|   bundler-audit (~> 0.7) | ||||
|   capistrano (~> 3.14) | ||||
|   capistrano-rails (~> 1.5) | ||||
|   capistrano-rbenv (~> 2.1) | ||||
|   capistrano-rails (~> 1.6) | ||||
|   capistrano-rbenv (~> 2.2) | ||||
|   capistrano-yarn (~> 2.0) | ||||
|   capybara (~> 3.33) | ||||
|   charlock_holmes (~> 0.7.7) | ||||
| @ -694,10 +710,9 @@ DEPENDENCIES | ||||
|   discard (~> 1.2) | ||||
|   doorkeeper (~> 5.4) | ||||
|   dotenv-rails (~> 2.7) | ||||
|   e2mmap (~> 0.1.0) | ||||
|   ed25519 (~> 1.2) | ||||
|   fabrication (~> 2.21) | ||||
|   faker (~> 2.13) | ||||
|   faker (~> 2.14) | ||||
|   fast_blank (~> 1.0) | ||||
|   fastimage | ||||
|   fog-core (<= 2.1.0) | ||||
| @ -709,7 +724,6 @@ DEPENDENCIES | ||||
|   htmlentities (~> 4.3) | ||||
|   http (~> 4.4) | ||||
|   http_accept_language (~> 2.1) | ||||
|   http_parser.rb (~> 0.6)! | ||||
|   httplog (~> 1.4.3) | ||||
|   i18n-tasks (~> 0.9) | ||||
|   idn-ruby | ||||
| @ -732,61 +746,65 @@ DEPENDENCIES | ||||
|   nsa (~> 0.2) | ||||
|   oj (~> 3.10) | ||||
|   omniauth (~> 1.9) | ||||
|   omniauth-cas (~> 1.1) | ||||
|   omniauth-cas (~> 2.0) | ||||
|   omniauth-rails_csrf_protection (~> 0.1) | ||||
|   omniauth-saml (~> 1.10) | ||||
|   ox (~> 2.13) | ||||
|   paperclip (~> 6.0) | ||||
|   paperclip-av-transcoder (~> 0.6) | ||||
|   parallel (~> 1.19) | ||||
|   parallel_tests (~> 3.0) | ||||
|   parallel (~> 1.20) | ||||
|   parallel_tests (~> 3.4) | ||||
|   parslet | ||||
|   pg (~> 1.2) | ||||
|   pghero (~> 2.5) | ||||
|   pghero (~> 2.7) | ||||
|   pkg-config (~> 1.4) | ||||
|   posix-spawn! | ||||
|   pluck_each (~> 0.1.3) | ||||
|   posix-spawn | ||||
|   premailer-rails | ||||
|   private_address_check (~> 0.5) | ||||
|   pry-byebug (~> 3.9) | ||||
|   pry-rails (~> 0.3) | ||||
|   puma (~> 4.3) | ||||
|   puma (~> 5.0) | ||||
|   pundit (~> 2.1) | ||||
|   rack (~> 2.2.3) | ||||
|   rack-attack (~> 6.3) | ||||
|   rack-cors (~> 1.1) | ||||
|   rails (~> 5.2.4.3) | ||||
|   rails (~> 5.2.4.4) | ||||
|   rails-controller-testing (~> 1.0) | ||||
|   rails-i18n (~> 5.1) | ||||
|   rails-settings-cached (~> 0.6) | ||||
|   rdf-normalize (~> 0.4) | ||||
|   redis (~> 4.2) | ||||
|   redis-namespace (~> 1.7) | ||||
|   redis-namespace (~> 1.8) | ||||
|   redis-rails (~> 5.0) | ||||
|   rqrcode (~> 1.1) | ||||
|   rspec-rails (~> 4.0) | ||||
|   rspec-sidekiq (~> 3.1) | ||||
|   rspec_junit_formatter (~> 0.4) | ||||
|   rubocop (~> 0.86) | ||||
|   rubocop-rails (~> 2.6) | ||||
|   rubocop (~> 1.3) | ||||
|   rubocop-rails (~> 2.8) | ||||
|   ruby-progressbar (~> 1.10) | ||||
|   sanitize (~> 5.2) | ||||
|   sidekiq (~> 6.0) | ||||
|   scenic (~> 1.5) | ||||
|   sidekiq (~> 6.1) | ||||
|   sidekiq-bulk (~> 0.2.0) | ||||
|   sidekiq-scheduler (~> 3.0) | ||||
|   sidekiq-unique-jobs (~> 6.0) | ||||
|   simple-navigation (~> 4.1) | ||||
|   simple_form (~> 5.0) | ||||
|   simplecov (~> 0.18) | ||||
|   simplecov (~> 0.19) | ||||
|   sprockets (~> 3.7.2) | ||||
|   sprockets-rails (~> 3.2) | ||||
|   stackprof | ||||
|   stoplight (~> 2.2.0) | ||||
|   stoplight (~> 2.2.1) | ||||
|   streamio-ffmpeg (~> 3.0) | ||||
|   strong_migrations (~> 0.6) | ||||
|   thor (~> 0.20) | ||||
|   thwait (~> 0.1.0) | ||||
|   tty-prompt (~> 0.21) | ||||
|   strong_migrations (~> 0.7) | ||||
|   thor (~> 1.0) | ||||
|   tty-prompt (~> 0.22) | ||||
|   twitter-text (~> 1.14) | ||||
|   tzinfo-data (~> 1.2020) | ||||
|   webmock (~> 3.8) | ||||
|   webpacker (~> 5.1) | ||||
|   webauthn (~> 3.0.0.alpha1) | ||||
|   webmock (~> 3.10) | ||||
|   webpacker (~> 5.2) | ||||
|   webpush | ||||
|   xorcist (~> 1.1) | ||||
|  | ||||
							
								
								
									
										2
									
								
								Procfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Procfile
									
									
									
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| web: if [ "$RUN_STREAMING" != "true" ]; then BIND=0.0.0.0 bundle exec puma -C config/puma.rb; else BIND=0.0.0.0 node ./streaming; fi | ||||
| web: bin/heroku-web | ||||
| worker: bundle exec sidekiq | ||||
|  | ||||
| # For the streaming API, you need a separate app that shares Postgres and Redis: | ||||
|  | ||||
| @ -1,12 +1,15 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class AboutController < ApplicationController | ||||
|   include RegistrationSpamConcern | ||||
|  | ||||
|   layout 'public' | ||||
|  | ||||
|   before_action :require_open_federation!, only: [:show, :more] | ||||
|   before_action :set_body_classes, only: :show | ||||
|   before_action :set_instance_presenter | ||||
|   before_action :set_expires_in, only: [:show, :more, :terms] | ||||
|   before_action :set_expires_in, only: [:more, :terms] | ||||
|   before_action :set_registration_form_time, only: :show | ||||
|  | ||||
|   skip_before_action :require_functional!, only: [:more, :terms] | ||||
|  | ||||
|  | ||||
| @ -29,8 +29,7 @@ class AccountsController < ApplicationController | ||||
|         end | ||||
|  | ||||
|         @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? | ||||
|         @statuses        = filtered_status_page | ||||
|         @statuses        = cache_collection(@statuses, Status) | ||||
|         @statuses        = cached_filtered_status_page | ||||
|         @rss_url         = rss_url | ||||
|  | ||||
|         unless @statuses.empty? | ||||
| @ -86,7 +85,7 @@ class AccountsController < ApplicationController | ||||
|   end | ||||
|  | ||||
|   def account_media_status_ids | ||||
|     @account.media_attachments.attached.reorder(nil).select(:status_id).distinct | ||||
|     @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id) | ||||
|   end | ||||
|  | ||||
|   def no_replies_scope | ||||
| @ -107,6 +106,10 @@ class AccountsController < ApplicationController | ||||
|     params[:username] | ||||
|   end | ||||
|  | ||||
|   def skip_temporary_suspension_response? | ||||
|     request.format == :json | ||||
|   end | ||||
|  | ||||
|   def rss_url | ||||
|     if tag_requested? | ||||
|       short_account_tag_url(@account, params[:tag], format: 'rss') | ||||
| @ -147,8 +150,13 @@ class AccountsController < ApplicationController | ||||
|     request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) | ||||
|   end | ||||
|  | ||||
|   def filtered_status_page | ||||
|     filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id)) | ||||
|   def cached_filtered_status_page | ||||
|     cache_collection_paginated_by_id( | ||||
|       filtered_statuses, | ||||
|       Status, | ||||
|       PAGE_SIZE, | ||||
|       params_slice(:max_id, :min_id, :since_id) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def params_slice(*keys) | ||||
|  | ||||
| @ -8,4 +8,8 @@ class ActivityPub::BaseController < Api::BaseController | ||||
|   def set_cache_headers | ||||
|     response.headers['Vary'] = 'Signature' if authorized_fetch_mode? | ||||
|   end | ||||
|  | ||||
|   def skip_temporary_suspension_response? | ||||
|     false | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -12,7 +12,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||
|  | ||||
|   def show | ||||
|     expires_in 3.minutes, public: public_fetch_mode? | ||||
|     render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true | ||||
|     render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter | ||||
|   end | ||||
|  | ||||
|   private | ||||
| @ -20,17 +20,9 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||
|   def set_items | ||||
|     case params[:id] | ||||
|     when 'featured' | ||||
|       @items = begin | ||||
|         # Because in public fetch mode we cache the response, there would be no | ||||
|         # benefit from performing the check below, since a blocked account or domain | ||||
|         # would likely be served the cache from the reverse proxy anyway | ||||
|  | ||||
|         if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) | ||||
|           [] | ||||
|         else | ||||
|           cache_collection(@account.pinned_statuses, Status) | ||||
|         end | ||||
|       end | ||||
|       @items = for_signed_account { cache_collection(@account.pinned_statuses, Status) } | ||||
|     when 'tags' | ||||
|       @items = for_signed_account { @account.featured_tags } | ||||
|     when 'devices' | ||||
|       @items = @account.devices | ||||
|     else | ||||
| @ -40,7 +32,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||
|  | ||||
|   def set_size | ||||
|     case params[:id] | ||||
|     when 'featured', 'devices' | ||||
|     when 'featured', 'devices', 'tags' | ||||
|       @size = @items.size | ||||
|     else | ||||
|       not_found | ||||
| @ -51,7 +43,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||
|     case params[:id] | ||||
|     when 'featured' | ||||
|       @type = :ordered | ||||
|     when 'devices' | ||||
|     when 'devices', 'tags' | ||||
|       @type = :unordered | ||||
|     else | ||||
|       not_found | ||||
| @ -66,4 +58,16 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController | ||||
|       items: @items | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def for_signed_account | ||||
|     # Because in public fetch mode we cache the response, there would be no | ||||
|     # benefit from performing the check below, since a blocked account or domain | ||||
|     # would likely be served the cache from the reverse proxy anyway | ||||
|  | ||||
|     if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) | ||||
|       [] | ||||
|     else | ||||
|       yield | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -0,0 +1,36 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseController | ||||
|   include SignatureVerification | ||||
|   include AccountOwnedConcern | ||||
|  | ||||
|   before_action :require_signature! | ||||
|   before_action :set_items | ||||
|   before_action :set_cache_headers | ||||
|  | ||||
|   def show | ||||
|     expires_in 0, public: false | ||||
|     render json: collection_presenter, | ||||
|            serializer: ActivityPub::CollectionSerializer, | ||||
|            adapter: ActivityPub::Adapter, | ||||
|            content_type: 'application/activity+json' | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def uri_prefix | ||||
|     signed_request_account.uri[/http(s?):\/\/[^\/]+\//] | ||||
|   end | ||||
|  | ||||
|   def set_items | ||||
|     @items = @account.followers.where(Account.arel_table[:uri].matches(uri_prefix + '%', false, true)).pluck(:uri) | ||||
|   end | ||||
|  | ||||
|   def collection_presenter | ||||
|     ActivityPub::CollectionPresenter.new( | ||||
|       id: account_followers_synchronization_url(@account), | ||||
|       type: :ordered, | ||||
|       items: @items | ||||
|     ) | ||||
|   end | ||||
| end | ||||
| @ -11,6 +11,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController | ||||
|  | ||||
|   def create | ||||
|     upgrade_account | ||||
|     process_collection_synchronization | ||||
|     process_payload | ||||
|     head 202 | ||||
|   end | ||||
| @ -32,6 +33,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController | ||||
|     params[:account_username].present? | ||||
|   end | ||||
|  | ||||
|   def skip_temporary_suspension_response? | ||||
|     true | ||||
|   end | ||||
|  | ||||
|   def body | ||||
|     return @body if defined?(@body) | ||||
|  | ||||
| @ -52,6 +57,19 @@ class ActivityPub::InboxesController < ActivityPub::BaseController | ||||
|     DeliveryFailureTracker.reset!(signed_request_account.inbox_url) | ||||
|   end | ||||
|  | ||||
|   def process_collection_synchronization | ||||
|     raw_params = request.headers['Collection-Synchronization'] | ||||
|     return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' | ||||
|  | ||||
|     # Re-using the syntax for signature parameters | ||||
|     tree   = SignatureParamsParser.new.parse(raw_params) | ||||
|     params = SignatureParamsTransformer.new.apply(tree) | ||||
|  | ||||
|     ActivityPub::PrepareFollowersSynchronizationService.new.call(signed_request_account, params) | ||||
|   rescue Parslet::ParseFailed | ||||
|     Rails.logger.warn 'Error parsing Collection-Synchronization header' | ||||
|   end | ||||
|  | ||||
|   def process_payload | ||||
|     ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id) | ||||
|   end | ||||
|  | ||||
| @ -20,9 +20,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||
|   def outbox_presenter | ||||
|     if page_requested? | ||||
|       ActivityPub::CollectionPresenter.new( | ||||
|         id: account_outbox_url(@account, page_params), | ||||
|         id: outbox_url(page_params), | ||||
|         type: :ordered, | ||||
|         part_of: account_outbox_url(@account), | ||||
|         part_of: outbox_url, | ||||
|         prev: prev_page, | ||||
|         next: next_page, | ||||
|         items: @statuses | ||||
| @ -32,12 +32,20 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||
|         id: account_outbox_url(@account), | ||||
|         type: :ordered, | ||||
|         size: @account.statuses_count, | ||||
|         first: account_outbox_url(@account, page: true), | ||||
|         last: account_outbox_url(@account, page: true, min_id: 0) | ||||
|         first: outbox_url(page: true), | ||||
|         last: outbox_url(page: true, min_id: 0) | ||||
|       ) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def outbox_url(**kwargs) | ||||
|     if params[:account_username].present? | ||||
|       account_outbox_url(@account, **kwargs) | ||||
|     else | ||||
|       instance_actor_outbox_url(**kwargs) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def next_page | ||||
|     account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT | ||||
|   end | ||||
| @ -49,9 +57,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||
|   def set_statuses | ||||
|     return unless page_requested? | ||||
|  | ||||
|     @statuses = @account.statuses.permitted_for(@account, signed_request_account) | ||||
|     @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id)) | ||||
|     @statuses = cache_collection(@statuses, Status) | ||||
|     @statuses = cache_collection_paginated_by_id( | ||||
|       @account.statuses.permitted_for(@account, signed_request_account), | ||||
|       Status, | ||||
|       LIMIT, | ||||
|       params_slice(:max_id, :min_id, :since_id) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def page_requested? | ||||
| @ -61,4 +72,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController | ||||
|   def page_params | ||||
|     { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact | ||||
|   end | ||||
|  | ||||
|   def set_account | ||||
|     @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -31,7 +31,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController | ||||
|   end | ||||
|  | ||||
|   def set_replies | ||||
|     @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses | ||||
|     @replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses | ||||
|     @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) | ||||
|     @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) | ||||
|   end | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Admin | ||||
|   class AccountsController < BaseController | ||||
|     before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject] | ||||
|     before_action :set_account, except: [:index] | ||||
|     before_action :require_remote_account!, only: [:redownload] | ||||
|     before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject] | ||||
|  | ||||
| @ -14,49 +14,65 @@ module Admin | ||||
|     def show | ||||
|       authorize @account, :show? | ||||
|  | ||||
|       @deletion_request        = @account.deletion_request | ||||
|       @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) | ||||
|       @moderation_notes        = @account.targeted_moderation_notes.latest | ||||
|       @warnings                = @account.targeted_account_warnings.latest.custom | ||||
|       @domain_block            = DomainBlock.rule_for(@account.domain) | ||||
|     end | ||||
|  | ||||
|     def memorialize | ||||
|       authorize @account, :memorialize? | ||||
|       @account.memorialize! | ||||
|       log_action :memorialize, @account | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def enable | ||||
|       authorize @account.user, :enable? | ||||
|       @account.user.enable! | ||||
|       log_action :enable, @account.user | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def approve | ||||
|       authorize @account.user, :approve? | ||||
|       @account.user.approve! | ||||
|       redirect_to admin_pending_accounts_path | ||||
|       redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def reject | ||||
|       authorize @account.user, :reject? | ||||
|       SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false) | ||||
|       redirect_to admin_pending_accounts_path | ||||
|       DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) | ||||
|       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) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def unsensitive | ||||
|       authorize @account, :unsensitive? | ||||
|       @account.unsensitize! | ||||
|       log_action :unsensitive, @account | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|     end | ||||
|  | ||||
|     def unsilence | ||||
|       authorize @account, :unsilence? | ||||
|       @account.unsilence! | ||||
|       log_action :unsilence, @account | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def unsuspend | ||||
|       authorize @account, :unsuspend? | ||||
|       @account.unsuspend! | ||||
|       Admin::UnsuspensionWorker.perform_async(@account.id) | ||||
|       log_action :unsuspend, @account | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def redownload | ||||
| @ -65,7 +81,7 @@ module Admin | ||||
|       @account.update!(last_webfingered_at: nil) | ||||
|       ResolveAccountService.new.call(@account) | ||||
|  | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def remove_avatar | ||||
| @ -76,7 +92,7 @@ module Admin | ||||
|  | ||||
|       log_action :remove_avatar, @account.user | ||||
|  | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     def remove_header | ||||
| @ -87,7 +103,7 @@ module Admin | ||||
|  | ||||
|       log_action :remove_header, @account.user | ||||
|  | ||||
|       redirect_to admin_account_path(@account.id) | ||||
|       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct) | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
| @ -71,7 +71,7 @@ class Admin::AnnouncementsController < Admin::BaseController | ||||
|   private | ||||
|  | ||||
|   def set_announcements | ||||
|     @announcements = AnnouncementFilter.new(filter_params).results.page(params[:page]) | ||||
|     @announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page]) | ||||
|   end | ||||
|  | ||||
|   def set_announcement | ||||
|  | ||||
| @ -29,6 +29,7 @@ module Admin | ||||
|           @domain_block = existing_domain_block | ||||
|           @domain_block.update(resource_params) | ||||
|         end | ||||
|  | ||||
|         if @domain_block.save | ||||
|           DomainBlockWorker.perform_async(@domain_block.id) | ||||
|           log_action :create, @domain_block | ||||
| @ -40,7 +41,7 @@ module Admin | ||||
|     end | ||||
|  | ||||
|     def update | ||||
|       authorize :domain_block, :create? | ||||
|       authorize :domain_block, :update? | ||||
|  | ||||
|       @domain_block.update(update_params) | ||||
|  | ||||
| @ -48,7 +49,7 @@ module Admin | ||||
|  | ||||
|       if @domain_block.save | ||||
|         DomainBlockWorker.perform_async(@domain_block.id, severity_changed) | ||||
|         log_action :create, @domain_block | ||||
|         log_action :update, @domain_block | ||||
|         redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg') | ||||
|       else | ||||
|         render :edit | ||||
| @ -73,11 +74,11 @@ module Admin | ||||
|     end | ||||
|  | ||||
|     def update_params | ||||
|       params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment) | ||||
|       params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) | ||||
|     end | ||||
|  | ||||
|     def resource_params | ||||
|       params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment) | ||||
|       params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -27,7 +27,7 @@ module Admin | ||||
|           ips       = [] | ||||
|  | ||||
|           Resolv::DNS.open do |dns| | ||||
|             dns.timeouts = 1 | ||||
|             dns.timeouts = 5 | ||||
|  | ||||
|             hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } | ||||
|  | ||||
|  | ||||
| @ -2,65 +2,31 @@ | ||||
|  | ||||
| module Admin | ||||
|   class InstancesController < BaseController | ||||
|     before_action :set_domain_block, only: :show | ||||
|     before_action :set_domain_allow, only: :show | ||||
|     before_action :set_instances, only: :index | ||||
|     before_action :set_instance, only: :show | ||||
|  | ||||
|     def index | ||||
|       authorize :instance, :index? | ||||
|  | ||||
|       @instances = ordered_instances | ||||
|     end | ||||
|  | ||||
|     def show | ||||
|       authorize :instance, :show? | ||||
|  | ||||
|       @following_count = Follow.where(account: Account.where(domain: params[:id])).count | ||||
|       @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count | ||||
|       @reports_count   = Report.where(target_account: Account.where(domain: params[:id])).count | ||||
|       @blocks_count    = Block.where(target_account: Account.where(domain: params[:id])).count | ||||
|       @available       = DeliveryFailureTracker.available?(params[:id]) | ||||
|       @media_storage   = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) | ||||
|       @private_comment = @domain_block&.private_comment | ||||
|       @public_comment  = @domain_block&.public_comment | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
|     def set_domain_block | ||||
|       @domain_block = DomainBlock.rule_for(params[:id]) | ||||
|     end | ||||
|  | ||||
|     def set_domain_allow | ||||
|       @domain_allow = DomainAllow.rule_for(params[:id]) | ||||
|     end | ||||
|  | ||||
|     def set_instance | ||||
|       resource   = Account.by_domain_accounts.find_by(domain: params[:id]) | ||||
|       resource ||= @domain_block | ||||
|       resource ||= @domain_allow | ||||
|       @instance = Instance.find(params[:id]) | ||||
|     end | ||||
|  | ||||
|       if resource | ||||
|         @instance = Instance.new(resource) | ||||
|       else | ||||
|         not_found | ||||
|       end | ||||
|     def set_instances | ||||
|       @instances = filtered_instances.page(params[:page]) | ||||
|     end | ||||
|  | ||||
|     def filtered_instances | ||||
|       InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results | ||||
|     end | ||||
|  | ||||
|     def paginated_instances | ||||
|       filtered_instances.page(params[:page]) | ||||
|     end | ||||
|  | ||||
|     helper_method :paginated_instances | ||||
|  | ||||
|     def ordered_instances | ||||
|       paginated_instances.map { |resource| Instance.new(resource) } | ||||
|     end | ||||
|  | ||||
|     def filter_params | ||||
|       params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS) | ||||
|     end | ||||
|  | ||||
							
								
								
									
										56
									
								
								app/controllers/admin/ip_blocks_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								app/controllers/admin/ip_blocks_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| module Admin | ||||
|   class IpBlocksController < BaseController | ||||
|     def index | ||||
|       authorize :ip_block, :index? | ||||
|  | ||||
|       @ip_blocks = IpBlock.page(params[:page]) | ||||
|       @form      = Form::IpBlockBatch.new | ||||
|     end | ||||
|  | ||||
|     def new | ||||
|       authorize :ip_block, :create? | ||||
|  | ||||
|       @ip_block = IpBlock.new(ip: '', severity: :no_access, expires_in: 1.year) | ||||
|     end | ||||
|  | ||||
|     def create | ||||
|       authorize :ip_block, :create? | ||||
|  | ||||
|       @ip_block = IpBlock.new(resource_params) | ||||
|  | ||||
|       if @ip_block.save | ||||
|         log_action :create, @ip_block | ||||
|         redirect_to admin_ip_blocks_path, notice: I18n.t('admin.ip_blocks.created_msg') | ||||
|       else | ||||
|         render :new | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     def batch | ||||
|       @form = Form::IpBlockBatch.new(form_ip_block_batch_params.merge(current_account: current_account, action: action_from_button)) | ||||
|       @form.save | ||||
|     rescue ActionController::ParameterMissing | ||||
|       flash[:alert] = I18n.t('admin.ip_blocks.no_ip_block_selected') | ||||
|     rescue Mastodon::NotPermittedError | ||||
|       flash[:alert] = I18n.t('admin.custom_emojis.not_permitted') | ||||
|     ensure | ||||
|       redirect_to admin_ip_blocks_path | ||||
|     end | ||||
|  | ||||
|     private | ||||
|  | ||||
|     def resource_params | ||||
|       params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in) | ||||
|     end | ||||
|  | ||||
|     def action_from_button | ||||
|       'delete' if params[:delete] | ||||
|     end | ||||
|  | ||||
|     def form_ip_block_batch_params | ||||
|       params.require(:form_ip_block_batch).permit(ip_block_ids: []) | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -14,7 +14,7 @@ module Admin | ||||
|       @statuses = @account.statuses.where(visibility: [:public, :unlisted]) | ||||
|  | ||||
|       if params[:media] | ||||
|         account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct | ||||
|         account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).group(:status_id) | ||||
|         @statuses.merge!(Status.where(id: account_media_status_ids)) | ||||
|       end | ||||
|  | ||||
|  | ||||
| @ -40,7 +40,7 @@ class Api::BaseController < ApplicationController | ||||
|     render json: { error: 'This action is not allowed' }, status: 403 | ||||
|   end | ||||
|  | ||||
|   rescue_from Mastodon::RaceConditionError do | ||||
|   rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do | ||||
|     render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503 | ||||
|   end | ||||
|  | ||||
| @ -71,6 +71,7 @@ class Api::BaseController < ApplicationController | ||||
|  | ||||
|   def limit_param(default_limit) | ||||
|     return default_limit unless params[:limit] | ||||
|  | ||||
|     [params[:limit].to_i.abs, default_limit * 2].min | ||||
|   end | ||||
|  | ||||
| @ -95,14 +96,14 @@ class Api::BaseController < ApplicationController | ||||
|   def require_user! | ||||
|     if !current_user | ||||
|       render json: { error: 'This method requires an authenticated user' }, status: 422 | ||||
|     elsif current_user.disabled? | ||||
|       render json: { error: 'Your login is currently disabled' }, status: 403 | ||||
|     elsif !current_user.confirmed? | ||||
|       render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403 | ||||
|     elsif !current_user.approved? | ||||
|       render json: { error: 'Your login is currently pending approval' }, status: 403 | ||||
|     elsif !current_user.functional? | ||||
|       render json: { error: 'Your login is currently disabled' }, status: 403 | ||||
|     else | ||||
|       set_user_activity | ||||
|       update_user_sign_in | ||||
|     end | ||||
|   end | ||||
|  | ||||
|  | ||||
							
								
								
									
										22
									
								
								app/controllers/api/v1/accounts/featured_tags_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/controllers/api/v1/accounts/featured_tags_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Api::V1::Accounts::FeaturedTagsController < Api::BaseController | ||||
|   before_action :set_account | ||||
|   before_action :set_featured_tags | ||||
|  | ||||
|   respond_to :json | ||||
|  | ||||
|   def index | ||||
|     render json: @featured_tags, each_serializer: REST::FeaturedTagSerializer | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_account | ||||
|     @account = Account.find(params[:account_id]) | ||||
|   end | ||||
|  | ||||
|   def set_featured_tags | ||||
|     @featured_tags = @account.suspended? ? [] : @account.featured_tags | ||||
|   end | ||||
| end | ||||
| @ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def hide_results? | ||||
|     (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) | ||||
|     @account.suspended? || (@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) | ||||
|   end | ||||
|  | ||||
|   def default_accounts | ||||
|  | ||||
| @ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def hide_results? | ||||
|     (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) | ||||
|     @account.suspended? || (@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account)) | ||||
|   end | ||||
|  | ||||
|   def default_accounts | ||||
|  | ||||
| @ -5,7 +5,7 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController | ||||
|   before_action :set_account | ||||
|  | ||||
|   def index | ||||
|     @proofs = @account.identity_proofs.active | ||||
|     @proofs = @account.suspended? ? [] : @account.identity_proofs.active | ||||
|     render json: @proofs, each_serializer: REST::IdentityProofSerializer | ||||
|   end | ||||
|  | ||||
|  | ||||
| @ -6,7 +6,7 @@ class Api::V1::Accounts::ListsController < Api::BaseController | ||||
|   before_action :set_account | ||||
|  | ||||
|   def index | ||||
|     @lists = @account.lists.where(account: current_account) | ||||
|     @lists = @account.suspended? ? [] : @account.lists.where(account: current_account) | ||||
|     render json: @lists, each_serializer: REST::ListSerializer | ||||
|   end | ||||
|  | ||||
|  | ||||
| @ -5,7 +5,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController | ||||
|   before_action :require_user! | ||||
|  | ||||
|   def index | ||||
|     accounts = Account.where(id: account_ids).select('id') | ||||
|     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 | ||||
|  | ||||
| @ -18,14 +18,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def load_statuses | ||||
|     cached_account_statuses | ||||
|     @account.suspended? ? [] : cached_account_statuses | ||||
|   end | ||||
|  | ||||
|   def cached_account_statuses | ||||
|     cache_collection account_statuses, Status | ||||
|   end | ||||
|  | ||||
|   def account_statuses | ||||
|     statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses | ||||
|  | ||||
|     statuses.merge!(only_media_scope) if truthy_param?(:only_media) | ||||
| @ -33,7 +29,12 @@ class Api::V1::Accounts::StatusesController < Api::BaseController | ||||
|     statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) | ||||
|     statuses.merge!(hashtag_scope)    if params[:tagged].present? | ||||
|  | ||||
|     statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|     cache_collection_paginated_by_id( | ||||
|       statuses, | ||||
|       Status, | ||||
|       limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|       params_slice(:max_id, :since_id, :min_id) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def permitted_account_statuses | ||||
| @ -41,17 +42,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def only_media_scope | ||||
|     Status.where(id: account_media_status_ids) | ||||
|   end | ||||
|  | ||||
|   def account_media_status_ids | ||||
|     # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids. | ||||
|     # Also, Avoid getting slow by not narrowing down by `statuses.account_id`. | ||||
|     # When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used | ||||
|     # and the table will be joined by `Merge Semi Join`, so the query will be slow. | ||||
|     @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account) | ||||
|             .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id]) | ||||
|             .reorder(id: :desc).distinct(:id).pluck(:id) | ||||
|     Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) | ||||
|   end | ||||
|  | ||||
|   def pinned_scope | ||||
|  | ||||
| @ -9,7 +9,6 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|  | ||||
|   before_action :require_user!, except: [:show, :create] | ||||
|   before_action :set_account, except: [:create] | ||||
|   before_action :check_account_suspension, only: [:show] | ||||
|   before_action :check_enabled_registrations, only: [:create] | ||||
|  | ||||
|   skip_before_action :require_authenticated_user!, only: :create | ||||
| @ -21,7 +20,7 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def create | ||||
|     token    = AppSignUpService.new.call(doorkeeper_token.application, account_params) | ||||
|     token    = AppSignUpService.new.call(doorkeeper_token.application, request.remote_ip, account_params) | ||||
|     response = Doorkeeper::OAuth::TokenResponse.new(token) | ||||
|  | ||||
|     headers.merge!(response.headers) | ||||
| @ -31,9 +30,8 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def follow | ||||
|     FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true) | ||||
|  | ||||
|     options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } } | ||||
|     follow  = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, 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 } } | ||||
|  | ||||
|     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) | ||||
|   end | ||||
| @ -44,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def mute | ||||
|     MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications)) | ||||
|     MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), duration: (params[:duration] || 0)) | ||||
|     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships | ||||
|   end | ||||
|  | ||||
| @ -73,10 +71,6 @@ class Api::V1::AccountsController < Api::BaseController | ||||
|     AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) | ||||
|   end | ||||
|  | ||||
|   def check_account_suspension | ||||
|     gone if @account.suspended? | ||||
|   end | ||||
|  | ||||
|   def account_params | ||||
|     params.permit(:username, :email, :password, :agreement, :locale, :reason) | ||||
|   end | ||||
|  | ||||
| @ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController | ||||
|     active | ||||
|     pending | ||||
|     disabled | ||||
|     sensitized | ||||
|     silenced | ||||
|     suspended | ||||
|     username | ||||
| @ -58,7 +59,20 @@ class Api::V1::Admin::AccountsController < Api::BaseController | ||||
|  | ||||
|   def reject | ||||
|     authorize @account.user, :reject? | ||||
|     SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false) | ||||
|     DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) | ||||
|     render json: @account, serializer: REST::Admin::AccountSerializer | ||||
|   end | ||||
|  | ||||
|   def destroy | ||||
|     authorize @account, :destroy? | ||||
|     Admin::AccountDeletionWorker.perform_async(@account.id) | ||||
|     render json: @account, serializer: REST::Admin::AccountSerializer | ||||
|   end | ||||
|  | ||||
|   def unsensitive | ||||
|     authorize @account, :unsensitive? | ||||
|     @account.unsensitize! | ||||
|     log_action :unsensitive, @account | ||||
|     render json: @account, serializer: REST::Admin::AccountSerializer | ||||
|   end | ||||
|  | ||||
| @ -72,6 +86,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController | ||||
|   def unsuspend | ||||
|     authorize @account, :unsuspend? | ||||
|     @account.unsuspend! | ||||
|     Admin::UnsuspensionWorker.perform_async(@account.id) | ||||
|     log_action :unsuspend, @account | ||||
|     render json: @account, serializer: REST::Admin::AccountSerializer | ||||
|   end | ||||
| @ -79,7 +94,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController | ||||
|   private | ||||
|  | ||||
|   def set_accounts | ||||
|     @accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|     @accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|   end | ||||
|  | ||||
|   def set_account | ||||
|  | ||||
| @ -63,7 +63,7 @@ class Api::V1::Admin::ReportsController < Api::BaseController | ||||
|   private | ||||
|  | ||||
|   def set_reports | ||||
|     @reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|     @reports = filtered_reports.order(id: :desc).with_accounts.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|   end | ||||
|  | ||||
|   def set_report | ||||
|  | ||||
| @ -18,6 +18,8 @@ class Api::V1::BlocksController < Api::BaseController | ||||
|  | ||||
|   def paginated_blocks | ||||
|     @paginated_blocks ||= Block.eager_load(target_account: :account_stat) | ||||
|                                .joins(:target_account) | ||||
|                                .merge(Account.without_suspended) | ||||
|                                .where(account: current_account) | ||||
|                                .paginate_by_max_id( | ||||
|                                  limit_param(DEFAULT_ACCOUNTS_LIMIT), | ||||
|  | ||||
| @ -17,14 +17,11 @@ class Api::V1::BookmarksController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def cached_bookmarks | ||||
|     cache_collection( | ||||
|       Status.reorder(nil).joins(:bookmarks).merge(results), | ||||
|       Status | ||||
|     ) | ||||
|     cache_collection(results.map(&:status), Status) | ||||
|   end | ||||
|  | ||||
|   def results | ||||
|     @_results ||= account_bookmarks.paginate_by_id( | ||||
|     @_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id( | ||||
|       limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|       params_slice(:max_id, :since_id, :min_id) | ||||
|     ) | ||||
|  | ||||
| @ -32,7 +32,7 @@ class Api::V1::ConversationsController < Api::BaseController | ||||
|  | ||||
|   def paginated_conversations | ||||
|     AccountConversation.where(account: current_account) | ||||
|                        .paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|                        .to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|   end | ||||
|  | ||||
|   def insert_pagination_headers | ||||
|  | ||||
| @ -26,7 +26,7 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def set_encrypted_messages | ||||
|     @encrypted_messages = @current_device.encrypted_messages.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|     @encrypted_messages = @current_device.encrypted_messages.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|   end | ||||
|  | ||||
|   def insert_pagination_headers | ||||
|  | ||||
| @ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def endorsed_accounts | ||||
|     current_account.endorsed_accounts.includes(:account_stat) | ||||
|     current_account.endorsed_accounts.includes(:account_stat).without_suspended | ||||
|   end | ||||
|  | ||||
|   def insert_pagination_headers | ||||
|  | ||||
| @ -17,14 +17,11 @@ class Api::V1::FavouritesController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def cached_favourites | ||||
|     cache_collection( | ||||
|       Status.reorder(nil).joins(:favourites).merge(results), | ||||
|       Status | ||||
|     ) | ||||
|     cache_collection(results.map(&:status), Status) | ||||
|   end | ||||
|  | ||||
|   def results | ||||
|     @_results ||= account_favourites.paginate_by_id( | ||||
|     @_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id( | ||||
|       limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|       params_slice(:max_id, :since_id, :min_id) | ||||
|     ) | ||||
|  | ||||
| @ -3,15 +3,15 @@ | ||||
| class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController | ||||
|   before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index | ||||
|   before_action :require_user! | ||||
|   before_action :set_most_used_tags, only: :index | ||||
|   before_action :set_recently_used_tags, only: :index | ||||
|  | ||||
|   def index | ||||
|     render json: @most_used_tags, each_serializer: REST::TagSerializer | ||||
|     render json: @recently_used_tags, each_serializer: REST::TagSerializer | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_most_used_tags | ||||
|     @most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10) | ||||
|   def set_recently_used_tags | ||||
|     @recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController | ||||
|  | ||||
|   def authorize | ||||
|     AuthorizeFollowService.new.call(account, current_account) | ||||
|     NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account)) | ||||
|     NotifyService.new.call(current_account, :follow, Follow.find_by(account: account, target_account: current_account)) | ||||
|     render json: account, serializer: REST::RelationshipSerializer, relationships: relationships | ||||
|   end | ||||
|  | ||||
| @ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def default_accounts | ||||
|     Account.includes(:follow_requests, :account_stat).references(:follow_requests) | ||||
|     Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests) | ||||
|   end | ||||
|  | ||||
|   def paginated_follow_requests | ||||
|  | ||||
| @ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController | ||||
|  | ||||
|   def index | ||||
|     expires_in 1.day, public: true | ||||
|     render_with_cache(expires_in: 1.day) { Account.remote.domains } | ||||
|     render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) } | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
| @ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController | ||||
|  | ||||
|   def load_accounts | ||||
|     if unlimited? | ||||
|       @list.accounts.includes(:account_stat).all | ||||
|       @list.accounts.without_suspended.includes(:account_stat).all | ||||
|     else | ||||
|       @list.accounts.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) | ||||
|       @list.accounts.without_suspended.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|  | ||||
| @ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def list_params | ||||
|     params.permit(:title) | ||||
|     params.permit(:title, :replies_policy) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -7,7 +7,7 @@ class Api::V1::MutesController < Api::BaseController | ||||
|  | ||||
|   def index | ||||
|     @accounts = load_accounts | ||||
|     render json: @accounts, each_serializer: REST::AccountSerializer | ||||
|     render json: @accounts, each_serializer: REST::MutedAccountSerializer | ||||
|   end | ||||
|  | ||||
|   private | ||||
| @ -18,6 +18,8 @@ class Api::V1::MutesController < Api::BaseController | ||||
|  | ||||
|   def paginated_mutes | ||||
|     @paginated_mutes ||= Mute.eager_load(:target_account) | ||||
|                              .joins(:target_account) | ||||
|                              .merge(Account.without_suspended) | ||||
|                              .where(account: current_account) | ||||
|                              .paginate_by_max_id( | ||||
|                                limit_param(DEFAULT_ACCOUNTS_LIMIT), | ||||
|  | ||||
| @ -14,7 +14,7 @@ class Api::V1::NotificationsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def show | ||||
|     @notification = current_account.notifications.find(params[:id]) | ||||
|     @notification = current_account.notifications.without_suspended.find(params[:id]) | ||||
|     render json: @notification, serializer: REST::NotificationSerializer | ||||
|   end | ||||
|  | ||||
| @ -31,18 +31,16 @@ class Api::V1::NotificationsController < Api::BaseController | ||||
|   private | ||||
|  | ||||
|   def load_notifications | ||||
|     cache_collection paginated_notifications, Notification | ||||
|   end | ||||
|  | ||||
|   def paginated_notifications | ||||
|     browserable_account_notifications.paginate_by_id( | ||||
|     cache_collection_paginated_by_id( | ||||
|       browserable_account_notifications, | ||||
|       Notification, | ||||
|       limit_param(DEFAULT_NOTIFICATIONS_LIMIT), | ||||
|       params_slice(:max_id, :since_id, :min_id) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def browserable_account_notifications | ||||
|     current_account.notifications.browserable(exclude_types, from_account) | ||||
|     current_account.notifications.without_suspended.browserable(exclude_types, from_account) | ||||
|   end | ||||
|  | ||||
|   def target_statuses_from_notifications | ||||
|  | ||||
| @ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController | ||||
|   def data_params | ||||
|     return {} if params[:data].blank? | ||||
|  | ||||
|     params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) | ||||
|     params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status]) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -32,7 +32,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController | ||||
|   private | ||||
|  | ||||
|   def set_statuses | ||||
|     @statuses = current_account.scheduled_statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|     @statuses = current_account.scheduled_statuses.to_a_paginated_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) | ||||
|   end | ||||
|  | ||||
|   def set_status | ||||
|  | ||||
| @ -5,7 +5,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController | ||||
|  | ||||
|   before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' } | ||||
|   before_action :require_user! | ||||
|   before_action :set_status | ||||
|   before_action :set_status, only: [:create] | ||||
|  | ||||
|   def create | ||||
|     current_account.bookmarks.find_or_create_by!(account: current_account, status: @status) | ||||
| @ -13,10 +13,20 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def destroy | ||||
|     bookmark = current_account.bookmarks.find_by(status: @status) | ||||
|     bookmark = current_account.bookmarks.find_by(status_id: params[:status_id]) | ||||
|  | ||||
|     if bookmark | ||||
|       @status = bookmark.status | ||||
|     else | ||||
|       @status = Status.find(params[:status_id]) | ||||
|       authorize @status, :show? | ||||
|     end | ||||
|  | ||||
|     bookmark&.destroy! | ||||
|  | ||||
|     render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false }) | ||||
|   rescue Mastodon::NotPermittedError | ||||
|     not_found | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
| @ -22,6 +22,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController | ||||
|  | ||||
|   def default_accounts | ||||
|     Account | ||||
|       .without_suspended | ||||
|       .includes(:favourites, :account_stat) | ||||
|       .references(:favourites) | ||||
|       .where(favourites: { status_id: @status.id }) | ||||
|  | ||||
| @ -5,7 +5,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController | ||||
|  | ||||
|   before_action -> { doorkeeper_authorize! :write, :'write:favourites' } | ||||
|   before_action :require_user! | ||||
|   before_action :set_status | ||||
|   before_action :set_status, only: [:create] | ||||
|  | ||||
|   def create | ||||
|     FavouriteService.new.call(current_account, @status) | ||||
| @ -13,8 +13,19 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def destroy | ||||
|     UnfavouriteWorker.perform_async(current_account.id, @status.id) | ||||
|     fav = current_account.favourites.find_by(status_id: params[:status_id]) | ||||
|  | ||||
|     if fav | ||||
|       @status = fav.status | ||||
|       UnfavouriteWorker.perform_async(current_account.id, @status.id) | ||||
|     else | ||||
|       @status = Status.find(params[:status_id]) | ||||
|       authorize @status, :show? | ||||
|     end | ||||
|  | ||||
|     render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }) | ||||
|   rescue Mastodon::NotPermittedError | ||||
|     not_found | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
| @ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def default_accounts | ||||
|     Account.includes(:statuses, :account_stat).references(:statuses) | ||||
|     Account.without_suspended.includes(:statuses, :account_stat).references(:statuses) | ||||
|   end | ||||
|  | ||||
|   def paginated_statuses | ||||
|  | ||||
| @ -16,30 +16,29 @@ class Api::V1::Timelines::PublicController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def load_statuses | ||||
|     cached_public_statuses | ||||
|     cached_public_statuses_page | ||||
|   end | ||||
|  | ||||
|   def cached_public_statuses | ||||
|     cache_collection public_statuses, Status | ||||
|   def cached_public_statuses_page | ||||
|     cache_collection(public_statuses, Status) | ||||
|   end | ||||
|  | ||||
|   def public_statuses | ||||
|     statuses = public_timeline_statuses.paginate_by_id( | ||||
|     public_feed.get( | ||||
|       limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|       params_slice(:max_id, :since_id, :min_id) | ||||
|       params[:max_id], | ||||
|       params[:since_id], | ||||
|       params[:min_id] | ||||
|     ) | ||||
|  | ||||
|     if truthy_param?(:only_media) | ||||
|       # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids. | ||||
|       status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id) | ||||
|       statuses.where(id: status_ids) | ||||
|     else | ||||
|       statuses | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def public_timeline_statuses | ||||
|     Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local)) | ||||
|   def public_feed | ||||
|     PublicFeed.new( | ||||
|       current_account, | ||||
|       local: truthy_param?(:local), | ||||
|       remote: truthy_param?(:remote), | ||||
|       only_media: truthy_param?(:only_media) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def insert_pagination_headers | ||||
|  | ||||
| @ -20,30 +20,29 @@ class Api::V1::Timelines::TagController < Api::BaseController | ||||
|   end | ||||
|  | ||||
|   def cached_tagged_statuses | ||||
|     cache_collection tagged_statuses, Status | ||||
|   end | ||||
|  | ||||
|   def tagged_statuses | ||||
|     if @tag.nil? | ||||
|       [] | ||||
|     else | ||||
|       statuses = tag_timeline_statuses.paginate_by_id( | ||||
|         limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|         params_slice(:max_id, :since_id, :min_id) | ||||
|       ) | ||||
|  | ||||
|       if truthy_param?(:only_media) | ||||
|         # `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids. | ||||
|         status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id) | ||||
|         statuses.where(id: status_ids) | ||||
|       else | ||||
|         statuses | ||||
|       end | ||||
|     end | ||||
|     @tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status) | ||||
|   end | ||||
|  | ||||
|   def tag_timeline_statuses | ||||
|     HashtagQueryService.new.call(@tag, params.slice(:any, :all, :none), current_account, truthy_param?(:local)) | ||||
|     tag_feed.get( | ||||
|       limit_param(DEFAULT_STATUSES_LIMIT), | ||||
|       params[:max_id], | ||||
|       params[:since_id], | ||||
|       params[:min_id] | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def tag_feed | ||||
|     TagFeed.new( | ||||
|       @tag, | ||||
|       current_account, | ||||
|       any: params[:any], | ||||
|       all: params[:all], | ||||
|       none: params[:none], | ||||
|       local: truthy_param?(:local), | ||||
|       remote: truthy_param?(:remote), | ||||
|       only_media: truthy_param?(:only_media) | ||||
|     ) | ||||
|   end | ||||
|  | ||||
|   def insert_pagination_headers | ||||
|  | ||||
| @ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController | ||||
|         reblog: alerts_enabled, | ||||
|         mention: alerts_enabled, | ||||
|         poll: alerts_enabled, | ||||
|         status: alerts_enabled, | ||||
|       }, | ||||
|     } | ||||
|  | ||||
| @ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController | ||||
|   end | ||||
|  | ||||
|   def data_params | ||||
|     @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) | ||||
|     @data_params ||= params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll, :status]) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -28,7 +28,7 @@ class ApplicationController < ActionController::Base | ||||
|   rescue_from ActiveRecord::RecordNotFound, with: :not_found | ||||
|   rescue_from Mastodon::NotPermittedError, with: :forbidden | ||||
|   rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error | ||||
|   rescue_from Mastodon::RaceConditionError, with: :service_unavailable | ||||
|   rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable | ||||
|   rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests | ||||
|  | ||||
|   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
|  | ||||
| class Auth::RegistrationsController < Devise::RegistrationsController | ||||
|   include Devise::Controllers::Rememberable | ||||
|   include RegistrationSpamConcern | ||||
|  | ||||
|   layout :determine_layout | ||||
|  | ||||
| @ -13,6 +14,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController | ||||
|   before_action :set_body_classes, only: [:new, :create, :edit, :update] | ||||
|   before_action :require_not_suspended!, only: [:update] | ||||
|   before_action :set_cache_headers, only: [:edit, :update] | ||||
|   before_action :set_registration_form_time, only: :new | ||||
|  | ||||
|   skip_before_action :require_functional!, only: [:edit, :update] | ||||
|  | ||||
| @ -45,16 +47,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController | ||||
|   def build_resource(hash = nil) | ||||
|     super(hash) | ||||
|  | ||||
|     resource.locale             = I18n.locale | ||||
|     resource.invite_code        = params[:invite_code] if resource.invite_code.blank? | ||||
|     resource.current_sign_in_ip = request.remote_ip | ||||
|     resource.locale                 = I18n.locale | ||||
|     resource.invite_code            = params[:invite_code] if resource.invite_code.blank? | ||||
|     resource.registration_form_time = session[:registration_form_time] | ||||
|     resource.sign_up_ip             = request.remote_ip | ||||
|  | ||||
|     resource.build_account if resource.account.nil? | ||||
|   end | ||||
|  | ||||
|   def configure_sign_up_params | ||||
|     devise_parameter_sanitizer.permit(:sign_up) do |u| | ||||
|       u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement) | ||||
|       u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|  | ||||
| @ -7,6 +7,7 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|  | ||||
|   skip_before_action :require_no_authentication, only: [:create] | ||||
|   skip_before_action :require_functional! | ||||
|   skip_before_action :update_user_sign_in | ||||
|  | ||||
|   include TwoFactorAuthenticationConcern | ||||
|   include SignInTokenAuthenticationConcern | ||||
| @ -24,6 +25,7 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|  | ||||
|   def create | ||||
|     super do |resource| | ||||
|       resource.update_sign_in!(request, new_sign_in: true) | ||||
|       remember_me(resource) | ||||
|       flash.delete(:notice) | ||||
|     end | ||||
| @ -37,11 +39,27 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|     store_location_for(:user, tmp_stored_location) if continue_after? | ||||
|   end | ||||
|  | ||||
|   def webauthn_options | ||||
|     user = find_user | ||||
|  | ||||
|     if user.webauthn_enabled? | ||||
|       options_for_get = WebAuthn::Credential.options_for_get( | ||||
|         allow: user.webauthn_credentials.pluck(:external_id) | ||||
|       ) | ||||
|  | ||||
|       session[:webauthn_challenge] = options_for_get.challenge | ||||
|  | ||||
|       render json: options_for_get, status: :ok | ||||
|     else | ||||
|       render json: { error: t('webauthn_credentials.not_enabled') }, status: :unauthorized | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   protected | ||||
|  | ||||
|   def find_user | ||||
|     if session[:attempt_user_id] | ||||
|       User.find(session[:attempt_user_id]) | ||||
|       User.find_by(id: session[:attempt_user_id]) | ||||
|     else | ||||
|       user   = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication | ||||
|       user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication | ||||
| @ -51,7 +69,7 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|   end | ||||
|  | ||||
|   def user_params | ||||
|     params.require(:user).permit(:email, :password, :otp_attempt, :sign_in_token_attempt) | ||||
|     params.require(:user).permit(:email, :password, :otp_attempt, :sign_in_token_attempt, credential: {}) | ||||
|   end | ||||
|  | ||||
|   def after_sign_in_path_for(resource) | ||||
| @ -74,6 +92,7 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|  | ||||
|   def require_no_authentication | ||||
|     super | ||||
|  | ||||
|     # Delete flash message that isn't entirely useful and may be confusing in | ||||
|     # most cases because /web doesn't display/clear flash messages. | ||||
|     flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated') | ||||
| @ -91,13 +110,30 @@ class Auth::SessionsController < Devise::SessionsController | ||||
|  | ||||
|   def home_paths(resource) | ||||
|     paths = [about_path] | ||||
|  | ||||
|     if single_user_mode? && resource.is_a?(User) | ||||
|       paths << short_account_path(username: resource.account) | ||||
|     end | ||||
|  | ||||
|     paths | ||||
|   end | ||||
|  | ||||
|   def continue_after? | ||||
|     truthy_param?(:continue) | ||||
|   end | ||||
|  | ||||
|   def restart_session | ||||
|     clear_attempt_from_session | ||||
|     redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout') | ||||
|   end | ||||
|  | ||||
|   def set_attempt_session(user) | ||||
|     session[:attempt_user_id]         = user.id | ||||
|     session[:attempt_user_updated_at] = user.updated_at.to_s | ||||
|   end | ||||
|  | ||||
|   def clear_attempt_from_session | ||||
|     session.delete(:attempt_user_id) | ||||
|     session.delete(:attempt_user_updated_at) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -29,6 +29,24 @@ module AccountOwnedConcern | ||||
|   end | ||||
|  | ||||
|   def check_account_suspension | ||||
|     expires_in(3.minutes, public: true) && gone if @account.suspended? | ||||
|     if @account.suspended_permanently? | ||||
|       permanent_suspension_response | ||||
|     elsif @account.suspended? && !skip_temporary_suspension_response? | ||||
|       temporary_suspension_response | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def skip_temporary_suspension_response? | ||||
|     false | ||||
|   end | ||||
|  | ||||
|   def permanent_suspension_response | ||||
|     expires_in(3.minutes, public: true) | ||||
|     gone | ||||
|   end | ||||
|  | ||||
|   def temporary_suspension_response | ||||
|     expires_in(3.minutes, public: true) | ||||
|     forbidden | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -47,4 +47,8 @@ module CacheConcern | ||||
|  | ||||
|     raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact | ||||
|   end | ||||
|  | ||||
|   def cache_collection_paginated_by_id(raw, klass, limit, options) | ||||
|     cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -32,7 +32,6 @@ module ChallengableConcern | ||||
|     if params.key?(:form_challenge) | ||||
|       if challenge_passed? | ||||
|         session[:challenge_passed_at] = Time.now.utc | ||||
|         return | ||||
|       else | ||||
|         flash.now[:alert] = I18n.t('challenge.invalid_password') | ||||
|         render_challenge | ||||
|  | ||||
| @ -5,7 +5,6 @@ module ExportControllerConcern | ||||
|  | ||||
|   included do | ||||
|     before_action :authenticate_user! | ||||
|     before_action :require_not_suspended! | ||||
|     before_action :load_export | ||||
|  | ||||
|     skip_before_action :require_functional! | ||||
| @ -30,8 +29,4 @@ module ExportControllerConcern | ||||
|   def export_filename | ||||
|     "#{controller_name}.csv" | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
							
								
								
									
										9
									
								
								app/controllers/concerns/registration_spam_concern.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/controllers/concerns/registration_spam_concern.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| module RegistrationSpamConcern | ||||
|   extend ActiveSupport::Concern | ||||
|  | ||||
|   def set_registration_form_time | ||||
|     session[:registration_form_time] = Time.now.utc | ||||
|   end | ||||
| end | ||||
| @ -18,7 +18,9 @@ module SignInTokenAuthenticationConcern | ||||
|   def authenticate_with_sign_in_token | ||||
|     user = self.resource = find_user | ||||
|  | ||||
|     if user_params[:sign_in_token_attempt].present? && session[:attempt_user_id] | ||||
|     if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s | ||||
|       restart_session | ||||
|     elsif user_params.key?(:sign_in_token_attempt) && session[:attempt_user_id] | ||||
|       authenticate_with_sign_in_token_attempt(user) | ||||
|     elsif user.present? && user.external_or_valid_password?(user_params[:password]) | ||||
|       prompt_for_sign_in_token(user) | ||||
| @ -27,7 +29,7 @@ module SignInTokenAuthenticationConcern | ||||
|  | ||||
|   def authenticate_with_sign_in_token_attempt(user) | ||||
|     if valid_sign_in_token_attempt?(user) | ||||
|       session.delete(:attempt_user_id) | ||||
|       clear_attempt_from_session | ||||
|       remember_me(user) | ||||
|       sign_in(user) | ||||
|     else | ||||
| @ -42,10 +44,10 @@ module SignInTokenAuthenticationConcern | ||||
|       UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later! | ||||
|     end | ||||
|  | ||||
|     set_locale do | ||||
|       session[:attempt_user_id] = user.id | ||||
|       @body_classes = 'lighter' | ||||
|       render :sign_in_token | ||||
|     end | ||||
|     set_attempt_session(user) | ||||
|  | ||||
|     @body_classes = 'lighter' | ||||
|  | ||||
|     set_locale { render :sign_in_token } | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -76,6 +76,7 @@ module SignatureVerification | ||||
|     raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window? | ||||
|  | ||||
|     verify_signature_strength! | ||||
|     verify_body_digest! | ||||
|  | ||||
|     account = account_from_key_id(signature_params['keyId']) | ||||
|  | ||||
| @ -126,12 +127,21 @@ module SignatureVerification | ||||
|   def verify_signature_strength! | ||||
|     raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)') | ||||
|     raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest') | ||||
|     raise SignatureVerificationError, 'Mastodon requires the Host header to be signed' unless signed_headers.include?('host') | ||||
|     raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host') | ||||
|     raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest') | ||||
|   end | ||||
|  | ||||
|   def verify_body_digest! | ||||
|     return unless signed_headers.include?('digest') | ||||
|  | ||||
|     digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] } | ||||
|     sha256  = digests.assoc('sha-256') | ||||
|     raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil? | ||||
|     raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1] | ||||
|   end | ||||
|  | ||||
|   def verify_signature(account, signature, compare_signed_string) | ||||
|     if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string) | ||||
|     if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string) | ||||
|       @signed_request_account = account | ||||
|       @signed_request_account | ||||
|     end | ||||
| @ -153,8 +163,6 @@ module SignatureVerification | ||||
|         raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank? | ||||
|  | ||||
|         "(expires): #{signature_params['expires']}" | ||||
|       elsif signed_header == 'digest' | ||||
|         "digest: #{body_digest}" | ||||
|       else | ||||
|         "#{signed_header}: #{request.headers[to_header_name(signed_header)]}" | ||||
|       end | ||||
| @ -187,7 +195,7 @@ module SignatureVerification | ||||
|   end | ||||
|  | ||||
|   def body_digest | ||||
|     "SHA-256=#{Digest::SHA256.base64digest(request_body)}" | ||||
|     @body_digest ||= Digest::SHA256.base64digest(request_body) | ||||
|   end | ||||
|  | ||||
|   def to_header_name(name) | ||||
|  | ||||
| @ -8,7 +8,23 @@ module TwoFactorAuthenticationConcern | ||||
|   end | ||||
|  | ||||
|   def two_factor_enabled? | ||||
|     find_user&.otp_required_for_login? | ||||
|     find_user&.two_factor_enabled? | ||||
|   end | ||||
|  | ||||
|   def valid_webauthn_credential?(user, webauthn_credential) | ||||
|     user_credential = user.webauthn_credentials.find_by!(external_id: webauthn_credential.id) | ||||
|  | ||||
|     begin | ||||
|       webauthn_credential.verify( | ||||
|         session[:webauthn_challenge], | ||||
|         public_key: user_credential.public_key, | ||||
|         sign_count: user_credential.sign_count | ||||
|       ) | ||||
|  | ||||
|       user_credential.update!(sign_count: webauthn_credential.sign_count) | ||||
|     rescue WebAuthn::Error | ||||
|       false | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def valid_otp_attempt?(user) | ||||
| @ -21,16 +37,33 @@ module TwoFactorAuthenticationConcern | ||||
|   def authenticate_with_two_factor | ||||
|     user = self.resource = find_user | ||||
|  | ||||
|     if user_params[:otp_attempt].present? && session[:attempt_user_id] | ||||
|       authenticate_with_two_factor_attempt(user) | ||||
|     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]) | ||||
|       prompt_for_two_factor(user) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def authenticate_with_two_factor_attempt(user) | ||||
|   def authenticate_with_two_factor_via_webauthn(user) | ||||
|     webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential]) | ||||
|  | ||||
|     if valid_webauthn_credential?(user, webauthn_credential) | ||||
|       clear_attempt_from_session | ||||
|       remember_me(user) | ||||
|       sign_in(user) | ||||
|       render json: { redirect_path: root_path }, status: :ok | ||||
|     else | ||||
|       render json: { error: t('webauthn_credentials.invalid_credential') }, status: :unprocessable_entity | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def authenticate_with_two_factor_via_otp(user) | ||||
|     if valid_otp_attempt?(user) | ||||
|       session.delete(:attempt_user_id) | ||||
|       clear_attempt_from_session | ||||
|       remember_me(user) | ||||
|       sign_in(user) | ||||
|     else | ||||
| @ -40,10 +73,18 @@ module TwoFactorAuthenticationConcern | ||||
|   end | ||||
|  | ||||
|   def prompt_for_two_factor(user) | ||||
|     set_locale do | ||||
|       session[:attempt_user_id] = user.id | ||||
|       @body_classes = 'lighter' | ||||
|       render :two_factor | ||||
|     set_attempt_session(user) | ||||
|  | ||||
|     @body_classes     = 'lighter' | ||||
|     @webauthn_enabled = user.webauthn_enabled? | ||||
|     @scheme_type      = begin | ||||
|       if user.webauthn_enabled? && user_params[:otp_attempt].blank? | ||||
|         'webauthn' | ||||
|       else | ||||
|         'totp' | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     set_locale { render :two_factor } | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -6,14 +6,13 @@ module UserTrackingConcern | ||||
|   UPDATE_SIGN_IN_HOURS = 24 | ||||
|  | ||||
|   included do | ||||
|     before_action :set_user_activity | ||||
|     before_action :update_user_sign_in | ||||
|   end | ||||
|  | ||||
|   private | ||||
|  | ||||
|   def set_user_activity | ||||
|     return unless user_needs_sign_in_update? | ||||
|     current_user.update_tracked_fields!(request) | ||||
|   def update_user_sign_in | ||||
|     current_user.update_sign_in!(request) if user_needs_sign_in_update? | ||||
|   end | ||||
|  | ||||
|   def user_needs_sign_in_update? | ||||
|  | ||||
| @ -9,7 +9,7 @@ class FiltersController < ApplicationController | ||||
|   before_action :set_body_classes | ||||
|  | ||||
|   def index | ||||
|     @filters = current_account.custom_filters | ||||
|     @filters = current_account.custom_filters.order(:phrase) | ||||
|   end | ||||
|  | ||||
|   def new | ||||
|  | ||||
| @ -52,6 +52,14 @@ class FollowerAccountsController < ApplicationController | ||||
|     account_followers_url(@account, page: page) unless page.nil? | ||||
|   end | ||||
|  | ||||
|   def next_page_url | ||||
|     page_url(follows.next_page) if follows.respond_to?(:next_page) | ||||
|   end | ||||
|  | ||||
|   def prev_page_url | ||||
|     page_url(follows.prev_page) if follows.respond_to?(:prev_page) | ||||
|   end | ||||
|  | ||||
|   def collection_presenter | ||||
|     if page_requested? | ||||
|       ActivityPub::CollectionPresenter.new( | ||||
| @ -60,8 +68,8 @@ class FollowerAccountsController < ApplicationController | ||||
|         size: @account.followers_count, | ||||
|         items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) }, | ||||
|         part_of: account_followers_url(@account), | ||||
|         next: page_url(follows.next_page), | ||||
|         prev: page_url(follows.prev_page) | ||||
|         next: next_page_url, | ||||
|         prev: prev_page_url | ||||
|       ) | ||||
|     else | ||||
|       ActivityPub::CollectionPresenter.new( | ||||
|  | ||||
| @ -52,6 +52,14 @@ class FollowingAccountsController < ApplicationController | ||||
|     account_following_index_url(@account, page: page) unless page.nil? | ||||
|   end | ||||
|  | ||||
|   def next_page_url | ||||
|     page_url(follows.next_page) if follows.respond_to?(:next_page) | ||||
|   end | ||||
|  | ||||
|   def prev_page_url | ||||
|     page_url(follows.prev_page) if follows.respond_to?(:prev_page) | ||||
|   end | ||||
|  | ||||
|   def collection_presenter | ||||
|     if page_requested? | ||||
|       ActivityPub::CollectionPresenter.new( | ||||
| @ -60,8 +68,8 @@ class FollowingAccountsController < ApplicationController | ||||
|         size: @account.following_count, | ||||
|         items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) }, | ||||
|         part_of: account_following_index_url(@account), | ||||
|         next: page_url(follows.next_page), | ||||
|         prev: page_url(follows.prev_page) | ||||
|         next: next_page_url, | ||||
|         prev: prev_page_url | ||||
|       ) | ||||
|     else | ||||
|       ActivityPub::CollectionPresenter.new( | ||||
|  | ||||
| @ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController | ||||
|   end | ||||
|  | ||||
|   def restrict_fields_to | ||||
|     %i(id type preferred_username inbox public_key endpoints url manually_approves_followers) | ||||
|     %i(id type preferred_username inbox outbox public_key endpoints url manually_approves_followers) | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | ||||
|  | ||||
|   before_action :store_current_location | ||||
|   before_action :authenticate_resource_owner! | ||||
|   before_action :require_not_suspended!, only: :destroy | ||||
|   before_action :set_body_classes | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
| @ -25,4 +26,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | ||||
|   def store_current_location | ||||
|     store_location_for(:user, request.url) | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -5,6 +5,7 @@ class RelationshipsController < ApplicationController | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_accounts, only: :show | ||||
|   before_action :set_relationships, only: :show | ||||
|   before_action :set_body_classes | ||||
|  | ||||
|   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship? | ||||
| @ -28,6 +29,10 @@ class RelationshipsController < ApplicationController | ||||
|     @accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40) | ||||
|   end | ||||
|  | ||||
|   def set_relationships | ||||
|     @relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id) | ||||
|   end | ||||
|  | ||||
|   def form_account_batch_params | ||||
|     params.require(:form_account_batch).permit(:action, account_ids: []) | ||||
|   end | ||||
| @ -49,7 +54,9 @@ class RelationshipsController < ApplicationController | ||||
|   end | ||||
|  | ||||
|   def action_from_button | ||||
|     if params[:unfollow] | ||||
|     if params[:follow] | ||||
|       'follow' | ||||
|     elsif params[:unfollow] | ||||
|       'unfollow' | ||||
|     elsif params[:remove_from_followers] | ||||
|       'remove_from_followers' | ||||
|  | ||||
| @ -1,9 +1,9 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::AliasesController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :require_not_suspended! | ||||
|   before_action :set_aliases, except: :destroy | ||||
|   before_action :set_alias, only: :destroy | ||||
|  | ||||
|  | ||||
| @ -1,9 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::ApplicationsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_application, only: [:show, :update, :destroy, :regenerate] | ||||
|   before_action :prepare_scopes, only: [:create, :update] | ||||
|  | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::BaseController < ApplicationController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_body_classes | ||||
|   before_action :set_cache_headers | ||||
|  | ||||
| @ -13,4 +16,8 @@ class Settings::BaseController < ApplicationController | ||||
|   def set_cache_headers | ||||
|     response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,14 +1,11 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::DeletesController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :check_enabled_deletion | ||||
|   before_action :authenticate_user! | ||||
|   before_action :require_not_suspended! | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :require_not_suspended! | ||||
|   before_action :check_enabled_deletion | ||||
|  | ||||
|   def show | ||||
|     @confirmation = Form::DeleteConfirmation.new | ||||
|   end | ||||
| @ -45,8 +42,8 @@ class Settings::DeletesController < Settings::BaseController | ||||
|   end | ||||
|  | ||||
|   def destroy_account! | ||||
|     current_account.suspend! | ||||
|     Admin::SuspensionWorker.perform_async(current_user.account_id, true) | ||||
|     current_account.suspend!(origin: :local) | ||||
|     AccountDeletionWorker.perform_async(current_user.account_id) | ||||
|     sign_out | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class BlockedAccountsController < ApplicationController | ||||
|     class BlockedAccountsController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class BlockedDomainsController < ApplicationController | ||||
|     class BlockedDomainsController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|  | ||||
							
								
								
									
										19
									
								
								app/controllers/settings/exports/bookmarks_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/controllers/settings/exports/bookmarks_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class BookmarksController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|         send_export_file | ||||
|       end | ||||
|  | ||||
|       private | ||||
|  | ||||
|       def export_data | ||||
|         @export.to_bookmarks_csv | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class FollowingAccountsController < ApplicationController | ||||
|     class FollowingAccountsController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class ListsController < ApplicationController | ||||
|     class ListsController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|  | ||||
| module Settings | ||||
|   module Exports | ||||
|     class MutedAccountsController < ApplicationController | ||||
|     class MutedAccountsController < BaseController | ||||
|       include ExportControllerConcern | ||||
|  | ||||
|       def index | ||||
|  | ||||
| @ -3,11 +3,6 @@ | ||||
| class Settings::ExportsController < Settings::BaseController | ||||
|   include Authorization | ||||
|  | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :require_not_suspended! | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   def show | ||||
| @ -16,8 +11,6 @@ class Settings::ExportsController < Settings::BaseController | ||||
|   end | ||||
|  | ||||
|   def create | ||||
|     raise Mastodon::NotPermittedError unless user_signed_in? | ||||
|  | ||||
|     backup = nil | ||||
|  | ||||
|     RedisLock.acquire(lock_options) do |lock| | ||||
| @ -37,8 +30,4 @@ class Settings::ExportsController < Settings::BaseController | ||||
|   def lock_options | ||||
|     { redis: Redis.current, key: "backup:#{current_user.id}" } | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,12 +1,9 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::FeaturedTagsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_featured_tags, only: :index | ||||
|   before_action :set_featured_tag, except: [:index, :create] | ||||
|   before_action :set_most_used_tags, only: :index | ||||
|   before_action :set_recently_used_tags, only: :index | ||||
|  | ||||
|   def index | ||||
|     @featured_tag = FeaturedTag.new | ||||
| @ -20,7 +17,7 @@ class Settings::FeaturedTagsController < Settings::BaseController | ||||
|       redirect_to settings_featured_tags_path | ||||
|     else | ||||
|       set_featured_tags | ||||
|       set_most_used_tags | ||||
|       set_recently_used_tags | ||||
|  | ||||
|       render :index | ||||
|     end | ||||
| @ -41,8 +38,8 @@ class Settings::FeaturedTagsController < Settings::BaseController | ||||
|     @featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?) | ||||
|   end | ||||
|  | ||||
|   def set_most_used_tags | ||||
|     @most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) | ||||
|   def set_recently_used_tags | ||||
|     @recently_used_tags = Tag.recently_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) | ||||
|   end | ||||
|  | ||||
|   def featured_tag_params | ||||
|  | ||||
| @ -1,9 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::IdentityProofsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :check_required_params, only: :new | ||||
|  | ||||
|   def index | ||||
|  | ||||
| @ -1,9 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::ImportsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_account | ||||
|  | ||||
|   def show | ||||
|  | ||||
| @ -1,13 +1,10 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::Migration::RedirectsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :require_not_suspended! | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :require_not_suspended! | ||||
|  | ||||
|   def new | ||||
|     @redirect = Form::Redirect.new | ||||
|   end | ||||
| @ -38,8 +35,4 @@ class Settings::Migration::RedirectsController < Settings::BaseController | ||||
|   def resource_params | ||||
|     params.require(:form_redirect).permit(:acct, :current_password, :current_username) | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -1,15 +1,12 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::MigrationsController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :require_not_suspended! | ||||
|   before_action :set_migrations | ||||
|   before_action :set_cooldown | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   def show | ||||
|     @migration = current_account.migrations.build | ||||
|   end | ||||
| @ -44,8 +41,4 @@ class Settings::MigrationsController < Settings::BaseController | ||||
|   def on_cooldown? | ||||
|     @cooldown.present? | ||||
|   end | ||||
|  | ||||
|   def require_not_suspended! | ||||
|     forbidden if current_account.suspended? | ||||
|   end | ||||
| end | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|  | ||||
| module Settings | ||||
|   class PicturesController < BaseController | ||||
|     before_action :authenticate_user! | ||||
|     before_action :set_account | ||||
|     before_action :set_picture | ||||
|  | ||||
|  | ||||
| @ -1,10 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::PreferencesController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|  | ||||
|   def show; end | ||||
|  | ||||
|   def update | ||||
| @ -48,6 +44,7 @@ class Settings::PreferencesController < Settings::BaseController | ||||
|       :setting_display_media, | ||||
|       :setting_expand_spoilers, | ||||
|       :setting_reduce_motion, | ||||
|       :setting_disable_swiping, | ||||
|       :setting_system_font_ui, | ||||
|       :setting_noindex, | ||||
|       :setting_theme, | ||||
|  | ||||
| @ -1,9 +1,6 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::ProfilesController < Settings::BaseController | ||||
|   layout 'admin' | ||||
|  | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_account | ||||
|  | ||||
|   def show | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class Settings::SessionsController < Settings::BaseController | ||||
|   before_action :authenticate_user! | ||||
|   before_action :set_session, only: :destroy | ||||
|  | ||||
|   skip_before_action :require_functional! | ||||
|  | ||||
|   before_action :require_not_suspended! | ||||
|   before_action :set_session, only: :destroy | ||||
|  | ||||
|   def destroy | ||||
|     @session.destroy! | ||||
|     flash[:notice] = I18n.t('sessions.revoke_success') | ||||
|  | ||||
| @ -5,31 +5,31 @@ module Settings | ||||
|     class ConfirmationsController < BaseController | ||||
|       include ChallengableConcern | ||||
|  | ||||
|       layout 'admin' | ||||
|       skip_before_action :require_functional! | ||||
|  | ||||
|       before_action :authenticate_user! | ||||
|       before_action :require_challenge! | ||||
|       before_action :ensure_otp_secret | ||||
|  | ||||
|       skip_before_action :require_functional! | ||||
|  | ||||
|       def new | ||||
|         prepare_two_factor_form | ||||
|       end | ||||
|  | ||||
|       def create | ||||
|         if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt]) | ||||
|         if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt], otp_secret: session[:new_otp_secret]) | ||||
|           flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success') | ||||
|  | ||||
|           current_user.otp_required_for_login = true | ||||
|           current_user.otp_secret = session[:new_otp_secret] | ||||
|           @recovery_codes = current_user.generate_otp_backup_codes! | ||||
|           current_user.save! | ||||
|  | ||||
|           UserMailer.two_factor_enabled(current_user).deliver_later! | ||||
|  | ||||
|           session.delete(:new_otp_secret) | ||||
|  | ||||
|           render 'settings/two_factor_authentication/recovery_codes/index' | ||||
|         else | ||||
|           flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code') | ||||
|           flash.now[:alert] = I18n.t('otp_authentication.wrong_code') | ||||
|           prepare_two_factor_form | ||||
|           render :new | ||||
|         end | ||||
| @ -43,12 +43,15 @@ module Settings | ||||
|  | ||||
|       def prepare_two_factor_form | ||||
|         @confirmation = Form::TwoFactorConfirmation.new | ||||
|         @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain) | ||||
|         @new_otp_secret = session[:new_otp_secret] | ||||
|         @provision_url = current_user.otp_provisioning_uri(current_user.email, | ||||
|                                                            otp_secret: @new_otp_secret, | ||||
|                                                            issuer: Rails.configuration.x.local_domain) | ||||
|         @qrcode = RQRCode::QRCode.new(@provision_url) | ||||
|       end | ||||
|  | ||||
|       def ensure_otp_secret | ||||
|         redirect_to settings_two_factor_authentication_path unless current_user.otp_secret | ||||
|         redirect_to settings_otp_authentication_path if session[:new_otp_secret].blank? | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user