Tobiasz Kubiak commited on
Commit
7b59e80
·
unverified ·
2 Parent(s): fda4b67a6c6e76

Merge pull request #15 from Tobkubos/hujjjjjj

Browse files
backend/app/api/factcheck_router.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from app.models.factcheck_schemas import FactCheckRequest, FactCheckResponse, FactCheckSource
3
+ from app.services.factcheck_service import analyze_with_gemini_grounding
4
+
5
+ router = APIRouter()
6
+
7
+ @router.post(
8
+ "/factcheck",
9
+ response_model=FactCheckResponse,
10
+ tags=["Fact-checking"],
11
+ summary="Zweryfikuj prawdziwość stwierdzenia"
12
+ )
13
+ async def fact_check_endpoint(payload: FactCheckRequest):
14
+ statement = payload.statement.strip()
15
+ if len(statement) < 10:
16
+ raise HTTPException(status_code=400, detail="Tekst do weryfikacji musi mieć co najmniej 10 znaków.")
17
+
18
+ # Wywołujemy usługę integrującą Google Search i Gemini
19
+ analysis = await analyze_with_gemini_grounding(statement)
20
+
21
+ # Konwersja słowników na obiekty Pydantic
22
+ formatted_sources = []
23
+ for s in analysis.get("sources", []):
24
+ formatted_sources.append(FactCheckSource(
25
+ title=s["title"],
26
+ url=s["url"],
27
+ snippet=s["snippet"]
28
+ ))
29
+
30
+ return FactCheckResponse(
31
+ verdict=analysis.get("verdict", "SPORNE"),
32
+ explanation=analysis.get("explanation", "Brak szczegółowego uzasadnienia."),
33
+ confidence=analysis.get("confidence", 0.5),
34
+ sources=formatted_sources
35
+ )
backend/app/api/routes.py CHANGED
@@ -222,4 +222,7 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
222
  analysis_time=analysis_result["analysis_time"],
223
  used_model=used_model,
224
  content_type=content_type,
225
- )
 
 
 
 
222
  analysis_time=analysis_result["analysis_time"],
223
  used_model=used_model,
224
  content_type=content_type,
225
+ )
226
+
227
+ from app.api.factcheck_router import router as factcheck_router
228
+ router.include_router(factcheck_router) #kupczak tu był
backend/app/models/factcheck_schemas.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List
3
+
4
+ class FactCheckRequest(BaseModel):
5
+ statement: str
6
+
7
+ class FactCheckSource(BaseModel):
8
+ title: str
9
+ url: str
10
+ snippet: str
11
+
12
+ class FactCheckResponse(BaseModel):
13
+ verdict: str # "PRAWDA", "FAŁSZ", "SPORNE"
14
+ explanation: str
15
+ confidence: float
16
+ sources: List[FactCheckSource]
backend/app/services/factcheck_service.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import json
3
+ import re
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List
7
+ import google.generativeai as genai
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ def load_env_fallback():
12
+ """
13
+ Ręcznie wczytuje plik .env do os.environ z pełną diagnostyką w konsoli.
14
+ """
15
+ if os.getenv("GEMINI_API_KEY"):
16
+ return
17
+
18
+ possible_paths = [
19
+ Path(".env"), # Bieżący folder roboczy
20
+ Path("backend/.env"), # Folder backend
21
+ Path(__file__).resolve().parent.parent.parent / ".env" # Ścieżka relatywna do serwisu
22
+ ]
23
+
24
+ print(f"\n🔍 [DIAGNOSTYKA .ENV] Bieżący katalog roboczy (CWD): {os.getcwd()}")
25
+
26
+ found_any = False
27
+ for path_obj in possible_paths:
28
+ resolved_path = path_obj.resolve()
29
+ exists = resolved_path.exists()
30
+ print(f"👉 Sprawdzam ścieżkę: {resolved_path} -> [Znaleziono: {'TAK' if exists else 'NIE'}]")
31
+
32
+ if exists:
33
+ found_any = True
34
+ print(f"📖 Próba wczytania pliku: {resolved_path}")
35
+ try:
36
+ loaded_keys = []
37
+ with open(resolved_path, "r", encoding="utf-8") as f:
38
+ for line in f:
39
+ line = line.strip()
40
+ if line and not line.startswith("#") and "=" in line:
41
+ key, val = line.split("=", 1)
42
+ k_clean = key.strip()
43
+ v_clean = val.strip().strip("'\"")
44
+ os.environ[k_clean] = v_clean
45
+ loaded_keys.append(k_clean)
46
+ print(f"✅ Pomyślnie wczytano klucze z pliku: {loaded_keys}")
47
+ break
48
+ except Exception as e:
49
+ print(f"❌ Błąd odczytu pliku .env: {e}")
50
+
51
+ if not found_any:
52
+ print("❌ Nie znaleziono pliku .env w żadnej z badanych lokalizacji!")
53
+
54
+ final_key = os.getenv("GEMINI_API_KEY")
55
+ print(f"🔑 Status GEMINI_API_KEY: {'ZNAJDZIONO (zaczyna się od: ' + final_key[:6] + '...)' if final_key else 'NIE ZNAJDZIONO!'}\n")
56
+
57
+ # Uruchamiamy wczytywanie środowiska przy imporcie tego serwisu
58
+ load_env_fallback()
59
+
60
+
61
+ async def analyze_with_gemini_grounding(statement: str) -> Dict[str, Any]:
62
+ """
63
+ Analizuje stwierdzenie, automatycznie przeszukując internet za pomocą
64
+ wbudowanego w Gemini narzędzia Google Search Grounding.
65
+ """
66
+ # Upewniamy się, że środowisko jest załadowane
67
+ load_env_fallback()
68
+
69
+ api_key = os.getenv("GEMINI_API_KEY")
70
+
71
+ if not api_key:
72
+ logger.error("Brak klucza GEMINI_API_KEY w środowisku systemowym!")
73
+ return {
74
+ "verdict": "SPORNE",
75
+ "explanation": "Błąd backendu: Brak skonfigurowanego klucza GEMINI_API_KEY w pliku .env.",
76
+ "confidence": 0.0,
77
+ "sources": []
78
+ }
79
+
80
+ genai.configure(api_key=api_key)
81
+
82
+ prompt = f"""Jesteś zaawansowanym asystentem do weryfikacji faktów (fact-checking).
83
+ Przeanalizuj poniższe stwierdzenie, korzystając z wyszukiwarki Google (masz do niej dostęp jako narzędzie), aby zweryfikować jego prawdziwość w czasie rzeczywistym.
84
+
85
+ STWIERDZENIE DO WERYFIKACJI:
86
+ "{statement}"
87
+
88
+ Twoja odpowiedź musi być wyłącznie poprawnym obiektem JSON (bez bloków kodu typu ```json, bez dodatkowego tekstu na początku ani na końcu).
89
+ Format JSON:
90
+ {{
91
+ "verdict": "PRAWDA" lub "FAŁSZ" lub "SPORNE",
92
+ "explanation": "Zwięzłe (2-4 zdania), merytoryczne i obiektywne uzasadnienie werdyktu w języku polskim, wyjaśniające co mówią fakty."
93
+ }}
94
+
95
+ Wskazówki do werdyktu:
96
+ - "PRAWDA": Najnowsze fakty i wiarygodne źródła w pełni potwierdzają to stwierdzenie.
97
+ - "FAŁSZ": Fakty jednoznacznie zaprzeczają temu stwierdzeniu.
98
+ - "SPORNE": Informacje w sieci są sprzeczne, jest to kwestia opinii lub brak jednoznacznych dowodów.
99
+ """
100
+
101
+ try:
102
+ model = genai.GenerativeModel(
103
+ model_name="gemini-2.5-flash",
104
+ tools=[
105
+ genai.protos.Tool(
106
+ google_search=genai.protos.Tool.GoogleSearch()
107
+ )
108
+ ]
109
+ )
110
+
111
+ response = model.generate_content(
112
+ prompt,
113
+ generation_config=genai.types.GenerationConfig(
114
+ temperature=0.0
115
+ )
116
+ )
117
+
118
+ raw_text = response.text.strip()
119
+ logger.info(f"Surowa odpowiedź Gemini: {raw_text}")
120
+
121
+ if raw_text.startswith("```"):
122
+ match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw_text, re.DOTALL)
123
+ if match:
124
+ raw_text = match.group(1)
125
+
126
+ result_json = json.loads(raw_text)
127
+
128
+ sources = []
129
+ candidate = response.candidates[0]
130
+ metadata = getattr(candidate, "grounding_metadata", None)
131
+
132
+ if metadata and getattr(metadata, "grounding_chunks", None):
133
+ for chunk in metadata.grounding_chunks:
134
+ if chunk.web:
135
+ sources.append({
136
+ "title": chunk.web.title,
137
+ "url": chunk.web.uri,
138
+ "snippet": "Źródło zweryfikowane bezpośrednio przez wyszukiwarkę Google."
139
+ })
140
+
141
+ return {
142
+ "verdict": result_json.get("verdict", "SPORNE"),
143
+ "explanation": result_json.get("explanation", "Brak uzasadnienia."),
144
+ "confidence": 0.95 if result_json.get("verdict") in ["PRAWDA", "FAŁSZ"] else 0.5,
145
+ "sources": sources
146
+ }
147
+
148
+ except Exception as e:
149
+ logger.error(f"Błąd analizy Gemini Grounding API: {e}", exc_info=True)
150
+ return {
151
+ "verdict": "SPORNE",
152
+ "explanation": f"Wystąpił błąd komunikacji z modelem językowym: {str(e)}",
153
+ "confidence": 0.0,
154
+ "sources": []
155
+ }
backend/requirements.txt CHANGED
@@ -13,4 +13,6 @@ Pillow
13
  slowapi
14
  pytest==7.4.3
15
  pytest-asyncio==0.21.1
16
- redis
 
 
 
13
  slowapi
14
  pytest==7.4.3
15
  pytest-asyncio==0.21.1
16
+ duckduckgo-search>=6.0.0
17
+ google-generativeai>=0.8.0
18
+ redis
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,20 +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
- console.log("Pomyślnie zarejestrowano komendy (/detect, /setup oraz menu kontekstowe)");
 
 
57
  } catch (error) {
58
  console.error("Błąd podczas rejestracji komend:", error);
59
  }
@@ -84,16 +92,19 @@ async function fetchGuildConfig(guildId) {
84
  logChannelId: data.log_channel_id,
85
  models: {
86
  text: data.active_text_model || "none",
87
- image: data.active_image_model || "none"
88
- }
89
  };
90
  }
91
  } catch (err) {
92
- console.error(`[CONFIG ERROR] Błąd pobierania konfiguracji dla gildii ${guildId}:`, err.message);
 
 
 
93
  }
94
  return {
95
  logChannelId: null,
96
- models: {}
97
  };
98
  }
99
 
@@ -102,37 +113,58 @@ function preparePayload(input, explicitContentType = null) {
102
 
103
  if (explicitContentType) {
104
  if (explicitContentType.startsWith("image/")) {
105
- return { type: "image", payload: { image_url: trimmed, content_type: "image" } };
 
 
 
106
  } else if (explicitContentType.startsWith("video/")) {
107
- return { type: "video", payload: { video_url: trimmed, content_type: "video" } };
 
 
 
108
  } else {
109
- return { type: "file", payload: { file_url: trimmed, content_type: "file" } };
 
 
 
110
  }
111
  }
112
 
113
  const isUrl = trimmed.startsWith("http://") || trimmed.startsWith("https://");
114
 
115
  if (isUrl) {
116
- const cleanUrl = trimmed.toLowerCase().split('?')[0];
117
-
118
  if (cleanUrl.match(/\.(png|jpg|jpeg|webp|gif)$/)) {
119
- return { type: "image", payload: { image_url: trimmed, content_type: "image" } };
 
 
 
120
  } else if (cleanUrl.match(/\.(mp4|webm|mov|avi)$/)) {
121
- return { type: "video", payload: { video_url: trimmed, content_type: "video" } };
 
 
 
122
  } else {
123
- return { type: "file", payload: { file_url: trimmed, content_type: "file" } };
 
 
 
124
  }
125
  }
126
 
127
- return {
128
- type: "text",
129
- payload: { text: trimmed, content_type: "text" }
130
  };
131
  }
132
 
133
  function getProgressBar(confidence, isDeepfake) {
134
  const totalBlocks = 10;
135
- const filledBlocks = Math.min(totalBlocks, Math.max(0, Math.round(confidence * totalBlocks)));
 
 
 
136
  const emptyBlocks = totalBlocks - filledBlocks;
137
  const blockEmoji = isDeepfake ? "🟥" : "🟩";
138
  return blockEmoji.repeat(filledBlocks) + "⬛".repeat(emptyBlocks);
@@ -141,25 +173,30 @@ function getProgressBar(confidence, isDeepfake) {
141
  // CAŁKOWICIE DYNAMICZNY GENERATOR WIDOKU SETUPU
142
  function generateSetupView(tempConfig, availableModels) {
143
  const embed = new EmbedBuilder()
144
- .setColor(0x5865F2)
145
  .setTitle("⚙️ Konfiguracja Systemu Detekcji")
146
- .setDescription("Wybierz kanał do wysyłania logów oraz aktywne modele dla poszczególnych formatów danych.")
 
 
147
  .setTimestamp()
148
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
149
 
150
- embed.addFields({
151
- name: "📂 Kanał logów (Raporty)",
152
- value: tempConfig.logChannelId ? `<#${tempConfig.logChannelId}>` : "*Wysyłanie tylko do konsoli*",
153
- inline: false
 
 
154
  });
155
 
156
  // Dynamicznie dodajemy pola dla każdego formatu zwróconego przez FastAPI
157
  for (const [contentType, models] of Object.entries(availableModels)) {
158
- const currentSelected = tempConfig.models[contentType] || models[0] || "Brak";
 
159
  embed.addFields({
160
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
161
  value: `\`${currentSelected}\``,
162
- inline: true
163
  });
164
  }
165
 
@@ -168,9 +205,7 @@ function generateSetupView(tempConfig, availableModels) {
168
  .setPlaceholder("Wybierz kanał dla raportów")
169
  .addChannelTypes(ChannelType.GuildText);
170
 
171
- const components = [
172
- new ActionRowBuilder().addComponents(channelSelect)
173
- ];
174
 
175
  // Dynamicznie generujemy menu rozwijane dla każdego formatu danych (tekst, obraz, wideo itp.)
176
  for (const [contentType, models] of Object.entries(availableModels)) {
@@ -178,10 +213,10 @@ function generateSetupView(tempConfig, availableModels) {
178
 
179
  const currentSelected = tempConfig.models[contentType] || models[0];
180
 
181
- const selectOptions = models.map(model => ({
182
  label: model,
183
  value: model,
184
- default: currentSelected === model
185
  }));
186
 
187
  const modelSelect = new StringSelectMenuBuilder()
@@ -202,14 +237,14 @@ function generateSetupView(tempConfig, availableModels) {
202
  .setCustomId("setup_cancel")
203
  .setLabel("Anuluj")
204
  .setStyle(ButtonStyle.Danger)
205
- .setEmoji("❌")
206
  );
207
 
208
  components.push(buttonsRow);
209
 
210
  return {
211
  embeds: [embed],
212
- components: components
213
  };
214
  }
215
 
@@ -223,11 +258,89 @@ async function sendLogToDiscord(guild, embedToSend) {
223
  await channel.send({ embeds: [embedToSend] });
224
  }
225
  } catch (err) {
226
- console.warn(`Nie można wysłać logu na kanał ${config.logChannelId}:`, err.message);
 
 
 
227
  }
228
  }
229
 
230
- async function handleAnalysis(interaction, userContent, targetMessage = null, explicitContentType = null) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
232
 
233
  try {
@@ -235,7 +348,9 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
235
  payload.guild_id = interaction.guildId;
236
  payload.user_id = interaction.user.id;
237
 
238
- console.log(`Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`);
 
 
239
 
240
  const response = await fetch(`${API_URL}/analyze`, {
241
  method: "POST",
@@ -247,13 +362,16 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
247
 
248
  if (!response.ok) {
249
  const errorData = await response.json().catch(() => ({}));
250
- console.error("Szczegóły błędu z FastAPI:", JSON.stringify(errorData, null, 2));
251
-
 
 
 
252
  let errorMsg = `Błąd serwera API (Status ${response.status})`;
253
  if (errorData.detail) {
254
  if (Array.isArray(errorData.detail)) {
255
  errorMsg = errorData.detail
256
- .map(err => `• Pole \`${err.loc.join(".")}\`: ${err.msg}`)
257
  .join("\n");
258
  } else {
259
  errorMsg = errorData.detail;
@@ -267,17 +385,22 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
267
  if (targetMessage) {
268
  try {
269
  if (data.is_deepfake) {
270
- await targetMessage.react('⚠️');
271
  } else {
272
- await targetMessage.react('');
273
  }
274
  } catch (reactError) {
275
- console.warn("Nie udało się dodać reakcji do wiadomości:", reactError.message);
 
 
 
276
  }
277
  }
278
 
279
- const embedColor = data.is_deepfake ? 0xFF0000 : 0x00FF00;
280
- const verdictText = data.is_deepfake ? "⚠️ Wykryto potencjalny Deepfake!" : "✅ Zawartość wydaje się oryginalna";
 
 
281
  const progressBar = getProgressBar(data.confidence, data.is_deepfake);
282
  const confidencePercent = (data.confidence * 100).toFixed(2);
283
 
@@ -286,13 +409,27 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
286
  .setTitle("🛡️ Wynik Analizy Treści")
287
  .setDescription(`**Werdykt:** ${verdictText}`)
288
  .addFields(
289
- { name: "Pewność modelu", value: `\`${confidencePercent}%\` \n${progressBar}` },
290
- { name: "Czas przetwarzania", value: `\`${data.analysis_time.toFixed(3)}s\``, inline: true },
 
 
 
 
 
 
 
291
  { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
292
- { name: "Format danych", value: `\`${data.content_type.toUpperCase()}\``, inline: true }
 
 
 
 
293
  )
294
  .setTimestamp()
295
- .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
 
 
 
296
 
297
  const buttonRow = new ActionRowBuilder().addComponents(
298
  new ButtonBuilder()
@@ -304,14 +441,13 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
304
  .setCustomId("reportError")
305
  .setLabel("Zgłoś błąd analizy")
306
  .setStyle(ButtonStyle.Danger)
307
- .setEmoji("⚠️")
308
  );
309
 
310
  await interaction.editReply({
311
  embeds: [embed],
312
- components: [buttonRow]
313
  });
314
-
315
  } catch (error) {
316
  console.error("Błąd podczas analizy:", error);
317
  await interaction.editReply({
@@ -320,8 +456,9 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
320
  }
321
  }
322
 
 
 
323
  client.on(Events.InteractionCreate, async (interaction) => {
324
-
325
  if (interaction.isChatInputCommand()) {
326
  if (interaction.commandName === "detect") {
327
  const modal = new ModalBuilder()
@@ -343,7 +480,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
343
 
344
  if (interaction.commandName === "setup") {
345
  const guildId = interaction.guildId;
346
-
347
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
348
 
349
  // Pobieramy konfigurację bezpośrednio z FastAPI
@@ -352,7 +489,8 @@ client.on(Events.InteractionCreate, async (interaction) => {
352
 
353
  if (!availableModels || Object.keys(availableModels).length === 0) {
354
  return interaction.editReply({
355
- 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!"
 
356
  });
357
  }
358
 
@@ -362,9 +500,9 @@ client.on(Events.InteractionCreate, async (interaction) => {
362
  }
363
  }
364
 
365
- activeSetupSessions.set(guildId, {
366
- config: { ...currentConfig },
367
- availableModels
368
  });
369
 
370
  const setupView = generateSetupView(currentConfig, availableModels);
@@ -378,7 +516,9 @@ client.on(Events.InteractionCreate, async (interaction) => {
378
  const tempSession = activeSetupSessions.get(guildId);
379
  if (tempSession) {
380
  tempSession.config.logChannelId = interaction.values[0];
381
- await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
 
 
382
  }
383
  }
384
  }
@@ -387,13 +527,15 @@ client.on(Events.InteractionCreate, async (interaction) => {
387
  if (interaction.isStringSelectMenu()) {
388
  const guildId = interaction.guildId;
389
  const tempSession = activeSetupSessions.get(guildId);
390
-
391
  if (tempSession) {
392
  // Sprawdzamy czy zmieniany jest model (szukamy przedrostka setup_model_)
393
  if (interaction.customId.startsWith("setup_model_")) {
394
  const contentType = interaction.customId.replace("setup_model_", "");
395
  tempSession.config.models[contentType] = interaction.values[0];
396
- await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
 
 
397
  }
398
  }
399
  }
@@ -401,12 +543,12 @@ client.on(Events.InteractionCreate, async (interaction) => {
401
  if (interaction.isMessageContextMenuCommand()) {
402
  if (interaction.commandName === "Wykryj deepfake") {
403
  const targetMessage = interaction.targetMessage;
404
-
405
  let contentToAnalyze = targetMessage.content;
406
  let explicitContentType = null;
407
 
408
  const attachment = targetMessage.attachments.first();
409
-
410
  if (attachment) {
411
  contentToAnalyze = attachment.url;
412
  explicitContentType = attachment.contentType;
@@ -414,14 +556,35 @@ client.on(Events.InteractionCreate, async (interaction) => {
414
 
415
  if (!contentToAnalyze || contentToAnalyze.trim().length === 0) {
416
  return interaction.reply({
417
- content: "❌ Ta wiadomość nie zawiera tekstu ani załączników do analizy.",
418
- flags: [MessageFlags.Ephemeral]
 
419
  });
420
  }
421
 
422
- await handleAnalysis(interaction, contentToAnalyze, targetMessage, explicitContentType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  }
424
  }
 
425
 
426
  if (interaction.isModalSubmit()) {
427
  if (interaction.customId === "detectModal") {
@@ -440,13 +603,13 @@ client.on(Events.InteractionCreate, async (interaction) => {
440
  const response = await fetch(`${API_URL}/guilds/${guildId}/setup`, {
441
  method: "POST",
442
  headers: {
443
- "Content-Type": "application/json"
444
  },
445
  body: JSON.stringify({
446
  active_text_model: tempSession.config.models?.text || "none",
447
  active_image_model: tempSession.config.models?.image || "none",
448
- log_channel_id: tempSession.config.logChannelId || null
449
- })
450
  });
451
 
452
  if (!response.ok) {
@@ -456,16 +619,17 @@ client.on(Events.InteractionCreate, async (interaction) => {
456
 
457
  activeSetupSessions.delete(guildId);
458
  await interaction.update({
459
- content: "✅ **Ustawienia zostały pomyślnie zapisane na backendzie!**",
 
460
  embeds: [],
461
- components: []
462
  });
463
  } catch (error) {
464
  console.error("[SETUP ERROR]", error);
465
  await interaction.update({
466
  content: `❌ **Wystąpił błąd podczas zapisywania konfiguracji:** ${error.message}`,
467
  embeds: [],
468
- components: []
469
  });
470
  }
471
  }
@@ -476,7 +640,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
476
  await interaction.update({
477
  content: "❌ **Konfiguracja została anulowana.**",
478
  embeds: [],
479
- components: []
480
  });
481
  }
482
 
@@ -493,30 +657,35 @@ client.on(Events.InteractionCreate, async (interaction) => {
493
  .setLabel("Zgłoś błąd analizy")
494
  .setStyle(ButtonStyle.Danger)
495
  .setEmoji("⚠️")
496
- .setDisabled(true)
497
  );
498
 
499
  // 2 UPDATE BUTTONS
500
  await interaction.update({
501
- embeds: [interaction.message.embeds[0]],
502
- components: [disabledRow]
503
  });
504
 
505
  // 3 CONFIRMATION
506
  await interaction.followUp({
507
- content: "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane.",
508
- flags: [MessageFlags.Ephemeral]
 
509
  });
510
 
511
- console.log(`[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błąd klasyfikacji.`);
 
 
512
 
513
  const originalEmbed = interaction.message.embeds[0];
514
  if (originalEmbed) {
515
  const logEmbed = EmbedBuilder.from(originalEmbed)
516
- .setColor(0xFFAA00)
517
  .setTitle("⚠️ Zgłoszenie błędu analizy")
518
- .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) zgłosił błąd analizy w poniższym raporcie.`);
519
-
 
 
520
  await sendLogToDiscord(interaction.guild, logEmbed);
521
  }
522
  }
@@ -535,30 +704,35 @@ client.on(Events.InteractionCreate, async (interaction) => {
535
  .setLabel("Zgłoś błąd analizy")
536
  .setStyle(ButtonStyle.Danger)
537
  .setEmoji("⚠️")
538
- .setDisabled(true)
539
  );
540
 
541
  // 2 DISABLE BUTTONS
542
  await interaction.update({
543
- embeds: [interaction.message.embeds[0]],
544
- components: [disabledRow]
545
  });
546
 
547
  // 3 CONFIRMATION
548
  await interaction.followUp({
549
- content: "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane.",
550
- flags: [MessageFlags.Ephemeral]
 
551
  });
552
 
553
- console.log(`[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację.`);
 
 
554
 
555
  const originalEmbed = interaction.message.embeds[0];
556
  if (originalEmbed) {
557
  const logEmbed = EmbedBuilder.from(originalEmbed)
558
- .setColor(0x00AAFF)
559
  .setTitle("✅ Potwierdzona poprawność analizy")
560
- .setDescription(`Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) potwierdził poprawność raportu.`);
561
-
 
 
562
  await sendLogToDiscord(interaction.guild, logEmbed);
563
  }
564
  }
@@ -570,4 +744,4 @@ client.on(Events.MessageCreate, (message) => {
570
  console.log(`Message from ${message.author.tag}: ${message.content}`);
571
  });
572
 
573
- 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 {
 
348
  payload.guild_id = interaction.guildId;
349
  payload.user_id = interaction.user.id;
350
 
351
+ console.log(
352
+ `Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`,
353
+ );
354
 
355
  const response = await fetch(`${API_URL}/analyze`, {
356
  method: "POST",
 
362
 
363
  if (!response.ok) {
364
  const errorData = await response.json().catch(() => ({}));
365
+ console.error(
366
+ "Szczegóły błędu z FastAPI:",
367
+ JSON.stringify(errorData, null, 2),
368
+ );
369
+
370
  let errorMsg = `Błąd serwera API (Status ${response.status})`;
371
  if (errorData.detail) {
372
  if (Array.isArray(errorData.detail)) {
373
  errorMsg = errorData.detail
374
+ .map((err) => `• Pole \`${err.loc.join(".")}\`: ${err.msg}`)
375
  .join("\n");
376
  } else {
377
  errorMsg = errorData.detail;
 
385
  if (targetMessage) {
386
  try {
387
  if (data.is_deepfake) {
388
+ await targetMessage.react("⚠️");
389
  } else {
390
+ await targetMessage.react("");
391
  }
392
  } catch (reactError) {
393
+ console.warn(
394
+ "Nie udało się dodać reakcji do wiadomości:",
395
+ reactError.message,
396
+ );
397
  }
398
  }
399
 
400
+ const embedColor = data.is_deepfake ? 0xff0000 : 0x00ff00;
401
+ const verdictText = data.is_deepfake
402
+ ? "⚠️ Wykryto potencjalny Deepfake!"
403
+ : "✅ Zawartość wydaje się oryginalna";
404
  const progressBar = getProgressBar(data.confidence, data.is_deepfake);
405
  const confidencePercent = (data.confidence * 100).toFixed(2);
406
 
 
409
  .setTitle("🛡️ Wynik Analizy Treści")
410
  .setDescription(`**Werdykt:** ${verdictText}`)
411
  .addFields(
412
+ {
413
+ name: "Pewność modelu",
414
+ value: `\`${confidencePercent}%\` \n${progressBar}`,
415
+ },
416
+ {
417
+ name: "Czas przetwarzania",
418
+ value: `\`${data.analysis_time.toFixed(3)}s\``,
419
+ inline: true,
420
+ },
421
  { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
422
+ {
423
+ name: "Format danych",
424
+ value: `\`${data.content_type.toUpperCase()}\``,
425
+ inline: true,
426
+ },
427
  )
428
  .setTimestamp()
429
+ .setFooter({
430
+ text: "Deepfake Detection Service",
431
+ iconURL: client.user.displayAvatarURL(),
432
+ });
433
 
434
  const buttonRow = new ActionRowBuilder().addComponents(
435
  new ButtonBuilder()
 
441
  .setCustomId("reportError")
442
  .setLabel("Zgłoś błąd analizy")
443
  .setStyle(ButtonStyle.Danger)
444
+ .setEmoji("⚠️"),
445
  );
446
 
447
  await interaction.editReply({
448
  embeds: [embed],
449
+ components: [buttonRow],
450
  });
 
451
  } catch (error) {
452
  console.error("Błąd podczas analizy:", error);
453
  await interaction.editReply({
 
456
  }
457
  }
458
 
459
+ //funkcja kupczaka
460
+
461
  client.on(Events.InteractionCreate, async (interaction) => {
 
462
  if (interaction.isChatInputCommand()) {
463
  if (interaction.commandName === "detect") {
464
  const modal = new ModalBuilder()
 
480
 
481
  if (interaction.commandName === "setup") {
482
  const guildId = interaction.guildId;
483
+
484
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
485
 
486
  // Pobieramy konfigurację bezpośrednio z FastAPI
 
489
 
490
  if (!availableModels || Object.keys(availableModels).length === 0) {
491
  return interaction.editReply({
492
+ content:
493
+ "❌ **Błąd konfiguracji:** Nie udało się nawiązać połączenia z backendem (FastAPI). Uruchom swój backend w Pythonie i spróbuj ponownie!",
494
  });
495
  }
496
 
 
500
  }
501
  }
502
 
503
+ activeSetupSessions.set(guildId, {
504
+ config: { ...currentConfig },
505
+ availableModels,
506
  });
507
 
508
  const setupView = generateSetupView(currentConfig, availableModels);
 
516
  const tempSession = activeSetupSessions.get(guildId);
517
  if (tempSession) {
518
  tempSession.config.logChannelId = interaction.values[0];
519
+ await interaction.update(
520
+ generateSetupView(tempSession.config, tempSession.availableModels),
521
+ );
522
  }
523
  }
524
  }
 
527
  if (interaction.isStringSelectMenu()) {
528
  const guildId = interaction.guildId;
529
  const tempSession = activeSetupSessions.get(guildId);
530
+
531
  if (tempSession) {
532
  // Sprawdzamy czy zmieniany jest model (szukamy przedrostka setup_model_)
533
  if (interaction.customId.startsWith("setup_model_")) {
534
  const contentType = interaction.customId.replace("setup_model_", "");
535
  tempSession.config.models[contentType] = interaction.values[0];
536
+ await interaction.update(
537
+ generateSetupView(tempSession.config, tempSession.availableModels),
538
+ );
539
  }
540
  }
541
  }
 
543
  if (interaction.isMessageContextMenuCommand()) {
544
  if (interaction.commandName === "Wykryj deepfake") {
545
  const targetMessage = interaction.targetMessage;
546
+
547
  let contentToAnalyze = targetMessage.content;
548
  let explicitContentType = null;
549
 
550
  const attachment = targetMessage.attachments.first();
551
+
552
  if (attachment) {
553
  contentToAnalyze = attachment.url;
554
  explicitContentType = attachment.contentType;
 
556
 
557
  if (!contentToAnalyze || contentToAnalyze.trim().length === 0) {
558
  return interaction.reply({
559
+ content:
560
+ "❌ Ta wiadomość nie zawiera tekstu ani załączników do analizy.",
561
+ flags: [MessageFlags.Ephemeral],
562
  });
563
  }
564
 
565
+ await handleAnalysis(
566
+ interaction,
567
+ contentToAnalyze,
568
+ targetMessage,
569
+ explicitContentType,
570
+ );
571
+ }
572
+ if (interaction.commandName === "Weryfikacja faktów") {
573
+ const targetMessage = interaction.targetMessage;
574
+ const contentToVerify = targetMessage.content;
575
+
576
+ if (!contentToVerify || contentToVerify.trim().length < 10) {
577
+ return interaction.reply({
578
+ content:
579
+ "❌ Wiadomość musi mieć przynajmniej 10 znaków tekstu, aby można było ją zweryfikować.",
580
+ flags: [MessageFlags.Ephemeral],
581
+ });
582
+ }
583
+
584
+ await handleFactCheck(interaction, contentToVerify);
585
  }
586
  }
587
+ //koniec funkcji kupczaka
588
 
589
  if (interaction.isModalSubmit()) {
590
  if (interaction.customId === "detectModal") {
 
603
  const response = await fetch(`${API_URL}/guilds/${guildId}/setup`, {
604
  method: "POST",
605
  headers: {
606
+ "Content-Type": "application/json",
607
  },
608
  body: JSON.stringify({
609
  active_text_model: tempSession.config.models?.text || "none",
610
  active_image_model: tempSession.config.models?.image || "none",
611
+ log_channel_id: tempSession.config.logChannelId || null,
612
+ }),
613
  });
614
 
615
  if (!response.ok) {
 
619
 
620
  activeSetupSessions.delete(guildId);
621
  await interaction.update({
622
+ content:
623
+ "✅ **Ustawienia zostały pomyślnie zapisane na backendzie!**",
624
  embeds: [],
625
+ components: [],
626
  });
627
  } catch (error) {
628
  console.error("[SETUP ERROR]", error);
629
  await interaction.update({
630
  content: `❌ **Wystąpił błąd podczas zapisywania konfiguracji:** ${error.message}`,
631
  embeds: [],
632
+ components: [],
633
  });
634
  }
635
  }
 
640
  await interaction.update({
641
  content: "❌ **Konfiguracja została anulowana.**",
642
  embeds: [],
643
+ components: [],
644
  });
645
  }
646
 
 
657
  .setLabel("Zgłoś błąd analizy")
658
  .setStyle(ButtonStyle.Danger)
659
  .setEmoji("⚠️")
660
+ .setDisabled(true),
661
  );
662
 
663
  // 2 UPDATE BUTTONS
664
  await interaction.update({
665
+ embeds: [interaction.message.embeds[0]],
666
+ components: [disabledRow],
667
  });
668
 
669
  // 3 CONFIRMATION
670
  await interaction.followUp({
671
+ content:
672
+ "✅ **Dziękujemy!** Twoje zgłoszenie błędu zostało zarejestrowane.",
673
+ flags: [MessageFlags.Ephemeral],
674
  });
675
 
676
+ console.log(
677
+ `[RAPORT BŁĘDU] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) zgłosił błąd klasyfikacji.`,
678
+ );
679
 
680
  const originalEmbed = interaction.message.embeds[0];
681
  if (originalEmbed) {
682
  const logEmbed = EmbedBuilder.from(originalEmbed)
683
+ .setColor(0xffaa00)
684
  .setTitle("⚠️ Zgłoszenie błędu analizy")
685
+ .setDescription(
686
+ `Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) zgłosił błąd analizy w poniższym raporcie.`,
687
+ );
688
+
689
  await sendLogToDiscord(interaction.guild, logEmbed);
690
  }
691
  }
 
704
  .setLabel("Zgłoś błąd analizy")
705
  .setStyle(ButtonStyle.Danger)
706
  .setEmoji("⚠️")
707
+ .setDisabled(true),
708
  );
709
 
710
  // 2 DISABLE BUTTONS
711
  await interaction.update({
712
+ embeds: [interaction.message.embeds[0]],
713
+ components: [disabledRow],
714
  });
715
 
716
  // 3 CONFIRMATION
717
  await interaction.followUp({
718
+ content:
719
+ "✅ **Dziękujemy!** Twoje potwierdzenie zostało pomyślnie zapisane.",
720
+ flags: [MessageFlags.Ephemeral],
721
  });
722
 
723
+ console.log(
724
+ `[POTWIERDZENIE] Użytkownik ${interaction.user.tag} (ID: ${interaction.user.id}) potwierdził poprawną klasyfikację.`,
725
+ );
726
 
727
  const originalEmbed = interaction.message.embeds[0];
728
  if (originalEmbed) {
729
  const logEmbed = EmbedBuilder.from(originalEmbed)
730
+ .setColor(0x00aaff)
731
  .setTitle("✅ Potwierdzona poprawność analizy")
732
+ .setDescription(
733
+ `Użytkownik **${interaction.user.tag}** (ID: \`${interaction.user.id}\`) potwierdził poprawność raportu.`,
734
+ );
735
+
736
  await sendLogToDiscord(interaction.guild, logEmbed);
737
  }
738
  }
 
744
  console.log(`Message from ${message.author.tag}: ${message.content}`);
745
  });
746
 
747
+ client.login(process.env.DISCORD_TOKEN);