@@ -27,11 +27,15 @@ public class ModerationService(
2727 IInfractionRepository infractionRepository ,
2828 IDeletedMessageRepository deletedMessageRepository ,
2929 IDeletedMessageBatchRepository deletedMessageBatchRepository ,
30- ModixContext db )
30+ ModixContext db ,
31+ IEnumerable < IInfractionTypeHandler > infractionTypeHandlers )
3132{
3233 public const string MUTE_ROLE_NAME = "MODiX_Moderation_Mute" ;
3334 private const int MaxReasonLength = 1000 ;
3435
36+ private readonly Dictionary < InfractionType , IInfractionTypeHandler > _handlersByType =
37+ infractionTypeHandlers . ToDictionary ( h => h . Type ) ;
38+
3539 public async Task AutoRescindExpiredInfractions ( )
3640 {
3741 var expiredInfractions = await db
@@ -58,7 +62,8 @@ await RescindInfractionAsync(expiredInfraction.Id,
5862 public async Task CreateInfractionAsync ( ulong guildId , ulong moderatorId , InfractionType type , ulong subjectId ,
5963 string reason , TimeSpan ? duration )
6064 {
61- authorizationService . RequireClaims ( _createInfractionClaimsByType [ type ] ) ;
65+ var handler = _handlersByType [ type ] ;
66+ authorizationService . RequireClaims ( handler . RequiredClaim ) ;
6267
6368 if ( reason is null )
6469 throw new ArgumentNullException ( nameof ( reason ) ) ;
@@ -67,18 +72,21 @@ public async Task CreateInfractionAsync(ulong guildId, ulong moderatorId, Infrac
6772 throw new ArgumentException ( $ "Reason must be less than { MaxReasonLength } characters in length",
6873 nameof ( reason ) ) ;
6974
70- if ( type is InfractionType . Notice or InfractionType . Warning && string . IsNullOrWhiteSpace ( reason ) )
75+ if ( handler . RequiresReason && string . IsNullOrWhiteSpace ( reason ) )
7176 throw new InvalidOperationException ( $ "{ type . ToString ( ) } infractions require a reason to be given") ;
7277
7378 var guild = await discordClient . GetGuildAsync ( guildId ) ;
7479 var subject = await userService . TryGetGuildUserAsync ( guild , subjectId , CancellationToken . None ) ;
7580
7681 using ( var transaction = await infractionRepository . BeginCreateTransactionAsync ( ) )
7782 {
78- if ( type is InfractionType . Ban or InfractionType . Mute )
83+ if ( handler . RequiresRankValidation )
7984 {
8085 await RequireSubjectRankLowerThanModeratorRankAsync ( guild , moderatorId , subject ) ;
86+ }
8187
88+ if ( handler . RequiresUniqueActiveInfraction )
89+ {
8290 if ( await infractionRepository . AnyAsync ( new InfractionSearchCriteria ( )
8391 {
8492 GuildId = guildId ,
@@ -111,17 +119,7 @@ await infractionRepository.CreateAsync(
111119 // Note that we'll need to upgrade to the latest Discord.NET version to get access to the audit log.
112120
113121 // Assuming that our Infractions repository is always correct, regarding the state of the Discord API.
114- switch ( type )
115- {
116- case InfractionType . Mute when subject is not null :
117- await subject . AddRoleAsync (
118- await GetDesignatedMuteRoleAsync ( guild ) ) ;
119- break ;
120-
121- case InfractionType . Ban :
122- await guild . AddBanAsync ( subjectId , reason : reason ) ;
123- break ;
124- }
122+ await handler . ApplyInfractionAsync ( guild , subject , subjectId , reason ) ;
125123 }
126124
127125 public async Task RescindInfractionAsync ( long infractionId , string ? reason = null )
@@ -185,36 +183,8 @@ await RequireSubjectRankLowerThanModeratorRankAsync(infraction.GuildId,
185183 await infractionRepository . TryDeleteAsync ( infraction . Id , authorizationService . CurrentUserId . Value ) ;
186184
187185 var guild = await discordClient . GetGuildAsync ( infraction . GuildId ) ;
188-
189- switch ( infraction . Type )
190- {
191- case InfractionType . Mute :
192-
193- if ( await userService . GuildUserExistsAsync ( guild . Id , infraction . Subject . Id ) )
194- {
195- var subject = await userService . GetGuildUserAsync ( guild . Id , infraction . Subject . Id ) ;
196- await subject . RemoveRoleAsync ( await GetDesignatedMuteRoleAsync ( guild ) ) ;
197- }
198- else
199- {
200- Log . Warning (
201- "Tried to unmute {User} while deleting mute infraction, but they weren't in the guild: {Guild}" ,
202- infraction . Subject . Id , guild . Id ) ;
203- }
204-
205- break ;
206-
207- case InfractionType . Ban :
208-
209- //If the infraction has already been rescinded, we don't need to actually perform the unmute/unban
210- //Doing so will return a 404 from Discord (trying to remove a nonexistant ban)
211- if ( infraction . RescindAction is null )
212- {
213- await guild . RemoveBanAsync ( infraction . Subject . Id ) ;
214- }
215-
216- break ;
217- }
186+ var handler = _handlersByType [ infraction . Type ] ;
187+ await handler . DeleteInfractionAsync ( guild , infraction . Subject . Id , infraction ) ;
218188 }
219189
220190 public async Task DeleteMessageAsync ( IMessage message , string reason , ulong deletedById ,
@@ -394,7 +364,8 @@ public async Task<bool> AnyActiveInfractions(ulong guildId, ulong userId, Infrac
394364 if ( infraction is null )
395365 return ( false , $ "An infraction with an ID of { infractionId } could not be found.") ;
396366
397- authorizationService . RequireClaims ( _createInfractionClaimsByType [ infraction . Type ] ) ;
367+ var handler = _handlersByType [ infraction . Type ] ;
368+ authorizationService . RequireClaims ( handler . RequiredClaim ) ;
398369
399370 // Allow users who created the infraction to bypass any further
400371 // validation and update their own infraction
@@ -443,8 +414,10 @@ private async Task DoRescindInfractionAsync(InfractionType type,
443414 string ? reason = null ,
444415 bool isAutoRescind = false )
445416 {
446- RequestOptions ? GetRequestOptions ( ) =>
447- string . IsNullOrEmpty ( reason ) ? null : new RequestOptions { AuditLogReason = reason } ;
417+ var handler = _handlersByType [ type ] ;
418+
419+ if ( ! handler . CanBeRescinded )
420+ throw new InvalidOperationException ( $ "{ type } infractions cannot be rescinded.") ;
448421
449422 if ( ! isAutoRescind )
450423 {
@@ -453,30 +426,7 @@ await RequireSubjectRankLowerThanModeratorRankAsync(guildId, authorizationServic
453426 }
454427
455428 var guild = await discordClient . GetGuildAsync ( guildId ) ;
456-
457- switch ( type )
458- {
459- case InfractionType . Mute :
460- if ( ! await userService . GuildUserExistsAsync ( guild . Id , subjectId ) )
461- {
462- Log . Information (
463- "Attempted to remove the mute role from {0} ({1}), but they were not in the server." ,
464- infraction ? . Subject . GetFullUsername ( ) ?? "Unknown user" ,
465- subjectId ) ;
466- break ;
467- }
468-
469- var subject = await userService . GetGuildUserAsync ( guild . Id , subjectId ) ;
470- await subject . RemoveRoleAsync ( await GetDesignatedMuteRoleAsync ( guild ) , GetRequestOptions ( ) ) ;
471- break ;
472-
473- case InfractionType . Ban :
474- await guild . RemoveBanAsync ( subjectId , GetRequestOptions ( ) ) ;
475- break ;
476-
477- default :
478- throw new InvalidOperationException ( $ "{ type } infractions cannot be rescinded.") ;
479- }
429+ await handler . RescindInfractionAsync ( guild , subjectId , reason , infraction ) ;
480430
481431 if ( infraction != null )
482432 {
@@ -485,21 +435,6 @@ await infractionRepository.TryRescindAsync(infraction.Id, authorizationService.C
485435 }
486436 }
487437
488- private async Task < IRole > GetDesignatedMuteRoleAsync ( IGuild guild )
489- {
490- var mapping = ( await designatedRoleMappingRepository . SearchBriefsAsync (
491- new DesignatedRoleMappingSearchCriteria ( )
492- {
493- GuildId = guild . Id , Type = DesignatedRoleType . ModerationMute , IsDeleted = false
494- } ) ) . FirstOrDefault ( ) ;
495-
496- if ( mapping == null )
497- throw new InvalidOperationException (
498- $ "There are currently no designated mute roles within guild { guild . Id } ") ;
499-
500- return guild . Roles . First ( x => x . Id == mapping . Role . Id ) ;
501- }
502-
503438 private async Task < IEnumerable < GuildRoleBrief > > GetRankRolesAsync ( ulong guildId )
504439 => ( await designatedRoleMappingRepository
505440 . SearchBriefsAsync ( new DesignatedRoleMappingSearchCriteria
@@ -554,13 +489,4 @@ private async Task<bool> DoesModeratorOutrankUserAsync(IGuild guild, ulong moder
554489
555490 return greatestSubjectRankPosition < greatestModeratorRankPosition ;
556491 }
557-
558- private static readonly Dictionary < InfractionType , AuthorizationClaim > _createInfractionClaimsByType
559- = new ( )
560- {
561- { InfractionType . Notice , AuthorizationClaim . ModerationNote } ,
562- { InfractionType . Warning , AuthorizationClaim . ModerationWarn } ,
563- { InfractionType . Mute , AuthorizationClaim . ModerationMute } ,
564- { InfractionType . Ban , AuthorizationClaim . ModerationBan }
565- } ;
566492}
0 commit comments