// @ts-check const passthrough = require("../../passthrough") const {discord, select, db} = passthrough const DEBUG_SPEEDBUMP = false function debugSpeedbump(message) { if (DEBUG_SPEEDBUMP) { console.log(message) } } const SPEEDBUMP_SPEED = 4000 // 4 seconds delay const SPEEDBUMP_UPDATE_FREQUENCY = 2 * 60 * 60 // 2 hours /** @type {Set} */ const KNOWN_BOTS = new Set([ "466378653216014359" // PluralKit ]) /** * Fetch new speedbump data for the channel and put it in the database as cache * @param {string} channelID * @param {number?} lastChecked */ async function updateCache(channelID, lastChecked) { const now = Math.floor(Date.now() / 1000) if (lastChecked && now - lastChecked < SPEEDBUMP_UPDATE_FREQUENCY) return const webhooks = await discord.snow.webhook.getChannelWebhooks(channelID) const found = webhooks.find(b => KNOWN_BOTS.has(b.application_id)) const foundApplication = found?.application_id const foundWebhook = found?.id db.prepare("UPDATE channel_room SET speedbump_id = ?, speedbump_webhook_id = ?, speedbump_checked = ? WHERE channel_id = ?").run(foundApplication, foundWebhook, now, channelID) } /** @type {Map} messageID -> number of gateway events currently bumping */ const bumping = new Map() /** * Slow down a message. After it passes the speedbump, return whether it's okay or if it's been deleted. * @param {string} messageID * @returns whether it was deleted */ async function doSpeedbump(messageID) { let value = (bumping.get(messageID) ?? 0) + 1 bumping.set(messageID, value) debugSpeedbump(`[speedbump] WAIT ${messageID}++ = ${value}`) await new Promise(resolve => setTimeout(resolve, SPEEDBUMP_SPEED)) if (!bumping.has(messageID)) { debugSpeedbump(`[speedbump] DELETED ${messageID}`) return true } value = (bumping.get(messageID) ?? 0) - 1 if (value <= 0) { debugSpeedbump(`[speedbump] OK ${messageID}-- = ${value}`) bumping.delete(messageID) return false } else { debugSpeedbump(`[speedbump] MULTI ${messageID}-- = ${value}`) bumping.set(messageID, value) return true } } /** * Check whether to slow down a message, and do it. After it passes the speedbump, return whether it's okay or if it's been deleted. * @param {string} channelID * @param {string} messageID * @param {string} [userID] if provided, only slow down the message when the user has used PK before * @returns whether it was deleted, and data about the channel's (not thread's) speedbump */ async function maybeDoSpeedbump(channelID, messageID, userID) { let row = select("channel_room", ["room_id", "thread_parent", "speedbump_id", "speedbump_webhook_id"], {channel_id: channelID}).get() if (row?.thread_parent) row = select("channel_room", ["room_id", "thread_parent", "speedbump_id", "speedbump_webhook_id"], {channel_id: row.thread_parent}).get() // webhooks belong to the channel, not the thread if (!row?.speedbump_webhook_id) return {affected: false, row: null} // channel not affected, no speedbump if (userID) { if (row.speedbump_webhook_id === userID) return {affected: false, row} // shortcut const userHasProxy = select("sim_proxy", "user_id", {proxy_owner_id: userID}).pluck().get() if (!userHasProxy) return {affected: false, row} // user has not used PK before, no speedbump } const affected = await doSpeedbump(messageID) return {affected, row} // maybe affected, and there is a speedbump } /** * @param {string} messageID */ function onMessageDelete(messageID) { bumping.delete(messageID) } module.exports.updateCache = updateCache module.exports.doSpeedbump = doSpeedbump module.exports.maybeDoSpeedbump = maybeDoSpeedbump module.exports.onMessageDelete = onMessageDelete