Tobkubos commited on
Commit
f19cc50
·
1 Parent(s): 2fb0fac
Files changed (3) hide show
  1. configManager.js +38 -0
  2. guildConfigs.json +7 -0
  3. index.js +244 -11
configManager.js ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const filePath = path.resolve("./guildConfigs.json");
5
+
6
+ export const DEFAULT_CONFIG = {
7
+ logChannelId: null,
8
+ textModel: "yaya36095/xlm-roberta-text-detector",
9
+ imageModel: "capcheck/ai-image-detection"
10
+ };
11
+
12
+ export function loadConfig(guildId) {
13
+ if (!fs.existsSync(filePath)) {
14
+ fs.writeFileSync(filePath, JSON.stringify({}));
15
+ }
16
+ try {
17
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
18
+ return data[guildId] || { ...DEFAULT_CONFIG };
19
+ } catch (err) {
20
+ console.error("Błąd podczas odczytu konfiguracji:", err);
21
+ return { ...DEFAULT_CONFIG };
22
+ }
23
+ }
24
+
25
+ export function saveConfig(guildId, newConfig) {
26
+ if (!fs.existsSync(filePath)) {
27
+ fs.writeFileSync(filePath, JSON.stringify({}));
28
+ }
29
+ try {
30
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
31
+ data[guildId] = { ...DEFAULT_CONFIG, ...data[guildId], ...newConfig };
32
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
33
+ return true;
34
+ } catch (err) {
35
+ console.error("Błąd podczas zapisu konfiguracji:", err);
36
+ return false;
37
+ }
38
+ }
guildConfigs.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "1515307986963267595": {
3
+ "logChannelId": "1515373138937123007",
4
+ "textModel": "yaya36095/xlm-roberta-text-detector",
5
+ "imageModel": "capcheck/ai-image-detection"
6
+ }
7
+ }
index.js CHANGED
@@ -13,9 +13,15 @@ import {
13
  ApplicationCommandType,
14
  EmbedBuilder,
15
  ButtonBuilder,
16
- ButtonStyle
 
 
 
 
17
  } from "discord.js";
18
 
 
 
19
  const client = new Client({
20
  intents: [
21
  GatewayIntentBits.Guilds,
@@ -26,6 +32,15 @@ const client = new Client({
26
 
27
  const API_URL = process.env.API_URL || "http://127.0.0.1:8000";
28
 
 
 
 
 
 
 
 
 
 
29
  client.once(Events.ClientReady, async () => {
30
  console.log(`Bot ready: ${client.user.tag}`);
31
 
@@ -36,17 +51,46 @@ client.once(Events.ClientReady, async () => {
36
  description: "Otwiera okienko do wklejenia linku lub tekstu do analizy",
37
  type: ApplicationCommandType.ChatInput
38
  },
 
 
 
 
 
 
39
  {
40
  name: "Przeanalizuj tekst",
41
  type: ApplicationCommandType.Message
42
  }
43
  ]);
44
- console.log("Pomyślnie zarejestrowano komendy (/detect oraz menu kontekstowe)");
45
  } catch (error) {
46
  console.error("Błąd podczas rejestracji komend:", error);
47
  }
48
  });
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  function preparePayload(input) {
51
  const trimmed = input.trim();
52
  const isUrl = trimmed.startsWith("http://") || trimmed.startsWith("https://");
@@ -98,13 +142,114 @@ function getProgressBar(confidence, isDeepfake) {
98
  return blockEmoji.repeat(filledBlocks) + "⬛".repeat(emptyBlocks);
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  async function handleAnalysis(interaction, userContent, targetMessage = null) {
102
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
103
 
 
 
104
  try {
105
  const { type, payload } = preparePayload(userContent);
106
 
107
- console.log(`Wysyłanie zapytania typu: ${type} do API...`);
 
 
 
 
 
 
108
 
109
  const response = await fetch(`${API_URL}/analyze`, {
110
  method: "POST",
@@ -163,7 +308,6 @@ async function handleAnalysis(interaction, userContent, targetMessage = null) {
163
  .setTimestamp()
164
  .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
165
 
166
- // TWORZENIE PRZYCISKÓW
167
  const buttonRow = new ActionRowBuilder().addComponents(
168
  new ButtonBuilder()
169
  .setCustomId("modelCorrect")
@@ -210,6 +354,54 @@ client.on(Events.InteractionCreate, async (interaction) => {
210
 
211
  await interaction.showModal(modal);
212
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
 
215
  if (interaction.isMessageContextMenuCommand()) {
@@ -241,26 +433,67 @@ client.on(Events.InteractionCreate, async (interaction) => {
241
  }
242
  }
243
 
244
- // GUZIKI
245
  if (interaction.isButton()) {
246
- // ZGŁOSZENIE BŁĘDU
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  if (interaction.customId === "reportError") {
248
  await interaction.reply({
249
- content: "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane. Pomoże nam ono udoskonalić algorytmy detekcji.",
250
  flags: [MessageFlags.Ephemeral]
251
  });
252
 
253
- console.log(`[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błędną klasyfikację bota.`);
 
 
 
 
 
 
 
 
 
 
254
  }
255
 
256
- // POTWIERDZENIE POPRAWNOŚCI
257
  if (interaction.customId === "modelCorrect") {
258
  await interaction.reply({
259
- content: "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane. Cieszymy się, że model zadziałał prawidłowo.",
260
  flags: [MessageFlags.Ephemeral]
261
  });
262
 
263
- console.log(`[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację bota.`);
 
 
 
 
 
 
 
 
 
 
264
  }
265
  }
266
  });
 
13
  ApplicationCommandType,
14
  EmbedBuilder,
15
  ButtonBuilder,
16
+ ButtonStyle,
17
+ PermissionFlagsBits,
18
+ ChannelSelectMenuBuilder,
19
+ StringSelectMenuBuilder,
20
+ ChannelType
21
  } from "discord.js";
22
 
23
+ import { loadConfig, saveConfig } from "./configManager.js";
24
+
25
  const client = new Client({
26
  intents: [
27
  GatewayIntentBits.Guilds,
 
32
 
33
  const API_URL = process.env.API_URL || "http://127.0.0.1:8000";
34
 
35
+ // Domyślne modele zapasowe (używane gdyby backend był wyłączony podczas konfiguracji)
36
+ const FALLBACK_MODELS = {
37
+ text: ["yaya36095/xlm-roberta-text-detector", "mock"],
38
+ image: ["capcheck/ai-image-detection", "mock"]
39
+ };
40
+
41
+ // Pamięć podręczna przechowuje konfigurację oraz pobrane dynamicznie modele
42
+ const activeSetupSessions = new Map();
43
+
44
  client.once(Events.ClientReady, async () => {
45
  console.log(`Bot ready: ${client.user.tag}`);
46
 
 
51
  description: "Otwiera okienko do wklejenia linku lub tekstu do analizy",
52
  type: ApplicationCommandType.ChatInput
53
  },
54
+ {
55
+ name: "setup",
56
+ description: "Ustawienia kanału logów i modeli analizy (Wymaga Administratora)",
57
+ default_member_permissions: PermissionFlagsBits.Administrator.toString(),
58
+ type: ApplicationCommandType.ChatInput
59
+ },
60
  {
61
  name: "Przeanalizuj tekst",
62
  type: ApplicationCommandType.Message
63
  }
64
  ]);
65
+ console.log("Pomyślnie zarejestrowano komendy (/detect, /setup oraz menu kontekstowe)");
66
  } catch (error) {
67
  console.error("Błąd podczas rejestracji komend:", error);
68
  }
69
  });
70
 
71
+ // Funkcja pobierająca aktualne modele bezpośrednio z FastAPI w czasie rzeczywistym
72
+ async function fetchAvailableModels() {
73
+ try {
74
+ const response = await fetch(API_URL);
75
+ if (response.ok) {
76
+ const data = await response.json();
77
+ if (data.available_models) {
78
+ const textModels = data.available_models.text || [];
79
+ const imageModels = data.available_models.image || [];
80
+
81
+ // Upewniamy się, że zawsze mamy opcję testową "mock"
82
+ if (!textModels.includes("mock")) textModels.push("mock");
83
+ if (!imageModels.includes("mock")) imageModels.push("mock");
84
+
85
+ return { text: textModels, image: imageModels };
86
+ }
87
+ }
88
+ } catch (err) {
89
+ console.warn("Nie udało się pobrać modeli z API (użyto modeli zapasowych):", err.message);
90
+ }
91
+ return FALLBACK_MODELS;
92
+ }
93
+
94
  function preparePayload(input) {
95
  const trimmed = input.trim();
96
  const isUrl = trimmed.startsWith("http://") || trimmed.startsWith("https://");
 
142
  return blockEmoji.repeat(filledBlocks) + "⬛".repeat(emptyBlocks);
143
  }
144
 
145
+ // ZMIANA: Funkcja przyjmuje teraz pobrane dynamicznie modele jako drugi parametr
146
+ function generateSetupView(tempConfig, availableModels) {
147
+ const embed = new EmbedBuilder()
148
+ .setColor(0x5865F2)
149
+ .setTitle("⚙️ Konfiguracja Systemu Detekcji")
150
+ .setDescription("Wybierz kanał do wysyłania logów oraz aktywne modele analizy z menu poniżej.")
151
+ .addFields(
152
+ {
153
+ name: "📂 Kanał logów (Raporty)",
154
+ value: tempConfig.logChannelId ? `<#${tempConfig.logChannelId}>` : "*Wysyłanie tylko do konsoli*",
155
+ inline: false
156
+ },
157
+ {
158
+ name: "📝 Model tekstowy",
159
+ value: `\`${tempConfig.textModel}\``,
160
+ inline: true
161
+ },
162
+ {
163
+ name: "🖼️ Model obrazów",
164
+ value: `\`${tempConfig.imageModel}\``,
165
+ inline: true
166
+ }
167
+ )
168
+ .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" })
169
+ .setTimestamp();
170
+
171
+ const channelSelect = new ChannelSelectMenuBuilder()
172
+ .setCustomId("setup_log_channel")
173
+ .setPlaceholder("Wybierz kanał dla raportów")
174
+ .addChannelTypes(ChannelType.GuildText);
175
+
176
+ // DYNAMICZNE mapowanie modeli tekstowych z API
177
+ const textOptions = availableModels.text.map(model => ({
178
+ label: model === "mock" ? "Mock (Model testowy)" : model,
179
+ value: model,
180
+ default: tempConfig.textModel === model
181
+ }));
182
+
183
+ const textModelSelect = new StringSelectMenuBuilder()
184
+ .setCustomId("setup_text_model")
185
+ .setPlaceholder("Wybierz model tekstu")
186
+ .addOptions(textOptions);
187
+
188
+ // DYNAMICZNE mapowanie modeli graficznych z API
189
+ const imageOptions = availableModels.image.map(model => ({
190
+ label: model === "mock" ? "Mock (Model testowy)" : model,
191
+ value: model,
192
+ default: tempConfig.imageModel === model
193
+ }));
194
+
195
+ const imageModelSelect = new StringSelectMenuBuilder()
196
+ .setCustomId("setup_image_model")
197
+ .setPlaceholder("Wybierz model obrazów")
198
+ .addOptions(imageOptions);
199
+
200
+ const buttonsRow = new ActionRowBuilder().addComponents(
201
+ new ButtonBuilder()
202
+ .setCustomId("setup_save")
203
+ .setLabel("Zapisz ustawienia")
204
+ .setStyle(ButtonStyle.Success)
205
+ .setEmoji("💾"),
206
+ new ButtonBuilder()
207
+ .setCustomId("setup_cancel")
208
+ .setLabel("Anuluj")
209
+ .setStyle(ButtonStyle.Danger)
210
+ .setEmoji("❌")
211
+ );
212
+
213
+ return {
214
+ embeds: [embed],
215
+ components: [
216
+ new ActionRowBuilder().addComponents(channelSelect),
217
+ new ActionRowBuilder().addComponents(textModelSelect),
218
+ new ActionRowBuilder().addComponents(imageModelSelect),
219
+ buttonsRow
220
+ ]
221
+ };
222
+ }
223
+
224
+ async function sendLogToDiscord(guild, embedToSend) {
225
+ const config = loadConfig(guild.id);
226
+ if (!config.logChannelId) return;
227
+
228
+ try {
229
+ const channel = await guild.channels.fetch(config.logChannelId);
230
+ if (channel) {
231
+ await channel.send({ embeds: [embedToSend] });
232
+ }
233
+ } catch (err) {
234
+ console.warn(`Nie można wysłać logu na kanał ${config.logChannelId}:`, err.message);
235
+ }
236
+ }
237
+
238
  async function handleAnalysis(interaction, userContent, targetMessage = null) {
239
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
240
 
241
+ const serverConfig = loadConfig(interaction.guildId);
242
+
243
  try {
244
  const { type, payload } = preparePayload(userContent);
245
 
246
+ if (type === "text") {
247
+ payload.model = serverConfig.textModel;
248
+ } else if (type === "image") {
249
+ payload.model = serverConfig.imageModel;
250
+ }
251
+
252
+ console.log(`Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model}...`);
253
 
254
  const response = await fetch(`${API_URL}/analyze`, {
255
  method: "POST",
 
308
  .setTimestamp()
309
  .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
310
 
 
311
  const buttonRow = new ActionRowBuilder().addComponents(
312
  new ButtonBuilder()
313
  .setCustomId("modelCorrect")
 
354
 
355
  await interaction.showModal(modal);
356
  }
357
+
358
+ // ZMIANA: Pobieranie modeli z API na żywo przed pokazaniem setupu
359
+ if (interaction.commandName === "setup") {
360
+ const guildId = interaction.guildId;
361
+ const currentConfig = loadConfig(guildId);
362
+
363
+ // Informujemy Discord, że pobieramy konfigurację z API
364
+ await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
365
+
366
+ // Pobieramy aktywne modele bezpośrednio z FastAPI
367
+ const availableModels = await fetchAvailableModels();
368
+
369
+ // Zapisujemy w sesji zarówno konfigurację, jak i pobrane modele
370
+ activeSetupSessions.set(guildId, {
371
+ config: { ...currentConfig },
372
+ availableModels
373
+ });
374
+
375
+ const setupView = generateSetupView(currentConfig, availableModels);
376
+ await interaction.editReply(setupView);
377
+ }
378
+ }
379
+
380
+ // OBSŁUGA ZMIANY KANAŁU LOGÓW
381
+ if (interaction.isChannelSelectMenu()) {
382
+ if (interaction.customId === "setup_log_channel") {
383
+ const guildId = interaction.guildId;
384
+ const tempSession = activeSetupSessions.get(guildId);
385
+ if (tempSession) {
386
+ tempSession.config.logChannelId = interaction.values[0];
387
+ await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
388
+ }
389
+ }
390
+ }
391
+
392
+ // OBSŁUGA ZMIANY MODELI
393
+ if (interaction.isStringSelectMenu()) {
394
+ const guildId = interaction.guildId;
395
+ const tempSession = activeSetupSessions.get(guildId);
396
+
397
+ if (tempSession) {
398
+ if (interaction.customId === "setup_text_model") {
399
+ tempSession.config.textModel = interaction.values[0];
400
+ } else if (interaction.customId === "setup_image_model") {
401
+ tempSession.config.imageModel = interaction.values[0];
402
+ }
403
+ await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
404
+ }
405
  }
406
 
407
  if (interaction.isMessageContextMenuCommand()) {
 
433
  }
434
  }
435
 
 
436
  if (interaction.isButton()) {
437
+ const guildId = interaction.guildId;
438
+
439
+ if (interaction.customId === "setup_save") {
440
+ const tempSession = activeSetupSessions.get(guildId);
441
+ if (tempSession) {
442
+ saveConfig(guildId, tempSession.config);
443
+ activeSetupSessions.delete(guildId);
444
+ await interaction.update({
445
+ content: "✅ **Ustawienia zostały pomyślnie zapisane!**",
446
+ embeds: [],
447
+ components: []
448
+ });
449
+ }
450
+ }
451
+
452
+ if (interaction.customId === "setup_cancel") {
453
+ activeSetupSessions.delete(guildId);
454
+ await interaction.update({
455
+ content: "❌ **Konfiguracja została anulowana.**",
456
+ embeds: [],
457
+ components: []
458
+ });
459
+ }
460
+
461
  if (interaction.customId === "reportError") {
462
  await interaction.reply({
463
+ content: "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane.",
464
  flags: [MessageFlags.Ephemeral]
465
  });
466
 
467
+ console.log(`[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błędną klasyfikację.`);
468
+
469
+ const originalEmbed = interaction.message.embeds[0];
470
+ if (originalEmbed) {
471
+ const logEmbed = EmbedBuilder.from(originalEmbed)
472
+ .setColor(0xFFAA00)
473
+ .setTitle("⚠️ Zgłoszenie błędu analizy")
474
+ .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) zgłosił błąd analizy w poniższym raporcie.`);
475
+
476
+ await sendLogToDiscord(interaction.guild, logEmbed);
477
+ }
478
  }
479
 
 
480
  if (interaction.customId === "modelCorrect") {
481
  await interaction.reply({
482
+ content: "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane.",
483
  flags: [MessageFlags.Ephemeral]
484
  });
485
 
486
+ console.log(`[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację.`);
487
+
488
+ const originalEmbed = interaction.message.embeds[0];
489
+ if (originalEmbed) {
490
+ const logEmbed = EmbedBuilder.from(originalEmbed)
491
+ .setColor(0x00AAFF)
492
+ .setTitle("✅ Potwierdzona poprawność analizy")
493
+ .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) potwierdził poprawność raportu.`);
494
+
495
+ await sendLogToDiscord(interaction.guild, logEmbed);
496
+ }
497
  }
498
  }
499
  });