@@ -47,92 +47,137 @@ public async Task ProcessMessage(IMessage originalMessage, CancellationToken tok
4747 }
4848
4949 var argText = content . Length > trigger . Length ? content [ trigger . Length ..] . Trim ( ) : string . Empty ;
50+ var tokens = argText . Split ( ' ' , StringSplitOptions . RemoveEmptyEntries ) ;
51+ if ( tokens . Length == 0 )
52+ {
53+ await originalMessage . Channel . SendMessageAsync ( "Usage: purgeold <#channel>|<channelId>|#name <count>" , messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
54+ return ;
55+ }
56+
57+ // Parse target channel from first token
58+ var channelArg = tokens [ 0 ] ;
59+ var guild = textChannel . Guild ;
60+ ITextChannel targetChannel = null ;
61+ if ( channelArg . StartsWith ( "<#" ) && channelArg . EndsWith ( ">" ) && channelArg . Length > 3 )
62+ {
63+ var inner = channelArg . Substring ( 2 , channelArg . Length - 3 ) ;
64+ if ( ulong . TryParse ( inner , out var mentionedId ) )
65+ {
66+ targetChannel = await guild . GetTextChannelAsync ( mentionedId ) ;
67+ }
68+ }
69+ else if ( ulong . TryParse ( channelArg , out var channelIdFromNumber ) )
70+ {
71+ targetChannel = await guild . GetTextChannelAsync ( channelIdFromNumber ) ;
72+ }
73+ else
74+ {
75+ var name = channelArg . StartsWith ( "#" ) && channelArg . Length > 1 ? channelArg [ 1 ..] : channelArg ;
76+ var allTextChannels = await guild . GetTextChannelsAsync ( ) ;
77+ targetChannel = allTextChannels . FirstOrDefault ( c => string . Equals ( c . Name , name , StringComparison . InvariantCultureIgnoreCase ) ) ;
78+ }
79+
80+ if ( targetChannel == null )
81+ {
82+ await originalMessage . Channel . SendMessageAsync ( "Channel not found. Specify a valid channel (mention, ID, or #name)." , messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
83+ return ;
84+ }
85+
86+ if ( targetChannel . IsPublicChannel ( ) )
87+ {
88+ await originalMessage . Channel . SendMessageAsync ( "Refusing to purge a public channel." , messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
89+ return ;
90+ }
91+
92+ // Parse count from second token
5093 int requestedCount = 0 ;
51- _ = int . TryParse ( argText , out requestedCount ) ;
94+ if ( tokens . Length > 1 )
95+ {
96+ _ = int . TryParse ( tokens [ 1 ] , out requestedCount ) ;
97+ }
98+
99+ if ( requestedCount <= 0 )
100+ {
101+ await originalMessage . Channel . SendMessageAsync ( "Please specify a positive count (e.g., purgeold #my-channel 100)." , messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
102+ return ;
103+ }
52104
53105 var deleteDelayMs = 5_000 ;
54106
55107 var cutoff = DateTimeOffset . UtcNow - TimeSpan . FromDays ( 30 ) ;
56108
57109 int deleted = 0 ;
58- var channelBreakdown = new List < string > ( ) ;
59- await originalMessage . Channel . SendMessageAsync ( $ "Purging up to { requestedCount } messages older than 30 days across private channels...", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
110+ await originalMessage . Channel . SendMessageAsync ( $ "Purging up to { requestedCount } messages older than 30 days in #{ targetChannel . Name } ...", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
60111
61- var guild = textChannel . Guild ;
62- var channels = await guild . GetTextChannelsAsync ( ) ;
63- foreach ( var channel in channels . Where ( c => ! c . IsPublicChannel ( ) ) )
112+ var channel = targetChannel ;
113+ var pinned = await channel . GetPinnedMessagesAsync ( options : token . ToRequestOptions ( ) ) ;
114+ var pinnedIds = new HashSet < ulong > ( pinned . Select ( x => x . Id ) ) ;
115+
116+ IMessage fromMessage = null ;
117+ DateTimeOffset ? lastPurgedTimestamp = null ;
118+ int deletedInThisChannel = 0 ;
119+ while ( deleted < requestedCount )
64120 {
65- if ( deleted >= requestedCount )
121+ IList < IMessage > messages = [ ] ;
122+ if ( fromMessage == null )
66123 {
67- break ;
124+ Console . WriteLine ( $ "Retrieving latest messages for #{ channel . Name } ...") ;
125+ messages = [ .. await channel . GetMessagesAsync ( ) . FlattenAsync ( ) ] ;
126+ }
127+ else
128+ {
129+ Console . WriteLine ( $ "Retrieving messages before { fromMessage . Id } for #{ channel . Name } ...") ;
130+ messages = [ .. await channel . GetMessagesAsync ( fromMessage , Direction . Before , 100 , options : token . ToRequestOptions ( ) ) . FlattenAsync ( ) ] ;
68131 }
69132
70- var pinned = await channel . GetPinnedMessagesAsync ( options : token . ToRequestOptions ( ) ) ;
71- var pinnedIds = new HashSet < ulong > ( pinned . Select ( x => x . Id ) ) ;
133+ if ( messages . Count == 0 )
134+ {
135+ break ;
136+ }
72137
73- IMessage fromMessage = null ;
74- DateTimeOffset ? lastPurgedTimestamp = null ;
75- int deletedInThisChannel = 0 ;
76- while ( deleted < requestedCount )
138+ foreach ( var message in messages )
77139 {
78- IList < IMessage > messages = [ ] ;
79- if ( fromMessage == null )
140+ fromMessage = message ;
141+
142+ if ( deleted >= requestedCount )
80143 {
81- Console . WriteLine ( $ "Retrieving latest messages for #{ channel . Name } ...") ;
82- messages = [ .. await channel . GetMessagesAsync ( ) . FlattenAsync ( ) ] ;
144+ break ;
83145 }
84- else
146+
147+ if ( pinnedIds . Contains ( message . Id ) )
85148 {
86- Console . WriteLine ( $ "Retrieving messages before { fromMessage . Id } for #{ channel . Name } ...") ;
87- messages = [ .. await channel . GetMessagesAsync ( fromMessage , Direction . Before , 100 , options : token . ToRequestOptions ( ) ) . FlattenAsync ( ) ] ;
88- }
149+ continue ;
150+ }
89151
90- foreach ( var message in messages )
152+ if ( message . Timestamp >= cutoff )
91153 {
92- fromMessage = message ;
93-
94- if ( deleted >= requestedCount )
95- {
96- break ;
97- }
98-
99- if ( pinnedIds . Contains ( message . Id ) )
100- {
101- continue ;
102- }
103-
104- if ( message . Timestamp >= cutoff )
105- {
106- continue ;
107- }
108-
109- try
110- {
111- var requestOptions = token . ToRequestOptions ( ) ;
112- requestOptions . RetryMode = RetryMode . Retry502 | RetryMode . RetryTimeouts ;
113- await message . DeleteAsync ( options : requestOptions ) ;
114- deleted ++ ;
115- deletedInThisChannel ++ ;
116- lastPurgedTimestamp = message . Timestamp ;
117- }
118- catch ( Exception ex )
119- {
120- _logger . LogWarning ( ex , "Failed to delete message {MessageId} in channel {ChannelId}" , message . Id , channel . Id ) ;
121- }
122-
123- await Task . Delay ( deleteDelayMs , token ) ;
124- }
125- }
154+ continue ;
155+ }
126156
127- if ( deletedInThisChannel > 0 )
128- {
129- channelBreakdown . Add ( $ "#{ channel . Name } ({ deletedInThisChannel } )") ;
130- await originalMessage . Channel . SendMessageAsync ( $ "Last message purged timestamp: { lastPurgedTimestamp . Value : O} ", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
157+ try
158+ {
159+ var requestOptions = token . ToRequestOptions ( ) ;
160+ requestOptions . RetryMode = RetryMode . Retry502 | RetryMode . RetryTimeouts ;
161+ await message . DeleteAsync ( options : requestOptions ) ;
162+ deleted ++ ;
163+ deletedInThisChannel ++ ;
164+ lastPurgedTimestamp = message . Timestamp ;
165+ }
166+ catch ( Exception ex )
167+ {
168+ _logger . LogWarning ( ex , "Failed to delete message {MessageId} in channel {ChannelId}" , message . Id , channel . Id ) ;
169+ }
170+
171+ await Task . Delay ( deleteDelayMs , token ) ;
131172 }
132173 }
133174
134- var breakdownText = channelBreakdown . Count > 0 ? $ " Deleted from: { string . Join ( ", " , channelBreakdown ) } " : string . Empty ;
135- await originalMessage . Channel . SendMessageAsync ( $ "Purged { deleted } message(s) across private channels.{ breakdownText } ", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
175+ if ( deletedInThisChannel > 0 )
176+ {
177+ await originalMessage . Channel . SendMessageAsync ( $ "Last message purged timestamp: { lastPurgedTimestamp . Value : O} ", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
178+ }
179+
180+ await originalMessage . Channel . SendMessageAsync ( $ "Purged { deleted } message(s) in #{ channel . Name } .", messageReference : new MessageReference ( originalMessage . Id ) , options : token . ToRequestOptions ( ) ) ;
136181 }
137182 }
138183}
0 commit comments