Skip to content

Commit a4124a2

Browse files
committed
add the ability to rotate logs
This feature allows users to delete logs periodically while choosing the retention time. It will help servers located in Europe to comply with GDPR and to keep the logging channels clean.
1 parent 644259d commit a4124a2

File tree

1 file changed

+119
-15
lines changed

1 file changed

+119
-15
lines changed

module_logs.lua

Lines changed: 119 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,110 @@
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

1012
Module.Name = "logs"
1113

14+
local messagesToDelete = {}
15+
1216
function 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
}
4763
end
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+
49109
function 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
})
102162
end
@@ -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
})
129189
end
@@ -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
})
237297
end
@@ -257,3 +317,47 @@ function Module:OnMessageCreate(message)
257317
table.remove(cachedMessages, 1)
258318
end
259319
end
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

Comments
 (0)