Merge tag 'v3.3.0' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira 2020-12-27 11:00:43 +01:00
commit cb085b4c44
877 changed files with 35407 additions and 11128 deletions

View File

@ -27,10 +27,10 @@ plugins:
enabled: true enabled: true
eslint: eslint:
enabled: true enabled: true
channel: eslint-6 channel: eslint-7
rubocop: rubocop:
enabled: true enabled: true
channel: rubocop-0-82 channel: rubocop-0-92
sass-lint: sass-lint:
enabled: true enabled: true
exclude_patterns: exclude_patterns:

View File

@ -1,7 +1,7 @@
--- ---
name: Bug Report name: Bug Report
about: If something isn't working as expected 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 --> <!-- Make sure that you are submitting a new bug that was not previously reported or already fixed -->

View File

@ -1,7 +1,6 @@
--- ---
name: Feature Request name: Feature Request
about: I have a suggestion about: I have a suggestion
--- ---
<!-- Please use a concise and distinct title for the issue --> <!-- Please use a concise and distinct title for the issue -->

View File

@ -11,7 +11,7 @@ updates:
interval: weekly interval: weekly
open-pull-requests-limit: 99 open-pull-requests-limit: 99
allow: allow:
- dependency-type: all - dependency-type: direct
- package-ecosystem: bundler - package-ecosystem: bundler
directory: "/" directory: "/"
@ -19,4 +19,4 @@ updates:
interval: weekly interval: weekly
open-pull-requests-limit: 99 open-pull-requests-limit: 99
allow: allow:
- dependency-type: all - dependency-type: direct

View File

@ -25,30 +25,68 @@ Layout/AccessModifierIndentation:
Layout/EmptyLineAfterMagicComment: Layout/EmptyLineAfterMagicComment:
Enabled: false Enabled: false
Layout/EmptyLineAfterGuardClause:
Enabled: false
Layout/EmptyLinesAroundAttributeAccessor:
Enabled: true
Layout/HashAlignment:
Enabled: false
# EnforcedHashRocketStyle: table
# EnforcedColonStyle: table
Layout/SpaceAroundMethodCallOperator:
Enabled: true
Layout/SpaceInsideHashLiteralBraces: Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: space EnforcedStyle: space
Lint/DeprecatedOpenSSLConstant:
Enabled: true
Lint/DuplicateElsifCondition:
Enabled: true
Lint/MixedRegexpCaptureTypes:
Enabled: true
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
Lint/UselessAccessModifier: Lint/UselessAccessModifier:
ContextCreatingMethods: ContextCreatingMethods:
- class_methods - class_methods
Metrics/AbcSize: Metrics/AbcSize:
Max: 100 Max: 100
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/BlockLength: Metrics/BlockLength:
Max: 35 Max: 55
Exclude: Exclude:
- 'lib/tasks/**/*' - 'lib/tasks/**/*'
- 'lib/mastodon/*_cli.rb'
Metrics/BlockNesting: Metrics/BlockNesting:
Max: 3 Max: 3
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/ClassLength: Metrics/ClassLength:
CountComments: false CountComments: false
Max: 300 Max: 400
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/CyclomaticComplexity: Metrics/CyclomaticComplexity:
Max: 25 Max: 25
Exclude:
- 'lib/mastodon/*_cli.rb'
Layout/LineLength: Layout/LineLength:
AllowURI: true AllowURI: true
@ -56,7 +94,9 @@ Layout/LineLength:
Metrics/MethodLength: Metrics/MethodLength:
CountComments: false CountComments: false
Max: 55 Max: 65
Exclude:
- 'lib/mastodon/*_cli.rb'
Metrics/ModuleLength: Metrics/ModuleLength:
CountComments: false CountComments: false
@ -67,34 +107,90 @@ Metrics/ParameterLists:
CountKeywordArgs: true CountKeywordArgs: true
Metrics/PerceivedComplexity: Metrics/PerceivedComplexity:
Max: 20 Max: 25
Naming/MemoizedInstanceVariableName: Naming/MemoizedInstanceVariableName:
Enabled: false Enabled: false
Naming/MethodParameterName:
Enabled: true
Rails: Rails:
Enabled: true Enabled: true
Rails/ApplicationController:
Enabled: false
Exclude:
- 'app/controllers/well_known/**/*.rb'
Rails/BelongsTo:
Enabled: false
Rails/ContentTag:
Enabled: false
Rails/EnumHash: Rails/EnumHash:
Enabled: false Enabled: false
Rails/HasAndBelongsToMany:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Rails/HttpStatus:
Enabled: false
Rails/Exit: Rails/Exit:
Exclude: Exclude:
- 'lib/mastodon/*' - 'lib/mastodon/*'
- 'lib/cli.rb' - 'lib/cli.rb'
Rails/FilePath:
Enabled: false
Rails/HasAndBelongsToMany:
Enabled: false
Rails/HasManyOrHasOneDependent:
Enabled: false
Rails/HelperInstanceVariable: Rails/HelperInstanceVariable:
Enabled: false 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: Style/ClassAndModuleChildren:
Enabled: false Enabled: false
@ -109,6 +205,15 @@ Style/Documentation:
Style/DoubleNegation: Style/DoubleNegation:
Enabled: true Enabled: true
Style/ExpandPathArguments:
Enabled: false
Style/ExponentialNotation:
Enabled: true
Style/FormatString:
Enabled: false
Style/FormatStringToken: Style/FormatStringToken:
Enabled: false Enabled: false
@ -118,9 +223,33 @@ Style/FrozenStringLiteralComment:
Style/GuardClause: Style/GuardClause:
Enabled: false 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: Style/Lambda:
Enabled: false Enabled: false
Style/MutableConstant:
Enabled: false
Style/PercentLiteralDelimiters: Style/PercentLiteralDelimiters:
PreferredDelimiters: PreferredDelimiters:
'%i': '()' '%i': '()'
@ -129,9 +258,36 @@ Style/PercentLiteralDelimiters:
Style/PerlBackrefs: Style/PerlBackrefs:
AutoCorrect: false 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: Style/RegexpLiteral:
Enabled: false Enabled: false
Style/RescueStandardError:
Enabled: false
Style/SignalException:
Enabled: false
Style/SlicingWithRange:
Enabled: true
Style/SymbolArray: Style/SymbolArray:
Enabled: false Enabled: false
@ -140,3 +296,6 @@ Style/TrailingCommaInArrayLiteral:
Style/TrailingCommaInHashLiteral: Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: 'comma' EnforcedStyleForMultiline: 'comma'
Style/UnpackFirst:
Enabled: false

View File

@ -1 +1 @@
2.6.6 2.7.2

View File

@ -5,38 +5,39 @@ Mastodon is available on [GitHub](https://github.com/tootsuite/mastodon)
and provided thanks to the work of the following contributors: and provided thanks to the work of the following contributors:
* [Gargron](https://github.com/Gargron) * [Gargron](https://github.com/Gargron)
* [dependabot-preview[bot]](https://github.com/apps/dependabot-preview)
* [ThibG](https://github.com/ThibG) * [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) * [dependabot[bot]](https://github.com/apps/dependabot)
* [ykzts](https://github.com/ykzts)
* [akihikodaki](https://github.com/akihikodaki) * [akihikodaki](https://github.com/akihikodaki)
* [mjankowski](https://github.com/mjankowski) * [mjankowski](https://github.com/mjankowski)
* [unarist](https://github.com/unarist) * [unarist](https://github.com/unarist)
* [yiskah](https://github.com/yiskah) * [yiskah](https://github.com/yiskah)
* [nolanlawson](https://github.com/nolanlawson) * [nolanlawson](https://github.com/nolanlawson)
* [abcang](https://github.com/abcang) * [abcang](https://github.com/abcang)
* [ysksn](https://github.com/ysksn)
* [mayaeh](https://github.com/mayaeh) * [mayaeh](https://github.com/mayaeh)
* [ysksn](https://github.com/ysksn)
* [sorin-davidoi](https://github.com/sorin-davidoi) * [sorin-davidoi](https://github.com/sorin-davidoi)
* [noellabo](https://github.com/noellabo)
* [lynlynlynx](https://github.com/lynlynlynx) * [lynlynlynx](https://github.com/lynlynlynx)
* [m4sk1n](mailto:me@m4sk.in) * [m4sk1n](mailto:me@m4sk.in)
* [Marcin Mikołajczak](mailto:me@m4sk.in) * [Marcin Mikołajczak](mailto:me@m4sk.in)
* [Kjwon15](https://github.com/Kjwon15) * [Kjwon15](https://github.com/Kjwon15)
* [noellabo](https://github.com/noellabo)
* [renatolond](https://github.com/renatolond) * [renatolond](https://github.com/renatolond)
* [alpaca-tc](https://github.com/alpaca-tc) * [alpaca-tc](https://github.com/alpaca-tc)
* [jeroenpraat](https://github.com/jeroenpraat) * [jeroenpraat](https://github.com/jeroenpraat)
* [nclm](https://github.com/nclm) * [nclm](https://github.com/nclm)
* [ineffyble](https://github.com/ineffyble) * [ineffyble](https://github.com/ineffyble)
* [shleeable](https://github.com/shleeable)
* [zunda](https://github.com/zunda) * [zunda](https://github.com/zunda)
* [shleeable](https://github.com/shleeable)
* [Masoud Abkenar](mailto:ampbox@gmail.com) * [Masoud Abkenar](mailto:ampbox@gmail.com)
* [blackle](https://github.com/blackle) * [blackle](https://github.com/blackle)
* [Quent-in](https://github.com/Quent-in) * [Quent-in](https://github.com/Quent-in)
* [JantsoP](https://github.com/JantsoP) * [JantsoP](https://github.com/JantsoP)
* [nullkal](https://github.com/nullkal) * [nullkal](https://github.com/nullkal)
* [yookoala](https://github.com/yookoala) * [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) * [Aditoo17](https://github.com/Aditoo17)
* [Quenty31](https://github.com/Quenty31) * [Quenty31](https://github.com/Quenty31)
* [marek-lach](https://github.com/marek-lach) * [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) * [danhunsaker](https://github.com/danhunsaker)
* [eramdam](https://github.com/eramdam) * [eramdam](https://github.com/eramdam)
* [takayamaki](https://github.com/takayamaki) * [takayamaki](https://github.com/takayamaki)
* [ariasuni](https://github.com/ariasuni)
* [masarakki](https://github.com/masarakki) * [masarakki](https://github.com/masarakki)
* [ticky](https://github.com/ticky) * [ticky](https://github.com/ticky)
* [trwnh](https://github.com/trwnh)
* [ThisIsMissEm](https://github.com/ThisIsMissEm) * [ThisIsMissEm](https://github.com/ThisIsMissEm)
* [hinaloe](https://github.com/hinaloe) * [hinaloe](https://github.com/hinaloe)
* [hcmiya](https://github.com/hcmiya) * [hcmiya](https://github.com/hcmiya)
@ -57,10 +58,10 @@ and provided thanks to the work of the following contributors:
* [yukimochi](https://github.com/yukimochi) * [yukimochi](https://github.com/yukimochi)
* [palindromordnilap](https://github.com/palindromordnilap) * [palindromordnilap](https://github.com/palindromordnilap)
* [rkarabut](https://github.com/rkarabut) * [rkarabut](https://github.com/rkarabut)
* [trwnh](https://github.com/trwnh)
* [nightpool](https://github.com/nightpool) * [nightpool](https://github.com/nightpool)
* [Artoria2e5](https://github.com/Artoria2e5) * [Artoria2e5](https://github.com/Artoria2e5)
* [marrus-sh](https://github.com/marrus-sh) * [marrus-sh](https://github.com/marrus-sh)
* [dunn](https://github.com/dunn)
* [krainboltgreene](https://github.com/krainboltgreene) * [krainboltgreene](https://github.com/krainboltgreene)
* [pfigel](https://github.com/pfigel) * [pfigel](https://github.com/pfigel)
* [BoFFire](https://github.com/BoFFire) * [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) * [ashleyhull-versent](https://github.com/ashleyhull-versent)
* [yhirano55](https://github.com/yhirano55) * [yhirano55](https://github.com/yhirano55)
* [rinsuki](https://github.com/rinsuki) * [rinsuki](https://github.com/rinsuki)
* [dunn](https://github.com/dunn)
* [devkral](https://github.com/devkral) * [devkral](https://github.com/devkral)
* [camponez](https://github.com/camponez) * [camponez](https://github.com/camponez)
* [hugogameiro](https://github.com/hugogameiro) * [hugogameiro](https://github.com/hugogameiro)
* [SerCom_KC](mailto:szescxz@gmail.com) * [SerCom_KC](mailto:szescxz@gmail.com)
* [aschmitz](https://github.com/aschmitz) * [aschmitz](https://github.com/aschmitz)
* [mfmfuyu](https://github.com/mfmfuyu)
* [kedamaDQ](https://github.com/kedamaDQ)
* [fpiesche](https://github.com/fpiesche) * [fpiesche](https://github.com/fpiesche)
* [gandaro](https://github.com/gandaro) * [gandaro](https://github.com/gandaro)
* [johnsudaar](https://github.com/johnsudaar) * [johnsudaar](https://github.com/johnsudaar)
* [trebmuh](https://github.com/trebmuh) * [trebmuh](https://github.com/trebmuh)
* [rmhasan](https://github.com/rmhasan) * [rmhasan](https://github.com/rmhasan)
* [kedamaDQ](https://github.com/kedamaDQ)
* [lindwurm](https://github.com/lindwurm) * [lindwurm](https://github.com/lindwurm)
* [victorhck](mailto:victorhck@geeko.site) * [victorhck](mailto:victorhck@geeko.site)
* [voidsatisfaction](https://github.com/voidsatisfaction) * [voidsatisfaction](https://github.com/voidsatisfaction)
* [mkljczk](https://github.com/mkljczk)
* [hikari-no-yume](https://github.com/hikari-no-yume) * [hikari-no-yume](https://github.com/hikari-no-yume)
* [seefood](https://github.com/seefood) * [seefood](https://github.com/seefood)
* [jackjennings](https://github.com/jackjennings) * [jackjennings](https://github.com/jackjennings)
* [mfmfuyu](https://github.com/mfmfuyu)
* [puckipedia](https://github.com/puckipedia) * [puckipedia](https://github.com/puckipedia)
* [spla](mailto:spla@mastodont.cat) * [spla](mailto:spla@mastodont.cat)
* [walf443](https://github.com/walf443) * [walf443](https://github.com/walf443)
@ -111,14 +112,15 @@ and provided thanks to the work of the following contributors:
* [Ashley](mailto:expenses@airmail.cc) * [Ashley](mailto:expenses@airmail.cc)
* [xqus](https://github.com/xqus) * [xqus](https://github.com/xqus)
* [pfm-eyesightjp](https://github.com/pfm-eyesightjp) * [pfm-eyesightjp](https://github.com/pfm-eyesightjp)
* [Samy KACIMI](mailto:samy.kacimi@gmail.com) * [fakenine](https://github.com/fakenine)
* [tsuwatch](https://github.com/tsuwatch) * [tsuwatch](https://github.com/tsuwatch)
* [victorhck](https://github.com/victorhck) * [victorhck](https://github.com/victorhck)
* [mkljczk](https://github.com/mkljczk)
* [manuelviens](https://github.com/manuelviens) * [manuelviens](https://github.com/manuelviens)
* [tateisu](https://github.com/tateisu)
* [fvh-P](https://github.com/fvh-P) * [fvh-P](https://github.com/fvh-P)
* [rtucker](https://github.com/rtucker) * [rtucker](https://github.com/rtucker)
* [Anna e só](mailto:contraexemplos@gmail.com) * [Anna e só](mailto:contraexemplos@gmail.com)
* [dariusk](https://github.com/dariusk)
* [kazu9su](https://github.com/kazu9su) * [kazu9su](https://github.com/kazu9su)
* [Komic](https://github.com/Komic) * [Komic](https://github.com/Komic)
* [lmorchard](https://github.com/lmorchard) * [lmorchard](https://github.com/lmorchard)
@ -145,9 +147,9 @@ and provided thanks to the work of the following contributors:
* [fhemberger](https://github.com/fhemberger) * [fhemberger](https://github.com/fhemberger)
* [Gomasy](https://github.com/Gomasy) * [Gomasy](https://github.com/Gomasy)
* [greysteil](https://github.com/greysteil) * [greysteil](https://github.com/greysteil)
* [hencatsmith](https://github.com/hencatsmith) * [hendotcat](https://github.com/hendotcat)
* [d6rkaiz](https://github.com/d6rkaiz) * [d6rkaiz](https://github.com/d6rkaiz)
* [Reverite](https://github.com/Reverite) * [ladyisatis](https://github.com/ladyisatis)
* [JohnD28](https://github.com/JohnD28) * [JohnD28](https://github.com/JohnD28)
* [znz](https://github.com/znz) * [znz](https://github.com/znz)
* [saper](https://github.com/saper) * [saper](https://github.com/saper)
@ -160,14 +162,14 @@ and provided thanks to the work of the following contributors:
* [leopku](https://github.com/leopku) * [leopku](https://github.com/leopku)
* [SansPseudoFix](https://github.com/SansPseudoFix) * [SansPseudoFix](https://github.com/SansPseudoFix)
* [spla](mailto:sp@mastodont.cat) * [spla](mailto:sp@mastodont.cat)
* [tateisu](https://github.com/tateisu)
* [tomfhowe](https://github.com/tomfhowe) * [tomfhowe](https://github.com/tomfhowe)
* [noraworld](https://github.com/noraworld) * [noraworld](https://github.com/noraworld)
* [lfuelling](https://github.com/lfuelling) * [lfuelling](https://github.com/lfuelling)
* [theboss](https://github.com/theboss) * [aji-su](https://github.com/aji-su)
* [nzws](https://github.com/nzws) * [nzws](https://github.com/nzws)
* [duxovni](https://github.com/duxovni) * [duxovni](https://github.com/duxovni)
* [smorimoto](https://github.com/smorimoto) * [smorimoto](https://github.com/smorimoto)
* [mashirozx](https://github.com/mashirozx)
* [178inaba](https://github.com/178inaba) * [178inaba](https://github.com/178inaba)
* [acid-chicken](https://github.com/acid-chicken) * [acid-chicken](https://github.com/acid-chicken)
* [xgess](https://github.com/xgess) * [xgess](https://github.com/xgess)
@ -175,7 +177,6 @@ and provided thanks to the work of the following contributors:
* [aablinov](https://github.com/aablinov) * [aablinov](https://github.com/aablinov)
* [stalker314314](https://github.com/stalker314314) * [stalker314314](https://github.com/stalker314314)
* [cutls](https://github.com/cutls) * [cutls](https://github.com/cutls)
* [dariusk](https://github.com/dariusk)
* [huertanix](https://github.com/huertanix) * [huertanix](https://github.com/huertanix)
* [eleboucher](https://github.com/eleboucher) * [eleboucher](https://github.com/eleboucher)
* [halkeye](https://github.com/halkeye) * [halkeye](https://github.com/halkeye)
@ -183,7 +184,7 @@ and provided thanks to the work of the following contributors:
* [treby](https://github.com/treby) * [treby](https://github.com/treby)
* [jpdevries](https://github.com/jpdevries) * [jpdevries](https://github.com/jpdevries)
* [gdpelican](https://github.com/gdpelican) * [gdpelican](https://github.com/gdpelican)
* [kmichl](https://github.com/kmichl) * [Korbinian](mailto:kontakt@korbinian-michl.de)
* [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name) * [Kurtis Rainbolt-Greene](mailto:me@kurtisrainboltgreene.name)
* [panarom](https://github.com/panarom) * [panarom](https://github.com/panarom)
* [Dar13](https://github.com/Dar13) * [Dar13](https://github.com/Dar13)
@ -225,6 +226,7 @@ and provided thanks to the work of the following contributors:
* [aaribaud](https://github.com/aaribaud) * [aaribaud](https://github.com/aaribaud)
* [pointlessone](https://github.com/pointlessone) * [pointlessone](https://github.com/pointlessone)
* [Andrew](mailto:andrewlchronister@gmail.com) * [Andrew](mailto:andrewlchronister@gmail.com)
* [arielrodrigues](https://github.com/arielrodrigues)
* [aurelien-reeves](https://github.com/aurelien-reeves) * [aurelien-reeves](https://github.com/aurelien-reeves)
* [elegaanz](https://github.com/elegaanz) * [elegaanz](https://github.com/elegaanz)
* [estuans](https://github.com/estuans) * [estuans](https://github.com/estuans)
@ -238,6 +240,7 @@ and provided thanks to the work of the following contributors:
* [muffinista](https://github.com/muffinista) * [muffinista](https://github.com/muffinista)
* [cdutson](https://github.com/cdutson) * [cdutson](https://github.com/cdutson)
* [farlistener](https://github.com/farlistener) * [farlistener](https://github.com/farlistener)
* [divergentdave](https://github.com/divergentdave)
* [DavidLibeau](https://github.com/DavidLibeau) * [DavidLibeau](https://github.com/DavidLibeau)
* [dmerejkowsky](https://github.com/dmerejkowsky) * [dmerejkowsky](https://github.com/dmerejkowsky)
* [ddevault](https://github.com/ddevault) * [ddevault](https://github.com/ddevault)
@ -276,7 +279,7 @@ and provided thanks to the work of the following contributors:
* [xPaw](https://github.com/xPaw) * [xPaw](https://github.com/xPaw)
* [petzah](https://github.com/petzah) * [petzah](https://github.com/petzah)
* [ignisf](https://github.com/ignisf) * [ignisf](https://github.com/ignisf)
* [raymestalez](https://github.com/raymestalez) * [lumenwrites](https://github.com/lumenwrites)
* [remram44](https://github.com/remram44) * [remram44](https://github.com/remram44)
* [sts10](https://github.com/sts10) * [sts10](https://github.com/sts10)
* [SuperSandro2000](https://github.com/SuperSandro2000) * [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) * [Sir-Boops](https://github.com/Sir-Boops)
* [stemid](https://github.com/stemid) * [stemid](https://github.com/stemid)
* [sumdog](https://github.com/sumdog) * [sumdog](https://github.com/sumdog)
* [OmmyZhang](https://github.com/OmmyZhang)
* [ThomasLeister](https://github.com/ThomasLeister) * [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) * [tototoshi](https://github.com/tototoshi)
* [TrashMacNugget](https://github.com/TrashMacNugget) * [TrashMacNugget](https://github.com/TrashMacNugget)
* [VirtuBox](https://github.com/VirtuBox) * [VirtuBox](https://github.com/VirtuBox)
@ -314,11 +318,13 @@ and provided thanks to the work of the following contributors:
* [matsurai25](https://github.com/matsurai25) * [matsurai25](https://github.com/matsurai25)
* [mecab](https://github.com/mecab) * [mecab](https://github.com/mecab)
* [nicobz25](https://github.com/nicobz25) * [nicobz25](https://github.com/nicobz25)
* [niwatori24](https://github.com/niwatori24)
* [oliverkeeble](https://github.com/oliverkeeble) * [oliverkeeble](https://github.com/oliverkeeble)
* [partev](https://github.com/partev) * [partev](https://github.com/partev)
* [pinfort](https://github.com/pinfort) * [pinfort](https://github.com/pinfort)
* [rbaumert](https://github.com/rbaumert) * [rbaumert](https://github.com/rbaumert)
* [rhoio](https://github.com/rhoio) * [rhoio](https://github.com/rhoio)
* [santiagorodriguez96](https://github.com/santiagorodriguez96)
* [sclaire-1](https://github.com/sclaire-1) * [sclaire-1](https://github.com/sclaire-1)
* [umonaca](https://github.com/umonaca) * [umonaca](https://github.com/umonaca)
* [usagi-f](https://github.com/usagi-f) * [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) * [wxcafe](https://github.com/wxcafe)
* [Grawl](https://github.com/Grawl) * [Grawl](https://github.com/Grawl)
* [新都心(Neet Shin)](mailto:nucx@dio-vox.com) * [新都心(Neet Shin)](mailto:nucx@dio-vox.com)
* [clarfon](https://github.com/clarfon) * [clarfonthey](https://github.com/clarfonthey)
* [cygnan](https://github.com/cygnan) * [cygnan](https://github.com/cygnan)
* [Awea](https://github.com/Awea) * [Awea](https://github.com/Awea)
* [eai04191](https://github.com/eai04191) * [eai04191](https://github.com/eai04191)
@ -358,11 +364,11 @@ and provided thanks to the work of the following contributors:
* [schas002](https://github.com/schas002) * [schas002](https://github.com/schas002)
* [contraexemplo](https://github.com/contraexemplo) * [contraexemplo](https://github.com/contraexemplo)
* [abackstrom](https://github.com/abackstrom) * [abackstrom](https://github.com/abackstrom)
* [arielrodrigues](https://github.com/arielrodrigues)
* [orlea](https://github.com/orlea) * [orlea](https://github.com/orlea)
* [armandfardeau](https://github.com/armandfardeau) * [armandfardeau](https://github.com/armandfardeau)
* [raboof](https://github.com/raboof) * [raboof](https://github.com/raboof)
* [jumbosushi](https://github.com/jumbosushi) * [jumbosushi](https://github.com/jumbosushi)
* [acuteaura](https://github.com/acuteaura)
* [ayumin](https://github.com/ayumin) * [ayumin](https://github.com/ayumin)
* [bzg](https://github.com/bzg) * [bzg](https://github.com/bzg)
* [BastienDurel](https://github.com/BastienDurel) * [BastienDurel](https://github.com/BastienDurel)
@ -389,7 +395,7 @@ and provided thanks to the work of the following contributors:
* [colindean](https://github.com/colindean) * [colindean](https://github.com/colindean)
* [DeeUnderscore](https://github.com/DeeUnderscore) * [DeeUnderscore](https://github.com/DeeUnderscore)
* [dachinat](https://github.com/dachinat) * [dachinat](https://github.com/dachinat)
* [shapeshifter-system](https://github.com/shapeshifter-system) * [monsterpit-firedemon](https://github.com/monsterpit-firedemon)
* [watilde](https://github.com/watilde) * [watilde](https://github.com/watilde)
* [daprice](https://github.com/daprice) * [daprice](https://github.com/daprice)
* [da2x](https://github.com/da2x) * [da2x](https://github.com/da2x)
@ -400,14 +406,13 @@ and provided thanks to the work of the following contributors:
* [singingwolfboy](https://github.com/singingwolfboy) * [singingwolfboy](https://github.com/singingwolfboy)
* [caldwell](https://github.com/caldwell) * [caldwell](https://github.com/caldwell)
* [davidcelis](https://github.com/davidcelis) * [davidcelis](https://github.com/davidcelis)
* [divergentdave](https://github.com/divergentdave)
* [davefp](https://github.com/davefp) * [davefp](https://github.com/davefp)
* [yipdw](https://github.com/yipdw) * [yipdw](https://github.com/yipdw)
* [debanshuk](https://github.com/debanshuk) * [debanshuk](https://github.com/debanshuk)
* [mascali33](https://github.com/mascali33) * [mascali33](https://github.com/mascali33)
* [DerekNonGeneric](https://github.com/DerekNonGeneric) * [DerekNonGeneric](https://github.com/DerekNonGeneric)
* [dblandin](https://github.com/dblandin) * [dblandin](https://github.com/dblandin)
* [Drew Gates](mailto:aranaur@users.noreply.github.com) * [Aranaur](https://github.com/Aranaur)
* [dtschust](https://github.com/dtschust) * [dtschust](https://github.com/dtschust)
* [Dryusdan](https://github.com/Dryusdan) * [Dryusdan](https://github.com/Dryusdan)
* [d3vgru](https://github.com/d3vgru) * [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) * [J Yeary](mailto:usbsnowcrash@users.noreply.github.com)
* [jack-michaud](https://github.com/jack-michaud) * [jack-michaud](https://github.com/jack-michaud)
* [Floppy](https://github.com/Floppy) * [Floppy](https://github.com/Floppy)
* [loomchild](https://github.com/loomchild) * [Jarek Lipski](mailto:pub@loomchild.net)
* [jglauche](https://github.com/jglauche) * [Jennifer Glauche](mailto:=^.^=@github19.jglauche.de)
* [jenkr55](https://github.com/jenkr55) * [Jennifer Kruse](mailto:jenkr55@gmail.com)
* [hyenagirl64](https://github.com/hyenagirl64) * [Jeremy Rose](mailto:nornagon@nornagon.net)
* [press5](https://github.com/press5) * [Jessica](mailto:46502909+hyenagirl64@users.noreply.github.com)
* [TrollDecker](https://github.com/TrollDecker) * [Jessica K. Litwin](mailto:jessica@litw.in)
* [jmontane](https://github.com/jmontane) * [Jo Decker](mailto:trolldecker@users.noreply.github.com)
* [Joan Montané](mailto:jmontane@users.noreply.github.com)
* [Jonathan Klee](mailto:klee.jonathan@gmail.com) * [Jonathan Klee](mailto:klee.jonathan@gmail.com)
* [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net) * [Jordan Guerder](mailto:jguerder@fr.pulseheberg.net)
* [Joseph Mingrone](mailto:jehops@users.noreply.github.com) * [Joseph Mingrone](mailto:jehops@users.noreply.github.com)
* [Josh Leeb-du Toit](mailto:mail@joshleeb.com)
* [Joshua Wood](mailto:josh@joshuawood.net) * [Joshua Wood](mailto:josh@joshuawood.net)
* [Julien](mailto:tiwy57@users.noreply.github.com) * [Julien](mailto:tiwy57@users.noreply.github.com)
* [Julien Deswaef](mailto:juego@requiem4tv.com) * [Julien Deswaef](mailto:juego@requiem4tv.com)
* [June Sallou](mailto:jnsll@users.noreply.github.com) * [June Sallou](mailto:jnsll@users.noreply.github.com)
* [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com) * [Jérémy Benoist](mailto:j0k3r@users.noreply.github.com)
* [KEINOS](mailto:github@keinos.com) * [KEINOS](mailto:github@keinos.com)
* [Kairui Song | 宋恺睿](mailto:ryncsn@gmail.com)
* [Keiji Matsuzaki](mailto:futoase@gmail.com) * [Keiji Matsuzaki](mailto:futoase@gmail.com)
* [Kevin Liu](mailto:kevin@potatofrom.space) * [Kevin Liu](mailto:kevin@potatofrom.space)
* [Kit Redgrave](mailto:qwertyitis@gmail.com) * [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) * [Lukas Burk](mailto:jemus42@users.noreply.github.com)
* [Manato Kameya](mailto:grabacr07+github@gmail.com) * [Manato Kameya](mailto:grabacr07+github@gmail.com)
* [Mantas](mailto:mistermantas@users.noreply.github.com) * [Mantas](mailto:mistermantas@users.noreply.github.com)
* [Marcin Mikołajczak](mailto:me@mkljczk.pl)
* [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com) * [Mareena Kunjachan](mailto:mareenakunjachan@gmail.com)
* [Marek Lach](mailto:marek.brohatwack.lach@gmail.com) * [Marek Lach](mailto:marek.brohatwack.lach@gmail.com)
* [Markus R](mailto:wirehack7@users.noreply.github.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) * [Norayr Chilingarian](mailto:norayr@arnet.am)
* [Noëlle Anthony](mailto:noelle.d.anthony@gmail.com) * [Noëlle Anthony](mailto:noelle.d.anthony@gmail.com)
* [N氏](mailto:uenok.htc@gmail.com) * [N氏](mailto:uenok.htc@gmail.com)
* [OSAMU SATO](mailto:satosamu@gmail.com)
* [Olivier Nicole](mailto:olivierthnicole@gmail.com) * [Olivier Nicole](mailto:olivierthnicole@gmail.com)
* [Oskari Noppa](mailto:noppa@users.noreply.github.com) * [Oskari Noppa](mailto:noppa@users.noreply.github.com)
* [Otakan](mailto:otakan951@gmail.com) * [Otakan](mailto:otakan951@gmail.com)
* [Padraig Fahy](mailto:tech@padraigfahy.com) * [Padraig Fahy](mailto:tech@padraigfahy.com)
* [Patrice Ferlet](mailto:metal3d@gmail.com)
* [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com) * [PatrickRWells](mailto:32802366+patrickrwells@users.noreply.github.com)
* [Paul](mailto:naydex.mc+github@gmail.com) * [Paul](mailto:naydex.mc+github@gmail.com)
* [Pete Keen](mailto:pete@petekeen.net) * [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) * [TakesxiSximada](mailto:takesxi.sximada@gmail.com)
* [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com) * [Tao Bror Bojlén](mailto:brortao@users.noreply.github.com)
* [Taras Gogol](mailto:taras2358@gmail.com) * [Taras Gogol](mailto:taras2358@gmail.com)
* [Tdxdxoz](mailto:tdxdxoz@gmail.com)
* [TheInventrix](mailto:theinventrix@users.noreply.github.com) * [TheInventrix](mailto:theinventrix@users.noreply.github.com)
* [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com) * [TheMainOne](mailto:50847364+theevilskeleton@users.noreply.github.com)
* [Thomas Alberola](mailto:thomas@needacoffee.fr) * [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) * [Wesley Ellis](mailto:tahnok@gmail.com)
* [Wiktor](mailto:wiktor@metacode.biz) * [Wiktor](mailto:wiktor@metacode.biz)
* [Wonderfall](mailto:wonderfall@schrodinger.io) * [Wonderfall](mailto:wonderfall@schrodinger.io)
* [Y.Yamashiro](mailto:shukukei@mojizuri.jp)
* [YDrogen](mailto:ydrogen45@gmail.com) * [YDrogen](mailto:ydrogen45@gmail.com)
* [YMHuang](mailto:ymhuang@fmbase.tw) * [YMHuang](mailto:ymhuang@fmbase.tw)
* [YOSHIOKA Eiichiro](mailto:yoshioka.eiichiro@gmail.com) * [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) * [jumoru](mailto:jumoru@mailbox.org)
* [kaiyou](mailto:pierre@jaury.eu) * [kaiyou](mailto:pierre@jaury.eu)
* [karlyeurl](mailto:karl.yeurl@gmail.com) * [karlyeurl](mailto:karl.yeurl@gmail.com)
* [kawaguchi](mailto:jiikko@users.noreply.github.com)
* [kedama](mailto:32974885+kedamadq@users.noreply.github.com) * [kedama](mailto:32974885+kedamadq@users.noreply.github.com)
* [kuro5hin](mailto:rusty@kuro5hin.org) * [kuro5hin](mailto:rusty@kuro5hin.org)
* [leo60228](mailto:leo@60228.dev) * [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) * [notozeki](mailto:notozeki@users.noreply.github.com)
* [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com) * [ntl-purism](mailto:57806346+ntl-purism@users.noreply.github.com)
* [nzws](mailto:git-yuzu@svk.jp) * [nzws](mailto:git-yuzu@svk.jp)
* [proxy](mailto:51172302+3n-k1@users.noreply.github.com)
* [rch850](mailto:rich850@gmail.com) * [rch850](mailto:rich850@gmail.com)
* [roikale](mailto:roikale@users.noreply.github.com) * [roikale](mailto:roikale@users.noreply.github.com)
* [rysiekpl](mailto:rysiek@hackerspace.pl) * [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: Following people have contributed to translation of Mastodon:
- ᏦᏁᎢᎵᏫ 😷 (*Spanish, Argentina*) - ᏦᏁᎢᎵᏫ 😷 (KNTRO) (*Spanish, Argentina*)
- Sveinn í Felli (*Icelandic*) - Sveinn í Felli (sveinki) (*Icelandic*)
- qezwan (*Persian, Sorani (Kurdish)*)
- Hồ Nhất Duy (kantcer) (*Vietnamese*)
- taicv (*Vietnamese*) - taicv (*Vietnamese*)
- ButterflyOfFire (*Arabic; French; Kabyle*) - Zoltán Gera (gerazo) (*Hungarian*)
- Duy (*Vietnamese*) - ButterflyOfFire (BoFFire) (*French, Arabic, Kabyle*)
- 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*)
- adrmzz (*Sardinian*) - 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*) - d5Ziif3K (*Ukrainian*)
- GiorgioHerbie (*Italian*) - GiorgioHerbie (*Italian*)
- Rafael H L Moretti (Moretti) (*Portuguese, Brazilian*)
- Saederup92 (*Danish*)
- christalleras (*Norwegian Nynorsk*) - christalleras (*Norwegian Nynorsk*)
- cybergene (cyber-gene) (*Japanese*)
- Taloran (*Norwegian Nynorsk*) - Taloran (*Norwegian Nynorsk*)
- ThibG (*French; Icelandic*) - ThibG (*French, Icelandic*)
- Akarshan Biswas (*Bengali*) - xatier (*Chinese Traditional*)
- otrapersona (*Spanish, Spanish, Mexico*)
- atarashiako (*Chinese Simplified*) - atarashiako (*Chinese Simplified*)
- 101010 (*Polish*) - 101010 (101010pl) (*Polish*)
- silkevicious (*Italian*) - silkevicious (*Italian*)
- Bertil Hedkvist (*Swedish*) - Floxu (fredrikdim1) (*Norwegian Nynorsk*)
- cybergene (*Japanese*) - Bertil Hedkvist (Berrahed) (*Swedish*)
- William(ѕ)ⁿ (wmlgr) (*Spanish*)
- norayr (*Armenian*) - norayr (*Armenian*)
- William(ѕ)ⁿ (*Spanish*) - Tiago Epifânio (tfve) (*Portuguese*)
- Tiago Epifânio (*Portuguese*) - Ryo (DrRyo) (*Korean*)
- Mentor Gashi (*Albanian*) - Mentor Gashi (mentorgashi.com) (*Albanian*)
- Jaz-Michael King (*Welsh*) - Jaz-Michael King (jazmichaelking) (*Welsh*)
- carolinagiorno (*Portuguese, Brazilian*) - carolinagiorno (*Portuguese, Brazilian*)
- Roby Thomas (*Malayalam*) - Roby Thomas (roby.thomas) (*Malayalam*)
- Bharat Kumar (*Hindi*) - Bharat Kumar (Marwari) (*Hindi*)
- ThonyVezbe (*Breton*)
- dkdarshan760 (*Sanskrit*)
- Tagomago (tagomago) (*French, Spanish*)
- tykayn (*French*) - tykayn (*French*)
- axi (*Finnish*) - axi (*Finnish*)
- Selyan Slimane AMIRI (*Kabyle*) - Selyan Slimane AMIRI (slimane_AMIRI) (*Kabyle*)
- Balázs Meskó (mesko.balazs) (*Hungarian*)
- taoxvx (*Danish*) - taoxvx (*Danish*)
- Hrach Mkrtchyan (*Armenian*) - Hrach Mkrtchyan (mhrach87) (*Armenian*)
- sabri (*Spanish; Spanish, Argentina*) - sabri (thetomatoisavegetable) (*Spanish, Spanish, Argentina*)
- Dewi (*Breton; French*) - Dewi (Unkorneg) (*French, Breton*)
- Coelacanthus (*Chinese Simplified*)
- syncopams (*Chinese Simplified, Chinese Traditional, Chinese Traditional, Hong Kong*)
- SteinarK (*Norwegian Nynorsk*) - SteinarK (*Norwegian Nynorsk*)
- Mathias B. Vagnes (*Norwegian*) - Sokratis Alichanidis (alichani) (*Greek*)
- dashersyed (*Urdu*) - Mathias B. Vagnes (vagnes) (*Norwegian*)
- ThonyVezbe (*Breton*) - dashersyed (*Urdu (Pakistan)*)
- Acolyte (*Ukrainian*) - Acolyte (666noob404) (*Ukrainian*)
- Conight Wang (*Chinese Simplified*) - Conight Wang (xfddwhh) (*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*)
- liffon (*Swedish*) - liffon (*Swedish*)
- dxwc (*Bengali*) - Damjan Dimitrioski (gnud) (*Macedonian*)
- Saederup92 (*Danish*) - PPNplus (*Thai*)
- Vanege (*Esperanto*) - shioko (*Chinese Simplified*)
- jmontane (*Catalan*) - v4vachan (*Malayalam*)
- Johan Schiff (*Swedish*) - Hakim Oubouali (zenata1) (*Standard Moroccan Tamazight*)
- Arunmozhi (*Tamil*) - Evgeny Petrov (kondra007) (*Russian*)
- kat (*Ukrainian; Russian*) - Gwenn (Belvar) (*Breton*)
- 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*)
- StanleyFrew (*French*) - 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*) - erikstl (*Esperanto*)
- twpenguin (*Chinese Traditional*)
- bobchao (*Chinese Traditional*)
- Esther (esthermations) (*Portuguese*)
- MadeInSteak (*Finnish*) - MadeInSteak (*Finnish*)
- Heimen Stoffels (*Dutch*) - Heimen Stoffels (vistausss) (*Dutch*)
- Rajarshi Guha (*Bengali*) - Rajarshi Guha (rajarshiguha) (*Bengali*)
- Andrew (*Romanian*) - Andrew (iAndrew3) (*Romanian*)
- Goudarz Jafari (*Persian*) - Gopal Sharma (gopalvirat) (*Hindi*)
- arethsu (*Swedish*) - arethsu (*Swedish*)
- Carlos Solís (*Esperanto*) - Tofiq Abdula (Xwla) (*Sorani (Kurdish)*)
- Parthan S Ramanujam (*Tamil*) - Carlos Solís (csolisr) (*Esperanto*)
- Ali Demirtaş (*Turkish*) - Parthan S Ramanujam (parthan) (*Tamil*)
- Kasper Nymand (*Danish*) - Kasper Nymand (KasperNymand) (*Danish*)
- TS (*Finnish*) - TS (morte) (*Finnish*)
- subram (*Turkish*)
- SensDeViata (*Ukrainian*) - SensDeViata (*Ukrainian*)
- Ptrcmd (ptrcmd) (*Chinese Traditional*)
- SergioFMiranda (*Portuguese, Brazilian*) - SergioFMiranda (*Portuguese, Brazilian*)
- OctolinGamer (*Portuguese, Brazilian*) - Scvoet (scvoet) (*Chinese Simplified*)
- hiroTS (*Chinese Traditional*)
- johne32rus23 (*Russian*)
- AzureNya (*Chinese Simplified*) - AzureNya (*Chinese Simplified*)
- Ram varma (*Tamil*) - OctolinGamer (octolingamer) (*Portuguese, Brazilian*)
- 北䑓如法 (*Japanese*) - Ram varma (ram4varma) (*Tamil*)
- Hexandcube (hexandcube) (*Polish*)
- 北䑓如法 (Nyoho) (*Japanese*)
- frumble (*German*) - frumble (*German*)
- kekkepikkuni (*Tamil*) - kekkepikkuni (*Tamil*)
- Neo_Chen (NeoChen1024) (*Chinese Traditional*)
- oorsutri (*Tamil*) - oorsutri (*Tamil*)
- Nithin V (*Tamil*) - Rhys Harrison (rhedders) (*Esperanto*)
- Miro Rauhala (*Finnish*) - Nithin V (Nithin896) (*Tamil*)
- Miro Rauhala (mirorauhala) (*Finnish*)
- diorama (*Italian*) - diorama (*Italian*)
- Rhys Harrison (*Esperanto*) - AlexKoala (alexkoala) (*Korean*)
- Guillaume Turchini (*French*) - Aswin C (officialcjunior) (*Malayalam*)
- Ganesh D (*Marathi*) - Guillaume Turchini (orion78fr) (*French*)
- Ganesh D (auntgd) (*Marathi*)
- dragnucs2 (*Arabic*) - dragnucs2 (*Arabic*)
- Pedro Henrique (*Portuguese, Brazilian*) - Ryan Ho (koungho) (*Chinese Traditional*)
- Tejas Harad (*Marathi*) - Pedro Henrique (exploronauta) (*Portuguese, Brazilian*)
- Vasanthan (*Tamil*) - Tejas Harad (h_tejas) (*Marathi*)
- 硫酸鶏 (*Japanese*) - Vasanthan (vasanthan) (*Tamil*)
- 硫酸鶏 (acid_chicken) (*Japanese*)
- clarmin b8 (clarminb8) (*Sorani (Kurdish)*)
- manukp (*Malayalam*) - manukp (*Malayalam*)
- psymyn (*Hebrew*) - psymyn (*Hebrew*)
- earth dweller (*Marathi*) - earth dweller (sanethoughtyt) (*Marathi*)
- meijerivoi (*Finnish*) - meijerivoi (toilet) (*Finnish*)
- essaar (*Tamil*) - essaar (*Tamil*)
- serubeena (*Swedish*) - serubeena (*Swedish*)
- Karol Kosek (krkkPL) (*Polish*)
- Rintan (*Japanese*) - Rintan (*Japanese*)
- Karol Kosek (*Polish*)
- valarivan (*Tamil*) - valarivan (*Tamil*)
- Sebastián Andil (*Slovak*) - Hernik (hernik27) (*Czech*)
- v4vachan (*Malayalam*) - Sebastián Andil (Selrond) (*Slovak*)
- KEINOS (*Japanese*) - Hinaloe (hinaloe) (*Japanese*)
- Ivan T. (*Chinese Traditional, Hong Kong*)
- filippodb (*Italian*) - filippodb (*Italian*)
- Balázs Meskó (*Hungarian*) - KEINOS (*Japanese*)
- Balázs Meskó (meskobalazs) (*Hungarian*)
- Bottle (suryasalem2010) (*Tamil*)
- JzshAC (*Chinese Simplified*) - JzshAC (*Chinese Simplified*)
- Bottle (*Tamil*) - Wrya ali (John12) (*Sorani (Kurdish)*)
- Khóo (*Chinese Traditional*) - Khóo (khootiatling) (*Chinese Traditional*)
- Steven Tappert (*German*) - Steven Tappert (sammy8806) (*German*)
- Antillion (*Spanish*) - Antillion (antillion99) (*Spanish*)
- ZiriSut (*Kabyle*) - Pukima (Pukimaa) (*German*)
- gowthamanb (*Tamil*)
- hiphipvargas (*Portuguese*)
- Arttu Ylhävuori (*Finnish*)
- Ch. (*Korean*)
- tctovsli (*Norwegian Nynorsk*)
- Hinaloe (*Japanese*)
- strubbl (*German*)
- vjasiegd (*Polish*)
- SamitiMed (*Thai*)
- Reg3xp (*Persian*) - 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*) - umelard (*Hebrew*)
- Antara2Cinta (Se7enTime) (*Indonesian*)
- VSx86 (*Russian*) - VSx86 (*Russian*)
- Daniel Dimitrov (*Bulgarian*) - Daniel Dimitrov (danny-dimitrov) (*Bulgarian*)
- mynameismonkey (*Welsh*)
- parnikkapore (*Thai*) - 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*) - SKELET (*Danish*)
- Renato "Lond" Cerqueira (*Portuguese, Brazilian*) - Mo_der Steven (SakuraPuare) (*Chinese Simplified*)
- Fei Yang (Fei1Yang) (*Chinese Traditional*)
- ALEM FARID (faridatcemlulaqbayli) (*Kabyle*)
- enipra (*Armenian*) - enipra (*Armenian*)
- musix (*Persian*) - musix (*Persian*)
- ギャラ (*Chinese Simplified; Japanese*) - Renato "Lond" Cerqueira (renatolond) (*Portuguese, Brazilian*)
- ALEM FARID (*Kabyle*) - ギャラ (gyara) (*Japanese, Chinese Simplified*)
- Hougo (hougo) (*French*)
- ybardapurkar (*Marathi*) - ybardapurkar (*Marathi*)
- Adrián Lattes (*Spanish*) - Adrián Lattes (haztecaso) (*Spanish*)
- TracyJacks (*Chinese Simplified*)
- rasheedgm (*Kannada*) - 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*) - GatoOscuro (*Spanish*)
- Lamin (*Japanese*) - mecqor labi (mecqorlabi) (*Persian*)
- Marcepanek_ (*Polish*) - Belkacem Mohammed (belkacem77) (*Kabyle*)
- Yann Aguettaz (*French*) - Navjot Singh (nspeaks) (*Hindi*)
- Feruz Oripov (*Russian*) - omquylzu (*Latvian*)
- Mick Onio (*Asturian*) - Ozai (*German*)
- hg6 (*Hindi*) - Sahak Petrosyan (petrosyan) (*Armenian*)
- Malik Mann (*German*) - siamano (*Thai, Esperanto*)
- padulafacundo (*Spanish*) - 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*) - r3dsp1 (*Chinese Traditional, Hong Kong*)
- Tianqi Zhang (*Chinese Simplified*) - padulafacundo (*Spanish*)
- Padraic Calpin (*Slovenian*) - hg6 (*Hindi*)
- cenegd (*Chinese Simplified*) - Orlando Murcio (Atos20) (*Spanish, Mexico*)
- piupiupiudiu (*Chinese Simplified*) - piupiupiudiu (*Chinese Simplified*)
- Hugh Liu (*Chinese Simplified*) - shdy (*German*)
- Rakino (*Chinese Simplified*) - Padraic Calpin (padraic-padraic) (*Slovenian*)
- Jothipazhani Nagarajan (*Tamil*) - Ильзира Рахматуллина (rahmatullinailzira53) (*Tatar*)
- Miquel Sabaté Solà (*Catalan*) - 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*) - AmazighNM (*Kabyle*)
- Solid Rhino (*Dutch*) - Jothipazhani Nagarajan (jothipazhani.n) (*Tamil*)
- Clash Clans (KURD12345) (*Sorani (Kurdish)*)
- hallomaurits (*Dutch*) - hallomaurits (*Dutch*)
- alnd hezh (alndhezh) (*Sorani (Kurdish)*)
- Solid Rhino (SolidRhino) (*Dutch*)
- k_taka (peaceroad) (*Japanese*)
- Hallo Abdullah (hallo_hamza12) (*Sorani (Kurdish)*)
- hussama (*Portuguese, Brazilian*) - hussama (*Portuguese, Brazilian*)
- shafouz (*Portuguese, Brazilian*) - Sébastien Feugère (smonff) (*French*)
- Tagada (*French*) - 林水溶 (shuiRong) (*Chinese Simplified*)
- Tom_ (*Czech*)
- SnDer (*Dutch*)
- eichkat3r (*German*) - eichkat3r (*German*)
- PifyZ (*French*)
- OminousCry (*Russian*) - OminousCry (*Russian*)
- Shrinivasan T (*Tamil*) - SnDer (*Dutch*)
- Nathaël Noguès (*French*) - PifyZ (*French*)
- Daniel M. (*Catalan*) - Tom_ (*Czech*)
- Swati Sani (*Urdu*) - Tagada (Tagadda) (*French*)
- Kk (*Kannada*) - shafouz (*Portuguese, Brazilian*)
- SusVersiva (*Catalan*) - Kahina Mess (K_hina) (*Kabyle*)
- Robin van der Vliet (*Esperanto*) - Nathaël Noguès (NatNgs) (*French*)
- Zinkokooo (*Basque*) - Kk (kishorkumara3) (*Kannada*)
- Tradjincal (*French*) - Swati Sani (swatisani) (*Urdu (Pakistan)*)
- Shrinivasan T (tshrinivasan) (*Tamil*)
- さっかりんにーさん (saccharin23) (*Japanese*)
- 夜楓Yoka (Yoka2627) (*Chinese Simplified*)
- Daniel M. (daniconil) (*Catalan*)
- Vikatakavi (*Kannada*) - Vikatakavi (*Kannada*)
- prabhjot (*Hindi*) - SusVersiva (*Catalan*)
- twpenguin (*Chinese Traditional*) - Tradjincal (tradjincal) (*French*)
- pullopen (*Chinese Simplified*)
- Robin van der Vliet (RobinvanderVliet) (*Esperanto*)
- Zinkokooo (*Basque*)
- mmokhi (*Persian*) - mmokhi (*Persian*)
- Livingston Samuel (livingston) (*Tamil*)
- prabhjot (*Hindi*)
- sergioaraujo1 (*Portuguese, Brazilian*) - sergioaraujo1 (*Portuguese, Brazilian*)
- Livingston Samuel (*Tamil*) - CyberAmoeba (pseudoobscura) (*Chinese Simplified*)
- tsundoker (*Malayalam*) - tsundoker (*Malayalam*)
- skaaarrr (*German*) - skaaarrr (*German*)
- 夜楓Yoka (*Chinese Simplified*) - Ricardo Colin (rysard) (*Spanish*)
- kiwi0 (*Italian*) - mkljczk (mykylyjczyk) (*Polish*)
- Philipp Fischbeck (PFischbeck) (*German*)
- fedot (*Russian*) - fedot (*Russian*)
- mkljczk (*Polish*) - Paz Galindo (paz.almendra.g) (*Spanish*)
- igordrozniak (*Polish*) - GaggiX (*Italian*)
- Ricardo Colin (*Spanish*)
- Esther (*Portuguese*)
- Paz Galindo (*Spanish*)
- Philipp Fischbeck (*German*)
- ralozkolya (*Georgian*) - ralozkolya (*Georgian*)
- JackXu (*Chinese Simplified*) - Zoé Bőle (zoe1337) (*German*)
- Allen Zhong (*Chinese Simplified*) - Lukas Fülling (lfuelling) (*German*)
- Zoé Bőle (*German*) - JackXu (Merman-Jack) (*Chinese Simplified*)
- Lukas Fülling (*German*) - Aymeric (AymBroussier) (*French*)
- Albatroz Jeremias (*Portuguese*) - Anoop (anoopp) (*Malayalam*)
- Samir Tighzert (*Kabyle*)
- Nocta (*French*)
- Anoop (*Malayalam*)
- pezcurrel (*Italian*) - pezcurrel (*Italian*)
- Dremski (*Bulgarian*) - Dremski (*Bulgarian*)
- Aymeric (*French*) - Xurxo Guerra (xguerrap) (*Galician*)
- tamaina (*Japanese*) - mashirozx (*Chinese Simplified*)
- Doug (*Portuguese, Brazilian*) - Albatroz Jeremias (albjeremias) (*Portuguese*)
- Matias Lavik (*Norwegian Nynorsk*) - Samir Tighzert (samir_t7) (*Kabyle*)
- Fleva (*Sardinian*) - Apple (blackteaovo) (*Chinese Simplified*)
- Nocta (*French*)
- OpenAlgeria (*Arabic*) - OpenAlgeria (*Arabic*)
- koppe-pan (*Japanese*) - tamaina (*Japanese*)
- Amith Raj Shetty (*Kannada*) - abidin toumi (Zet24) (*Arabic*)
- xpac1985 (xpac) (*German*)
- Kaede (kaedech) (*Japanese*)
- ÀŘǾŚ PÀŚĦÀÍ (arospashai) (*Sorani (Kurdish)*)
- Matias Lavik (matiaslavik) (*Norwegian Nynorsk*)
- smedvedev (*Russian*) - 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*) - random_person (*Spanish*)
- Sais Lakshmanan (*Tamil*) - djoerd (*Dutch*)
- mikel (*Spanish*) - Baban Abdulrahman (baban.abdulrehman) (*Sorani (Kurdish)*)
- Mohammad Adnan Mahmood (*Arabic*) - 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*)

View File

@ -5,7 +5,6 @@ libidn11
libidn11-dev libidn11-dev
libpq-dev libpq-dev
libprotobuf-dev libprotobuf-dev
libssl-dev
libxdamage1 libxdamage1
libxfixes3 libxfixes3
protobuf-compiler protobuf-compiler

View File

@ -3,6 +3,208 @@ Changelog
All notable changes to this project will be documented in this file. 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 ## [3.2.1] - 2020-10-19
### Added ### 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) - Only then proceed to start removing their data (slow)
- Clear out media attachments in a separate worker (slow) - Clear out media attachments in a separate worker (slow)
## [v3.1.5] - 2020-07-07 ## [3.1.5] - 2020-07-07
### Security ### Security
- Fix media attachment enumeration ([ThibG](https://github.com/tootsuite/mastodon/pull/14254)) - 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)) - 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)) - 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 ### Added
- Add `vi` to available locales ([taicv](https://github.com/tootsuite/mastodon/pull/13542)) - 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 - 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 - 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 ### Added
- Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381)) - Add ability to filter audit log in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/13381))

View File

@ -4,7 +4,7 @@ FROM ubuntu:20.04 as build-dep
SHELL ["bash", "-c"] SHELL ["bash", "-c"]
# Install Node v12 (LTS) # Install Node v12 (LTS)
ENV NODE_VER="12.16.3" ENV NODE_VER="12.20.0"
RUN ARCH= && \ RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \ dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \ case "${dpkgArch##*-}" in \
@ -36,10 +36,11 @@ RUN apt update && \
./autogen.sh && \ ./autogen.sh && \
./configure --prefix=/opt/jemalloc && \ ./configure --prefix=/opt/jemalloc && \
make -j$(nproc) > /dev/null && \ 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 # Install Ruby
ENV RUBY_VER="2.6.6" ENV RUBY_VER="2.7.2"
ENV CPPFLAGS="-I/opt/jemalloc/include" ENV CPPFLAGS="-I/opt/jemalloc/include"
ENV LDFLAGS="-L/opt/jemalloc/lib/" ENV LDFLAGS="-L/opt/jemalloc/lib/"
RUN apt update && \ RUN apt update && \
@ -56,7 +57,8 @@ RUN apt update && \
--disable-install-doc && \ --disable-install-doc && \
ln -s /opt/jemalloc/lib/* /usr/lib/ && \ ln -s /opt/jemalloc/lib/* /usr/lib/ && \
make -j$(nproc) > /dev/null && \ 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" 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/* rm -rf /var/lib/apt/lists/*
# Add tini # Add tini
ENV TINI_VERSION="0.18.0" ENV TINI_VERSION="0.19.0"
ENV TINI_SUM="12d20136605531b09a2c2dac02ccee85e1b874eb322ef6baf7561cd93f93c855" RUN dpkgArch="$(dpkg --print-architecture)" && \
ADD https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini /tini ARCH=$dpkgArch && \
RUN echo "$TINI_SUM tini" | sha256sum -c - wget https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$ARCH \
RUN chmod +x /tini 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 over mastodon source, and dependencies from building, and set permissions
COPY --chown=mastodon:mastodon . /opt/mastodon COPY --chown=mastodon:mastodon . /opt/mastodon

62
Gemfile
View File

@ -5,22 +5,19 @@ ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.4' gem 'pkg-config', '~> 1.4'
gem 'puma', '~> 4.3' gem 'puma', '~> 5.0'
gem 'rails', '~> 5.2.4.3' gem 'rails', '~> 5.2.4.4'
gem 'sprockets', '~> 3.7.2' gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 0.20' gem 'thor', '~> 1.0'
gem 'rack', '~> 2.2.3' gem 'rack', '~> 2.2.3'
gem 'thwait', '~> 0.1.0'
gem 'e2mmap', '~> 0.1.0'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 1.2' gem 'pg', '~> 1.2'
gem 'makara', '~> 0.4' gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.5' gem 'pghero', '~> 2.7'
gem 'dotenv-rails', '~> 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-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0' gem 'paperclip', '~> 6.0'
@ -30,7 +27,7 @@ gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10' gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.7' gem 'addressable', '~> 2.7'
gem 'bootsnap', '~> 1.4', require: false gem 'bootsnap', '~> 1.5', require: false
gem 'browser' gem 'browser'
gem 'charlock_holmes', '~> 0.7.7' gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639' gem 'iso-639'
@ -44,9 +41,10 @@ group :pam_authentication, optional: true do
end end
gem 'net-ldap', '~> 0.16' gem 'net-ldap', '~> 0.16'
gem 'omniauth-cas', '~> 1.1' gem 'omniauth-cas', '~> 2.0'
gem 'omniauth-saml', '~> 1.10' gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.9' gem 'omniauth', '~> 1.9'
gem 'omniauth-rails_csrf_protection', '~> 0.1'
gem 'color_diff', '~> 0.1' gem 'color_diff', '~> 0.1'
gem 'discard', '~> 1.2' gem 'discard', '~> 1.2'
@ -55,12 +53,11 @@ gem 'ed25519', '~> 1.2'
gem 'fast_blank', '~> 1.0' gem 'fast_blank', '~> 1.0'
gem 'fastimage' gem 'fastimage'
gem 'hiredis', '~> 0.6' 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 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
gem 'htmlentities', '~> 4.3' gem 'htmlentities', '~> 4.3'
gem 'http', '~> 4.4' gem 'http', '~> 4.4'
gem 'http_accept_language', '~> 2.1' 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 'httplog', '~> 1.4.3'
gem 'idn-ruby', require: 'idn' gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2' gem 'kaminari', '~> 1.2'
@ -72,8 +69,8 @@ gem 'nsa', '~> 0.2'
gem 'oj', '~> 3.10' gem 'oj', '~> 3.10'
gem 'ox', '~> 2.13' gem 'ox', '~> 2.13'
gem 'parslet' gem 'parslet'
gem 'parallel', '~> 1.19' gem 'parallel', '~> 1.20'
gem 'posix-spawn', git: 'https://github.com/rtomayko/posix-spawn', ref: '58465d2e213991f8afb13b984854a49fcdcc980c' gem 'posix-spawn'
gem 'pundit', '~> 2.1' gem 'pundit', '~> 2.1'
gem 'premailer-rails' gem 'premailer-rails'
gem 'rack-attack', '~> 6.3' gem 'rack-attack', '~> 6.3'
@ -85,20 +82,22 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.1' gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10' gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.2' gem 'sanitize', '~> 5.2'
gem 'sidekiq', '~> 6.0' gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.1'
gem 'sidekiq-scheduler', '~> 3.0' gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0' gem 'sidekiq-unique-jobs', '~> 6.0'
gem 'sidekiq-bulk', '~>0.2.0' gem 'sidekiq-bulk', '~>0.2.0'
gem 'simple-navigation', '~> 4.1' gem 'simple-navigation', '~> 4.1'
gem 'simple_form', '~> 5.0' gem 'simple_form', '~> 5.0'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'stoplight', '~> 2.2.0' gem 'stoplight', '~> 2.2.1'
gem 'strong_migrations', '~> 0.6' gem 'strong_migrations', '~> 0.7'
gem 'tty-prompt', '~> 0.21', require: false gem 'tty-prompt', '~> 0.22', require: false
gem 'twitter-text', '~> 1.14' gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2020' gem 'tzinfo-data', '~> 1.2020'
gem 'webpacker', '~> 5.1' gem 'webpacker', '~> 5.2'
gem 'webpush' gem 'webpush'
gem 'webauthn', '~> 3.0.0.alpha1'
gem 'json-ld' gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.1' gem 'json-ld-preloaded', '~> 3.1'
@ -120,33 +119,33 @@ end
group :test do group :test do
gem 'capybara', '~> 3.33' gem 'capybara', '~> 3.33'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.13' gem 'faker', '~> 2.14'
gem 'microformats', '~> 4.2' gem 'microformats', '~> 4.2'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1' gem 'rspec-sidekiq', '~> 3.1'
gem 'simplecov', '~> 0.18', require: false gem 'simplecov', '~> 0.19', require: false
gem 'webmock', '~> 3.8' gem 'webmock', '~> 3.10'
gem 'parallel_tests', '~> 3.0' gem 'parallel_tests', '~> 3.4'
gem 'rspec_junit_formatter', '~> 0.4' gem 'rspec_junit_formatter', '~> 0.4'
end end
group :development do group :development do
gem 'active_record_query_trace', '~> 1.7' gem 'active_record_query_trace', '~> 1.8'
gem 'annotate', '~> 3.1' gem 'annotate', '~> 3.1'
gem 'better_errors', '~> 2.7' gem 'better_errors', '~> 2.9'
gem 'binding_of_caller', '~> 0.7' gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 6.1' gem 'bullet', '~> 6.1'
gem 'letter_opener', '~> 1.7' gem 'letter_opener', '~> 1.7'
gem 'letter_opener_web', '~> 1.4' gem 'letter_opener_web', '~> 1.4'
gem 'memory_profiler' gem 'memory_profiler'
gem 'rubocop', '~> 0.86', require: false gem 'rubocop', '~> 1.3', require: false
gem 'rubocop-rails', '~> 2.6', require: false gem 'rubocop-rails', '~> 2.8', require: false
gem 'brakeman', '~> 4.8', require: false gem 'brakeman', '~> 4.10', require: false
gem 'bundler-audit', '~> 0.7', require: false gem 'bundler-audit', '~> 0.7', require: false
gem 'capistrano', '~> 3.14' gem 'capistrano', '~> 3.14'
gem 'capistrano-rails', '~> 1.5' gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-yarn', '~> 2.0' gem 'capistrano-yarn', '~> 2.0'
gem 'stackprof' gem 'stackprof'
@ -159,3 +158,6 @@ end
gem 'concurrent-ruby', require: false gem 'concurrent-ruby', require: false
gem 'connection_pool', require: false gem 'connection_pool', require: false
gem 'xorcist', '~> 1.1'
gem 'pluck_each', '~> 0.1.3'

View File

@ -6,21 +6,6 @@ GIT
health_check (4.0.0.pre) health_check (4.0.0.pre)
rails (>= 4.0) 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 GIT
remote: https://github.com/witgo/nilsimsa remote: https://github.com/witgo/nilsimsa
revision: fd184883048b922b176939f851338d0a4971a532 revision: fd184883048b922b176939f851338d0a4971a532
@ -31,25 +16,25 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.2.4.3) actioncable (5.2.4.4)
actionpack (= 5.2.4.3) actionpack (= 5.2.4.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailer (5.2.4.3) actionmailer (5.2.4.4)
actionpack (= 5.2.4.3) actionpack (= 5.2.4.4)
actionview (= 5.2.4.3) actionview (= 5.2.4.4)
activejob (= 5.2.4.3) activejob (= 5.2.4.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.2.4.3) actionpack (5.2.4.4)
actionview (= 5.2.4.3) actionview (= 5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.2.4.3) actionview (5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -59,21 +44,21 @@ GEM
activemodel (>= 4.1, < 6.1) activemodel (>= 4.1, < 6.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.7) active_record_query_trace (1.8)
activejob (5.2.4.3) activejob (5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.2.4.3) activemodel (5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
activerecord (5.2.4.3) activerecord (5.2.4.4)
activemodel (= 5.2.4.3) activemodel (= 5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
arel (>= 9.0) arel (>= 9.0)
activestorage (5.2.4.3) activestorage (5.2.4.4)
actionpack (= 5.2.4.3) actionpack (= 5.2.4.4)
activerecord (= 5.2.4.3) activerecord (= 5.2.4.4)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (5.2.4.3) activesupport (5.2.4.4)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -82,6 +67,7 @@ GEM
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
airbrussh (1.4.0) airbrussh (1.4.0)
sshkit (>= 1.6.1, != 1.7.0) sshkit (>= 1.6.1, != 1.7.0)
android_key_attestation (0.3.0)
annotate (3.1.1) annotate (3.1.1)
activerecord (>= 3.2, < 7.0) activerecord (>= 3.2, < 7.0)
rake (>= 10.4, < 14.0) rake (>= 10.4, < 14.0)
@ -91,34 +77,36 @@ GEM
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
awrence (1.1.1)
aws-eventstream (1.1.0) aws-eventstream (1.1.0)
aws-partitions (1.338.0) aws-partitions (1.397.0)
aws-sdk-core (3.103.0) aws-sdk-core (3.109.3)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0) aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.36.0) aws-sdk-kms (1.39.0)
aws-sdk-core (~> 3, >= 3.99.0) aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.73.0) aws-sdk-s3 (1.85.0)
aws-sdk-core (~> 3, >= 3.102.1) aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.1) aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.13) bcrypt (3.1.16)
better_errors (2.7.1) better_errors (2.9.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
rack (>= 0.9.0) rack (>= 0.9.0)
bindata (2.4.8)
binding_of_caller (0.8.0) binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blurhash (0.1.4) blurhash (0.1.4)
ffi (~> 1.10.0) ffi (~> 1.10.0)
bootsnap (1.4.6) bootsnap (1.5.1)
msgpack (~> 1.0) msgpack (~> 1.0)
brakeman (4.8.2) brakeman (4.10.0)
browser (4.2.0) browser (4.2.0)
builder (3.2.4) builder (3.2.4)
bullet (6.1.0) bullet (6.1.0)
@ -133,12 +121,12 @@ GEM
i18n i18n
rake (>= 10.0.0) rake (>= 10.0.0)
sshkit (>= 1.9.0) sshkit (>= 1.9.0)
capistrano-bundler (1.6.0) capistrano-bundler (2.0.1)
capistrano (~> 3.1) capistrano (~> 3.1)
capistrano-rails (1.5.0) capistrano-rails (1.6.1)
capistrano (~> 3.1) capistrano (~> 3.1)
capistrano-bundler (~> 1.1) capistrano-bundler (>= 1.1, < 3)
capistrano-rbenv (2.1.6) capistrano-rbenv (2.2.0)
capistrano (~> 3.1) capistrano (~> 3.1)
sshkit (~> 1.3) sshkit (~> 1.3)
capistrano-yarn (2.0.2) capistrano-yarn (2.0.2)
@ -153,12 +141,13 @@ GEM
xpath (~> 3.2) xpath (~> 3.2)
case_transform (0.2) case_transform (0.2)
activesupport activesupport
cbor (0.5.9.6)
charlock_holmes (0.7.7) charlock_holmes (0.7.7)
chewy (5.1.0) chewy (5.1.0)
activesupport (>= 4.0) activesupport (>= 4.0)
elasticsearch (>= 2.0.0) elasticsearch (>= 2.0.0)
elasticsearch-dsl elasticsearch-dsl
chunky_png (1.3.11) chunky_png (1.3.12)
cld3 (3.3.0) cld3 (3.3.0)
ffi (>= 1.1.0, < 1.12.0) ffi (>= 1.1.0, < 1.12.0)
climate_control (0.2.0) climate_control (0.2.0)
@ -166,15 +155,17 @@ GEM
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
coderay (1.1.3) coderay (1.1.3)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.1.6) concurrent-ruby (1.1.7)
connection_pool (2.2.3) connection_pool (2.2.3)
crack (0.4.3) cose (1.0.0)
safe_yaml (~> 1.0.0) cbor (~> 0.5.9)
openssl-signature_algorithm (~> 0.4.0)
crack (0.4.4)
crass (1.0.6) crass (1.0.6)
css_parser (1.7.1) css_parser (1.7.1)
addressable addressable
debug_inspector (0.0.3) debug_inspector (0.0.3)
devise (4.7.2) devise (4.7.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
@ -197,34 +188,33 @@ GEM
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.4.0) doorkeeper (5.4.0)
railties (>= 5) railties (>= 5)
dotenv (2.7.5) dotenv (2.7.6)
dotenv-rails (2.7.5) dotenv-rails (2.7.6)
dotenv (= 2.7.5) dotenv (= 2.7.6)
railties (>= 3.2, < 6.1) railties (>= 3.2)
e2mmap (0.1.0) e2mmap (0.1.0)
ed25519 (1.2.4) ed25519 (1.2.4)
elasticsearch (7.8.0) elasticsearch (7.9.0)
elasticsearch-api (= 7.8.0) elasticsearch-api (= 7.9.0)
elasticsearch-transport (= 7.8.0) elasticsearch-transport (= 7.9.0)
elasticsearch-api (7.8.0) elasticsearch-api (7.9.0)
multi_json multi_json
elasticsearch-dsl (0.1.9) elasticsearch-dsl (0.1.9)
elasticsearch-transport (7.8.0) elasticsearch-transport (7.9.0)
faraday (~> 1) faraday (~> 1)
multi_json multi_json
encryptor (3.0.0) encryptor (3.0.0)
equatable (0.6.1)
erubi (1.9.0) erubi (1.9.0)
et-orbi (1.2.4) et-orbi (1.2.4)
tzinfo tzinfo
excon (0.75.0) excon (0.76.0)
fabrication (2.21.1) fabrication (2.21.1)
faker (2.13.0) faker (2.14.0)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
faraday (1.0.1) faraday (1.0.1)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
fast_blank (1.0.0) fast_blank (1.0.0)
fastimage (2.1.7) fastimage (2.2.0)
ffi (1.10.0) ffi (1.10.0)
ffi-compiler (1.0.1) ffi-compiler (1.0.1)
ffi (>= 1.0.0) ffi (>= 1.0.0)
@ -242,7 +232,7 @@ GEM
fog-json (>= 1.0) fog-json (>= 1.0)
ipaddress (>= 0.8) ipaddress (>= 0.8)
formatador (0.2.5) formatador (0.2.5)
fugit (1.3.6) fugit (1.3.9)
et-orbi (~> 1.1, >= 1.1.8) et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.3) raabro (~> 1.3)
fuubar (2.5.0) fuubar (2.5.0)
@ -250,7 +240,7 @@ GEM
ruby-progressbar (~> 1.4) ruby-progressbar (~> 1.4)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
hamlit (2.11.0) hamlit (2.13.0)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
@ -281,7 +271,7 @@ GEM
httplog (1.4.3) httplog (1.4.3)
rack (>= 1.0) rack (>= 1.0)
rainbow (>= 2.0.0) rainbow (>= 2.0.0)
i18n (1.8.3) i18n (1.8.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-tasks (0.9.31) i18n-tasks (0.9.31)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
@ -299,7 +289,7 @@ GEM
jmespath (1.4.0) jmespath (1.4.0)
json (2.3.1) json (2.3.1)
json-canonicalization (0.2.0) json-canonicalization (0.2.0)
json-ld (3.1.4) json-ld (3.1.5)
htmlentities (~> 4.3) htmlentities (~> 4.3)
json-canonicalization (~> 0.2) json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
@ -310,7 +300,7 @@ GEM
json-ld (~> 3.1) json-ld (~> 3.1)
rdf (~> 3.1) rdf (~> 3.1)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jwt (2.2.1) jwt (2.2.2)
kaminari (1.2.1) kaminari (1.2.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.1) kaminari-actionview (= 1.2.1)
@ -337,7 +327,7 @@ GEM
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.6.0) loofah (2.7.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
@ -350,7 +340,7 @@ GEM
redis (>= 3.0.5) redis (>= 3.0.5)
memory_profiler (0.9.14) memory_profiler (0.9.14)
method_source (1.0.0) method_source (1.0.0)
microformats (4.2.0) microformats (4.2.1)
json (~> 2.2) json (~> 2.2)
nokogiri (~> 1.10) nokogiri (~> 1.10)
mime-types (3.3.1) mime-types (3.3.1)
@ -359,17 +349,16 @@ GEM
mimemagic (0.3.5) mimemagic (0.3.5)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.14.1) minitest (5.14.2)
msgpack (1.3.3) msgpack (1.3.3)
multi_json (1.14.1) multi_json (1.15.0)
multipart-post (2.1.1) multipart-post (2.1.1)
necromancer (0.5.1) net-ldap (0.16.3)
net-ldap (0.16.2)
net-scp (3.0.0) net-scp (3.0.0)
net-ssh (>= 2.6.5, < 7.0.0) net-ssh (>= 2.6.5, < 7.0.0)
net-ssh (6.1.0) net-ssh (6.1.0)
nio4r (2.5.2) nio4r (2.5.4)
nokogiri (1.10.9) nokogiri (1.10.10)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
nokogumbo (2.0.2) nokogumbo (2.0.2)
nokogiri (~> 1.8, >= 1.8.4) nokogiri (~> 1.8, >= 1.8.4)
@ -378,19 +367,24 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.6) oj (3.10.16)
omniauth (1.9.1) omniauth (1.9.1)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
omniauth-cas (1.1.1) omniauth-cas (2.0.0)
addressable (~> 2.3) addressable (~> 2.3)
nokogiri (~> 1.5) nokogiri (~> 1.5)
omniauth (~> 1.2) 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) omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.9) ruby-saml (~> 1.9)
openssl (2.2.0)
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.13.2) ox (2.13.4)
paperclip (6.0.0) paperclip (6.0.0)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@ -400,20 +394,23 @@ GEM
paperclip-av-transcoder (0.6.4) paperclip-av-transcoder (0.6.4)
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.19.2) parallel (1.20.1)
parallel_tests (3.0.0) parallel_tests (3.4.0)
parallel parallel
parser (2.7.1.4) parser (2.7.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
parslet (2.0.0) parslet (2.0.0)
pastel (0.7.4) pastel (0.8.0)
equatable (~> 0.6)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.2.3) pg (1.2.3)
pghero (2.5.1) pghero (2.7.2)
activerecord (>= 5) activerecord (>= 5)
pkg-config (1.4.1) pkg-config (1.4.4)
premailer (1.11.1) pluck_each (0.1.3)
activerecord (> 3.2.0)
activesupport (> 3.0.0)
posix-spawn (0.3.15)
premailer (1.14.2)
addressable addressable
css_parser (>= 1.6.0) css_parser (>= 1.6.0)
htmlentities (>= 4.0.0) htmlentities (>= 4.0.0)
@ -429,12 +426,12 @@ GEM
pry (~> 0.13.0) pry (~> 0.13.0)
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (4.0.5) public_suffix (4.0.6)
puma (4.3.5) puma (5.0.4)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.3.1) raabro (1.3.3)
rack (2.2.3) rack (2.2.3)
rack-attack (6.3.1) rack-attack (6.3.1)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
@ -444,18 +441,18 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (5.2.4.3) rails (5.2.4.4)
actioncable (= 5.2.4.3) actioncable (= 5.2.4.4)
actionmailer (= 5.2.4.3) actionmailer (= 5.2.4.4)
actionpack (= 5.2.4.3) actionpack (= 5.2.4.4)
actionview (= 5.2.4.3) actionview (= 5.2.4.4)
activejob (= 5.2.4.3) activejob (= 5.2.4.4)
activemodel (= 5.2.4.3) activemodel (= 5.2.4.4)
activerecord (= 5.2.4.3) activerecord (= 5.2.4.4)
activestorage (= 5.2.4.3) activestorage (= 5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 5.2.4.3) railties (= 5.2.4.4)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
@ -471,20 +468,20 @@ GEM
railties (>= 5.0, < 6) railties (>= 5.0, < 6)
rails-settings-cached (0.6.6) rails-settings-cached (0.6.6)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (5.2.4.3) railties (5.2.4.4)
actionpack (= 5.2.4.3) actionpack (= 5.2.4.4)
activesupport (= 5.2.4.3) activesupport (= 5.2.4.4)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.19.0, < 2.0) thor (>= 0.19.0, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.1) rake (13.0.1)
rdf (3.1.4) rdf (3.1.7)
hamster (~> 3.0) hamster (~> 3.0)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.4.0) rdf-normalize (0.4.0)
rdf (~> 3.1) rdf (~> 3.1)
redis (4.2.1) redis (4.2.5)
redis-actionpack (5.2.0) redis-actionpack (5.2.0)
actionpack (>= 5, < 7) actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3) redis-rack (>= 2.1.0, < 3)
@ -492,9 +489,9 @@ GEM
redis-activesupport (5.2.0) redis-activesupport (5.2.0)
activesupport (>= 3, < 7) activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-namespace (1.7.0) redis-namespace (1.8.0)
redis (>= 3.0.4) redis (>= 3.0.4)
redis-rack (2.1.2) redis-rack (2.1.3)
rack (>= 2.0.8, < 3) rack (>= 2.0.8, < 3)
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-rails (5.0.2) redis-rails (5.0.2)
@ -503,7 +500,7 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.9.0) redis-store (1.9.0)
redis (>= 4, < 5) redis (>= 4, < 5)
regexp_parser (1.7.1) regexp_parser (1.8.2)
request_store (1.5.0) request_store (1.5.0)
rack (>= 1.4) rack (>= 1.4)
responders (3.0.1) responders (3.0.1)
@ -516,7 +513,7 @@ GEM
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 0.1) rqrcode_core (~> 0.1)
rqrcode_core (0.1.2) rqrcode_core (0.1.2)
rspec-core (3.9.2) rspec-core (3.9.3)
rspec-support (~> 3.9.3) rspec-support (~> 3.9.3)
rspec-expectations (3.9.2) rspec-expectations (3.9.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
@ -538,33 +535,38 @@ GEM
rspec-support (3.9.3) rspec-support (3.9.3)
rspec_junit_formatter (0.4.1) rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0) rspec-core (>= 2, < 4, != 2.12.0)
rubocop (0.86.0) rubocop (1.3.1)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.0.1) parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7) regexp_parser (>= 1.8)
rexml rexml
rubocop-ast (>= 0.0.3, < 1.0) rubocop-ast (>= 1.1.1)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.1.0) rubocop-ast (1.1.1)
parser (>= 2.7.0.1) parser (>= 2.7.1.5)
rubocop-rails (2.6.0) rubocop-rails (2.8.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.82.0) rubocop (>= 0.87.0)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby-saml (1.11.0) ruby-saml (1.11.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0) rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.5) safety_net_attestation (0.4.0)
jwt (~> 2.0)
sanitize (5.2.1) sanitize (5.2.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.8.0) nokogiri (>= 1.8.0)
nokogumbo (~> 2.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) semantic_range (2.3.0)
sidekiq (6.1.0) sidekiq (6.1.2)
connection_pool (>= 2.2.2) connection_pool (>= 2.2.2)
rack (~> 2.0) rack (~> 2.0)
redis (>= 4.2.0) redis (>= 4.2.0)
@ -577,74 +579,87 @@ GEM
sidekiq (>= 3) sidekiq (>= 3)
thwait thwait
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (6.0.22) sidekiq-unique-jobs (6.0.25)
concurrent-ruby (~> 1.0, >= 1.0.5) concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0) sidekiq (>= 4.0, < 7.0)
thor (~> 0) thor (>= 0.20, < 2.0)
simple-navigation (4.1.0) simple-navigation (4.1.0)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
simple_form (5.0.2) simple_form (5.0.3)
actionpack (>= 5.0) actionpack (>= 5.0)
activemodel (>= 5.0) activemodel (>= 5.0)
simplecov (0.18.5) simplecov (0.19.1)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov-html (0.12.2) simplecov-html (0.12.3)
sprockets (3.7.2) sprockets (3.7.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.1) sprockets-rails (3.2.2)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sshkit (1.21.0) sshkit (1.21.0)
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
stackprof (0.2.15) stackprof (0.2.16)
statsd-ruby (1.4.0) statsd-ruby (1.4.0)
stoplight (2.2.0) stoplight (2.2.1)
streamio-ffmpeg (3.0.2) streamio-ffmpeg (3.0.2)
multi_json (~> 1.8) multi_json (~> 1.8)
strong_migrations (0.6.8) strong_migrations (0.7.2)
activerecord (>= 5) activerecord (>= 5)
temple (0.8.2) temple (0.8.2)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
terrapin (0.6.0) terrapin (0.6.0)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
thor (0.20.3) thor (1.0.1)
thread_safe (0.3.6) thread_safe (0.3.6)
thwait (0.1.0) thwait (0.2.0)
e2mmap
tilt (2.0.10) 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-cursor (0.7.1)
tty-prompt (0.21.0) tty-prompt (0.22.0)
necromancer (~> 0.5.0) pastel (~> 0.8)
pastel (~> 0.7.0) tty-reader (~> 0.8)
tty-reader (~> 0.7.0) tty-reader (0.8.0)
tty-reader (0.7.0)
tty-cursor (~> 0.7) tty-cursor (~> 0.7)
tty-screen (~> 0.7) tty-screen (~> 0.8)
wisper (~> 2.0.0) wisper (~> 2.0)
tty-screen (0.8.0) tty-screen (0.8.1)
twitter-text (1.14.7) twitter-text (1.14.7)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.7) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2020.1) tzinfo-data (1.2020.4)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.7) unf_ext (0.0.7.7)
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
uniform_notifier (1.13.0) uniform_notifier (1.13.0)
warden (1.2.8) warden (1.2.9)
rack (>= 2.0.6) rack (>= 2.0.9)
webmock (3.8.3) 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) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
webpacker (5.1.1) webpacker (5.2.1)
activesupport (>= 5.2) activesupport (>= 5.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
@ -652,10 +667,11 @@ GEM
webpush (0.3.8) webpush (0.3.8)
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) jwt (~> 2.0)
websocket-driver (0.7.2) websocket-driver (0.7.3)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
wisper (2.0.1) wisper (2.0.1)
xorcist (1.1.2)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -664,21 +680,21 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
active_model_serializers (~> 0.10) active_model_serializers (~> 0.10)
active_record_query_trace (~> 1.7) active_record_query_trace (~> 1.8)
addressable (~> 2.7) addressable (~> 2.7)
annotate (~> 3.1) annotate (~> 3.1)
aws-sdk-s3 (~> 1.73) aws-sdk-s3 (~> 1.85)
better_errors (~> 2.7) better_errors (~> 2.9)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
blurhash (~> 0.1) blurhash (~> 0.1)
bootsnap (~> 1.4) bootsnap (~> 1.5)
brakeman (~> 4.8) brakeman (~> 4.10)
browser browser
bullet (~> 6.1) bullet (~> 6.1)
bundler-audit (~> 0.7) bundler-audit (~> 0.7)
capistrano (~> 3.14) capistrano (~> 3.14)
capistrano-rails (~> 1.5) capistrano-rails (~> 1.6)
capistrano-rbenv (~> 2.1) capistrano-rbenv (~> 2.2)
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 3.33) capybara (~> 3.33)
charlock_holmes (~> 0.7.7) charlock_holmes (~> 0.7.7)
@ -694,10 +710,9 @@ DEPENDENCIES
discard (~> 1.2) discard (~> 1.2)
doorkeeper (~> 5.4) doorkeeper (~> 5.4)
dotenv-rails (~> 2.7) dotenv-rails (~> 2.7)
e2mmap (~> 0.1.0)
ed25519 (~> 1.2) ed25519 (~> 1.2)
fabrication (~> 2.21) fabrication (~> 2.21)
faker (~> 2.13) faker (~> 2.14)
fast_blank (~> 1.0) fast_blank (~> 1.0)
fastimage fastimage
fog-core (<= 2.1.0) fog-core (<= 2.1.0)
@ -709,7 +724,6 @@ DEPENDENCIES
htmlentities (~> 4.3) htmlentities (~> 4.3)
http (~> 4.4) http (~> 4.4)
http_accept_language (~> 2.1) http_accept_language (~> 2.1)
http_parser.rb (~> 0.6)!
httplog (~> 1.4.3) httplog (~> 1.4.3)
i18n-tasks (~> 0.9) i18n-tasks (~> 0.9)
idn-ruby idn-ruby
@ -732,61 +746,65 @@ DEPENDENCIES
nsa (~> 0.2) nsa (~> 0.2)
oj (~> 3.10) oj (~> 3.10)
omniauth (~> 1.9) omniauth (~> 1.9)
omniauth-cas (~> 1.1) omniauth-cas (~> 2.0)
omniauth-rails_csrf_protection (~> 0.1)
omniauth-saml (~> 1.10) omniauth-saml (~> 1.10)
ox (~> 2.13) ox (~> 2.13)
paperclip (~> 6.0) paperclip (~> 6.0)
paperclip-av-transcoder (~> 0.6) paperclip-av-transcoder (~> 0.6)
parallel (~> 1.19) parallel (~> 1.20)
parallel_tests (~> 3.0) parallel_tests (~> 3.4)
parslet parslet
pg (~> 1.2) pg (~> 1.2)
pghero (~> 2.5) pghero (~> 2.7)
pkg-config (~> 1.4) pkg-config (~> 1.4)
posix-spawn! pluck_each (~> 0.1.3)
posix-spawn
premailer-rails premailer-rails
private_address_check (~> 0.5) private_address_check (~> 0.5)
pry-byebug (~> 3.9) pry-byebug (~> 3.9)
pry-rails (~> 0.3) pry-rails (~> 0.3)
puma (~> 4.3) puma (~> 5.0)
pundit (~> 2.1) pundit (~> 2.1)
rack (~> 2.2.3) rack (~> 2.2.3)
rack-attack (~> 6.3) rack-attack (~> 6.3)
rack-cors (~> 1.1) rack-cors (~> 1.1)
rails (~> 5.2.4.3) rails (~> 5.2.4.4)
rails-controller-testing (~> 1.0) rails-controller-testing (~> 1.0)
rails-i18n (~> 5.1) rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6) rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.4) rdf-normalize (~> 0.4)
redis (~> 4.2) redis (~> 4.2)
redis-namespace (~> 1.7) redis-namespace (~> 1.8)
redis-rails (~> 5.0) redis-rails (~> 5.0)
rqrcode (~> 1.1) rqrcode (~> 1.1)
rspec-rails (~> 4.0) rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.1) rspec-sidekiq (~> 3.1)
rspec_junit_formatter (~> 0.4) rspec_junit_formatter (~> 0.4)
rubocop (~> 0.86) rubocop (~> 1.3)
rubocop-rails (~> 2.6) rubocop-rails (~> 2.8)
ruby-progressbar (~> 1.10) ruby-progressbar (~> 1.10)
sanitize (~> 5.2) sanitize (~> 5.2)
sidekiq (~> 6.0) scenic (~> 1.5)
sidekiq (~> 6.1)
sidekiq-bulk (~> 0.2.0) sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0) sidekiq-scheduler (~> 3.0)
sidekiq-unique-jobs (~> 6.0) sidekiq-unique-jobs (~> 6.0)
simple-navigation (~> 4.1) simple-navigation (~> 4.1)
simple_form (~> 5.0) simple_form (~> 5.0)
simplecov (~> 0.18) simplecov (~> 0.19)
sprockets (~> 3.7.2) sprockets (~> 3.7.2)
sprockets-rails (~> 3.2) sprockets-rails (~> 3.2)
stackprof stackprof
stoplight (~> 2.2.0) stoplight (~> 2.2.1)
streamio-ffmpeg (~> 3.0) streamio-ffmpeg (~> 3.0)
strong_migrations (~> 0.6) strong_migrations (~> 0.7)
thor (~> 0.20) thor (~> 1.0)
thwait (~> 0.1.0) tty-prompt (~> 0.22)
tty-prompt (~> 0.21)
twitter-text (~> 1.14) twitter-text (~> 1.14)
tzinfo-data (~> 1.2020) tzinfo-data (~> 1.2020)
webmock (~> 3.8) webauthn (~> 3.0.0.alpha1)
webpacker (~> 5.1) webmock (~> 3.10)
webpacker (~> 5.2)
webpush webpush
xorcist (~> 1.1)

View File

@ -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 worker: bundle exec sidekiq
# For the streaming API, you need a separate app that shares Postgres and Redis: # For the streaming API, you need a separate app that shares Postgres and Redis:

View File

@ -1,12 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class AboutController < ApplicationController class AboutController < ApplicationController
include RegistrationSpamConcern
layout 'public' layout 'public'
before_action :require_open_federation!, only: [:show, :more] before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show before_action :set_body_classes, only: :show
before_action :set_instance_presenter 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] skip_before_action :require_functional!, only: [:more, :terms]

View File

@ -29,8 +29,7 @@ class AccountsController < ApplicationController
end end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page @statuses = cached_filtered_status_page
@statuses = cache_collection(@statuses, Status)
@rss_url = rss_url @rss_url = rss_url
unless @statuses.empty? unless @statuses.empty?
@ -86,7 +85,7 @@ class AccountsController < ApplicationController
end end
def account_media_status_ids 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 end
def no_replies_scope def no_replies_scope
@ -107,6 +106,10 @@ class AccountsController < ApplicationController
params[:username] params[:username]
end end
def skip_temporary_suspension_response?
request.format == :json
end
def rss_url def rss_url
if tag_requested? if tag_requested?
short_account_tag_url(@account, params[:tag], format: 'rss') 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) request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end end
def filtered_status_page def cached_filtered_status_page
filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id)) cache_collection_paginated_by_id(
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
end end
def params_slice(*keys) def params_slice(*keys)

View File

@ -8,4 +8,8 @@ class ActivityPub::BaseController < Api::BaseController
def set_cache_headers def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
end end
def skip_temporary_suspension_response?
false
end
end end

View File

@ -12,7 +12,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def show def show
expires_in 3.minutes, public: public_fetch_mode? 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 end
private private
@ -20,17 +20,9 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items def set_items
case params[:id] case params[:id]
when 'featured' when 'featured'
@items = begin @items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
# Because in public fetch mode we cache the response, there would be no when 'tags'
# benefit from performing the check below, since a blocked account or domain @items = for_signed_account { @account.featured_tags }
# 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
when 'devices' when 'devices'
@items = @account.devices @items = @account.devices
else else
@ -40,7 +32,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size def set_size
case params[:id] case params[:id]
when 'featured', 'devices' when 'featured', 'devices', 'tags'
@size = @items.size @size = @items.size
else else
not_found not_found
@ -51,7 +43,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
case params[:id] case params[:id]
when 'featured' when 'featured'
@type = :ordered @type = :ordered
when 'devices' when 'devices', 'tags'
@type = :unordered @type = :unordered
else else
not_found not_found
@ -66,4 +58,16 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
items: @items items: @items
) )
end 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 end

View File

@ -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

View File

@ -11,6 +11,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
def create def create
upgrade_account upgrade_account
process_collection_synchronization
process_payload process_payload
head 202 head 202
end end
@ -32,6 +33,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
params[:account_username].present? params[:account_username].present?
end end
def skip_temporary_suspension_response?
true
end
def body def body
return @body if defined?(@body) return @body if defined?(@body)
@ -52,6 +57,19 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
DeliveryFailureTracker.reset!(signed_request_account.inbox_url) DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
end 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 def process_payload
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id) ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
end end

View File

@ -20,9 +20,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def outbox_presenter def outbox_presenter
if page_requested? if page_requested?
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(
id: account_outbox_url(@account, page_params), id: outbox_url(page_params),
type: :ordered, type: :ordered,
part_of: account_outbox_url(@account), part_of: outbox_url,
prev: prev_page, prev: prev_page,
next: next_page, next: next_page,
items: @statuses items: @statuses
@ -32,12 +32,20 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
id: account_outbox_url(@account), id: account_outbox_url(@account),
type: :ordered, type: :ordered,
size: @account.statuses_count, size: @account.statuses_count,
first: account_outbox_url(@account, page: true), first: outbox_url(page: true),
last: account_outbox_url(@account, page: true, min_id: 0) last: outbox_url(page: true, min_id: 0)
) )
end end
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 def next_page
account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
end end
@ -49,9 +57,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses def set_statuses
return unless page_requested? return unless page_requested?
@statuses = @account.statuses.permitted_for(@account, signed_request_account) @statuses = cache_collection_paginated_by_id(
@statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id)) @account.statuses.permitted_for(@account, signed_request_account),
@statuses = cache_collection(@statuses, Status) Status,
LIMIT,
params_slice(:max_id, :min_id, :since_id)
)
end end
def page_requested? def page_requested?
@ -61,4 +72,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def page_params def page_params
{ page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
end end
def set_account
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
end
end end

View File

@ -31,7 +31,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end end
def set_replies 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.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end end

View File

@ -2,7 +2,7 @@
module Admin module Admin
class AccountsController < BaseController 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_remote_account!, only: [:redownload]
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject] before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
@ -14,49 +14,65 @@ module Admin
def show def show
authorize @account, :show? authorize @account, :show?
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account) @account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@moderation_notes = @account.targeted_moderation_notes.latest @moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.targeted_account_warnings.latest.custom @warnings = @account.targeted_account_warnings.latest.custom
@domain_block = DomainBlock.rule_for(@account.domain)
end end
def memorialize def memorialize
authorize @account, :memorialize? authorize @account, :memorialize?
@account.memorialize! @account.memorialize!
log_action :memorialize, @account 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 end
def enable def enable
authorize @account.user, :enable? authorize @account.user, :enable?
@account.user.enable! @account.user.enable!
log_action :enable, @account.user 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 end
def approve def approve
authorize @account.user, :approve? authorize @account.user, :approve?
@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 end
def reject def reject
authorize @account.user, :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)
redirect_to admin_pending_accounts_path 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 end
def unsilence def unsilence
authorize @account, :unsilence? authorize @account, :unsilence?
@account.unsilence! @account.unsilence!
log_action :unsilence, @account 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 end
def unsuspend def unsuspend
authorize @account, :unsuspend? authorize @account, :unsuspend?
@account.unsuspend! @account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account 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 end
def redownload def redownload
@ -65,7 +81,7 @@ module Admin
@account.update!(last_webfingered_at: nil) @account.update!(last_webfingered_at: nil)
ResolveAccountService.new.call(@account) 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 end
def remove_avatar def remove_avatar
@ -76,7 +92,7 @@ module Admin
log_action :remove_avatar, @account.user 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 end
def remove_header def remove_header
@ -87,7 +103,7 @@ module Admin
log_action :remove_header, @account.user 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 end
private private

View File

@ -71,7 +71,7 @@ class Admin::AnnouncementsController < Admin::BaseController
private private
def set_announcements def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page]) @announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
end end
def set_announcement def set_announcement

View File

@ -29,6 +29,7 @@ module Admin
@domain_block = existing_domain_block @domain_block = existing_domain_block
@domain_block.update(resource_params) @domain_block.update(resource_params)
end end
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id) DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block log_action :create, @domain_block
@ -40,7 +41,7 @@ module Admin
end end
def update def update
authorize :domain_block, :create? authorize :domain_block, :update?
@domain_block.update(update_params) @domain_block.update(update_params)
@ -48,7 +49,7 @@ module Admin
if @domain_block.save if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed) 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') redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else else
render :edit render :edit
@ -73,11 +74,11 @@ module Admin
end end
def update_params 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 end
def resource_params 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 end
end end

View File

@ -27,7 +27,7 @@ module Admin
ips = [] ips = []
Resolv::DNS.open do |dns| 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 } hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }

View File

@ -2,65 +2,31 @@
module Admin module Admin
class InstancesController < BaseController class InstancesController < BaseController
before_action :set_domain_block, only: :show before_action :set_instances, only: :index
before_action :set_domain_allow, only: :show
before_action :set_instance, only: :show before_action :set_instance, only: :show
def index def index
authorize :instance, :index? authorize :instance, :index?
@instances = ordered_instances
end end
def show def show
authorize :instance, :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 end
private 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 def set_instance
resource = Account.by_domain_accounts.find_by(domain: params[:id]) @instance = Instance.find(params[:id])
resource ||= @domain_block end
resource ||= @domain_allow
if resource def set_instances
@instance = Instance.new(resource) @instances = filtered_instances.page(params[:page])
else
not_found
end
end end
def filtered_instances def filtered_instances
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
end 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 def filter_params
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS) params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
end end

View 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

View File

@ -14,7 +14,7 @@ module Admin
@statuses = @account.statuses.where(visibility: [:public, :unlisted]) @statuses = @account.statuses.where(visibility: [:public, :unlisted])
if params[:media] 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)) @statuses.merge!(Status.where(id: account_media_status_ids))
end end

View File

@ -40,7 +40,7 @@ class Api::BaseController < ApplicationController
render json: { error: 'This action is not allowed' }, status: 403 render json: { error: 'This action is not allowed' }, status: 403
end 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 render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
end end
@ -71,6 +71,7 @@ class Api::BaseController < ApplicationController
def limit_param(default_limit) def limit_param(default_limit)
return default_limit unless params[:limit] return default_limit unless params[:limit]
[params[:limit].to_i.abs, default_limit * 2].min [params[:limit].to_i.abs, default_limit * 2].min
end end
@ -95,14 +96,14 @@ class Api::BaseController < ApplicationController
def require_user! def require_user!
if !current_user if !current_user
render json: { error: 'This method requires an authenticated user' }, status: 422 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? elsif !current_user.confirmed?
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403 render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
elsif !current_user.approved? elsif !current_user.approved?
render json: { error: 'Your login is currently pending approval' }, status: 403 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 else
set_user_activity update_user_sign_in
end end
end end

View 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

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -5,7 +5,7 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
before_action :set_account before_action :set_account
def index def index
@proofs = @account.identity_proofs.active @proofs = @account.suspended? ? [] : @account.identity_proofs.active
render json: @proofs, each_serializer: REST::IdentityProofSerializer render json: @proofs, each_serializer: REST::IdentityProofSerializer
end end

View File

@ -6,7 +6,7 @@ class Api::V1::Accounts::ListsController < Api::BaseController
before_action :set_account before_action :set_account
def index 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 render json: @lists, each_serializer: REST::ListSerializer
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
before_action :require_user! before_action :require_user!
def index 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 # .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor. # we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact @accounts = accounts.index_by(&:id).values_at(*account_ids).compact

View File

@ -18,14 +18,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def load_statuses def load_statuses
cached_account_statuses @account.suspended? ? [] : cached_account_statuses
end end
def cached_account_statuses def cached_account_statuses
cache_collection account_statuses, Status
end
def account_statuses
statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
statuses.merge!(only_media_scope) if truthy_param?(:only_media) 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!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
statuses.merge!(hashtag_scope) if params[:tagged].present? 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 end
def permitted_account_statuses def permitted_account_statuses
@ -41,17 +42,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def only_media_scope def only_media_scope
Status.where(id: account_media_status_ids) Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
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)
end end
def pinned_scope def pinned_scope

View File

@ -9,7 +9,6 @@ class Api::V1::AccountsController < Api::BaseController
before_action :require_user!, except: [:show, :create] before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create] before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show]
before_action :check_enabled_registrations, only: [:create] before_action :check_enabled_registrations, only: [:create]
skip_before_action :require_authenticated_user!, only: :create skip_before_action :require_authenticated_user!, only: :create
@ -21,7 +20,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def create 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) response = Doorkeeper::OAuth::TokenResponse.new(token)
headers.merge!(response.headers) headers.merge!(response.headers)
@ -31,9 +30,8 @@ class Api::V1::AccountsController < Api::BaseController
end end
def follow def follow
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true) 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 } }
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end end
@ -44,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def mute 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 render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end end
@ -73,10 +71,6 @@ class Api::V1::AccountsController < Api::BaseController
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options)
end end
def check_account_suspension
gone if @account.suspended?
end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason) params.permit(:username, :email, :password, :agreement, :locale, :reason)
end end

View File

@ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
active active
pending pending
disabled disabled
sensitized
silenced silenced
suspended suspended
username username
@ -58,7 +59,20 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def reject def reject
authorize @account.user, :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 render json: @account, serializer: REST::Admin::AccountSerializer
end end
@ -72,6 +86,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def unsuspend def unsuspend
authorize @account, :unsuspend? authorize @account, :unsuspend?
@account.unsuspend! @account.unsuspend!
Admin::UnsuspensionWorker.perform_async(@account.id)
log_action :unsuspend, @account log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer render json: @account, serializer: REST::Admin::AccountSerializer
end end
@ -79,7 +94,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
private private
def set_accounts 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 end
def set_account def set_account

View File

@ -63,7 +63,7 @@ class Api::V1::Admin::ReportsController < Api::BaseController
private private
def set_reports 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 end
def set_report def set_report

View File

@ -18,6 +18,8 @@ class Api::V1::BlocksController < Api::BaseController
def paginated_blocks def paginated_blocks
@paginated_blocks ||= Block.eager_load(target_account: :account_stat) @paginated_blocks ||= Block.eager_load(target_account: :account_stat)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account) .where(account: current_account)
.paginate_by_max_id( .paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT), limit_param(DEFAULT_ACCOUNTS_LIMIT),

View File

@ -17,14 +17,11 @@ class Api::V1::BookmarksController < Api::BaseController
end end
def cached_bookmarks def cached_bookmarks
cache_collection( cache_collection(results.map(&:status), Status)
Status.reorder(nil).joins(:bookmarks).merge(results),
Status
)
end end
def results def results
@_results ||= account_bookmarks.paginate_by_id( @_results ||= account_bookmarks.eager_load(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )

View File

@ -32,7 +32,7 @@ class Api::V1::ConversationsController < Api::BaseController
def paginated_conversations def paginated_conversations
AccountConversation.where(account: current_account) 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 end
def insert_pagination_headers def insert_pagination_headers

View File

@ -26,7 +26,7 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
end end
def set_encrypted_messages 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 end
def insert_pagination_headers def insert_pagination_headers

View File

@ -25,7 +25,7 @@ class Api::V1::EndorsementsController < Api::BaseController
end end
def endorsed_accounts def endorsed_accounts
current_account.endorsed_accounts.includes(:account_stat) current_account.endorsed_accounts.includes(:account_stat).without_suspended
end end
def insert_pagination_headers def insert_pagination_headers

View File

@ -17,14 +17,11 @@ class Api::V1::FavouritesController < Api::BaseController
end end
def cached_favourites def cached_favourites
cache_collection( cache_collection(results.map(&:status), Status)
Status.reorder(nil).joins(:favourites).merge(results),
Status
)
end end
def results def results
@_results ||= account_favourites.paginate_by_id( @_results ||= account_favourites.eager_load(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )

View File

@ -3,15 +3,15 @@
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
before_action :require_user! before_action :require_user!
before_action :set_most_used_tags, only: :index before_action :set_recently_used_tags, only: :index
def index def index
render json: @most_used_tags, each_serializer: REST::TagSerializer render json: @recently_used_tags, each_serializer: REST::TagSerializer
end end
private private
def set_most_used_tags def set_recently_used_tags
@most_used_tags = Tag.most_used(current_account).where.not(id: current_account.featured_tags).limit(10) @recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10)
end end
end end

View File

@ -13,7 +13,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize def authorize
AuthorizeFollowService.new.call(account, current_account) 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 render json: account, serializer: REST::RelationshipSerializer, relationships: relationships
end end
@ -37,7 +37,7 @@ class Api::V1::FollowRequestsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:follow_requests, :account_stat).references(:follow_requests) Account.without_suspended.includes(:follow_requests, :account_stat).references(:follow_requests)
end end
def paginated_follow_requests def paginated_follow_requests

View File

@ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
def index def index
expires_in 1.day, public: true 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 end
private private

View File

@ -37,9 +37,9 @@ class Api::V1::Lists::AccountsController < Api::BaseController
def load_accounts def load_accounts
if unlimited? if unlimited?
@list.accounts.includes(:account_stat).all @list.accounts.without_suspended.includes(:account_stat).all
else 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
end end

View File

@ -38,6 +38,6 @@ class Api::V1::ListsController < Api::BaseController
end end
def list_params def list_params
params.permit(:title) params.permit(:title, :replies_policy)
end end
end end

View File

@ -7,7 +7,7 @@ class Api::V1::MutesController < Api::BaseController
def index def index
@accounts = load_accounts @accounts = load_accounts
render json: @accounts, each_serializer: REST::AccountSerializer render json: @accounts, each_serializer: REST::MutedAccountSerializer
end end
private private
@ -18,6 +18,8 @@ class Api::V1::MutesController < Api::BaseController
def paginated_mutes def paginated_mutes
@paginated_mutes ||= Mute.eager_load(:target_account) @paginated_mutes ||= Mute.eager_load(:target_account)
.joins(:target_account)
.merge(Account.without_suspended)
.where(account: current_account) .where(account: current_account)
.paginate_by_max_id( .paginate_by_max_id(
limit_param(DEFAULT_ACCOUNTS_LIMIT), limit_param(DEFAULT_ACCOUNTS_LIMIT),

View File

@ -14,7 +14,7 @@ class Api::V1::NotificationsController < Api::BaseController
end end
def show def show
@notification = current_account.notifications.find(params[:id]) @notification = current_account.notifications.without_suspended.find(params[:id])
render json: @notification, serializer: REST::NotificationSerializer render json: @notification, serializer: REST::NotificationSerializer
end end
@ -31,18 +31,16 @@ class Api::V1::NotificationsController < Api::BaseController
private private
def load_notifications def load_notifications
cache_collection paginated_notifications, Notification cache_collection_paginated_by_id(
end browserable_account_notifications,
Notification,
def paginated_notifications
browserable_account_notifications.paginate_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT), limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )
end end
def browserable_account_notifications def browserable_account_notifications
current_account.notifications.browserable(exclude_types, from_account) current_account.notifications.without_suspended.browserable(exclude_types, from_account)
end end
def target_statuses_from_notifications def target_statuses_from_notifications

View File

@ -52,6 +52,6 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
def data_params def data_params
return {} if params[:data].blank? 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
end end

View File

@ -32,7 +32,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
private private
def set_statuses 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 end
def set_status def set_status

View File

@ -5,7 +5,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' } before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user! before_action :require_user!
before_action :set_status before_action :set_status, only: [:create]
def create def create
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status) current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
@ -13,10 +13,20 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
end end
def destroy 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! bookmark&.destroy!
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false }) render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, bookmarks_map: { @status.id => false })
rescue Mastodon::NotPermittedError
not_found
end end
private private

View File

@ -22,6 +22,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts def default_accounts
Account Account
.without_suspended
.includes(:favourites, :account_stat) .includes(:favourites, :account_stat)
.references(:favourites) .references(:favourites)
.where(favourites: { status_id: @status.id }) .where(favourites: { status_id: @status.id })

View File

@ -5,7 +5,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' } before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user! before_action :require_user!
before_action :set_status before_action :set_status, only: [:create]
def create def create
FavouriteService.new.call(current_account, @status) FavouriteService.new.call(current_account, @status)
@ -13,8 +13,19 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
end end
def destroy 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 }) render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
rescue Mastodon::NotPermittedError
not_found
end end
private private

View File

@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:statuses, :account_stat).references(:statuses) Account.without_suspended.includes(:statuses, :account_stat).references(:statuses)
end end
def paginated_statuses def paginated_statuses

View File

@ -16,30 +16,29 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end end
def load_statuses def load_statuses
cached_public_statuses cached_public_statuses_page
end end
def cached_public_statuses def cached_public_statuses_page
cache_collection public_statuses, Status cache_collection(public_statuses, Status)
end end
def public_statuses def public_statuses
statuses = public_timeline_statuses.paginate_by_id( public_feed.get(
limit_param(DEFAULT_STATUSES_LIMIT), 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 end
def public_timeline_statuses def public_feed
Status.as_public_timeline(current_account, truthy_param?(:remote) ? :remote : truthy_param?(:local)) PublicFeed.new(
current_account,
local: truthy_param?(:local),
remote: truthy_param?(:remote),
only_media: truthy_param?(:only_media)
)
end end
def insert_pagination_headers def insert_pagination_headers

View File

@ -20,30 +20,29 @@ class Api::V1::Timelines::TagController < Api::BaseController
end end
def cached_tagged_statuses def cached_tagged_statuses
cache_collection tagged_statuses, Status @tag.nil? ? [] : cache_collection(tag_timeline_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
end end
def tag_timeline_statuses 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 end
def insert_pagination_headers def insert_pagination_headers

View File

@ -22,6 +22,7 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
reblog: alerts_enabled, reblog: alerts_enabled,
mention: alerts_enabled, mention: alerts_enabled,
poll: alerts_enabled, poll: alerts_enabled,
status: alerts_enabled,
}, },
} }
@ -57,6 +58,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
end end
def data_params 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
end end

View File

@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error 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 rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller? before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?

View File

@ -2,6 +2,7 @@
class Auth::RegistrationsController < Devise::RegistrationsController class Auth::RegistrationsController < Devise::RegistrationsController
include Devise::Controllers::Rememberable include Devise::Controllers::Rememberable
include RegistrationSpamConcern
layout :determine_layout layout :determine_layout
@ -13,6 +14,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_body_classes, only: [:new, :create, :edit, :update] before_action :set_body_classes, only: [:new, :create, :edit, :update]
before_action :require_not_suspended!, only: [:update] before_action :require_not_suspended!, only: [:update]
before_action :set_cache_headers, only: [:edit, :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] skip_before_action :require_functional!, only: [:edit, :update]
@ -45,16 +47,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def build_resource(hash = nil) def build_resource(hash = nil)
super(hash) super(hash)
resource.locale = I18n.locale resource.locale = I18n.locale
resource.invite_code = params[:invite_code] if resource.invite_code.blank? resource.invite_code = params[:invite_code] if resource.invite_code.blank?
resource.current_sign_in_ip = request.remote_ip resource.registration_form_time = session[:registration_form_time]
resource.sign_up_ip = request.remote_ip
resource.build_account if resource.account.nil? resource.build_account if resource.account.nil?
end end
def configure_sign_up_params def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up) do |u| 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
end end

View File

@ -7,6 +7,7 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_no_authentication, only: [:create] skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional! skip_before_action :require_functional!
skip_before_action :update_user_sign_in
include TwoFactorAuthenticationConcern include TwoFactorAuthenticationConcern
include SignInTokenAuthenticationConcern include SignInTokenAuthenticationConcern
@ -24,6 +25,7 @@ class Auth::SessionsController < Devise::SessionsController
def create def create
super do |resource| super do |resource|
resource.update_sign_in!(request, new_sign_in: true)
remember_me(resource) remember_me(resource)
flash.delete(:notice) flash.delete(:notice)
end end
@ -37,11 +39,27 @@ class Auth::SessionsController < Devise::SessionsController
store_location_for(:user, tmp_stored_location) if continue_after? store_location_for(:user, tmp_stored_location) if continue_after?
end 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 protected
def find_user def find_user
if session[:attempt_user_id] if session[:attempt_user_id]
User.find(session[:attempt_user_id]) User.find_by(id: session[:attempt_user_id])
else else
user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication user = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
@ -51,7 +69,7 @@ class Auth::SessionsController < Devise::SessionsController
end end
def user_params 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 end
def after_sign_in_path_for(resource) def after_sign_in_path_for(resource)
@ -74,6 +92,7 @@ class Auth::SessionsController < Devise::SessionsController
def require_no_authentication def require_no_authentication
super super
# Delete flash message that isn't entirely useful and may be confusing in # Delete flash message that isn't entirely useful and may be confusing in
# most cases because /web doesn't display/clear flash messages. # most cases because /web doesn't display/clear flash messages.
flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated') 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) def home_paths(resource)
paths = [about_path] paths = [about_path]
if single_user_mode? && resource.is_a?(User) if single_user_mode? && resource.is_a?(User)
paths << short_account_path(username: resource.account) paths << short_account_path(username: resource.account)
end end
paths paths
end end
def continue_after? def continue_after?
truthy_param?(:continue) truthy_param?(:continue)
end 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 end

View File

@ -29,6 +29,24 @@ module AccountOwnedConcern
end end
def check_account_suspension 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
end end

View File

@ -47,4 +47,8 @@ module CacheConcern
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
end 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 end

View File

@ -32,7 +32,6 @@ module ChallengableConcern
if params.key?(:form_challenge) if params.key?(:form_challenge)
if challenge_passed? if challenge_passed?
session[:challenge_passed_at] = Time.now.utc session[:challenge_passed_at] = Time.now.utc
return
else else
flash.now[:alert] = I18n.t('challenge.invalid_password') flash.now[:alert] = I18n.t('challenge.invalid_password')
render_challenge render_challenge

View File

@ -5,7 +5,6 @@ module ExportControllerConcern
included do included do
before_action :authenticate_user! before_action :authenticate_user!
before_action :require_not_suspended!
before_action :load_export before_action :load_export
skip_before_action :require_functional! skip_before_action :require_functional!
@ -30,8 +29,4 @@ module ExportControllerConcern
def export_filename def export_filename
"#{controller_name}.csv" "#{controller_name}.csv"
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View 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

View File

@ -18,7 +18,9 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token def authenticate_with_sign_in_token
user = self.resource = find_user 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) authenticate_with_sign_in_token_attempt(user)
elsif user.present? && user.external_or_valid_password?(user_params[:password]) elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_sign_in_token(user) prompt_for_sign_in_token(user)
@ -27,7 +29,7 @@ module SignInTokenAuthenticationConcern
def authenticate_with_sign_in_token_attempt(user) def authenticate_with_sign_in_token_attempt(user)
if valid_sign_in_token_attempt?(user) if valid_sign_in_token_attempt?(user)
session.delete(:attempt_user_id) clear_attempt_from_session
remember_me(user) remember_me(user)
sign_in(user) sign_in(user)
else 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! UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later!
end end
set_locale do set_attempt_session(user)
session[:attempt_user_id] = user.id
@body_classes = 'lighter' @body_classes = 'lighter'
render :sign_in_token
end set_locale { render :sign_in_token }
end end
end end

View File

@ -76,6 +76,7 @@ module SignatureVerification
raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window? raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
verify_signature_strength! verify_signature_strength!
verify_body_digest!
account = account_from_key_id(signature_params['keyId']) account = account_from_key_id(signature_params['keyId'])
@ -126,12 +127,21 @@ module SignatureVerification
def verify_signature_strength! 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 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 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') raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
end 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) 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 = account
@signed_request_account @signed_request_account
end end
@ -153,8 +163,6 @@ module SignatureVerification
raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank? raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
"(expires): #{signature_params['expires']}" "(expires): #{signature_params['expires']}"
elsif signed_header == 'digest'
"digest: #{body_digest}"
else else
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}" "#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
end end
@ -187,7 +195,7 @@ module SignatureVerification
end end
def body_digest def body_digest
"SHA-256=#{Digest::SHA256.base64digest(request_body)}" @body_digest ||= Digest::SHA256.base64digest(request_body)
end end
def to_header_name(name) def to_header_name(name)

View File

@ -8,7 +8,23 @@ module TwoFactorAuthenticationConcern
end end
def two_factor_enabled? 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 end
def valid_otp_attempt?(user) def valid_otp_attempt?(user)
@ -21,16 +37,33 @@ module TwoFactorAuthenticationConcern
def authenticate_with_two_factor def authenticate_with_two_factor
user = self.resource = find_user user = self.resource = find_user
if user_params[:otp_attempt].present? && session[:attempt_user_id] if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
authenticate_with_two_factor_attempt(user) 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]) elsif user.present? && user.external_or_valid_password?(user_params[:password])
prompt_for_two_factor(user) prompt_for_two_factor(user)
end end
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) if valid_otp_attempt?(user)
session.delete(:attempt_user_id) clear_attempt_from_session
remember_me(user) remember_me(user)
sign_in(user) sign_in(user)
else else
@ -40,10 +73,18 @@ module TwoFactorAuthenticationConcern
end end
def prompt_for_two_factor(user) def prompt_for_two_factor(user)
set_locale do set_attempt_session(user)
session[:attempt_user_id] = user.id
@body_classes = 'lighter' @body_classes = 'lighter'
render :two_factor @webauthn_enabled = user.webauthn_enabled?
@scheme_type = begin
if user.webauthn_enabled? && user_params[:otp_attempt].blank?
'webauthn'
else
'totp'
end
end end
set_locale { render :two_factor }
end end
end end

View File

@ -6,14 +6,13 @@ module UserTrackingConcern
UPDATE_SIGN_IN_HOURS = 24 UPDATE_SIGN_IN_HOURS = 24
included do included do
before_action :set_user_activity before_action :update_user_sign_in
end end
private private
def set_user_activity def update_user_sign_in
return unless user_needs_sign_in_update? current_user.update_sign_in!(request) if user_needs_sign_in_update?
current_user.update_tracked_fields!(request)
end end
def user_needs_sign_in_update? def user_needs_sign_in_update?

View File

@ -9,7 +9,7 @@ class FiltersController < ApplicationController
before_action :set_body_classes before_action :set_body_classes
def index def index
@filters = current_account.custom_filters @filters = current_account.custom_filters.order(:phrase)
end end
def new def new

View File

@ -52,6 +52,14 @@ class FollowerAccountsController < ApplicationController
account_followers_url(@account, page: page) unless page.nil? account_followers_url(@account, page: page) unless page.nil?
end 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 def collection_presenter
if page_requested? if page_requested?
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(
@ -60,8 +68,8 @@ class FollowerAccountsController < ApplicationController
size: @account.followers_count, size: @account.followers_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) }, items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
part_of: account_followers_url(@account), part_of: account_followers_url(@account),
next: page_url(follows.next_page), next: next_page_url,
prev: page_url(follows.prev_page) prev: prev_page_url
) )
else else
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(

View File

@ -52,6 +52,14 @@ class FollowingAccountsController < ApplicationController
account_following_index_url(@account, page: page) unless page.nil? account_following_index_url(@account, page: page) unless page.nil?
end 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 def collection_presenter
if page_requested? if page_requested?
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(
@ -60,8 +68,8 @@ class FollowingAccountsController < ApplicationController
size: @account.following_count, size: @account.following_count,
items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) }, items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
part_of: account_following_index_url(@account), part_of: account_following_index_url(@account),
next: page_url(follows.next_page), next: next_page_url,
prev: page_url(follows.prev_page) prev: prev_page_url
) )
else else
ActivityPub::CollectionPresenter.new( ActivityPub::CollectionPresenter.new(

View File

@ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController
end end
def restrict_fields_to 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
end end

View File

@ -5,6 +5,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :store_current_location before_action :store_current_location
before_action :authenticate_resource_owner! before_action :authenticate_resource_owner!
before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes before_action :set_body_classes
skip_before_action :require_functional! skip_before_action :require_functional!
@ -25,4 +26,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def store_current_location def store_current_location
store_location_for(:user, request.url) store_location_for(:user, request.url)
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -5,6 +5,7 @@ class RelationshipsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_accounts, only: :show before_action :set_accounts, only: :show
before_action :set_relationships, only: :show
before_action :set_body_classes before_action :set_body_classes
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship? 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) @accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
end end
def set_relationships
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
end
def form_account_batch_params def form_account_batch_params
params.require(:form_account_batch).permit(:action, account_ids: []) params.require(:form_account_batch).permit(:action, account_ids: [])
end end
@ -49,7 +54,9 @@ class RelationshipsController < ApplicationController
end end
def action_from_button def action_from_button
if params[:unfollow] if params[:follow]
'follow'
elsif params[:unfollow]
'unfollow' 'unfollow'
elsif params[:remove_from_followers] elsif params[:remove_from_followers]
'remove_from_followers' 'remove_from_followers'

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::AliasesController < Settings::BaseController 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_aliases, except: :destroy
before_action :set_alias, only: :destroy before_action :set_alias, only: :destroy

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ApplicationsController < Settings::BaseController class Settings::ApplicationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate] before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update] before_action :prepare_scopes, only: [:create, :update]

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::BaseController < ApplicationController class Settings::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_body_classes before_action :set_body_classes
before_action :set_cache_headers before_action :set_cache_headers
@ -13,4 +16,8 @@ class Settings::BaseController < ApplicationController
def set_cache_headers def set_cache_headers
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate' response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -1,14 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::DeletesController < Settings::BaseController 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! skip_before_action :require_functional!
before_action :require_not_suspended!
before_action :check_enabled_deletion
def show def show
@confirmation = Form::DeleteConfirmation.new @confirmation = Form::DeleteConfirmation.new
end end
@ -45,8 +42,8 @@ class Settings::DeletesController < Settings::BaseController
end end
def destroy_account! def destroy_account!
current_account.suspend! current_account.suspend!(origin: :local)
Admin::SuspensionWorker.perform_async(current_user.account_id, true) AccountDeletionWorker.perform_async(current_user.account_id)
sign_out sign_out
end end
end end

View File

@ -2,7 +2,7 @@
module Settings module Settings
module Exports module Exports
class BlockedAccountsController < ApplicationController class BlockedAccountsController < BaseController
include ExportControllerConcern include ExportControllerConcern
def index def index

View File

@ -2,7 +2,7 @@
module Settings module Settings
module Exports module Exports
class BlockedDomainsController < ApplicationController class BlockedDomainsController < BaseController
include ExportControllerConcern include ExportControllerConcern
def index def index

View 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

View File

@ -2,7 +2,7 @@
module Settings module Settings
module Exports module Exports
class FollowingAccountsController < ApplicationController class FollowingAccountsController < BaseController
include ExportControllerConcern include ExportControllerConcern
def index def index

View File

@ -2,7 +2,7 @@
module Settings module Settings
module Exports module Exports
class ListsController < ApplicationController class ListsController < BaseController
include ExportControllerConcern include ExportControllerConcern
def index def index

View File

@ -2,7 +2,7 @@
module Settings module Settings
module Exports module Exports
class MutedAccountsController < ApplicationController class MutedAccountsController < BaseController
include ExportControllerConcern include ExportControllerConcern
def index def index

View File

@ -3,11 +3,6 @@
class Settings::ExportsController < Settings::BaseController class Settings::ExportsController < Settings::BaseController
include Authorization include Authorization
layout 'admin'
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional! skip_before_action :require_functional!
def show def show
@ -16,8 +11,6 @@ class Settings::ExportsController < Settings::BaseController
end end
def create def create
raise Mastodon::NotPermittedError unless user_signed_in?
backup = nil backup = nil
RedisLock.acquire(lock_options) do |lock| RedisLock.acquire(lock_options) do |lock|
@ -37,8 +30,4 @@ class Settings::ExportsController < Settings::BaseController
def lock_options def lock_options
{ redis: Redis.current, key: "backup:#{current_user.id}" } { redis: Redis.current, key: "backup:#{current_user.id}" }
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -1,12 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::FeaturedTagsController < Settings::BaseController class Settings::FeaturedTagsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_featured_tags, only: :index before_action :set_featured_tags, only: :index
before_action :set_featured_tag, except: [:index, :create] 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 def index
@featured_tag = FeaturedTag.new @featured_tag = FeaturedTag.new
@ -20,7 +17,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
redirect_to settings_featured_tags_path redirect_to settings_featured_tags_path
else else
set_featured_tags set_featured_tags
set_most_used_tags set_recently_used_tags
render :index render :index
end end
@ -41,8 +38,8 @@ class Settings::FeaturedTagsController < Settings::BaseController
@featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?) @featured_tags = current_account.featured_tags.order(statuses_count: :desc).reject(&:new_record?)
end end
def set_most_used_tags def set_recently_used_tags
@most_used_tags = Tag.most_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10) @recently_used_tags = Tag.recently_used(current_account).where.not(id: @featured_tags.map(&:id)).limit(10)
end end
def featured_tag_params def featured_tag_params

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::IdentityProofsController < Settings::BaseController class Settings::IdentityProofsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :check_required_params, only: :new before_action :check_required_params, only: :new
def index def index

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ImportsController < Settings::BaseController class Settings::ImportsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_account before_action :set_account
def show def show

View File

@ -1,13 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::Migration::RedirectsController < Settings::BaseController class Settings::Migration::RedirectsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :require_not_suspended!
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :require_not_suspended!
def new def new
@redirect = Form::Redirect.new @redirect = Form::Redirect.new
end end
@ -38,8 +35,4 @@ class Settings::Migration::RedirectsController < Settings::BaseController
def resource_params def resource_params
params.require(:form_redirect).permit(:acct, :current_password, :current_username) params.require(:form_redirect).permit(:acct, :current_password, :current_username)
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -1,15 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::MigrationsController < Settings::BaseController class Settings::MigrationsController < Settings::BaseController
layout 'admin' skip_before_action :require_functional!
before_action :authenticate_user!
before_action :require_not_suspended! before_action :require_not_suspended!
before_action :set_migrations before_action :set_migrations
before_action :set_cooldown before_action :set_cooldown
skip_before_action :require_functional!
def show def show
@migration = current_account.migrations.build @migration = current_account.migrations.build
end end
@ -44,8 +41,4 @@ class Settings::MigrationsController < Settings::BaseController
def on_cooldown? def on_cooldown?
@cooldown.present? @cooldown.present?
end end
def require_not_suspended!
forbidden if current_account.suspended?
end
end end

View File

@ -2,7 +2,6 @@
module Settings module Settings
class PicturesController < BaseController class PicturesController < BaseController
before_action :authenticate_user!
before_action :set_account before_action :set_account
before_action :set_picture before_action :set_picture

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::PreferencesController < Settings::BaseController class Settings::PreferencesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show; end def show; end
def update def update
@ -48,6 +44,7 @@ class Settings::PreferencesController < Settings::BaseController
:setting_display_media, :setting_display_media,
:setting_expand_spoilers, :setting_expand_spoilers,
:setting_reduce_motion, :setting_reduce_motion,
:setting_disable_swiping,
:setting_system_font_ui, :setting_system_font_ui,
:setting_noindex, :setting_noindex,
:setting_theme, :setting_theme,

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ProfilesController < Settings::BaseController class Settings::ProfilesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_account before_action :set_account
def show def show

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::SessionsController < Settings::BaseController class Settings::SessionsController < Settings::BaseController
before_action :authenticate_user!
before_action :set_session, only: :destroy
skip_before_action :require_functional! skip_before_action :require_functional!
before_action :require_not_suspended!
before_action :set_session, only: :destroy
def destroy def destroy
@session.destroy! @session.destroy!
flash[:notice] = I18n.t('sessions.revoke_success') flash[:notice] = I18n.t('sessions.revoke_success')

View File

@ -5,31 +5,31 @@ module Settings
class ConfirmationsController < BaseController class ConfirmationsController < BaseController
include ChallengableConcern include ChallengableConcern
layout 'admin' skip_before_action :require_functional!
before_action :authenticate_user!
before_action :require_challenge! before_action :require_challenge!
before_action :ensure_otp_secret before_action :ensure_otp_secret
skip_before_action :require_functional!
def new def new
prepare_two_factor_form prepare_two_factor_form
end end
def create 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') flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success')
current_user.otp_required_for_login = true current_user.otp_required_for_login = true
current_user.otp_secret = session[:new_otp_secret]
@recovery_codes = current_user.generate_otp_backup_codes! @recovery_codes = current_user.generate_otp_backup_codes!
current_user.save! current_user.save!
UserMailer.two_factor_enabled(current_user).deliver_later! UserMailer.two_factor_enabled(current_user).deliver_later!
session.delete(:new_otp_secret)
render 'settings/two_factor_authentication/recovery_codes/index' render 'settings/two_factor_authentication/recovery_codes/index'
else else
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code') flash.now[:alert] = I18n.t('otp_authentication.wrong_code')
prepare_two_factor_form prepare_two_factor_form
render :new render :new
end end
@ -43,12 +43,15 @@ module Settings
def prepare_two_factor_form def prepare_two_factor_form
@confirmation = Form::TwoFactorConfirmation.new @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) @qrcode = RQRCode::QRCode.new(@provision_url)
end end
def ensure_otp_secret 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 end
end end

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