Skip to content

Commit 44250e1

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 44250e1

File tree

1 file changed

+118
-15
lines changed

1 file changed

+118
-15
lines changed

module_logs.lua

Lines changed: 118 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,109 @@
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 Discordia = Discordia
7+
local Clock = Discordia.Clock
8+
local Date = Discordia.Date
9+
910

1011
Module.Name = "logs"
1112

13+
local messagesToDelete = {}
14+
1215
function Module:GetConfigTable()
1316
return {
1417
{
1518
Name = "ChannelManagementLogChannel",
1619
Description = "Where channel created/updated/deleted should be logged",
17-
Type = bot.ConfigType.Channel,
20+
Type = Bot.ConfigType.Channel,
1821
Optional = true
1922
},
2023
{
2124
Name = "DeletedMessageChannel",
2225
Description = "Where deleted messages should be logged",
23-
Type = bot.ConfigType.Channel,
26+
Type = Bot.ConfigType.Channel,
2427
Optional = true
2528
},
29+
{
30+
Name = "EnableLogRotation",
31+
Description = "Enable log rotation",
32+
Type = Bot.ConfigType.Boolean,
33+
Default = false
34+
},
35+
{
36+
Name = "LogRetentionPeriod",
37+
Description = "The retention period of logs",
38+
Type = Bot.ConfigType.Duration,
39+
Default = 2 * 365 * 24 * 60 * 60 -- 2 years
40+
},
2641
{
2742
Name = "IgnoredDeletedMessageChannels",
2843
Description = "Messages deleted in those channels will not be logged",
29-
Type = bot.ConfigType.Channel,
44+
Type = Bot.ConfigType.Channel,
3045
Array = true,
3146
Default = {}
3247
},
3348
{
3449
Name = "NicknameChangedLogChannel",
3550
Description = "Where nickname changes should be logged",
36-
Type = bot.ConfigType.Channel,
51+
Type = Bot.ConfigType.Channel,
3752
Optional = true
3853
},
3954
{
4055
Global = true,
4156
Name = "PersistentMessageCacheSize",
4257
Description = "How many of the last messages of every text channel should stay in bot memory?",
43-
Type = bot.ConfigType.Integer,
58+
Type = Bot.ConfigType.Integer,
4459
Default = 50
4560
},
4661
}
4762
end
4863

64+
65+
function Module:OnLoaded()
66+
self.RotationClock = Clock()
67+
self.RotationClock:on("day", function ()
68+
self:ForEachGuild(function (guildId, config, data, persistentData)
69+
local guild = Client:getGuild(guildId)
70+
if (guild) then
71+
local config = self:GetConfig(guild)
72+
if not config.EnableLogRotation then
73+
return
74+
end
75+
76+
Module:RotateLogs(guild,
77+
config.LogRetentionPeriod,
78+
{
79+
config.ChannelManagementLogChannel,
80+
config.DeletedMessageChannel,
81+
config.NicknameChangedLogChannel,
82+
}
83+
)
84+
end
85+
end)
86+
end)
87+
88+
self.DeletionClock = Clock()
89+
self.DeletionClock:on("sec", function ()
90+
if next(messagesToDelete) then
91+
table.remove(messagesToDelete):delete()
92+
end
93+
end)
94+
95+
return true
96+
end
97+
98+
function Module:OnUnload()
99+
self.RotationClock:stop()
100+
self.DeletionClock:stop()
101+
end
102+
103+
function Module:OnReady()
104+
self.RotationClock:start()
105+
self.DeletionClock:start()
106+
end
107+
49108
function Module:OnEnable(guild)
50109
local data = self:GetData(guild)
51110

@@ -96,7 +155,7 @@ function Module:OnChannelDelete(channel)
96155
embed = {
97156
title = "Channel deleted",
98157
description = channel.name,
99-
timestamp = discordia.Date():toISO('T', 'Z')
158+
timestamp = Date():toISO('T', 'Z')
100159
}
101160
})
102161
end
@@ -123,7 +182,7 @@ function Module:OnChannelCreate(channel)
123182
embed = {
124183
title = "Channel created",
125184
description = "<#" .. channel.id .. ">",
126-
timestamp = discordia.Date():toISO('T', 'Z')
185+
timestamp = Date():toISO('T', 'Z')
127186
}
128187
})
129188
end
@@ -154,7 +213,7 @@ function Module:OnMemberUpdate(member)
154213
embed = {
155214
title = "Nickname changed",
156215
description = string.format("%s - `%s` → `%s`", member.mentionString, data.nicknames[member.id], member.name),
157-
timestamp = discordia.Date():toISO('T', 'Z')
216+
timestamp = Date():toISO('T', 'Z')
158217
}
159218
})
160219
end
@@ -164,7 +223,7 @@ function Module:OnMemberUpdate(member)
164223
embed = {
165224
title = "Username changed",
166225
description = string.format("%s - `%s` → `%s`", member.mentionString, data.usernames[member.id], member.user.username),
167-
timestamp = discordia.Date():toISO('T', 'Z')
226+
timestamp = Date():toISO('T', 'Z')
168227
}
169228
})
170229
end
@@ -199,7 +258,7 @@ function Module:OnMessageDelete(message)
199258
embed.footer = {
200259
text = string.format("Author ID: %s | Message ID: %s", message.author.id, message.id)
201260
}
202-
embed.timestamp = discordia.Date():toISO('T', 'Z')
261+
embed.timestamp = Date():toISO('T', 'Z')
203262

204263
logChannel:send({
205264
embed = embed
@@ -231,7 +290,7 @@ function Module:OnMessageDeleteUncached(channel, messageId)
231290
footer = {
232291
text = string.format("Message ID: %s", messageId)
233292
},
234-
timestamp = discordia.Date():toISO('T', 'Z')
293+
timestamp = Date():toISO('T', 'Z')
235294
}
236295
})
237296
end
@@ -257,3 +316,47 @@ function Module:OnMessageCreate(message)
257316
table.remove(cachedMessages, 1)
258317
end
259318
end
319+
320+
local function isMessageTooOld(message, logRetentionPeriod)
321+
local messageTimestamp = Date.fromSnowflake(message.id):toSeconds()
322+
return os.difftime(os.time(), messageTimestamp) > logRetentionPeriod
323+
end
324+
325+
local function scheduleOldMessagesToDeletion(firstMessage, channel, logRetentionPeriod)
326+
local buf = {}
327+
328+
repeat
329+
-- Sort by id to keep the temporal order
330+
local messages = channel:getMessagesAfter(firstMessage.id, 100):toArray("id")
331+
table.insert(messages, 1, firstMessage)
332+
333+
for _, msg in ipairs(messages) do
334+
if msg.author.id == Client.user.id
335+
and (msg.embed and msg.embed.description and msg.embed.description:match("Deleted message"))
336+
and isMessageTooOld(msg, logRetentionPeriod) then
337+
table.insert(buf, msg)
338+
end
339+
end
340+
341+
firstMessage = messages[#messages]
342+
until next(buf) or messages == nil
343+
344+
messagesToDelete = buf
345+
end
346+
347+
function Module:RotateLogs(guild, logRetentionPeriod, logChannels)
348+
local done = {}
349+
for _, logChannelId in pairs(logChannels) do
350+
if not done[logChannelId] then
351+
local logChannel = guild:getChannel(logChannelId)
352+
local firstMessage = logChannel:getFirstMessage()
353+
354+
if firstMessage then
355+
scheduleOldMessagesToDeletion(firstMessage, logChannel, logRetentionPeriod)
356+
end
357+
358+
done[logChannelId] = true
359+
end
360+
end
361+
end
362+

0 commit comments

Comments
 (0)