/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2023 Vendicated and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { createStore } from "@api/DataStore"; import { DataStore } from "@api/index"; import { Settings } from "@api/Settings"; import { Flogger, Native, settings } from "."; import { LoggedMessage, LoggedMessageIds, LoggedMessageJSON, LoggedMessages, MessageRecord } from "./types"; import { cleanupMessage, getNative, sortMessagesByDate } from "./utils"; import { cacheMessageImages, deleteMessageImages } from "./utils/saveImage"; export const defaultLoggedMessages = { deletedMessages: {}, editedMessages: {}, }; export const LOGGED_MESSAGES_KEY = "logged-messages-hi"; export const MessageLoggerStore = createStore("MessageLoggerData", "MessageLoggerStore"); // this gets used by the logs modal. logs modal should only use saved messages not messages that are being processed // also hasMessageInLogs should only check saved messages not the ones that are being processed export let savedLoggedMessages: LoggedMessages = { ...defaultLoggedMessages }; export let loggedMessages: LoggedMessages = { ...defaultLoggedMessages }; (async () => { try { const Native = getNative(); const res = await Native.getLogsFromFs(); if (res != null) { Flogger.log("Got logged messages from native wont be checking DataStore"); const cleaned = await cleanMessages(res, Native); loggedMessages = cleaned; savedLoggedMessages = cleaned; return; } if (IS_WEB) { Flogger.log("hii. no point in checking DataStore if. we already did up there ^^"); return; } const data = await DataStore.get(LOGGED_MESSAGES_KEY, MessageLoggerStore); if (data == null) { Flogger.log("No logged messages in DataStore"); return; } Flogger.log("Loading logged messages from DataStore and writing to native"); Native.writeLogs(JSON.stringify(data)); loggedMessages = data; savedLoggedMessages = res; } catch (error) { console.error("Error loading logged messages from the store:", error); } })(); // api export const saveLoggedMessages = async () => { if (settings.store.saveMessages) { await Native.writeLogs(JSON.stringify(loggedMessages)); } savedLoggedMessages = loggedMessages; }; export const addMessage = async (message: LoggedMessage | LoggedMessageJSON, key: keyof LoggedMessageIds, isBulk = false) => { if (settings.store.saveImages && key === "deletedMessages") await cacheMessageImages(message); const finalMessage = cleanupMessage(message); loggedMessages[message.id] = { message: finalMessage }; if (!loggedMessages[key][message.channel_id]) loggedMessages[key][message.channel_id] = []; if (!loggedMessages[key][message.channel_id].includes(message.id)) loggedMessages[key][message.channel_id].push(message.id); // if limit is negative or 0 there is no limit if (settings.store.messageLimit > 0 && (Object.keys(loggedMessages).length - 2) > settings.store.messageLimit) await deleteOldestMessageWithoutSaving(loggedMessages); if (!isBulk) await saveLoggedMessages(); }; export const removeFromKey = ( message_id: string, channel_id: string, loggedMessages: LoggedMessages, key: keyof LoggedMessageIds, ) => { if (loggedMessages[key][channel_id]) { loggedMessages[key][channel_id] = loggedMessages[key][channel_id].filter(msgid => msgid !== message_id); if (loggedMessages[key][channel_id].length === 0) { delete loggedMessages[key][channel_id]; } } }; function removeLogWithoutSaving(messageId: string, loggedMessages: LoggedMessages) { const record = loggedMessages[messageId]; if (record) { const channel_id = record.message?.channel_id; if (channel_id != null) { removeFromKey(messageId, channel_id, loggedMessages, "editedMessages"); removeFromKey(messageId, channel_id, loggedMessages, "deletedMessages"); } delete loggedMessages[messageId]; } return loggedMessages; } export async function removeLogs(ids: string[]) { for (const msgId of ids) { removeLogWithoutSaving(msgId, loggedMessages); } await saveLoggedMessages(); } export async function removeLog(id: string) { const record = loggedMessages[id]; if (record?.message) deleteMessageImages(record.message); removeLogWithoutSaving(id, loggedMessages); await saveLoggedMessages(); } export async function clearLogs() { await Native.writeLogs(JSON.stringify(defaultLoggedMessages)); loggedMessages = defaultLoggedMessages; savedLoggedMessages = defaultLoggedMessages; } // utils export const hasMessageInLogs = (messageId: string) => { const bruh = Object.values(savedLoggedMessages) .filter(m => !Array.isArray(m)) as MessageRecord[]; return bruh.find(m => m.message?.id === messageId); }; export const hasLogs = async () => { const hasDeletedMessages = Object.keys(loggedMessages.deletedMessages).length > 0; const hasEditedMessages = Object.keys(loggedMessages.editedMessages).length > 0; const hasMessages = Object.keys(loggedMessages).filter(m => m !== "editedMessages" && m !== "deletedMessages").length > 0; if (hasDeletedMessages && hasEditedMessages && hasMessages) return true; return false; }; export function findLoggedChannelByMessageIdSync(messageId: string, loggedMessages: LoggedMessages, key: keyof LoggedMessageIds) { for (const channelId in loggedMessages[key]) { if (loggedMessages[key][channelId].includes(messageId)) return channelId; } return null; } export async function findLoggedChannelByMessage(messageId: string, key?: keyof LoggedMessageIds): Promise<[string | null, keyof LoggedMessageIds]> { if (!key) { const id1 = findLoggedChannelByMessageIdSync(messageId, loggedMessages, "deletedMessages"); if (id1) return [id1, "deletedMessages"]; const id2 = findLoggedChannelByMessageIdSync(messageId, loggedMessages, "editedMessages"); return [id2, "editedMessages"]; } return [findLoggedChannelByMessageIdSync(messageId, loggedMessages, key), key]; } export function getOldestMessage(loggedMessageIds: LoggedMessages) { const messags = Object.values(loggedMessageIds) .filter(m => !Array.isArray(m) && m.message != null) as MessageRecord[]; const sortedMessages = messags.sort((a, b) => sortMessagesByDate(a.message.timestamp, b.message.timestamp)); const oldestMessage = sortedMessages[sortedMessages.length - 1]; return oldestMessage ?? null; } export function getMessage(channelId: string, messageId: string) { const messags = Object.values(savedLoggedMessages) .filter(m => !Array.isArray(m) && m.message != null) as MessageRecord[]; return messags.find(m => m.message.channel_id === channelId && m.message.id === messageId); } export async function deleteOldestMessageWithoutSaving(loggedMessages: LoggedMessages) { const oldestMessage = getOldestMessage(loggedMessages); if (!oldestMessage || !oldestMessage.message) { console.warn("couldnt find oldest message. oldestMessage == null || oldestMessage.message == null"); return loggedMessages; } const { message } = oldestMessage; const [channelId, key] = await findLoggedChannelByMessage(message.id); if (!channelId || !key) { console.warn("couldnt find oldest message. channelId =", channelId, " key =", key); return loggedMessages; } removeLogWithoutSaving(message.id, loggedMessages); // console.log("removing", message); return loggedMessages; } async function cleanMessages(loggedMessages: LoggedMessages, _Native: any = Native) { try { const cleaned = { ...loggedMessages }; if (IS_WEB) return cleaned; const messageRecords = Object.values(cleaned) .filter(m => !Array.isArray(m)) as MessageRecord[]; let hasChanged = false; for (const messageRecord of messageRecords) { const { message } = messageRecord; if (message?.attachments) { for (const attachment of message.attachments) { if (attachment.blobUrl) { hasChanged = true; delete attachment.blobUrl; } } } } if (hasChanged) await _Native.writeLogs(Settings.plugins.MessageLoggerEnhanced.logsDir, JSON.stringify(cleaned)); return cleaned; } catch (err) { Flogger.error("Error cleaning messages:", err); return loggedMessages; } }