import { runPrepQuery, numRows } from "../Utility/db.js"; import { Warn, Log, Err, Debug } from "./loggerUtility.js"; import { Webhook } from 'discord-webhook-node'; import crypto from "crypto"; const linkshells = {}; const reloadModel = (lsName, serverId, ffxiver) => { runPrepQuery( "SELECT * FROM linkshells WHERE server = ? AND `name` = ? AND `ffxiver` = ? LIMIT 0,1", [serverId, lsName, ffxiver], async (r, f, e) => { if (!e) { if (numRows(r)) { updateMemory(r[0]) } else { Warn(`Unable to locate linkshell model ${lsName}, ${serverId}, ${ffxiver}`); } } else { console.log(e); } } ); }; export const getLsFromChannelId = (channelId) => { for(const ls in linkshells) { for(const channel in linkshells[ls]?.channels) { if(linkshells[ls]?.channels[channel]?.channelId == channelId) { return linkshells[ls] } } } return false } const reloadModelById = (id) => { runPrepQuery("SELECT * FROM linkshells WHERE id = ? LIMIT 0,1", [id], async (r, f, e) => { if (!e) { if (numRows(r)) { updateMemory(r[0]) } else { Warn(`Unable to locate linkshell model ${id},`); } } else { console.log(e); } }); }; const getLSChannels = (linkshellId) => { return new Promise((resolve, reject) => { const out = []; runPrepQuery("SELECT * FROM linkshellchannels WHERE linkshellId = ?", [linkshellId], (r, f, e) => { if (!e) { if (numRows(r)) { for (const row of r) { row.Webhook = new Webhook(row.webhookUrl) row.hookHistory = [] out.push(row); } } resolve(out); } else { console.log(e); reject(e); } }); }); }; export const createNewLS = async (lsName, serverId, ffxiver, ownerId) => { return new Promise(async (resolve, reject) => { if (!(await lsExists(lsName, serverId, ffxiver))) { runPrepQuery( "INSERT INTO linkshells (`name`, `server`, `discord_owner_id`, `ffxiver`) VALUES(?,?,?,?)", [lsName, serverId, ownerId, ffxiver], (r, f, e) => { if (!e) { reloadModel(lsName, serverId, ffxiver); resolve(true); } else { reject(e); } } ); } else { resolve(false); } }); }; export const addChannelForLinkshell = (linkshellId, channelId, guildId, webhookUrl) => { return new Promise((resolve, reject) => { runPrepQuery("DELETE FROM linkshellchannels WHERE guildId = ? AND channelId = ? LIMIT 1", [guildId, channelId], (r,f,e) => { if(!e) { runPrepQuery( "INSERT INTO linkshellchannels (channelId, linkshellId, guildId, webhookUrl) VALUES(?,?,?,?)", [channelId, linkshellId, guildId, webhookUrl], (r, f, e) => { if (!e) { reloadModelById(linkshellId); resolve(true); } else { reject(e); } } ); } else { reject(e); } }) }); }; export const getLSFromGuildId = (gid) => { for (const platform in linkshells) { for (const server in linkshells[platform]) { for (const linkshell in linkshells[platform][server]) { for (const channel in linkshells[platform][server][linkshell].channels) { if (linkshells[platform][server][linkshell].channels[channel].guildId == gid) { return getLSModel(linkshell, platform, server) } } } } } return false; }; export const getLSModel = (ls, platform, server) => { try { return linkshells[platform][server][ls] ? linkshells[platform][server][ls] : false; } catch (e) { console.log(e) return false } }; export const updateLSModel = (ls, newModel) => { if (!linkshells[ls]) return false; linkshells[ls] = { ...linkshells[ls], ...newModel }; return true; }; export const lsExists = (ls, server, version) => { return new Promise((resolve, reject) => { const lsExistsResult = runPrepQuery( "SELECT * FROM linkshells WHERE server = ? AND `name` = ? AND `ffxiver` = ? LIMIT 0,1", [server, ls, version], (r, f, e) => { if (e) return reject(e); if (numRows(r)) { resolve(true); } else { resolve(false); } } ); }); }; export const loadModelFromDB = () => { return new Promise((resolve, reject) => { runPrepQuery("SELECT * FROM linkshells", [], async (r, f, e) => { if (!e) { if (numRows(r)) { for(const row of r) { updateMemory(row) } } } else { console.log(e); } resolve(true) }); }) }; const updateMemory = async (row) => { if(!linkshells[row.ffxiver]) linkshells[row.ffxiver] = {} if(!linkshells[row.ffxiver][row.server]) linkshells[row.ffxiver][row.server] = {} if(linkshells[row.ffxiver][row.server][row.name]?.webhookTimer) clearTimeout(linkshells[row.ffxiver][row.server][row.name].webhookTimer) linkshells[row.ffxiver][row.server][row.name] = row linkshells[row.ffxiver][row.server][row.name].channels = await getLSChannels(row.id); linkshells[row.ffxiver][row.server][row.name].webhookQueue = [] linkshells[row.ffxiver][row.server][row.name].webhookTimer = processWebhookQueue(linkshells[row.ffxiver][row.server][row.name]) } const processWebhookQueue = (linkshell) => { const nextItem = linkshell.webhookQueue.shift() if(!nextItem) { return setTimeout(() => { processWebhookQueue(linkshell) }, 100) } const packet = nextItem.packet const trimmedMsg = packet.payload.message.replace(/[\n\r]/g, '').trim() console.log(`"${packet.payload.name + trimmedMsg}"`) const msgHash = crypto.createHash("md5").update(packet.payload.name + trimmedMsg).digest("hex"); for (const channel of linkshell.channels) { if (!findInRecent(msgHash, channel.hookHistory)) { channel.hookHistory.push({msgHash: msgHash, timeStamp: Date.now()}) Debug(`${msgHash} added to message hashes.`) channel.Webhook.setUsername(packet.payload.name); channel.Webhook.setAvatar("https://ui-avatars.com/api/?background=random&name=" + packet.payload.name) channel.Webhook.send(trimmedMsg); if(channel.hookHistory.length > 100) { const removed = channel.hookHistory.shift() Debug(`${removed.msgHash} removed from hashes.`) } } } return setTimeout(() => { processWebhookQueue(linkshell) }, 100) } const findInRecent = (msgHash, history) => { for(const idx in history) { const msgHashData = history[idx] if(msgHashData.msgHash == msgHash) { Debug(`Found Message Hash`) if(msgHashData.timeStamp + 5000 > Date.now()) { Debug(`Hash Timestamp Not Expired ${msgHashData.timeStamp + 5000} / ${Date.now()}`) return true } else { Debug(`Hash Timestamp Expired ${msgHashData.timeStamp + 5000} / ${Date.now()}`) } } else { Debug(`Message Hash Not Found. ${msgHash}`) } } return false }