import { attachments, db, ticketMessages, tickets, userPreferences, } from "@/database"; import { getPublicUrl, s3 } from "@/utils/s3"; import { AttachmentBuilder, type Client, Events, type GuildMember, type Message, } from "discord.js"; import { and, eq } from "drizzle-orm"; import { nanoid } from "nanoid"; import Uwuifier from "uwuifier"; import { handleMessage } from "@/automod"; import config from "@/config"; import { hasManagerPermissions } from "@/utils/discord/permissions"; import { loggers } from "@/utils/logging"; import { getRateLimitInfo } from "@/utils/rateLimit"; const logger = loggers.events.child({ name: "messageCreate" }); type UploadStatus = "queued" | "uploading" | "done" | "failed"; async function handleUwuification(message: Message): Promise { if (message.author.bot) return false; if (!message.content && message.attachments.size === 0) return false; const userPref = db .select() .from(userPreferences) .where(eq(userPreferences.userId, message.author.id)) .get(); if (userPref?.uwuifyMessages !== "enabled") return false; // const allowed = await checkUwuifyRateLimit(message.author.id); const allowed = true; if (!allowed) { const info = await getRateLimitInfo(message.author.id); const resetTime = info?.resetAt ? Math.floor(info.resetAt.getTime() / 1000) : Date.now(); try { await message.reply({ content: `you've reached the uwuification limit (25 per hour). your limit resets , proxying has been disabled.`, }); await db .update(userPreferences) .set({ uwuifyMessages: "disabled" }) .where(eq(userPreferences.userId, message.author.id)); } catch (err) { logger.warn(err, "Failed to send rate limit message"); } return false; } const uwuifier = new Uwuifier(); const uwuifiedContent = message.content ? uwuifier.uwuifySentence(message.content) : ""; const blacklistedWords = [ "twerks", "sees bulge", "notices buldge", "starts twerking", "wank", ]; const finalText = blacklistedWords.reduce((acc, word) => { return acc.replace(new RegExp(word, "gi"), "----"); }, uwuifiedContent); try { let replyReference = null; if (message.reference?.messageId) { try { replyReference = await message.channel.messages.fetch( message.reference.messageId, ); } catch (error) { logger.warn( error, "Failed to fetch referenced message for uwuified reply", ); } } const files: AttachmentBuilder[] = []; for (const attachment of message.attachments.values()) { try { const resp = await fetch(attachment.url); if (!resp.ok) { logger.warn( `Failed to fetch attachment ${attachment.name}, status ${resp.status}`, ); continue; } const buffer = await resp.arrayBuffer(); const attachmentBuilder = new AttachmentBuilder(Buffer.from(buffer), { name: attachment.name, }); files.push(attachmentBuilder); } catch (error) { logger.error(error, `Failed to download attachment ${attachment.name}`); } } message.delete(); const contentParts = [ `**<@${message.author.id}>**: ${finalText || "(no text)"}`, ]; if (message.channel.isSendable()) { const sendOptions: { content: string; files?: AttachmentBuilder[]; reply?: { messageReference: string }; } = { content: contentParts.join("\n"), }; if (files.length > 0) { sendOptions.files = files; } if (replyReference) { sendOptions.reply = { messageReference: replyReference.id, }; } message.channel.send({ allowedMentions: { users: [], }, ...sendOptions, }); } return true; } catch (error) { logger.error(error, "Failed to uwuify and resend message"); return false; } } export default { event: Events.MessageCreate, async execute(client: Client, message: Message) { if (!client.user) return; await handleMessage(client, message); const triggers = ["grok is this true", "grok is this false "]; if ( message.channel.isSendable() && triggers.some((trigger) => message.content.toLowerCase().includes(trigger), ) ) { const chance = Math.random() * 100; if (chance < 25 || message.author.id === "470956810866655242") { await message.channel.send("gorque* 🇫🇷🥖"); message.channel.send("but oui"); } else { message.channel.send("yeh"); } } if ( message.channel.isSendable() && message.content.includes("gorque is this true") ) { message.channel.send("oui 🇫🇷🥖"); } await handleUwuification(message); const ticket = db .select() .from(tickets) .where( and( eq(tickets.channelId, message.channelId), eq(tickets.guildId, message.guildId ?? ""), ), ) .get(); if (!ticket) return; try { const isSystem = message.author.id === client.user.id; const isModerator = await hasManagerPermissions( message?.member as GuildMember, ); const authorType = isSystem ? "system" : isModerator ? "staff" : "user"; const anonymisedContent = message.content.replace( new RegExp(ticket.authorId, "g"), "-".repeat(ticket.authorId.length), ); const savedMessage = db .insert(ticketMessages) .values({ ticketId: ticket.id, authorId: message.author.id, authorType: authorType, content: anonymisedContent, createdAt: message.createdAt, }) .returning() .get(); if (message.attachments.size > 0) { const attachmentsArray = Array.from(message.attachments.values()); const total = attachmentsArray.length; const statusList: { attachment: (typeof attachmentsArray)[number]; status: UploadStatus; url?: string; }[] = attachmentsArray.map((att) => ({ attachment: att, status: "queued", })); const formatSize = (size: number) => { if (size < 1024) return `${size} B`; if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`; if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(2)} MB`; return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`; }; const renderBoard = () => { const list = statusList .map((item) => { const name = item.attachment.name ?? "unknown"; const emoji = (() => { switch (item.status) { case "done": return ":white_check_mark:"; case "uploading": return ":hourglass_flowing_sand:"; case "queued": return ":black_large_square:"; case "failed": return ":x:"; } })(); if (item.status === "done" && item.url) return `${emoji} [${name}](<${item.url}>) (${formatSize(item.attachment.size)})`; return `${emoji} ${name} (${formatSize(item.attachment.size)})`; }) .join("\n"); return list; }; let statusMessage: Message | null = null; if (message.channel.isSendable()) { statusMessage = await message.reply({ content: `**Attachment upload status**\n${renderBoard()}`, }); } for (let i = 0; i < total; i++) { const { attachment } = statusList[i]; if (!statusMessage) continue; try { statusList[i].status = "uploading"; await statusMessage.edit( `**Attachment upload status**\n${renderBoard()}`, ); const key = `${ticket.anonymousId}/${savedMessage.id}/${nanoid( 8, )}_${attachment.name}`; const file = s3.file(key); const resp = await fetch(attachment.url); const blob = await resp.blob(); await file.write(blob); const publicUrl = getPublicUrl(key); await db.insert(attachments).values({ id: attachment.id, ownerType: "ticket_message", ownerId: savedMessage.id, fileName: attachment.name, contentType: attachment.contentType ?? "application/octet-stream", size: attachment.size, bucket: config.s3.bucket, key: key, url: publicUrl, }); statusList[i].status = "done"; statusList[i].url = publicUrl; await statusMessage.edit( `**Attachment upload status**\n${renderBoard()}`, ); } catch (error) { logger.error( error, `Failed to upload attachment ${attachment.name}`, ); statusList[i].status = "failed"; await statusMessage.edit( `**Attachment upload status**\n${renderBoard()}`, ); } } } } catch (error) { logger.error(error, "Failed to log ticket message"); await message.reply({ content: "Failed to log this message.", allowedMentions: { parse: [], }, }); } }, };