|
|
|
|
@@ -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<true>) => {
|
|
|
|
|
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<true>) => {
|
|
|
|
|
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) => {
|
|
|
|
|
|