blob: e1aa359c957cb886cfb36abd677e5f192c0e5919 [file] [log] [blame]
PineaFan752af462022-12-31 21:59:38 +00001import type { NucleusClient } from "../utils/client.js";
Skyler Grey11236ba2022-08-08 21:13:33 +01002import { LinkCheck, MalwareCheck, NSFWCheck, SizeCheck, TestString, TestImage } from "../reflex/scanners.js";
pineafan63fc5e22022-08-04 22:04:10 +01003import logAttachment from "../premium/attachmentLogs.js";
PineaFan0d06edc2023-01-17 22:10:31 +00004import { messageException } from "../utils/createTemporaryStorage.js";
pineafan63fc5e22022-08-04 22:04:10 +01005import getEmojiByName from "../utils/getEmojiByName.js";
6import client from "../utils/client.js";
pineafan0f5cc782022-08-12 21:55:42 +01007import { callback as statsChannelUpdate } from "../reflex/statsChannelUpdate.js";
TheCodedProfc7e8ef12023-04-22 22:28:11 -04008import { ChannelType, GuildMember, Message, ThreadChannel } from "discord.js";
TheCodedProfb7a7b992023-03-05 16:11:59 -05009import singleNotify from "../utils/singleNotify.js";
pineafan813bdf42022-07-24 10:39:10 +010010
pineafan63fc5e22022-08-04 22:04:10 +010011export const event = "messageCreate";
pineafan813bdf42022-07-24 10:39:10 +010012
TheCodedProfc7e8ef12023-04-22 22:28:11 -040013function checkUserForExceptions(user: GuildMember, exceptions: { roles: string[]; users: string[] }) {
14 if (exceptions.users.includes(user.id)) return true;
15 for (const role of user.roles.cache.values()) {
16 if (exceptions.roles.includes(role.id)) return true;
17 }
18 return false;
19}
20
PineaFan752af462022-12-31 21:59:38 +000021export async function callback(_client: NucleusClient, message: Message) {
Skyler Grey75ea9172022-08-06 10:22:23 +010022 if (!message.guild) return;
PineappleFan13892c62023-03-05 07:00:07 +000023 const config = await client.memory.readGuildInfo(message.guild.id);
Skyler9f5fca82023-03-05 09:12:03 +000024
Skyler Greyda16adf2023-03-05 10:22:12 +000025 if (
26 config.autoPublish.enabled &&
27 config.autoPublish.channels.includes(message.channel.id) &&
28 message.channel.type === ChannelType.GuildAnnouncement &&
29 message.reference === null
Skyler9f5fca82023-03-05 09:12:03 +000030 ) {
Skyler Grey67691762023-03-06 09:58:19 +000031 if (message.channel.permissionsFor(message.guild.members.me!)!.has("ManageMessages")) {
TheCodedProfb7a7b992023-03-05 16:11:59 -050032 await message.crosspost();
33 } else {
Skyler Greyf4f21c42023-03-08 14:36:29 +000034 await singleNotify(
35 `Nucleus does not have Manage Messages in <#${message.channel.id}>`,
36 message.guild.id,
37 true
38 );
TheCodedProfb7a7b992023-03-05 16:11:59 -050039 }
PineappleFan13892c62023-03-05 07:00:07 +000040 }
41
pineafan63fc5e22022-08-04 22:04:10 +010042 if (message.author.bot) return;
PineaFan538d3752023-01-12 21:48:23 +000043 if (message.channel.isDMBased()) return;
Skyler Grey75ea9172022-08-06 10:22:23 +010044 try {
pineafan6de4da52023-03-07 20:43:44 +000045 await statsChannelUpdate((await message.guild.members.fetch(message.author.id)).user, message.guild);
Skyler Grey75ea9172022-08-06 10:22:23 +010046 } catch (e) {
47 console.log(e);
48 }
pineafan813bdf42022-07-24 10:39:10 +010049
TheCodedProf6ec331b2023-02-20 12:13:06 -050050 const { log, isLogging, NucleusColors, entry, renderUser, renderDelta, renderChannel } = client.logger;
pineafan813bdf42022-07-24 10:39:10 +010051
pineafan63fc5e22022-08-04 22:04:10 +010052 const fileNames = await logAttachment(message);
TheCodedProf22586d52023-04-23 16:46:32 -040053 const lowerCaseContent = message.content.toLowerCase() || "";
Skyler Greyda16adf2023-03-05 10:22:12 +000054 if (config.filters.clean.channels.includes(message.channel.id)) {
TheCodedProfc7e8ef12023-04-22 22:28:11 -040055 if (!checkUserForExceptions(message.member!, config.filters.clean.allowed)) return await message.delete();
TheCodedProfad0b8202023-02-14 14:27:09 -050056 }
TheCodedProfbaee2c12023-02-18 16:11:06 -050057
pineafan63fc5e22022-08-04 22:04:10 +010058 const filter = getEmojiByName("ICONS.FILTER");
59 let attachmentJump = "";
Skyler Greydaf11b52023-04-23 21:17:42 +000060 console.log(config.logging.attachments.saved);
Skyler Grey75ea9172022-08-06 10:22:23 +010061 if (config.logging.attachments.saved[message.channel.id + message.id]) {
Skyler Grey11236ba2022-08-08 21:13:33 +010062 attachmentJump = ` [[View attachments]](${config.logging.attachments.saved[message.channel.id + message.id]})`;
Skyler Grey75ea9172022-08-06 10:22:23 +010063 }
pineafan63fc5e22022-08-04 22:04:10 +010064 const list = {
pineafan813bdf42022-07-24 10:39:10 +010065 messageId: entry(message.id, `\`${message.id}\``),
66 sentBy: entry(message.author.id, renderUser(message.author)),
67 sentIn: entry(message.channel.id, renderChannel(message.channel)),
TheCodedProf6ec331b2023-02-20 12:13:06 -050068 deleted: entry(Date.now(), renderDelta(Date.now())),
pineafan813bdf42022-07-24 10:39:10 +010069 mentions: message.mentions.users.size,
Skyler Grey11236ba2022-08-08 21:13:33 +010070 attachments: entry(message.attachments.size, message.attachments.size + attachmentJump),
pineafan813bdf42022-07-24 10:39:10 +010071 repliedTo: entry(
PineaFan538d3752023-01-12 21:48:23 +000072 (message.reference ? message.reference.messageId : null) ?? null,
Skyler Grey75ea9172022-08-06 10:22:23 +010073 message.reference
74 ? `[[Jump to message]](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.reference.messageId})`
75 : "None"
pineafan813bdf42022-07-24 10:39:10 +010076 )
pineafan63fc5e22022-08-04 22:04:10 +010077 };
pineafan813bdf42022-07-24 10:39:10 +010078
79 if (config.filters.invite.enabled) {
TheCodedProfc7e8ef12023-04-22 22:28:11 -040080 if (
Skyler Greydaf11b52023-04-23 21:17:42 +000081 !(
82 config.filters.invite.allowed.channels.includes(message.channel.id) ||
83 checkUserForExceptions(message.member!, config.filters.invite.allowed)
84 ) &&
85 /(?:https?:\/\/)?discord(?:app)?\.(?:com\/invite|gg)\/[a-zA-Z0-9]+\/?/.test(lowerCaseContent)
TheCodedProfc7e8ef12023-04-22 22:28:11 -040086 ) {
Skyler Greydaf11b52023-04-23 21:17:42 +000087 messageException(message.guild.id, message.channel.id, message.id);
88 await message.delete();
89 const data = {
90 meta: {
91 type: "messageDelete",
92 displayName: "Message Deleted",
93 calculateType: "autoModeratorDeleted",
94 color: NucleusColors.red,
95 emoji: "MESSAGE.DELETE",
96 timestamp: Date.now()
97 },
98 separate: {
99 start:
100 filter +
101 " Contained invite\n\n" +
102 (lowerCaseContent
103 ? `**Message:**\n\`\`\`${message.content}\`\`\``
104 : "**Message:** *Message had no content*")
105 },
106 list: list,
107 hidden: {
108 guild: message.channel.guild.id
109 }
110 };
111 return log(data);
pineafan813bdf42022-07-24 10:39:10 +0100112 }
113 }
114
115 if (fileNames.files.length > 0) {
pineafan63fc5e22022-08-04 22:04:10 +0100116 for (const element of fileNames.files) {
pineafan63fc5e22022-08-04 22:04:10 +0100117 const url = element.url ? element.url : element.local;
Skyler Greyda16adf2023-03-05 10:22:12 +0000118 if (
Skyler Grey0d885222023-03-08 21:46:37 +0000119 /\.(jpg|jpeg|png|apng|gif|gifv|webm|webp|mp4|wav|mp3|ogg|jfif|mpeg-\d|avi|h\.264|h\.265)$/.test(
Skyler Greyda16adf2023-03-05 10:22:12 +0000120 url.toLowerCase()
121 )
122 ) {
Skyler Grey0d885222023-03-08 21:46:37 +0000123 // j(pe?g|fif)|a?png|gifv?|w(eb[mp]|av)|mp([34]|eg-\d)|ogg|avi|h\.26(4|5)
124 // ^no
Skyler Grey75ea9172022-08-06 10:22:23 +0100125 if (
Skyler Greyc634e2b2022-08-06 17:50:48 +0100126 config.filters.images.NSFW &&
Skyler Grey0d885222023-03-08 21:46:37 +0000127 !(message.channel instanceof ThreadChannel ? message.channel.parent?.nsfw : message.channel.nsfw) &&
Skyler Grey74169642023-03-09 11:59:09 +0000128 (await NSFWCheck(element.url))
Skyler Grey75ea9172022-08-06 10:22:23 +0100129 ) {
Skyler Grey0d885222023-03-08 21:46:37 +0000130 messageException(message.guild.id, message.channel.id, message.id);
131 await message.delete();
132 const data = {
133 meta: {
134 type: "messageDelete",
135 displayName: "Message Deleted",
136 calculateType: "autoModeratorDeleted",
137 color: NucleusColors.red,
138 emoji: "MESSAGE.DELETE",
139 timestamp: Date.now()
140 },
141 separate: {
142 start:
143 filter +
144 " Image detected as NSFW\n\n" +
TheCodedProf22586d52023-04-23 16:46:32 -0400145 (lowerCaseContent
146 ? `**Message:**\n\`\`\`${message.content}\`\`\``
Skyler Grey0d885222023-03-08 21:46:37 +0000147 : "**Message:** *Message had no content*")
148 },
149 list: list,
150 hidden: {
151 guild: message.channel.guild.id
152 }
153 };
154 return log(data);
pineafan813bdf42022-07-24 10:39:10 +0100155 }
Skyler Greyc634e2b2022-08-06 17:50:48 +0100156 if (config.filters.wordFilter.enabled) {
157 const text = await TestImage(url);
158 const check = TestString(
159 text ?? "",
160 config.filters.wordFilter.words.loose,
161 config.filters.wordFilter.words.strict
162 );
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400163 if (
164 check !== null &&
165 (!checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
166 !config.filters.wordFilter.allowed.channels.includes(message.channel.id))
167 ) {
PineaFan0d06edc2023-01-17 22:10:31 +0000168 messageException(message.guild.id, message.channel.id, message.id);
Skyler Greyc634e2b2022-08-06 17:50:48 +0100169 await message.delete();
170 const data = {
171 meta: {
172 type: "messageDelete",
173 displayName: "Message Deleted",
174 calculateType: "autoModeratorDeleted",
175 color: NucleusColors.red,
176 emoji: "MESSAGE.DELETE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500177 timestamp: Date.now()
Skyler Greyc634e2b2022-08-06 17:50:48 +0100178 },
179 separate: {
180 start:
181 filter +
182 " Image contained filtered word\n\n" +
TheCodedProf22586d52023-04-23 16:46:32 -0400183 (lowerCaseContent
184 ? `**Message:**\n\`\`\`${message.content}\`\`\``
Skyler Greyc634e2b2022-08-06 17:50:48 +0100185 : "**Message:** *Message had no content*")
186 },
187 list: list,
188 hidden: {
189 guild: message.channel.guild.id
190 }
191 };
192 return log(data);
193 }
194 }
195 if (config.filters.images.size) {
196 if (url.match(/\.+(webp|png|jpg)$/gi)) {
197 if (!(await SizeCheck(element))) {
PineaFan0d06edc2023-01-17 22:10:31 +0000198 messageException(message.guild.id, message.channel.id, message.id);
Skyler Greyc634e2b2022-08-06 17:50:48 +0100199 await message.delete();
200 const data = {
201 meta: {
202 type: "messageDelete",
203 displayName: "Message Deleted",
204 calculateType: "autoModeratorDeleted",
205 color: NucleusColors.red,
206 emoji: "MESSAGE.DELETE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500207 timestamp: Date.now()
Skyler Greyc634e2b2022-08-06 17:50:48 +0100208 },
209 separate: {
210 start:
211 filter +
212 " Image was too small\n\n" +
TheCodedProf22586d52023-04-23 16:46:32 -0400213 (lowerCaseContent
214 ? `**Message:**\n\`\`\`${message.content}\`\`\``
Skyler Greyc634e2b2022-08-06 17:50:48 +0100215 : "**Message:** *Message had no content*")
216 },
217 list: list,
218 hidden: {
219 guild: message.channel.guild.id
220 }
221 };
222 return log(data);
223 }
224 }
225 }
226 }
Skyler Grey0d885222023-03-08 21:46:37 +0000227 if (config.filters.malware && (await MalwareCheck(url))) {
228 messageException(message.guild.id, message.channel.id, message.id);
229 await message.delete();
230 const data = {
231 meta: {
232 type: "messageDelete",
233 displayName: "Message Deleted",
234 calculateType: "autoModeratorDeleted",
235 color: NucleusColors.red,
236 emoji: "MESSAGE.DELETE",
237 timestamp: Date.now()
238 },
239 separate: {
240 start:
241 filter +
242 " File detected as malware\n\n" +
Skyler Greydaf11b52023-04-23 21:17:42 +0000243 (lowerCaseContent
244 ? `**Message:**\n\`\`\`${message.content}\`\`\``
245 : "**Message:** *Message had no content*")
Skyler Grey0d885222023-03-08 21:46:37 +0000246 },
247 list: list,
248 hidden: {
249 guild: message.channel.guild.id
250 }
251 };
252 return log(data);
pineafan813bdf42022-07-24 10:39:10 +0100253 }
pineafan63fc5e22022-08-04 22:04:10 +0100254 }
pineafan813bdf42022-07-24 10:39:10 +0100255 }
pineafan813bdf42022-07-24 10:39:10 +0100256
pineafan63fc5e22022-08-04 22:04:10 +0100257 const linkDetectionTypes = await LinkCheck(message);
pineafan813bdf42022-07-24 10:39:10 +0100258 if (linkDetectionTypes.length > 0) {
PineaFan0d06edc2023-01-17 22:10:31 +0000259 messageException(message.guild.id, message.channel.id, message.id);
pineafan63fc5e22022-08-04 22:04:10 +0100260 await message.delete();
261 const data = {
pineafan813bdf42022-07-24 10:39:10 +0100262 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100263 type: "messageDelete",
264 displayName: "Message Deleted",
265 calculateType: "autoModeratorDeleted",
pineafan813bdf42022-07-24 10:39:10 +0100266 color: NucleusColors.red,
pineafan63fc5e22022-08-04 22:04:10 +0100267 emoji: "MESSAGE.DELETE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500268 timestamp: Date.now()
pineafan813bdf42022-07-24 10:39:10 +0100269 },
270 separate: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100271 start:
272 filter +
273 ` Link filtered as ${linkDetectionTypes[0]?.toLowerCase()}\n\n` +
Skyler Greydaf11b52023-04-23 21:17:42 +0000274 (lowerCaseContent
275 ? `**Message:**\n\`\`\`${message.content}\`\`\``
276 : "**Message:** *Message had no content*")
pineafan813bdf42022-07-24 10:39:10 +0100277 },
278 list: list,
279 hidden: {
280 guild: message.channel.guild.id
281 }
pineafan63fc5e22022-08-04 22:04:10 +0100282 };
pineafan813bdf42022-07-24 10:39:10 +0100283 return log(data);
284 }
pineafane23c4ec2022-07-27 21:56:27 +0100285
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400286 if (
287 config.filters.wordFilter.enabled &&
Skyler Grey73fbcdd2023-04-23 21:23:35 +0000288 !(
289 checkUserForExceptions(message.member!, config.filters.wordFilter.allowed) ||
290 config.filters.wordFilter.allowed.channels.includes(message.channel.id)
291 )
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400292 ) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100293 const check = TestString(
TheCodedProf22586d52023-04-23 16:46:32 -0400294 lowerCaseContent,
Skyler Grey75ea9172022-08-06 10:22:23 +0100295 config.filters.wordFilter.words.loose,
TheCodedProf21d4b0f2023-04-22 21:02:51 -0400296 config.filters.wordFilter.words.strict
Skyler Grey75ea9172022-08-06 10:22:23 +0100297 );
298 if (check !== null) {
PineaFan0d06edc2023-01-17 22:10:31 +0000299 messageException(message.guild.id, message.channel.id, message.id);
pineafan63fc5e22022-08-04 22:04:10 +0100300 await message.delete();
301 const data = {
pineafan813bdf42022-07-24 10:39:10 +0100302 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100303 type: "messageDelete",
304 displayName: "Message Deleted",
305 calculateType: "autoModeratorDeleted",
pineafan813bdf42022-07-24 10:39:10 +0100306 color: NucleusColors.red,
pineafan63fc5e22022-08-04 22:04:10 +0100307 emoji: "MESSAGE.DELETE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500308 timestamp: Date.now()
pineafan813bdf42022-07-24 10:39:10 +0100309 },
310 separate: {
Skyler Grey75ea9172022-08-06 10:22:23 +0100311 start:
312 filter +
313 " Message contained filtered word\n\n" +
Skyler Greydaf11b52023-04-23 21:17:42 +0000314 (lowerCaseContent
315 ? `**Message:**\n\`\`\`${message.content}\`\`\``
316 : "**Message:** *Message had no content*")
pineafan813bdf42022-07-24 10:39:10 +0100317 },
318 list: list,
319 hidden: {
320 guild: message.channel.guild.id
321 }
pineafan63fc5e22022-08-04 22:04:10 +0100322 };
pineafan813bdf42022-07-24 10:39:10 +0100323 return log(data);
324 }
325 }
326
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400327 if (
328 config.filters.pings.everyone &&
329 message.mentions.everyone &&
330 !checkUserForExceptions(message.member!, config.filters.pings.allowed)
331 ) {
Skyler Greyda16adf2023-03-05 10:22:12 +0000332 if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
pineafan63fc5e22022-08-04 22:04:10 +0100333 const data = {
pineafane23c4ec2022-07-27 21:56:27 +0100334 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100335 type: "everyonePing",
336 displayName: "Everyone Pinged",
337 calculateType: "messageMassPing",
pineafane23c4ec2022-07-27 21:56:27 +0100338 color: NucleusColors.yellow,
pineafan63fc5e22022-08-04 22:04:10 +0100339 emoji: "MESSAGE.PING.EVERYONE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500340 timestamp: Date.now()
pineafane23c4ec2022-07-27 21:56:27 +0100341 },
342 separate: {
Skyler Greydaf11b52023-04-23 21:17:42 +0000343 start: lowerCaseContent
344 ? `**Message:**\n\`\`\`${message.content}\`\`\``
345 : "**Message:** *Message had no content*"
pineafane23c4ec2022-07-27 21:56:27 +0100346 },
347 list: list,
348 hidden: {
349 guild: message.channel.guild.id
pineafan813bdf42022-07-24 10:39:10 +0100350 }
pineafan63fc5e22022-08-04 22:04:10 +0100351 };
pineafane23c4ec2022-07-27 21:56:27 +0100352 return log(data);
353 }
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400354 if (config.filters.pings.roles && !checkUserForExceptions(message.member!, config.filters.pings.allowed)) {
Skyler Grey75ea9172022-08-06 10:22:23 +0100355 for (const roleId in message.mentions.roles) {
pineafan63fc5e22022-08-04 22:04:10 +0100356 if (!config.filters.pings.allowed.roles.includes(roleId)) {
PineaFan0d06edc2023-01-17 22:10:31 +0000357 messageException(message.guild.id, message.channel.id, message.id);
pineafan63fc5e22022-08-04 22:04:10 +0100358 await message.delete();
Skyler Greyda16adf2023-03-05 10:22:12 +0000359 if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
pineafan63fc5e22022-08-04 22:04:10 +0100360 const data = {
pineafane23c4ec2022-07-27 21:56:27 +0100361 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100362 type: "rolePing",
363 displayName: "Role Pinged",
364 calculateType: "messageMassPing",
pineafane23c4ec2022-07-27 21:56:27 +0100365 color: NucleusColors.yellow,
pineafan63fc5e22022-08-04 22:04:10 +0100366 emoji: "MESSAGE.PING.ROLE",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500367 timestamp: Date.now()
pineafane23c4ec2022-07-27 21:56:27 +0100368 },
369 separate: {
TheCodedProf22586d52023-04-23 16:46:32 -0400370 start: lowerCaseContent
371 ? `**Message:**\n\`\`\`${message.content}\`\`\``
Skyler Grey75ea9172022-08-06 10:22:23 +0100372 : "**Message:** *Message had no content*"
pineafane23c4ec2022-07-27 21:56:27 +0100373 },
374 list: list,
375 hidden: {
376 guild: message.channel.guild.id
pineafan813bdf42022-07-24 10:39:10 +0100377 }
pineafan63fc5e22022-08-04 22:04:10 +0100378 };
pineafane23c4ec2022-07-27 21:56:27 +0100379 return log(data);
pineafan813bdf42022-07-24 10:39:10 +0100380 }
381 }
pineafane23c4ec2022-07-27 21:56:27 +0100382 }
TheCodedProfc7e8ef12023-04-22 22:28:11 -0400383 if (
384 message.mentions.users.size >= config.filters.pings.mass &&
385 config.filters.pings.mass &&
386 !checkUserForExceptions(message.member!, config.filters.pings.allowed)
387 ) {
PineaFan0d06edc2023-01-17 22:10:31 +0000388 messageException(message.guild.id, message.channel.id, message.id);
pineafan63fc5e22022-08-04 22:04:10 +0100389 await message.delete();
Skyler Greyda16adf2023-03-05 10:22:12 +0000390 if (!(await isLogging(message.guild.id, "messageMassPing"))) return;
pineafan63fc5e22022-08-04 22:04:10 +0100391 const data = {
pineafane23c4ec2022-07-27 21:56:27 +0100392 meta: {
pineafan63fc5e22022-08-04 22:04:10 +0100393 type: "massPing",
394 displayName: "Mass Ping",
395 calculateType: "messageMassPing",
pineafane23c4ec2022-07-27 21:56:27 +0100396 color: NucleusColors.yellow,
pineafan63fc5e22022-08-04 22:04:10 +0100397 emoji: "MESSAGE.PING.MASS",
TheCodedProf6ec331b2023-02-20 12:13:06 -0500398 timestamp: Date.now()
pineafane23c4ec2022-07-27 21:56:27 +0100399 },
400 separate: {
Skyler Greydaf11b52023-04-23 21:17:42 +0000401 start: lowerCaseContent
402 ? `**Message:**\n\`\`\`${message.content}\`\`\``
403 : "**Message:** *Message had no content*"
pineafane23c4ec2022-07-27 21:56:27 +0100404 },
405 list: list,
406 hidden: {
407 guild: message.channel.guild.id
pineafan813bdf42022-07-24 10:39:10 +0100408 }
pineafan63fc5e22022-08-04 22:04:10 +0100409 };
pineafane23c4ec2022-07-27 21:56:27 +0100410 return log(data);
pineafan813bdf42022-07-24 10:39:10 +0100411 }
412}