Equicord/src/equicordplugins/messageLoggerEnhanced/utils/misc.ts
Jake b752707def
More DB and miscellaneous fixes (#958)
* Trim MessageLoggerEnhanced hot paths

* Add in-memory DataStore fallback

* Tighten DataStore memory store types
2026-04-09 18:09:16 -04:00

173 lines
6.1 KiB
TypeScript

/*
* 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 <https://www.gnu.org/licenses/>.
*/
import { PluginNative } from "@utils/types";
import { findByCodeLazy, findLazy } from "@webpack";
import { ChannelStore, moment, UserStore } from "@webpack/common";
import { DBMessageStatus } from "../db";
import { LoggedMessageJSON } from "../types";
import { DEFAULT_IMAGE_CACHE_DIR } from "./constants";
import { DISCORD_EPOCH } from "./index";
const MessageClass: any = findLazy(m => m?.prototype?.isEdited);
const AuthorClass = findLazy(m => m?.prototype?.getAvatarURL);
const sanitizeEmbed = findByCodeLazy('"embed_"),');
export function getGuildIdByChannel(channel_id: string) {
return ChannelStore.getChannel(channel_id)?.guild_id;
}
export const isGhostPinged = (message?: LoggedMessageJSON) => {
return message?.ghostPinged || message?.deleted && hasPingged(message);
};
export const hasPingged = (message?: LoggedMessageJSON | { mention_everyone: boolean, mentions: any[]; }) => {
return message && !!(
message.mention_everyone ||
message.mentions?.find(m => (typeof m === "string" ? m : m.id) === UserStore.getCurrentUser().id)
);
};
export const getMessageStatus = (message: LoggedMessageJSON) => {
if (isGhostPinged(message)) return DBMessageStatus.GHOST_PINGED;
if (message.deleted) return DBMessageStatus.DELETED;
if (message.editHistory?.length) return DBMessageStatus.EDITED;
throw new Error("Unknown message status");
};
export const discordIdToDate = (id: string) => new Date((parseInt(id) / 4194304) + DISCORD_EPOCH);
export const sortMessagesByDate = (timestampA: string, timestampB: string) => {
// very expensive
// const timestampA = discordIdToDate(a).getTime();
// const timestampB = discordIdToDate(b).getTime();
// return timestampB - timestampA;
// newest first
if (timestampA < timestampB) {
return 1;
} else if (timestampA > timestampB) {
return -1;
} else {
return 0;
}
};
// stolen from mlv2
export function findLastIndex<T>(array: T[], predicate: (e: T, t: number, n: T[]) => boolean) {
let l = array.length;
while (l--) {
if (predicate(array[l], l, array))
return l;
}
return -1;
}
const getTimestamp = (timestamp: any): Date => {
return new Date(timestamp);
};
export const mapTimestamp = (m: any) => {
if (m.timestamp) m.timestamp = getTimestamp(m.timestamp);
if (m.editedTimestamp) m.editedTimestamp = getTimestamp(m.editedTimestamp);
if (m.embeds) m.embeds = m.embeds.map(e => sanitizeEmbed(m.channel_id, m.id, e));
return m;
};
export const messageJsonToMessageClass = (log: { message: LoggedMessageJSON; }) => {
// console.time("message populate");
if (!log?.message) return null;
const message: any = new MessageClass(log.message);
message.timestamp = getTimestamp(message.timestamp);
const editHistory = message.editHistory?.map(mapTimestamp);
if (editHistory && editHistory.length > 0) {
message.editHistory = editHistory;
}
if (message.editedTimestamp)
message.editedTimestamp = getTimestamp(message.editedTimestamp);
if (message.firstEditTimestamp)
message.firstEditTimestamp = getTimestamp(message.firstEditTimestamp);
message.author = UserStore.getUser(message.author.id) ?? new AuthorClass(message.author);
message.author.nick = message.author.globalName ?? message.author.username;
message.embeds = message.embeds.map(e => sanitizeEmbed(message.channel_id, message.id, e));
if (message.poll)
message.poll.expiry = moment(message.poll.expiry);
if (message.messageSnapshots)
message.messageSnapshots.map(m => mapTimestamp(m.message));
// console.timeEnd("message populate");
return message;
};
export function parseJSON(json?: string | null) {
try {
return JSON.parse(json!);
} finally {
return null;
}
}
export async function doesBlobUrlExist(url: string) {
const res = await fetch(url);
return res.ok;
}
export function getNative(): PluginNative<typeof import("../native")> {
if (IS_WEB) {
const Native = {
writeLogs: async () => { },
getDefaultNativeImageDir: async () => DEFAULT_IMAGE_CACHE_DIR,
getDefaultNativeDataDir: async () => "",
deleteFileNative: async () => { },
chooseDir: async (x: string) => "",
getSettings: async () => ({ imageCacheDir: DEFAULT_IMAGE_CACHE_DIR, logsDir: "" }),
init: async () => { },
initDirs: async () => { },
getImageNative: async (x: string) => new Uint8Array(0),
getNativeSavedImages: async () => new Map(),
messageLoggerEnhancedUniqueIdThingyIdkMan: async () => { },
showItemInFolder: async () => { },
writeImageNative: async () => { },
chooseFile: async () => "",
downloadAttachment: async () => ({ error: "web", path: null }),
startNativeLogExport: async () => "" as any,
finishNativeLogExport: async () => { },
writeNativeLogChunk: async () => { },
startNativeLogImport: async () => "" as any,
readNativeLogChunk: async () => null,
closeNativeLogImport: async () => { }
} satisfies PluginNative<typeof import("../native")>;
return Native;
}
return Object.values(VencordNative.pluginHelpers)
.find(m => m.messageLoggerEnhancedUniqueIdThingyIdkMan) as PluginNative<typeof import("../native")>;
}