import { Request, Response } from "express"; import { Logger } from "../utils/logger"; import { db } from "../databases/databases"; import { isUserVIP } from "../utils/isUserVIP"; import { getHashCache } from "../utils/getHashCache"; import { HashedUserID, UserID } from "../types/user.model"; import { generateWarningDiscord, warningData, dispatchEvent } from "../utils/webhookUtils"; import { WarningType } from "../types/warning.model"; type warningEntry = { userID: HashedUserID, issueTime: number, issuerUserID: HashedUserID, enabled: boolean, reason: string } const MAX_EDIT_DELAY = 900000; // 15 mins const getUsername = (userID: HashedUserID) => db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID], { useReplica: true }); export async function postWarning(req: Request, res: Response): Promise { if (!req.body.userID) return res.status(400).json({ "message": "Missing parameters" }); const issuerUserID: HashedUserID = req.body.issuerUserID ? await getHashCache(req.body.issuerUserID as UserID) : null; const userID: HashedUserID = issuerUserID ? req.body.userID : await getHashCache(req.body.userID as UserID); const issueTime = new Date().getTime(); const enabled: boolean = req.body.enabled ?? true; const reason: string = req.body.reason ?? ""; const type: WarningType = req.body.type ?? WarningType.SponsorBlock; if ((!issuerUserID && enabled) || (issuerUserID && !await isUserVIP(issuerUserID))) { Logger.warn(`Permission violation: User ${issuerUserID} attempted to warn user ${userID}.`); return res.status(403).json({ "message": "Not a VIP" }); } let resultStatus = ""; try { if (enabled) { if (!reason) { return res.status(400).json({ "message": "Missing warning reason" }); } const previousWarning = await db.prepare("get", 'SELECT * FROM "warnings" WHERE "userID" = ? AND "type" = ? AND "enabled" = 1', [userID, type]) as warningEntry; if (!previousWarning) { await db.prepare( "run", 'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled", "reason", "type") VALUES (?, ?, ?, 1, ?, ?)', [userID, issueTime, issuerUserID, reason, type] ); resultStatus = "issued to"; // allow a warning to be edited by the same vip within 15 mins of issuing } else if (issuerUserID === previousWarning.issuerUserID && (Date.now() - MAX_EDIT_DELAY) < previousWarning.issueTime) { await db.prepare( "run", 'UPDATE "warnings" SET "reason" = ? WHERE "userID" = ? AND "issueTime" = ?', [reason, userID, previousWarning.issueTime] ); resultStatus = "edited for"; } else { return res.sendStatus(409); } } else { await db.prepare("run", 'UPDATE "warnings" SET "enabled" = 0, "disableTime" = ? WHERE "userID" = ? AND "type" = ? AND "enabled" = 1', [issueTime, userID, type]); resultStatus = "removed from"; } const targetUsername = await getUsername(userID) ?? null; const issuerUsername = await getUsername(issuerUserID) ?? null; const webhookData = { target: { userID, username: targetUsername }, issuer: { userID: issuerUserID, username: issuerUsername }, reason } as warningData; try { const warning = generateWarningDiscord(webhookData); dispatchEvent("warning", warning); } catch /* istanbul ignore next */ (err) { Logger.error(`Error sending warning to Discord ${err}`); } return res.status(200).json({ message: `Tip ${resultStatus} user '${userID}'.`, }); } catch (e) { Logger.error(e as string); return res.sendStatus(500); } }