Trololindo commited on
Commit
fb28d92
·
2 Parent(s): 9fe5e6c87bf5cb

Merge branch 'hujjjjjj' of https://github.com/Tobkubos/DiscordBot into hackaton-delenda-est

Browse files
Files changed (1) hide show
  1. index.js +272 -119
index.js CHANGED
@@ -1,23 +1,23 @@
1
  import dotenv from "dotenv";
2
  dotenv.config();
3
 
4
- import {
5
- Client,
6
- GatewayIntentBits,
7
- Events,
8
- ModalBuilder,
9
- TextInputBuilder,
10
- TextInputStyle,
11
- ActionRowBuilder,
12
- MessageFlags,
13
- ApplicationCommandType,
14
- EmbedBuilder,
15
- ButtonBuilder,
16
- ButtonStyle,
17
- PermissionFlagsBits,
18
- ChannelSelectMenuBuilder,
19
- StringSelectMenuBuilder,
20
- ChannelType
21
  } from "discord.js";
22
 
23
  const client = new Client({
@@ -40,24 +40,28 @@ client.once(Events.ClientReady, async () => {
40
  {
41
  name: "detect",
42
  description: "Otwiera okienko do wklejenia linku lub tekstu do analizy",
43
- type: ApplicationCommandType.ChatInput
44
  },
45
  {
46
  name: "setup",
47
- description: "Ustawienia kanału logów i modeli analizy (Wymaga Administratora)",
48
- default_member_permissions: PermissionFlagsBits.Administrator.toString(),
49
- type: ApplicationCommandType.ChatInput
 
 
50
  },
51
  {
52
  name: "Wykryj deepfake",
53
- type: ApplicationCommandType.Message
54
  },
55
  {
56
  name: "Weryfikacja faktów", // <--- TA LINIA
57
- type: ApplicationCommandType.Message
58
- }
59
  ]);
60
- console.log("Pomyślnie zarejestrowano komendy (/detect, /setup oraz menu kontekstowe)");
 
 
61
  } catch (error) {
62
  console.error("Błąd podczas rejestracji komend:", error);
63
  }
@@ -88,16 +92,19 @@ async function fetchGuildConfig(guildId) {
88
  logChannelId: data.log_channel_id,
89
  models: {
90
  text: data.active_text_model || "none",
91
- image: data.active_image_model || "none"
92
- }
93
  };
94
  }
95
  } catch (err) {
96
- console.error(`[CONFIG ERROR] Błąd pobierania konfiguracji dla gildii ${guildId}:`, err.message);
 
 
 
97
  }
98
  return {
99
  logChannelId: null,
100
- models: {}
101
  };
102
  }
103
 
@@ -106,37 +113,58 @@ function preparePayload(input, explicitContentType = null) {
106
 
107
  if (explicitContentType) {
108
  if (explicitContentType.startsWith("image/")) {
109
- return { type: "image", payload: { image_url: trimmed, content_type: "image" } };
 
 
 
110
  } else if (explicitContentType.startsWith("video/")) {
111
- return { type: "video", payload: { video_url: trimmed, content_type: "video" } };
 
 
 
112
  } else {
113
- return { type: "file", payload: { file_url: trimmed, content_type: "file" } };
 
 
 
114
  }
115
  }
116
 
117
  const isUrl = trimmed.startsWith("http://") || trimmed.startsWith("https://");
118
 
119
  if (isUrl) {
120
- const cleanUrl = trimmed.toLowerCase().split('?')[0];
121
-
122
  if (cleanUrl.match(/\.(png|jpg|jpeg|webp|gif)$/)) {
123
- return { type: "image", payload: { image_url: trimmed, content_type: "image" } };
 
 
 
124
  } else if (cleanUrl.match(/\.(mp4|webm|mov|avi)$/)) {
125
- return { type: "video", payload: { video_url: trimmed, content_type: "video" } };
 
 
 
126
  } else {
127
- return { type: "file", payload: { file_url: trimmed, content_type: "file" } };
 
 
 
128
  }
129
  }
130
 
131
- return {
132
- type: "text",
133
- payload: { text: trimmed, content_type: "text" }
134
  };
135
  }
136
 
137
  function getProgressBar(confidence, isDeepfake) {
138
  const totalBlocks = 10;
139
- const filledBlocks = Math.min(totalBlocks, Math.max(0, Math.round(confidence * totalBlocks)));
 
 
 
140
  const emptyBlocks = totalBlocks - filledBlocks;
141
  const blockEmoji = isDeepfake ? "🟥" : "🟩";
142
  return blockEmoji.repeat(filledBlocks) + "⬛".repeat(emptyBlocks);
@@ -145,25 +173,30 @@ function getProgressBar(confidence, isDeepfake) {
145
  // CAŁKOWICIE DYNAMICZNY GENERATOR WIDOKU SETUPU
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 dla poszczególnych formatów danych.")
 
 
151
  .setTimestamp()
152
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
153
 
154
- embed.addFields({
155
- name: "📂 Kanał logów (Raporty)",
156
- value: tempConfig.logChannelId ? `<#${tempConfig.logChannelId}>` : "*Wysyłanie tylko do konsoli*",
157
- inline: false
 
 
158
  });
159
 
160
  // Dynamicznie dodajemy pola dla każdego formatu zwróconego przez FastAPI
161
  for (const [contentType, models] of Object.entries(availableModels)) {
162
- const currentSelected = tempConfig.models[contentType] || models[0] || "Brak";
 
163
  embed.addFields({
164
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
165
  value: `\`${currentSelected}\``,
166
- inline: true
167
  });
168
  }
169
 
@@ -172,9 +205,7 @@ function generateSetupView(tempConfig, availableModels) {
172
  .setPlaceholder("Wybierz kanał dla raportów")
173
  .addChannelTypes(ChannelType.GuildText);
174
 
175
- const components = [
176
- new ActionRowBuilder().addComponents(channelSelect)
177
- ];
178
 
179
  // Dynamicznie generujemy menu rozwijane dla każdego formatu danych (tekst, obraz, wideo itp.)
180
  for (const [contentType, models] of Object.entries(availableModels)) {
@@ -182,10 +213,10 @@ function generateSetupView(tempConfig, availableModels) {
182
 
183
  const currentSelected = tempConfig.models[contentType] || models[0];
184
 
185
- const selectOptions = models.map(model => ({
186
  label: model,
187
  value: model,
188
- default: currentSelected === model
189
  }));
190
 
191
  const modelSelect = new StringSelectMenuBuilder()
@@ -206,14 +237,14 @@ function generateSetupView(tempConfig, availableModels) {
206
  .setCustomId("setup_cancel")
207
  .setLabel("Anuluj")
208
  .setStyle(ButtonStyle.Danger)
209
- .setEmoji("❌")
210
  );
211
 
212
  components.push(buttonsRow);
213
 
214
  return {
215
  embeds: [embed],
216
- components: components
217
  };
218
  }
219
 
@@ -227,18 +258,98 @@ async function sendLogToDiscord(guild, embedToSend) {
227
  await channel.send({ embeds: [embedToSend] });
228
  }
229
  } catch (err) {
230
- console.warn(`Nie można wysłać logu na kanał ${config.logChannelId}:`, err.message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
  }
233
 
234
- async function handleAnalysis(interaction, userContent, targetMessage = null, explicitContentType = null) {
 
 
 
 
 
 
235
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
236
 
237
  try {
238
  const { type, payload } = preparePayload(userContent, explicitContentType);
239
  payload.guild_id = interaction.guildId;
240
 
241
- console.log(`Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`);
 
 
242
 
243
  const response = await fetch(`${API_URL}/analyze`, {
244
  method: "POST",
@@ -250,13 +361,16 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
250
 
251
  if (!response.ok) {
252
  const errorData = await response.json().catch(() => ({}));
253
- console.error("Szczegóły błędu z FastAPI:", JSON.stringify(errorData, null, 2));
254
-
 
 
 
255
  let errorMsg = `Błąd serwera API (Status ${response.status})`;
256
  if (errorData.detail) {
257
  if (Array.isArray(errorData.detail)) {
258
  errorMsg = errorData.detail
259
- .map(err => `• Pole \`${err.loc.join(".")}\`: ${err.msg}`)
260
  .join("\n");
261
  } else {
262
  errorMsg = errorData.detail;
@@ -270,17 +384,22 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
270
  if (targetMessage) {
271
  try {
272
  if (data.is_deepfake) {
273
- await targetMessage.react('⚠️');
274
  } else {
275
- await targetMessage.react('');
276
  }
277
  } catch (reactError) {
278
- console.warn("Nie udało się dodać reakcji do wiadomości:", reactError.message);
 
 
 
279
  }
280
  }
281
 
282
- const embedColor = data.is_deepfake ? 0xFF0000 : 0x00FF00;
283
- const verdictText = data.is_deepfake ? "⚠️ Wykryto potencjalny Deepfake!" : "✅ Zawartość wydaje się oryginalna";
 
 
284
  const progressBar = getProgressBar(data.confidence, data.is_deepfake);
285
  const confidencePercent = (data.confidence * 100).toFixed(2);
286
 
@@ -289,13 +408,27 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
289
  .setTitle("🛡️ Wynik Analizy Treści")
290
  .setDescription(`**Werdykt:** ${verdictText}`)
291
  .addFields(
292
- { name: "Pewność modelu", value: `\`${confidencePercent}%\` \n${progressBar}` },
293
- { name: "Czas przetwarzania", value: `\`${data.analysis_time.toFixed(3)}s\``, inline: true },
 
 
 
 
 
 
 
294
  { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
295
- { name: "Format danych", value: `\`${data.content_type.toUpperCase()}\``, inline: true }
 
 
 
 
296
  )
297
  .setTimestamp()
298
- .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
 
 
 
299
 
300
  const buttonRow = new ActionRowBuilder().addComponents(
301
  new ButtonBuilder()
@@ -307,14 +440,13 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
307
  .setCustomId("reportError")
308
  .setLabel("Zgłoś błąd analizy")
309
  .setStyle(ButtonStyle.Danger)
310
- .setEmoji("⚠️")
311
  );
312
 
313
  await interaction.editReply({
314
  embeds: [embed],
315
- components: [buttonRow]
316
  });
317
-
318
  } catch (error) {
319
  console.error("Błąd podczas analizy:", error);
320
  await interaction.editReply({
@@ -326,7 +458,6 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
326
  //funkcja kupczaka
327
 
328
  client.on(Events.InteractionCreate, async (interaction) => {
329
-
330
  if (interaction.isChatInputCommand()) {
331
  if (interaction.commandName === "detect") {
332
  const modal = new ModalBuilder()
@@ -348,7 +479,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
348
 
349
  if (interaction.commandName === "setup") {
350
  const guildId = interaction.guildId;
351
-
352
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
353
 
354
  // Pobieramy konfigurację bezpośrednio z FastAPI
@@ -357,7 +488,8 @@ client.on(Events.InteractionCreate, async (interaction) => {
357
 
358
  if (!availableModels || Object.keys(availableModels).length === 0) {
359
  return interaction.editReply({
360
- content: "❌ **Błąd konfiguracji:** Nie udało się nawiązać połączenia z backendem (FastAPI). Uruchom swój backend w Pythonie i spróbuj ponownie!"
 
361
  });
362
  }
363
 
@@ -367,9 +499,9 @@ client.on(Events.InteractionCreate, async (interaction) => {
367
  }
368
  }
369
 
370
- activeSetupSessions.set(guildId, {
371
- config: { ...currentConfig },
372
- availableModels
373
  });
374
 
375
  const setupView = generateSetupView(currentConfig, availableModels);
@@ -383,7 +515,9 @@ client.on(Events.InteractionCreate, async (interaction) => {
383
  const tempSession = activeSetupSessions.get(guildId);
384
  if (tempSession) {
385
  tempSession.config.logChannelId = interaction.values[0];
386
- await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
 
 
387
  }
388
  }
389
  }
@@ -392,13 +526,15 @@ client.on(Events.InteractionCreate, async (interaction) => {
392
  if (interaction.isStringSelectMenu()) {
393
  const guildId = interaction.guildId;
394
  const tempSession = activeSetupSessions.get(guildId);
395
-
396
  if (tempSession) {
397
  // Sprawdzamy czy zmieniany jest model (szukamy przedrostka setup_model_)
398
  if (interaction.customId.startsWith("setup_model_")) {
399
  const contentType = interaction.customId.replace("setup_model_", "");
400
  tempSession.config.models[contentType] = interaction.values[0];
401
- await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
 
 
402
  }
403
  }
404
  }
@@ -406,12 +542,12 @@ client.on(Events.InteractionCreate, async (interaction) => {
406
  if (interaction.isMessageContextMenuCommand()) {
407
  if (interaction.commandName === "Wykryj deepfake") {
408
  const targetMessage = interaction.targetMessage;
409
-
410
  let contentToAnalyze = targetMessage.content;
411
  let explicitContentType = null;
412
 
413
  const attachment = targetMessage.attachments.first();
414
-
415
  if (attachment) {
416
  contentToAnalyze = attachment.url;
417
  explicitContentType = attachment.contentType;
@@ -419,30 +555,36 @@ client.on(Events.InteractionCreate, async (interaction) => {
419
 
420
  if (!contentToAnalyze || contentToAnalyze.trim().length === 0) {
421
  return interaction.reply({
422
- content: "❌ Ta wiadomość nie zawiera tekstu ani załączników do analizy.",
423
- flags: [MessageFlags.Ephemeral]
 
424
  });
425
  }
426
 
427
- await handleAnalysis(interaction, contentToAnalyze, targetMessage, explicitContentType);
 
 
 
 
 
428
  }
429
- if (interaction.commandName === "Weryfikacja faktów") {
430
  const targetMessage = interaction.targetMessage;
431
  const contentToVerify = targetMessage.content;
432
 
433
  if (!contentToVerify || contentToVerify.trim().length < 10) {
434
  return interaction.reply({
435
- content: "❌ Wiadomość musi mieć przynajmniej 10 znaków tekstu, aby można było ją zweryfikować.",
436
- flags: [MessageFlags.Ephemeral]
 
437
  });
438
  }
439
 
440
  await handleFactCheck(interaction, contentToVerify);
441
  }
442
-
443
  }
444
  //koniec funkcji kupczaka
445
-
446
  if (interaction.isModalSubmit()) {
447
  if (interaction.customId === "detectModal") {
448
  const userContent = interaction.fields.getTextInputValue("detectInput");
@@ -460,13 +602,13 @@ client.on(Events.InteractionCreate, async (interaction) => {
460
  const response = await fetch(`${API_URL}/guilds/${guildId}/setup`, {
461
  method: "POST",
462
  headers: {
463
- "Content-Type": "application/json"
464
  },
465
  body: JSON.stringify({
466
  active_text_model: tempSession.config.models?.text || "none",
467
  active_image_model: tempSession.config.models?.image || "none",
468
- log_channel_id: tempSession.config.logChannelId || null
469
- })
470
  });
471
 
472
  if (!response.ok) {
@@ -476,16 +618,17 @@ client.on(Events.InteractionCreate, async (interaction) => {
476
 
477
  activeSetupSessions.delete(guildId);
478
  await interaction.update({
479
- content: "✅ **Ustawienia zostały pomyślnie zapisane na backendzie!**",
 
480
  embeds: [],
481
- components: []
482
  });
483
  } catch (error) {
484
  console.error("[SETUP ERROR]", error);
485
  await interaction.update({
486
  content: `❌ **Wystąpił błąd podczas zapisywania konfiguracji:** ${error.message}`,
487
  embeds: [],
488
- components: []
489
  });
490
  }
491
  }
@@ -496,7 +639,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
496
  await interaction.update({
497
  content: "❌ **Konfiguracja została anulowana.**",
498
  embeds: [],
499
- components: []
500
  });
501
  }
502
 
@@ -513,30 +656,35 @@ client.on(Events.InteractionCreate, async (interaction) => {
513
  .setLabel("Zgłoś błąd analizy")
514
  .setStyle(ButtonStyle.Danger)
515
  .setEmoji("⚠️")
516
- .setDisabled(true)
517
  );
518
 
519
  // 2 UPDATE BUTTONS
520
  await interaction.update({
521
- embeds: [interaction.message.embeds[0]],
522
- components: [disabledRow]
523
  });
524
 
525
  // 3 CONFIRMATION
526
  await interaction.followUp({
527
- content: "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane.",
528
- flags: [MessageFlags.Ephemeral]
 
529
  });
530
 
531
- console.log(`[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błąd klasyfikacji.`);
 
 
532
 
533
  const originalEmbed = interaction.message.embeds[0];
534
  if (originalEmbed) {
535
  const logEmbed = EmbedBuilder.from(originalEmbed)
536
- .setColor(0xFFAA00)
537
  .setTitle("⚠️ Zgłoszenie błędu analizy")
538
- .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) zgłosił błąd analizy w poniższym raporcie.`);
539
-
 
 
540
  await sendLogToDiscord(interaction.guild, logEmbed);
541
  }
542
  }
@@ -555,30 +703,35 @@ client.on(Events.InteractionCreate, async (interaction) => {
555
  .setLabel("Zgłoś błąd analizy")
556
  .setStyle(ButtonStyle.Danger)
557
  .setEmoji("⚠️")
558
- .setDisabled(true)
559
  );
560
 
561
  // 2 DISABLE BUTTONS
562
  await interaction.update({
563
- embeds: [interaction.message.embeds[0]],
564
- components: [disabledRow]
565
  });
566
 
567
  // 3 CONFIRMATION
568
  await interaction.followUp({
569
- content: "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane.",
570
- flags: [MessageFlags.Ephemeral]
 
571
  });
572
 
573
- console.log(`[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację.`);
 
 
574
 
575
  const originalEmbed = interaction.message.embeds[0];
576
  if (originalEmbed) {
577
  const logEmbed = EmbedBuilder.from(originalEmbed)
578
- .setColor(0x00AAFF)
579
  .setTitle("✅ Potwierdzona poprawność analizy")
580
- .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) potwierdził poprawność raportu.`);
581
-
 
 
582
  await sendLogToDiscord(interaction.guild, logEmbed);
583
  }
584
  }
@@ -590,4 +743,4 @@ client.on(Events.MessageCreate, (message) => {
590
  console.log(`Message from ${message.author.tag}: ${message.content}`);
591
  });
592
 
593
- client.login(process.env.DISCORD_TOKEN);
 
1
  import dotenv from "dotenv";
2
  dotenv.config();
3
 
4
+ import {
5
+ Client,
6
+ GatewayIntentBits,
7
+ Events,
8
+ ModalBuilder,
9
+ TextInputBuilder,
10
+ TextInputStyle,
11
+ ActionRowBuilder,
12
+ MessageFlags,
13
+ ApplicationCommandType,
14
+ EmbedBuilder,
15
+ ButtonBuilder,
16
+ ButtonStyle,
17
+ PermissionFlagsBits,
18
+ ChannelSelectMenuBuilder,
19
+ StringSelectMenuBuilder,
20
+ ChannelType,
21
  } from "discord.js";
22
 
23
  const client = new Client({
 
40
  {
41
  name: "detect",
42
  description: "Otwiera okienko do wklejenia linku lub tekstu do analizy",
43
+ type: ApplicationCommandType.ChatInput,
44
  },
45
  {
46
  name: "setup",
47
+ description:
48
+ "Ustawienia kanału logów i modeli analizy (Wymaga Administratora)",
49
+ default_member_permissions:
50
+ PermissionFlagsBits.Administrator.toString(),
51
+ type: ApplicationCommandType.ChatInput,
52
  },
53
  {
54
  name: "Wykryj deepfake",
55
+ type: ApplicationCommandType.Message,
56
  },
57
  {
58
  name: "Weryfikacja faktów", // <--- TA LINIA
59
+ type: ApplicationCommandType.Message,
60
+ },
61
  ]);
62
+ console.log(
63
+ "Pomyślnie zarejestrowano komendy (/detect, /setup oraz menu kontekstowe)",
64
+ );
65
  } catch (error) {
66
  console.error("Błąd podczas rejestracji komend:", error);
67
  }
 
92
  logChannelId: data.log_channel_id,
93
  models: {
94
  text: data.active_text_model || "none",
95
+ image: data.active_image_model || "none",
96
+ },
97
  };
98
  }
99
  } catch (err) {
100
+ console.error(
101
+ `[CONFIG ERROR] Błąd pobierania konfiguracji dla gildii ${guildId}:`,
102
+ err.message,
103
+ );
104
  }
105
  return {
106
  logChannelId: null,
107
+ models: {},
108
  };
109
  }
110
 
 
113
 
114
  if (explicitContentType) {
115
  if (explicitContentType.startsWith("image/")) {
116
+ return {
117
+ type: "image",
118
+ payload: { image_url: trimmed, content_type: "image" },
119
+ };
120
  } else if (explicitContentType.startsWith("video/")) {
121
+ return {
122
+ type: "video",
123
+ payload: { video_url: trimmed, content_type: "video" },
124
+ };
125
  } else {
126
+ return {
127
+ type: "file",
128
+ payload: { file_url: trimmed, content_type: "file" },
129
+ };
130
  }
131
  }
132
 
133
  const isUrl = trimmed.startsWith("http://") || trimmed.startsWith("https://");
134
 
135
  if (isUrl) {
136
+ const cleanUrl = trimmed.toLowerCase().split("?")[0];
137
+
138
  if (cleanUrl.match(/\.(png|jpg|jpeg|webp|gif)$/)) {
139
+ return {
140
+ type: "image",
141
+ payload: { image_url: trimmed, content_type: "image" },
142
+ };
143
  } else if (cleanUrl.match(/\.(mp4|webm|mov|avi)$/)) {
144
+ return {
145
+ type: "video",
146
+ payload: { video_url: trimmed, content_type: "video" },
147
+ };
148
  } else {
149
+ return {
150
+ type: "file",
151
+ payload: { file_url: trimmed, content_type: "file" },
152
+ };
153
  }
154
  }
155
 
156
+ return {
157
+ type: "text",
158
+ payload: { text: trimmed, content_type: "text" },
159
  };
160
  }
161
 
162
  function getProgressBar(confidence, isDeepfake) {
163
  const totalBlocks = 10;
164
+ const filledBlocks = Math.min(
165
+ totalBlocks,
166
+ Math.max(0, Math.round(confidence * totalBlocks)),
167
+ );
168
  const emptyBlocks = totalBlocks - filledBlocks;
169
  const blockEmoji = isDeepfake ? "🟥" : "🟩";
170
  return blockEmoji.repeat(filledBlocks) + "⬛".repeat(emptyBlocks);
 
173
  // CAŁKOWICIE DYNAMICZNY GENERATOR WIDOKU SETUPU
174
  function generateSetupView(tempConfig, availableModels) {
175
  const embed = new EmbedBuilder()
176
+ .setColor(0x5865f2)
177
  .setTitle("⚙️ Konfiguracja Systemu Detekcji")
178
+ .setDescription(
179
+ "Wybierz kanał do wysyłania logów oraz aktywne modele dla poszczególnych formatów danych.",
180
+ )
181
  .setTimestamp()
182
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
183
 
184
+ embed.addFields({
185
+ name: "📂 Kanał logów (Raporty)",
186
+ value: tempConfig.logChannelId
187
+ ? `<#${tempConfig.logChannelId}>`
188
+ : "*Wysyłanie tylko do konsoli*",
189
+ inline: false,
190
  });
191
 
192
  // Dynamicznie dodajemy pola dla każdego formatu zwróconego przez FastAPI
193
  for (const [contentType, models] of Object.entries(availableModels)) {
194
+ const currentSelected =
195
+ tempConfig.models[contentType] || models[0] || "Brak";
196
  embed.addFields({
197
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
198
  value: `\`${currentSelected}\``,
199
+ inline: true,
200
  });
201
  }
202
 
 
205
  .setPlaceholder("Wybierz kanał dla raportów")
206
  .addChannelTypes(ChannelType.GuildText);
207
 
208
+ const components = [new ActionRowBuilder().addComponents(channelSelect)];
 
 
209
 
210
  // Dynamicznie generujemy menu rozwijane dla każdego formatu danych (tekst, obraz, wideo itp.)
211
  for (const [contentType, models] of Object.entries(availableModels)) {
 
213
 
214
  const currentSelected = tempConfig.models[contentType] || models[0];
215
 
216
+ const selectOptions = models.map((model) => ({
217
  label: model,
218
  value: model,
219
+ default: currentSelected === model,
220
  }));
221
 
222
  const modelSelect = new StringSelectMenuBuilder()
 
237
  .setCustomId("setup_cancel")
238
  .setLabel("Anuluj")
239
  .setStyle(ButtonStyle.Danger)
240
+ .setEmoji("❌"),
241
  );
242
 
243
  components.push(buttonsRow);
244
 
245
  return {
246
  embeds: [embed],
247
+ components: components,
248
  };
249
  }
250
 
 
258
  await channel.send({ embeds: [embedToSend] });
259
  }
260
  } catch (err) {
261
+ console.warn(
262
+ `Nie można wysłać logu na kanał ${config.logChannelId}:`,
263
+ err.message,
264
+ );
265
+ }
266
+ }
267
+
268
+ async function handleFactCheck(interaction, statement) {
269
+ await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
270
+
271
+ try {
272
+ console.log(`Wysyłanie zapytania do weryfikacji faktów: "${statement.slice(0, 30)}..."`);
273
+
274
+ const response = await fetch(`${API_URL}/factcheck`, {
275
+ method: "POST",
276
+ headers: {
277
+ "Content-Type": "application/json",
278
+ },
279
+ body: JSON.stringify({ statement: statement }),
280
+ });
281
+
282
+ if (!response.ok) {
283
+ const errorData = await response.json().catch(() => ({}));
284
+ throw new Error(errorData.detail || `Błąd API (Status ${response.status})`);
285
+ }
286
+
287
+ const data = await response.json();
288
+
289
+ let embedColor = 0xFFAA00; // Żółty domyślnie (SPORNE)
290
+ let verdictEmoji = "⚖️";
291
+
292
+ if (data.verdict === "PRAWDA") {
293
+ embedColor = 0x00FF00; // Zielony
294
+ verdictEmoji = "✅";
295
+ } else if (data.verdict === "FAŁSZ") {
296
+ embedColor = 0xFF0000; // Czerwony
297
+ verdictEmoji = "❌";
298
+ }
299
+
300
+ const embed = new EmbedBuilder()
301
+ .setColor(embedColor)
302
+ .setTitle(`${verdictEmoji} Wynik Weryfikacji Faktów`)
303
+ .setDescription(`**Badane stwierdzenie:**\n*"${statement}"*\n\n**Werdykt:** \`${data.verdict}\``)
304
+ .addFields(
305
+ { name: "📝 Analiza merytoryczna", value: data.explanation },
306
+ { name: "🎯 Pewność analizy", value: `\`${(data.confidence * 100).toFixed(0)}%\``, inline: true }
307
+ )
308
+ .setTimestamp()
309
+ .setFooter({ text: "System Fact-checkingowy", iconURL: client.user.displayAvatarURL() });
310
+
311
+ // Formatowanie źródeł
312
+ if (data.sources && data.sources.length > 0) {
313
+ const sourcesText = data.sources
314
+ .map((src, idx) => `**[${idx + 1}]** [${src.title}](${src.url})\n*${src.snippet.slice(0, 150)}...*`)
315
+ .join("\n\n");
316
+
317
+ // Discord ma limit 1024 znaków na jedno pole w Embedzie, zabezpieczamy się przed jego przekroczeniem
318
+ const truncatedSources = sourcesText.length > 1024 ? sourcesText.slice(0, 1000) + "..." : sourcesText;
319
+
320
+ embed.addFields({ name: "🔗 Wykorzystane źródła internetowe", value: truncatedSources });
321
+ } else {
322
+ embed.addFields({ name: "🔗 Wykorzystane źródła internetowe", value: "Brak bezpośrednich źródeł." });
323
+ }
324
+
325
+ await interaction.editReply({
326
+ embeds: [embed]
327
+ });
328
+
329
+ } catch (error) {
330
+ console.error("Błąd podczas weryfikacji faktów:", error);
331
+ await interaction.editReply({
332
+ content: `❌ Nie udało się przeprowadzić weryfikacji faktów.\n\n**Szczegóły błędu:**\n${error.message}`,
333
+ });
334
  }
335
  }
336
 
337
+
338
+ async function handleAnalysis(
339
+ interaction,
340
+ userContent,
341
+ targetMessage = null,
342
+ explicitContentType = null,
343
+ ) {
344
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
345
 
346
  try {
347
  const { type, payload } = preparePayload(userContent, explicitContentType);
348
  payload.guild_id = interaction.guildId;
349
 
350
+ console.log(
351
+ `Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`,
352
+ );
353
 
354
  const response = await fetch(`${API_URL}/analyze`, {
355
  method: "POST",
 
361
 
362
  if (!response.ok) {
363
  const errorData = await response.json().catch(() => ({}));
364
+ console.error(
365
+ "Szczegóły błędu z FastAPI:",
366
+ JSON.stringify(errorData, null, 2),
367
+ );
368
+
369
  let errorMsg = `Błąd serwera API (Status ${response.status})`;
370
  if (errorData.detail) {
371
  if (Array.isArray(errorData.detail)) {
372
  errorMsg = errorData.detail
373
+ .map((err) => `• Pole \`${err.loc.join(".")}\`: ${err.msg}`)
374
  .join("\n");
375
  } else {
376
  errorMsg = errorData.detail;
 
384
  if (targetMessage) {
385
  try {
386
  if (data.is_deepfake) {
387
+ await targetMessage.react("⚠️");
388
  } else {
389
+ await targetMessage.react("");
390
  }
391
  } catch (reactError) {
392
+ console.warn(
393
+ "Nie udało się dodać reakcji do wiadomości:",
394
+ reactError.message,
395
+ );
396
  }
397
  }
398
 
399
+ const embedColor = data.is_deepfake ? 0xff0000 : 0x00ff00;
400
+ const verdictText = data.is_deepfake
401
+ ? "⚠️ Wykryto potencjalny Deepfake!"
402
+ : "✅ Zawartość wydaje się oryginalna";
403
  const progressBar = getProgressBar(data.confidence, data.is_deepfake);
404
  const confidencePercent = (data.confidence * 100).toFixed(2);
405
 
 
408
  .setTitle("🛡️ Wynik Analizy Treści")
409
  .setDescription(`**Werdykt:** ${verdictText}`)
410
  .addFields(
411
+ {
412
+ name: "Pewność modelu",
413
+ value: `\`${confidencePercent}%\` \n${progressBar}`,
414
+ },
415
+ {
416
+ name: "Czas przetwarzania",
417
+ value: `\`${data.analysis_time.toFixed(3)}s\``,
418
+ inline: true,
419
+ },
420
  { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
421
+ {
422
+ name: "Format danych",
423
+ value: `\`${data.content_type.toUpperCase()}\``,
424
+ inline: true,
425
+ },
426
  )
427
  .setTimestamp()
428
+ .setFooter({
429
+ text: "Deepfake Detection Service",
430
+ iconURL: client.user.displayAvatarURL(),
431
+ });
432
 
433
  const buttonRow = new ActionRowBuilder().addComponents(
434
  new ButtonBuilder()
 
440
  .setCustomId("reportError")
441
  .setLabel("Zgłoś błąd analizy")
442
  .setStyle(ButtonStyle.Danger)
443
+ .setEmoji("⚠️"),
444
  );
445
 
446
  await interaction.editReply({
447
  embeds: [embed],
448
+ components: [buttonRow],
449
  });
 
450
  } catch (error) {
451
  console.error("Błąd podczas analizy:", error);
452
  await interaction.editReply({
 
458
  //funkcja kupczaka
459
 
460
  client.on(Events.InteractionCreate, async (interaction) => {
 
461
  if (interaction.isChatInputCommand()) {
462
  if (interaction.commandName === "detect") {
463
  const modal = new ModalBuilder()
 
479
 
480
  if (interaction.commandName === "setup") {
481
  const guildId = interaction.guildId;
482
+
483
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
484
 
485
  // Pobieramy konfigurację bezpośrednio z FastAPI
 
488
 
489
  if (!availableModels || Object.keys(availableModels).length === 0) {
490
  return interaction.editReply({
491
+ content:
492
+ "❌ **Błąd konfiguracji:** Nie udało się nawiązać połączenia z backendem (FastAPI). Uruchom swój backend w Pythonie i spróbuj ponownie!",
493
  });
494
  }
495
 
 
499
  }
500
  }
501
 
502
+ activeSetupSessions.set(guildId, {
503
+ config: { ...currentConfig },
504
+ availableModels,
505
  });
506
 
507
  const setupView = generateSetupView(currentConfig, availableModels);
 
515
  const tempSession = activeSetupSessions.get(guildId);
516
  if (tempSession) {
517
  tempSession.config.logChannelId = interaction.values[0];
518
+ await interaction.update(
519
+ generateSetupView(tempSession.config, tempSession.availableModels),
520
+ );
521
  }
522
  }
523
  }
 
526
  if (interaction.isStringSelectMenu()) {
527
  const guildId = interaction.guildId;
528
  const tempSession = activeSetupSessions.get(guildId);
529
+
530
  if (tempSession) {
531
  // Sprawdzamy czy zmieniany jest model (szukamy przedrostka setup_model_)
532
  if (interaction.customId.startsWith("setup_model_")) {
533
  const contentType = interaction.customId.replace("setup_model_", "");
534
  tempSession.config.models[contentType] = interaction.values[0];
535
+ await interaction.update(
536
+ generateSetupView(tempSession.config, tempSession.availableModels),
537
+ );
538
  }
539
  }
540
  }
 
542
  if (interaction.isMessageContextMenuCommand()) {
543
  if (interaction.commandName === "Wykryj deepfake") {
544
  const targetMessage = interaction.targetMessage;
545
+
546
  let contentToAnalyze = targetMessage.content;
547
  let explicitContentType = null;
548
 
549
  const attachment = targetMessage.attachments.first();
550
+
551
  if (attachment) {
552
  contentToAnalyze = attachment.url;
553
  explicitContentType = attachment.contentType;
 
555
 
556
  if (!contentToAnalyze || contentToAnalyze.trim().length === 0) {
557
  return interaction.reply({
558
+ content:
559
+ "❌ Ta wiadomość nie zawiera tekstu ani załączników do analizy.",
560
+ flags: [MessageFlags.Ephemeral],
561
  });
562
  }
563
 
564
+ await handleAnalysis(
565
+ interaction,
566
+ contentToAnalyze,
567
+ targetMessage,
568
+ explicitContentType,
569
+ );
570
  }
571
+ if (interaction.commandName === "Weryfikacja faktów") {
572
  const targetMessage = interaction.targetMessage;
573
  const contentToVerify = targetMessage.content;
574
 
575
  if (!contentToVerify || contentToVerify.trim().length < 10) {
576
  return interaction.reply({
577
+ content:
578
+ "❌ Wiadomość musi mieć przynajmniej 10 znaków tekstu, aby można było ją zweryfikować.",
579
+ flags: [MessageFlags.Ephemeral],
580
  });
581
  }
582
 
583
  await handleFactCheck(interaction, contentToVerify);
584
  }
 
585
  }
586
  //koniec funkcji kupczaka
587
+
588
  if (interaction.isModalSubmit()) {
589
  if (interaction.customId === "detectModal") {
590
  const userContent = interaction.fields.getTextInputValue("detectInput");
 
602
  const response = await fetch(`${API_URL}/guilds/${guildId}/setup`, {
603
  method: "POST",
604
  headers: {
605
+ "Content-Type": "application/json",
606
  },
607
  body: JSON.stringify({
608
  active_text_model: tempSession.config.models?.text || "none",
609
  active_image_model: tempSession.config.models?.image || "none",
610
+ log_channel_id: tempSession.config.logChannelId || null,
611
+ }),
612
  });
613
 
614
  if (!response.ok) {
 
618
 
619
  activeSetupSessions.delete(guildId);
620
  await interaction.update({
621
+ content:
622
+ "✅ **Ustawienia zostały pomyślnie zapisane na backendzie!**",
623
  embeds: [],
624
+ components: [],
625
  });
626
  } catch (error) {
627
  console.error("[SETUP ERROR]", error);
628
  await interaction.update({
629
  content: `❌ **Wystąpił błąd podczas zapisywania konfiguracji:** ${error.message}`,
630
  embeds: [],
631
+ components: [],
632
  });
633
  }
634
  }
 
639
  await interaction.update({
640
  content: "❌ **Konfiguracja została anulowana.**",
641
  embeds: [],
642
+ components: [],
643
  });
644
  }
645
 
 
656
  .setLabel("Zgłoś błąd analizy")
657
  .setStyle(ButtonStyle.Danger)
658
  .setEmoji("⚠️")
659
+ .setDisabled(true),
660
  );
661
 
662
  // 2 UPDATE BUTTONS
663
  await interaction.update({
664
+ embeds: [interaction.message.embeds[0]],
665
+ components: [disabledRow],
666
  });
667
 
668
  // 3 CONFIRMATION
669
  await interaction.followUp({
670
+ content:
671
+ "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane.",
672
+ flags: [MessageFlags.Ephemeral],
673
  });
674
 
675
+ console.log(
676
+ `[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błąd klasyfikacji.`,
677
+ );
678
 
679
  const originalEmbed = interaction.message.embeds[0];
680
  if (originalEmbed) {
681
  const logEmbed = EmbedBuilder.from(originalEmbed)
682
+ .setColor(0xffaa00)
683
  .setTitle("⚠️ Zgłoszenie błędu analizy")
684
+ .setDescription(
685
+ `Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) zgłosił błąd analizy w poniższym raporcie.`,
686
+ );
687
+
688
  await sendLogToDiscord(interaction.guild, logEmbed);
689
  }
690
  }
 
703
  .setLabel("Zgłoś błąd analizy")
704
  .setStyle(ButtonStyle.Danger)
705
  .setEmoji("⚠️")
706
+ .setDisabled(true),
707
  );
708
 
709
  // 2 DISABLE BUTTONS
710
  await interaction.update({
711
+ embeds: [interaction.message.embeds[0]],
712
+ components: [disabledRow],
713
  });
714
 
715
  // 3 CONFIRMATION
716
  await interaction.followUp({
717
+ content:
718
+ "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane.",
719
+ flags: [MessageFlags.Ephemeral],
720
  });
721
 
722
+ console.log(
723
+ `[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację.`,
724
+ );
725
 
726
  const originalEmbed = interaction.message.embeds[0];
727
  if (originalEmbed) {
728
  const logEmbed = EmbedBuilder.from(originalEmbed)
729
+ .setColor(0x00aaff)
730
  .setTitle("✅ Potwierdzona poprawność analizy")
731
+ .setDescription(
732
+ `Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) potwierdził poprawność raportu.`,
733
+ );
734
+
735
  await sendLogToDiscord(interaction.guild, logEmbed);
736
  }
737
  }
 
743
  console.log(`Message from ${message.author.tag}: ${message.content}`);
744
  });
745
 
746
+ client.login(process.env.DISCORD_TOKEN);