@@ -27,6 +27,7 @@ import {
2727 moderation_entry ,
2828 basic_moderation_with_user ,
2929 basic_moderation ,
30+ note_moderation_types ,
3031} from "./schemata.js" ;
3132import { set_interval } from "../../../../utils/node.js" ;
3233
@@ -76,6 +77,10 @@ export class ParseError extends Error {
7677 }
7778}
7879
80+ export type revoke_handler_options = {
81+ allow_no_entry ?: boolean ;
82+ } ;
83+
7984// Returns the corresponding duration in milliseconds,
8085// or null for permanent duration.
8186// Throws ParseError when parsing fails.
@@ -145,7 +150,7 @@ export abstract class ModerationComponent extends BotComponent {
145150 sleep_list : SleepList < mongo . WithId < moderation_entry > , mongo . BSON . ObjectId > ;
146151 timer : NodeJS . Timer | null = null ;
147152
148- static non_duration_moderation_set = new Set ( [ "warn" , "kick" , "softban" , "note" ] ) ;
153+ static non_duration_moderation_set = new Set ( [ "warn" , "kick" , "softban" , "note" , "voice_note" ] ) ;
149154
150155 static moderations_count = new PromClient . Gauge ( {
151156 name : "tccpp_moderations_count" ,
@@ -306,6 +311,12 @@ export abstract class ModerationComponent extends BotComponent {
306311 // Check if the moderation is currently applied in Discord (role present, ban in place, etc.)
307312 abstract is_moderation_applied_in_discord ( moderation : basic_moderation_with_user ) : Promise < boolean > ;
308313
314+ // Apply revoke action to Discord without an existing moderation entry.
315+ // Must be overridden in subclasses that use allow_no_entry mode.
316+ async apply_revoke_to_discord ( _member : Discord . GuildMember ) : Promise < void > {
317+ throw new Error ( "apply_revoke_to_discord must be overridden when using allow_no_entry" ) ;
318+ }
319+
309320 // Check if there are other active moderations of the same type for this user (excluding the given entry)
310321 async has_other_active_moderations ( entry : mongo . WithId < moderation_entry > ) : Promise < boolean > {
311322 const query : mongo . Filter < moderation_entry > = {
@@ -576,7 +587,7 @@ export abstract class ModerationComponent extends BotComponent {
576587 ] ;
577588 }
578589 this . staff_action_log . send ( message_options ) . catch ( this . wheatley . critical_error . bind ( this . wheatley ) ) ;
579- if ( moderation . type !== "note" ) {
590+ if ( ! note_moderation_types . includes ( moderation . type ) ) {
580591 this . public_action_log
581592 . send ( {
582593 embeds : [
@@ -727,8 +738,24 @@ export abstract class ModerationComponent extends BotComponent {
727738 expunged : null ,
728739 link : command . get_or_forge_url ( ) ,
729740 } ;
730- const cant_dm = ! ( await this . notify_user ( user , this . past_participle , moderation ) ) ;
741+ const is_note_type = note_moderation_types . includes ( this . type ) ;
742+ const notification_failed = await ( async ( ) => {
743+ if ( ! is_note_type ) {
744+ return ! ( await this . notify_user ( user , this . past_participle , moderation ) ) ;
745+ } else {
746+ return false ;
747+ }
748+ } ) ( ) ;
731749 await this . issue_moderation ( moderation ) ;
750+ const success_message = is_note_type
751+ ? `Note added for ${ user . displayName } `
752+ : `${ user . displayName } was ${ this . past_participle } ` ;
753+ const reason_line = ( ( ) => {
754+ if ( ! command . is_slash ( ) || ! reason ) {
755+ return null ;
756+ }
757+ return is_note_type ? `**Note:** ${ reason } ` : `**Reason:** ${ reason } ` ;
758+ } ) ( ) ;
732759 await command . reply ( {
733760 content :
734761 basic_moderation_info . type === "ban"
@@ -739,8 +766,8 @@ export abstract class ModerationComponent extends BotComponent {
739766 . setColor ( colors . wheatley )
740767 . setDescription (
741768 build_description (
742- `${ this . wheatley . emoji . success } ***${ user . displayName } was ${ this . past_participle } ***` ,
743- command . is_slash ( ) && reason ? `**Reason:** ${ reason } ` : null ,
769+ `${ this . wheatley . emoji . success } ***${ success_message } ***` ,
770+ reason_line ,
744771 ( ! this . is_once_off && duration_string === null ) || reason === null
745772 ? `Remember to provide a ${ [
746773 ! this . is_once_off && duration_string === null ? "duration" : null ,
@@ -752,13 +779,16 @@ export abstract class ModerationComponent extends BotComponent {
752779 ! this . is_once_off && duration_string !== null
753780 ? `**Duration**: ${ duration == null ? "permanent" : time_to_human ( duration ) } `
754781 : null ,
755- cant_dm ? "Note: Couldn't notify user (DM and thread fallback both failed)." : null ,
782+ notification_failed
783+ ? "Note: Couldn't notify user (DM and thread fallback both failed)."
784+ : null ,
756785 ) ,
757786 )
758787 . setFooter ( {
759788 text : `Case ${ moderation . case_number } ` ,
760789 } ) ,
761790 ] ,
791+ ephemeral_if_possible : is_note_type ,
762792 } ) ;
763793 } catch ( e ) {
764794 if ( e instanceof ParseError ) {
@@ -841,6 +871,7 @@ export abstract class ModerationComponent extends BotComponent {
841871 user : Discord . User ,
842872 reason : string | null ,
843873 additional_moderation_properties : any = { } ,
874+ options : revoke_handler_options = { } ,
844875 ) {
845876 assert ( ! this . is_once_off ) ;
846877 try {
@@ -862,7 +893,17 @@ export abstract class ModerationComponent extends BotComponent {
862893 sort : { issued_at : - 1 } ,
863894 } ,
864895 ) ;
865- if ( ! res ) {
896+ if ( ! res && options . allow_no_entry ) {
897+ const member = await this . wheatley . try_fetch_guild_member ( user ) ;
898+ if ( member ) {
899+ await this . apply_revoke_to_discord ( member ) ;
900+ }
901+ const message =
902+ `${ this . wheatley . emoji . success } ` + `***${ user . displayName } was un${ this . past_participle } ***` ;
903+ await command . reply ( {
904+ embeds : [ new Discord . EmbedBuilder ( ) . setColor ( colors . wheatley ) . setDescription ( message ) ] ,
905+ } ) ;
906+ } else if ( ! res ) {
866907 await this . reply_with_error ( command , `User is not ${ this . past_participle } ` ) ;
867908 } else {
868909 this . sleep_list . remove ( res . _id ) ;
@@ -904,7 +945,7 @@ export abstract class ModerationComponent extends BotComponent {
904945 . setDescription ( remaining_message ) ,
905946 ] ,
906947 } ) ;
907- if ( res . type !== "note" ) {
948+ if ( ! note_moderation_types . includes ( res . type ) ) {
908949 await this . public_action_log . send ( {
909950 embeds : [
910951 Modlogs . case_summary (
0 commit comments