diff --git a/apps/discord-bot/src/env.ts b/apps/discord-bot/src/env.ts index 82f4d4b..9369330 100644 --- a/apps/discord-bot/src/env.ts +++ b/apps/discord-bot/src/env.ts @@ -33,5 +33,4 @@ export const env = parseEnv({ .transform((x) => x.split(",").map((x) => x.trim())) .optional() .default([]), - REPORTS_JSON: z.string().optional(), }); diff --git a/apps/discord-bot/src/modes/bot.ts b/apps/discord-bot/src/modes/bot.ts index 0a53b8f..cf0db16 100644 --- a/apps/discord-bot/src/modes/bot.ts +++ b/apps/discord-bot/src/modes/bot.ts @@ -4,13 +4,13 @@ import { env } from "~/env"; import { questCheckCron } from "~/quests"; import { trackingCron } from "~/tracking"; import { commands } from "~/commands"; -import { handleReportButton, handleReportModal, handleEditButton, handleDeleteButton, handleEditModal, importReports, REPORT_BUTTON_ID, REPORT_MODAL_ID, REPORT_EDIT_BUTTON_PREFIX, REPORT_DELETE_BUTTON_PREFIX, REPORT_EDIT_MODAL_PREFIX } from "~/reporting"; +import { handleReportButton, handleReportModal, handleEditButton, handleDeleteButton, handleEditModal, migrateReportEmbeds, REPORT_BUTTON_ID, REPORT_MODAL_ID, REPORT_EDIT_BUTTON_PREFIX, REPORT_DELETE_BUTTON_PREFIX, REPORT_EDIT_MODAL_PREFIX } from "~/reporting"; const onReady = async (client: Client) => { logger.info(`Client ready`); logger.info(`Connected as @${client.user.username}`); - await importReports(client); + void migrateReportEmbeds(client); await questCheckCron(client); setInterval(() => void questCheckCron(client), env.WOV_FETCH_INTERVAL); diff --git a/apps/discord-bot/src/reporting.ts b/apps/discord-bot/src/reporting.ts index efd733f..fe253ba 100644 --- a/apps/discord-bot/src/reporting.ts +++ b/apps/discord-bot/src/reporting.ts @@ -3,9 +3,8 @@ import { EmbedBuilder, FileUploadBuilder, LabelBuilder, MessageFlags, ModalBuilder, TextInputBuilder, TextInputStyle, type ButtonInteraction, type Client, type FileUploadModalData, type ModalSubmitInteraction, type TextChannel, } from "discord.js"; -import { z } from "zod"; import { createLogger } from "@lbf-bot/utils"; -import { db, tables, eq, and } from "@lbf-bot/database"; +import { db, tables, eq } from "@lbf-bot/database"; import { env } from "~/env"; import { searchPlayer } from "~/wov"; @@ -17,11 +16,18 @@ export const REPORT_EDIT_BUTTON_PREFIX = "report:edit"; export const REPORT_DELETE_BUTTON_PREFIX = "report:delete"; export const REPORT_EDIT_MODAL_PREFIX = "report:edit:modal"; -const buildReportEmbed = (report: { playerName: string; playerId: string; reason: string; reporterId: string }) => +const formatDate = (date: Date): string => { + const d = String(date.getDate()).padStart(2, "0"); + const m = String(date.getMonth() + 1).padStart(2, "0"); + return `${d}/${m}/${date.getFullYear()}`; +}; + +const buildReportEmbed = (report: { playerName: string; playerId: string; reason: string; reporterId: string; createdAt: Date }) => new EmbedBuilder() .setDescription([ `**Pseudo**: \`${report.playerName}\``, `**ID**: \`${report.playerId}\``, + `**Date**: \`${formatDate(report.createdAt)}\``, `**Raison**: \`\`\`${report.reason}\`\`\``, `-# Signalé par <@${report.reporterId}>` ].join("\n")); @@ -181,7 +187,7 @@ export const handleReportModal = async (interaction: ModalSubmitInteraction, cli if (reportChannel?.type === ChannelType.GuildText) { const reportMessage = await reportChannel.send({ content: "─────────────────────────────────", - embeds: [buildReportEmbed({ playerName, playerId: player.id, reason, reporterId: interaction.user.id })], + embeds: [buildReportEmbed({ playerName, playerId: player.id, reason, reporterId: interaction.user.id, createdAt: new Date() })], components: [reportActionRow(inserted.id)], }); messageLink = `https://discord.com/channels/${reportChannel.guild.id}/${reportChannel.id}/${reportMessage.id}`; @@ -242,102 +248,38 @@ export const handleDeleteButton = async (interaction: ButtonInteraction, reportI await interaction.editReply({ content: "Signalement supprimé." }); }; -const importEntrySchema = z.object({ - username: z.string(), - author_id: z.string(), - reason: z.string(), - date: z.string(), -}); - -const parseImportDate = (date: string): Date => { - const [month, day, year] = date.split("/").map(Number); - return new Date(2000 + year, month - 1, day); -}; - -export const importReports = async (client: Client) => { - if (!env.REPORTS_JSON) return; - - let rawJson: unknown; - try { - rawJson = JSON.parse(env.REPORTS_JSON); - } catch { - logger.error("REPORTS_JSON is not valid JSON"); - return; - } - - const parsed = z.array(importEntrySchema).safeParse(rawJson); - if (!parsed.success) { - logger.error("Invalid REPORTS_JSON:", parsed.error.issues); - return; - } - - const reportChannel = await client.channels.fetch(env.DISCORD_REPORT_CHANNEL); - if (reportChannel?.type !== ChannelType.GuildText) { - logger.error("Invalid 'DISCORD_REPORT_CHANNEL' for import"); - return; - } - - let imported = 0; - let skipped = 0; +export const migrateReportEmbeds = async (client: Client) => { + const reports = await db.select().from(tables.reports); + let migrated = 0; let failed = 0; - for (const entry of parsed.data) { - const player = await searchPlayer(entry.username); - if (!player) { - logger.warn(`Import: player not found, skipping — ${entry.username}`); - failed++; - continue; - } + for (const report of reports) { + if (!report.messageLink) continue; - const existing = await db.query.reports.findFirst({ - columns: { id: true }, - where: and(eq(tables.reports.playerId, player.id), eq(tables.reports.reason, entry.reason)), - }); - - if (existing) { - skipped++; - continue; - } + const parts = report.messageLink.split("/"); + const channelId = parts.at(-2); + const messageId = parts.at(-1); + if (!channelId || !messageId) continue; try { - const [inserted] = await db - .insert(tables.reports) - .values({ - reporterId: entry.author_id, - playerName: entry.username, - playerId: player.id, - reason: entry.reason, - createdAt: parseImportDate(entry.date), - }) - .returning({ id: tables.reports.id }); + const channel = await client.channels.fetch(channelId); + if (channel?.type !== ChannelType.GuildText) continue; - const reportMessage = await reportChannel.send({ - content: "─────────────────────────────────", - embeds: [buildReportEmbed({ playerName: entry.username, playerId: player.id, reason: entry.reason, reporterId: entry.author_id })], - components: [reportActionRow(inserted.id)], + const message = await channel.messages.fetch(messageId); + await message.edit({ + embeds: [buildReportEmbed(report)], + components: [reportActionRow(report.id)], }); - - const messageLink = `https://discord.com/channels/${reportChannel.guild.id}/${reportChannel.id}/${reportMessage.id}`; - const screenshotsMessage = await reportChannel.send({ content: "-# Pas de screenshots" }); - - await db.update(tables.reports) - .set({ messageLink, screenshotsMessageId: screenshotsMessage.id }) - .where(eq(tables.reports.id, inserted.id)); - - imported++; - } catch (error) { - logger.error(`Import: failed for ${entry.username}:`, error); + migrated++; + } catch { + logger.warn(`Embed migration failed for report ${report.id}`); failed++; } - // avoid spamming too much the APIs await new Promise(resolve => setTimeout(resolve, 500)); - if (imported > 0 && imported % 50 === 0) { - await new Promise(resolve => setTimeout(resolve, 60000)); - } } - logger.info(`Import complete: ${imported} imported, ${skipped} skipped, ${failed} not found`); + logger.info(`Embed migration complete: ${migrated} updated, ${failed} failed`); }; export const handleEditModal = async (interaction: ModalSubmitInteraction, client: Client, reportId: string, channelId: string, messageId: string) => { diff --git a/docker-compose.yml b/docker-compose.yml index 13b936f..4ac9e33 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,6 @@ services: - QUEST_REWARDS - QUEST_REWARDS_ARE_GEMS - QUEST_EXCLUDE - - REPORTS_JSON networks: lbf-network: