22-- This file is part of the "Not a Bot" application
33-- For conditions of distribution and use, see copyright notice in LICENSE
44
5- local client = Client
6- local discordia = Discordia
7- local bot = Bot
8- local enums = discordia .enums
5+ local Bot = Bot
6+ local Client = Client
7+ local Discordia = Discordia
8+ local Clock = Discordia .Clock
9+ local Date = Discordia .Date
10+
911
1012Module .Name = " logs"
1113
14+ local messagesToDelete = {}
15+
1216function Module :GetConfigTable ()
1317 return {
1418 {
1519 Name = " ChannelManagementLogChannel" ,
1620 Description = " Where channel created/updated/deleted should be logged" ,
17- Type = bot .ConfigType .Channel ,
21+ Type = Bot .ConfigType .Channel ,
1822 Optional = true
1923 },
2024 {
2125 Name = " DeletedMessageChannel" ,
2226 Description = " Where deleted messages should be logged" ,
23- Type = bot .ConfigType .Channel ,
27+ Type = Bot .ConfigType .Channel ,
2428 Optional = true
2529 },
30+ {
31+ Name = " EnableLogRotation" ,
32+ Description = " Enable log rotation" ,
33+ Type = Bot .ConfigType .Boolean ,
34+ Default = false
35+ },
36+ {
37+ Name = " LogRetentionPeriod" ,
38+ Description = " The retention period of logs" ,
39+ Type = Bot .ConfigType .Duration ,
40+ Default = 2 * 365 * 24 * 60 * 60 -- 2 years
41+ },
2642 {
2743 Name = " IgnoredDeletedMessageChannels" ,
2844 Description = " Messages deleted in those channels will not be logged" ,
29- Type = bot .ConfigType .Channel ,
45+ Type = Bot .ConfigType .Channel ,
3046 Array = true ,
3147 Default = {}
3248 },
3349 {
3450 Name = " NicknameChangedLogChannel" ,
3551 Description = " Where nickname changes should be logged" ,
36- Type = bot .ConfigType .Channel ,
52+ Type = Bot .ConfigType .Channel ,
3753 Optional = true
3854 },
3955 {
4056 Global = true ,
4157 Name = " PersistentMessageCacheSize" ,
4258 Description = " How many of the last messages of every text channel should stay in bot memory?" ,
43- Type = bot .ConfigType .Integer ,
59+ Type = Bot .ConfigType .Integer ,
4460 Default = 50
4561 },
4662 }
4763end
4864
65+
66+ function Module :OnLoaded ()
67+ self .RotationClock = Clock ()
68+ self .RotationClock :on (" day" , function ()
69+ self :ForEachGuild (function (guildId , config , data , persistentData )
70+ local guild = Client :getGuild (guildId )
71+ if (guild ) then
72+ local config = self :GetConfig (guild )
73+ if not config .EnableLogRotation then
74+ return
75+ end
76+
77+ Module :RotateLogs (guild ,
78+ config .LogRetentionPeriod ,
79+ {
80+ config .ChannelManagementLogChannel ,
81+ config .DeletedMessageChannel ,
82+ config .NicknameChangedLogChannel ,
83+ }
84+ )
85+ end
86+ end )
87+ end )
88+
89+ self .DeletionClock = Clock ()
90+ self .DeletionClock :on (" sec" , function ()
91+ if next (messagesToDelete ) then
92+ table.remove (messagesToDelete ):delete ()
93+ end
94+ end )
95+
96+ return true
97+ end
98+
99+ function Module :OnUnload ()
100+ self .RotationClock :stop ()
101+ self .DeletionClock :stop ()
102+ end
103+
104+ function Module :OnReady ()
105+ self .RotationClock :start ()
106+ self .DeletionClock :start ()
107+ end
108+
49109function Module :OnEnable (guild )
50110 local data = self :GetData (guild )
51111
@@ -96,7 +156,7 @@ function Module:OnChannelDelete(channel)
96156 embed = {
97157 title = " Channel deleted" ,
98158 description = channel .name ,
99- timestamp = discordia . Date ():toISO (' T' , ' Z' )
159+ timestamp = Date ():toISO (' T' , ' Z' )
100160 }
101161 })
102162end
@@ -123,7 +183,7 @@ function Module:OnChannelCreate(channel)
123183 embed = {
124184 title = " Channel created" ,
125185 description = " <#" .. channel .id .. " >" ,
126- timestamp = discordia . Date ():toISO (' T' , ' Z' )
186+ timestamp = Date ():toISO (' T' , ' Z' )
127187 }
128188 })
129189end
@@ -154,7 +214,7 @@ function Module:OnMemberUpdate(member)
154214 embed = {
155215 title = " Nickname changed" ,
156216 description = string.format (" %s - `%s` → `%s`" , member .mentionString , data .nicknames [member .id ], member .name ),
157- timestamp = discordia . Date ():toISO (' T' , ' Z' )
217+ timestamp = Date ():toISO (' T' , ' Z' )
158218 }
159219 })
160220 end
@@ -164,7 +224,7 @@ function Module:OnMemberUpdate(member)
164224 embed = {
165225 title = " Username changed" ,
166226 description = string.format (" %s - `%s` → `%s`" , member .mentionString , data .usernames [member .id ], member .user .username ),
167- timestamp = discordia . Date ():toISO (' T' , ' Z' )
227+ timestamp = Date ():toISO (' T' , ' Z' )
168228 }
169229 })
170230 end
@@ -199,7 +259,7 @@ function Module:OnMessageDelete(message)
199259 embed .footer = {
200260 text = string.format (" Author ID: %s | Message ID: %s" , message .author .id , message .id )
201261 }
202- embed .timestamp = discordia . Date ():toISO (' T' , ' Z' )
262+ embed .timestamp = Date ():toISO (' T' , ' Z' )
203263
204264 logChannel :send ({
205265 embed = embed
@@ -231,7 +291,7 @@ function Module:OnMessageDeleteUncached(channel, messageId)
231291 footer = {
232292 text = string.format (" Message ID: %s" , messageId )
233293 },
234- timestamp = discordia . Date ():toISO (' T' , ' Z' )
294+ timestamp = Date ():toISO (' T' , ' Z' )
235295 }
236296 })
237297end
@@ -257,3 +317,47 @@ function Module:OnMessageCreate(message)
257317 table.remove (cachedMessages , 1 )
258318 end
259319end
320+
321+ local function isMessageTooOld (message , logRetentionPeriod )
322+ local messageTimestamp = Date .fromSnowflake (message .id ):toSeconds ()
323+ return os.difftime (os.time (), messageTimestamp ) > logRetentionPeriod
324+ end
325+
326+ local function scheduleOldMessagesToDeletion (firstMessage , channel , logRetentionPeriod )
327+ local buf = {}
328+
329+ repeat
330+ -- Sort by id to keep the temporal order
331+ local messages = channel :getMessagesAfter (firstMessage .id , 100 ):toArray (" id" )
332+ table.insert (messages , 1 , firstMessage )
333+
334+ for _ , msg in ipairs (messages ) do
335+ if msg .author .id == Client .user .id
336+ and (msg .embed and msg .embed .description and msg .embed .description :match (" Deleted message" ))
337+ and isMessageTooOld (msg , logRetentionPeriod ) then
338+ table.insert (buf , msg )
339+ end
340+ end
341+
342+ firstMessage = messages [# messages ]
343+ until next (buf ) or messages == nil
344+
345+ messagesToDelete = buf
346+ end
347+
348+ function Module :RotateLogs (guild , logRetentionPeriod , logChannels )
349+ local done = {}
350+ for _ , logChannelId in pairs (logChannels ) do
351+ if not done [logChannelId ] then
352+ local logChannel = guild :getChannel (logChannelId )
353+ local firstMessage = logChannel :getFirstMessage ()
354+
355+ if firstMessage then
356+ scheduleOldMessagesToDeletion (firstMessage , logChannel , logRetentionPeriod )
357+ end
358+
359+ done [logChannelId ] = true
360+ end
361+ end
362+ end
363+
0 commit comments