Forgets commited on
Commit
44fa57c
·
verified ·
1 Parent(s): 37632c8

Update endpoints/antibot.js

Browse files
Files changed (1) hide show
  1. endpoints/antibot.js +145 -225
endpoints/antibot.js CHANGED
@@ -2,20 +2,31 @@ const fs = require('fs');
2
  const path = require('path');
3
  const { extractTextFromImage, uploadImageToHosting } = require('./imageProcessor');
4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  async function extractTextFromBuffer(imageBuffer) {
6
  try {
7
- if (!imageBuffer) {
8
- throw new Error('No image buffer provided');
9
- }
10
- if (!Buffer.isBuffer(imageBuffer)) {
11
- throw new Error('extractTextFromBuffer expects a Buffer');
12
- }
13
 
14
  console.log('🖼️ DEBUG extractTextFromBuffer: Received buffer, size:', imageBuffer.length, 'bytes');
15
 
16
- // Kirim buffer langsung ke extractTextFromImage (tidak perlu membuat file temporary)
17
  const result = await extractTextFromImage(imageBuffer);
18
-
19
  console.log('✅ DEBUG extractTextFromBuffer: Hasil ekstraksi:', result);
20
  return result;
21
  } catch (error) {
@@ -24,251 +35,160 @@ async function extractTextFromBuffer(imageBuffer) {
24
  }
25
  }
26
 
27
- function mapAnswer(soalArray, jawaban, botIndex) {
28
- console.log(`🤖 DEBUG mapAnswer: Bot ${botIndex}, Jawaban: "${jawaban}"`);
29
- return jawaban;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
 
32
  function normalizeText(text) {
33
- const normalized = text.toLowerCase().replace(/[^\w\s]/g, '').trim();
34
- console.log(`🔤 DEBUG normalizeText: "${text}" -> "${normalized}"`);
 
 
 
 
 
 
 
35
  return normalized;
36
  }
37
 
38
- function isValueMatch(value, targetSoal) {
39
- console.log(`🔍 DEBUG isValueMatch: Value="${value}", Soal="${targetSoal}"`);
40
- const numberMap = {
41
- '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five',
42
- '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine', '10': 'ten',
43
- 'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5',
44
- 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9', 'ten': '10',
45
- 'I': '1','II': '2','III': '3','IV': '4','V': '5','VI': '6','VII': '7','VIII': '8','IX': '9','X': '10',
46
- 'i': '1','ii': '2','iii': '3','iv': '4','v': '5','vi': '6','vii': '7','viii': '8','ix': '9','x': '10',
47
- 'slx': '6','s1x': '6','six': '6','f0ur': '4','f0r': '4','fuor': '4','f1ve': '5','fiv': '5','f1v': '5',
48
- 'e1ght': '8','elght': '8','eight': '8','n1ne': '9','n1n': '9','nne': '9','se7en': '7','sven': '7','seven': '7',
49
- 'thre': '3','tree': '3','thr33': '3','tw0': '2','to': '2','tw': '2','0ne': '1','on': '1','oen': '1'
50
- };
51
-
52
- const leetMap = {
53
- 'O': '0','o': '0','I': '1','i': '1','l': '1','Z': '2','z': '2','E': '3','e': '3',
54
- 'A': '4','a': '4','S': '5','s': '5','G': '6','g': '6','T': '7','t': '7','B': '8','b': '8',
55
- 'Q': '9','q': '9','U': '4','u': '4','R': '2','r': '2','N': '9','n': '9','V': '7','v': '7'
56
- };
 
57
 
58
- const normalizedValue = normalizeText(value);
59
- const normalizedSoal = normalizeText(targetSoal);
 
 
 
 
 
 
60
 
61
- if (normalizedValue === normalizedSoal) {
62
- console.log('✅ DEBUG: Match exact normalized');
63
- return true;
 
 
 
 
 
 
 
 
64
  }
 
65
 
66
- const convertLeet = (text) => text.split('').map(char => leetMap[char] || char).join('');
67
- const leetValue = convertLeet(value);
68
- const leetSoal = convertLeet(targetSoal);
69
- console.log(`🔢 DEBUG Leet: Value="${leetValue}", Soal="${leetSoal}"`);
 
70
 
71
- if (leetValue === normalizedSoal || normalizedValue === leetSoal || leetValue === leetSoal) {
72
- console.log('✅ DEBUG: Match leet/normalized combination');
 
 
 
 
73
  return true;
74
  }
75
 
76
- const mappedValue = numberMap[normalizedValue] || numberMap[value] || normalizedValue;
77
- const mappedSoal = numberMap[normalizedSoal] || numberMap[targetSoal] || normalizedSoal;
78
- console.log(`🔄 DEBUG Number Map: Value="${mappedValue}", Soal="${mappedSoal}"`);
79
-
80
- if (mappedValue === normalizedSoal || normalizedValue === mappedSoal || mappedValue === mappedSoal) {
81
- console.log('✅ DEBUG: Match mapped combinations');
82
  return true;
83
  }
84
-
85
- const similarity = calculateSimilarity(normalizedValue, normalizedSoal);
86
- console.log(`📊 DEBUG Similarity: ${similarity}`);
87
- if (similarity >= 0.8) {
88
- console.log('✅ DEBUG: Match similarity >= 0.8');
89
  return true;
90
  }
91
-
92
- try {
93
- const valueResult = evaluateSimpleMath(value);
94
- const soalResult = evaluateSimpleMath(targetSoal);
95
- console.log(`🧮 DEBUG Math: Value=${valueResult}, Soal=${soalResult}`);
96
- if (valueResult !== null && soalResult !== null && valueResult === soalResult) {
97
- console.log('✅ DEBUG: Match math evaluation');
98
- return true;
99
- }
100
- } catch (e) {
101
- console.log('❌ DEBUG Math evaluation failed');
102
  }
103
 
104
- console.log('❌ DEBUG: No match found');
105
- return false;
106
- }
107
-
108
- function calculateSimilarity(str1, str2) {
109
- if (str1 === str2) return 1;
110
- if (str1.length === 0 || str2.length === 0) return 0;
111
- const longer = str1.length > str2.length ? str1 : str2;
112
- const shorter = str1.length > str2.length ? str2 : str1;
113
- if (longer.includes(shorter)) return shorter.length / longer.length;
114
- let matches = 0;
115
- for (let i = 0; i < shorter.length; i++) {
116
- if (shorter[i] === longer[i]) matches++;
117
- }
118
- return matches / longer.length;
119
- }
120
-
121
- function evaluateSimpleMath(expression) {
122
- if (!expression) return null;
123
- const cleanExpr = expression.toString().replace(/[^\d+\-*/.()]/g, '');
124
- if (!cleanExpr) return null;
125
- try {
126
- if (cleanExpr.length > 10) return null;
127
- const result = Function(`"use strict"; return (${cleanExpr})`)();
128
- return typeof result === 'number' ? result : null;
129
- } catch (e) {
130
- return null;
131
  }
132
- }
133
 
134
- function parseSoalText(text) {
135
- console.log(`📝 DEBUG parseSoalText: Input text: "${text}"`);
136
- const ignoreWords = [
137
- 'hi','how','are','you','hello','hey','tentu','berikut','adalah','teks','dari','gambar',
138
- 'dipisahkan','sesuai','permintaan','anda','hanya','berikan','jangan','tambahkan',
139
- 'kata','apapun','seperti','atau','penjelasan','lain','saja'
140
- ];
141
- const delimiters = /[.,:;\\/\\s]+/;
142
- let parts = text.split(delimiters)
143
- .filter(part => part.trim() !== '')
144
- .filter(part => !ignoreWords.includes(part.toLowerCase()));
145
-
146
- if (parts.length === 0) {
147
- parts = text.split(/\s+/)
148
- .filter(part => part.trim() !== '')
149
- .filter(part => !ignoreWords.includes(part.toLowerCase()));
150
  }
151
 
152
- parts = parts.filter(part => part.length <= 3 || !isNaN(part));
153
- parts = parts.slice(0, 3);
154
- console.log(`📝 DEBUG parseSoalText: Filtered parts (max 3):`, parts);
155
- return parts;
156
- }
157
-
158
- async function antibot(data) {
159
- console.log('🚀 DEBUG antibot: Memulai proses antibot');
160
- console.log('📊 DEBUG: Data received - main:', data.main ? '✅' : '❌', 'bots:', data.bots?.length || 0);
161
- try {
162
- const { main, bots } = data;
163
- console.log('🖼️ DEBUG: Processing main image...');
164
- const mainBuffer = Buffer.from(main, 'base64');
165
- const mainText = await extractTextFromBuffer(mainBuffer);
166
- console.log('📄 DEBUG Main Text Result:', mainText);
167
- if (!mainText.status) {
168
- throw new Error('Gagal mengekstrak teks dari gambar utama: ' + mainText.response);
169
- }
170
-
171
- const soalArray = parseSoalText(mainText.response);
172
- console.log(`📋 DEBUG: Soal array:`, soalArray);
173
- if (soalArray.length === 0) {
174
- throw new Error('Tidak ada soal yang terdeteksi');
175
- }
176
-
177
- const botResults = [];
178
- console.log(`🤖 DEBUG: Processing ${bots.length} bots...`);
179
- for (let i = 0; i < bots.length; i++) {
180
- const bot = bots[i];
181
- console.log(`🤖 DEBUG: Processing bot ${i+1}/${bots.length} - ID: ${bot.id}`);
182
- try {
183
- const botBuffer = Buffer.from(bot.img, 'base64');
184
- const botText = await extractTextFromBuffer(botBuffer);
185
- const mappedValue = mapAnswer(soalArray, botText.response, i);
186
- botResults.push({ id: bot.id, text: botText.response, value: mappedValue, normalized: normalizeText(botText.response) });
187
- console.log(`✅ DEBUG Bot ${bot.id}:`, { text: botText.response, value: mappedValue, normalized: normalizeText(botText.response) });
188
- } catch (error) {
189
- console.error(`❌ DEBUG Bot ${bot.id} Error:`, error.message);
190
- botResults.push({ id: bot.id, text: '', value: '', normalized: '', error: error.message });
191
- }
192
- }
193
-
194
- console.log('🔍 DEBUG: Starting matching process...');
195
- const result = [];
196
- const usedIds = new Set();
197
- let successfulMatches = 0;
198
-
199
- const availableSoal = [...soalArray];
200
- for (const bot of botResults) {
201
- let matchedSoal = null;
202
- let matchIndex = -1;
203
- if (bot.value && bot.value.trim() !== '') {
204
- for (let i = 0; i < availableSoal.length; i++) {
205
- if (isValueMatch(bot.value, availableSoal[i])) {
206
- matchedSoal = availableSoal[i];
207
- matchIndex = i;
208
- successfulMatches++;
209
- console.log(`✅ DEBUG: Bot ${bot.id} matched with soal "${matchedSoal}"`);
210
- break;
211
- }
212
- }
213
- }
214
- if (matchedSoal) {
215
- result.push({ id: bot.id, soal: matchedSoal, matchType: 'exact' });
216
- availableSoal.splice(matchIndex, 1);
217
- usedIds.add(bot.id);
218
- } else {
219
- result.push({ id: null, soal: '', matchType: 'none' });
220
- }
221
- }
222
-
223
- console.log(`📊 DEBUG: Successful matches: ${successfulMatches}`);
224
-
225
- if (successfulMatches >= 2) {
226
- console.log('✅ DEBUG: Minimal 2 match terpenuhi, mengisi bot yang belum match');
227
- for (let i = 0; i < result.length; i++) {
228
- if (!result[i].id && availableSoal.length > 0) {
229
- const bot = botResults[i];
230
- result[i].id = bot.id;
231
- result[i].soal = availableSoal.shift();
232
- result[i].matchType = 'fallback';
233
- console.log(`🔄 DEBUG: Bot ${bot.id} diisi dengan soal tersisa`);
234
- }
235
- }
236
- }
237
-
238
- for (let i = 0; i < result.length; i++) {
239
- if (!result[i].id) {
240
- if (successfulMatches >= 2) {
241
- result[i].id = botResults[i].id;
242
- result[i].matchType = 'qualified';
243
- } else {
244
- result[i].id = 'invalid';
245
- result[i].matchType = 'invalid';
246
- }
247
  }
248
  }
 
249
 
250
- console.log('🎉 DEBUG: Process completed successfully');
251
- console.log('📋 DEBUG Final Result:', result);
 
252
 
253
- return {
254
- success: true,
255
- data: {
256
- soal: soalArray,
257
- soalLeet: soalArray,
258
- botResults: botResults,
259
- result: result.map(r => ({ id: r.id })),
260
- debug: {
261
- parsedSoal: soalArray,
262
- matches: result.map(r => ({ id: r.id, matchType: r.matchType, soal: r.soal })),
263
- totalResults: result.length,
264
- successfulMatches: successfulMatches
265
- }
266
- }
267
- };
268
- } catch (error) {
269
- console.error('💥 DEBUG antibot Error:', error.message);
270
- return { success: false, error: error.message, data: { soal: [], botResults: [], result: [] } };
271
  }
 
272
  }
273
 
274
- module.exports = antibot;
 
 
 
 
 
 
 
 
2
  const path = require('path');
3
  const { extractTextFromImage, uploadImageToHosting } = require('./imageProcessor');
4
 
5
+ // Map angka kata -> digit
6
+ const NUMBER_WORDS = {
7
+ 'nol': '0', 'satu': '1', 'dua': '2', 'tiga': '3', 'empat': '4', 'lima': '5',
8
+ 'enam': '6', 'tujuh': '7', 'delapan': '8', 'sembilan': '9', 'sepuluh': '10',
9
+ 'sebelas': '11', 'belas': '', 'puluh': '', 'ratus': '', 'ribu': ''
10
+ };
11
+
12
+ // Simple leet replacements
13
+ const LEET_MAP = {
14
+ '4': 'a', '@': 'a', '8': 'b', '3': 'e', '6': 'g', '1': 'i', '!': 'i', '0': 'o',
15
+ '5': 's', '$': 's', '7': 't', '+': 't', '2': 'z'
16
+ };
17
+
18
+ function safeString(s) {
19
+ return (s || '').toString();
20
+ }
21
+
22
  async function extractTextFromBuffer(imageBuffer) {
23
  try {
24
+ if (!imageBuffer) throw new Error('No image buffer provided');
25
+ if (!Buffer.isBuffer(imageBuffer)) throw new Error('extractTextFromBuffer expects a Buffer');
 
 
 
 
26
 
27
  console.log('🖼️ DEBUG extractTextFromBuffer: Received buffer, size:', imageBuffer.length, 'bytes');
28
 
 
29
  const result = await extractTextFromImage(imageBuffer);
 
30
  console.log('✅ DEBUG extractTextFromBuffer: Hasil ekstraksi:', result);
31
  return result;
32
  } catch (error) {
 
35
  }
36
  }
37
 
38
+ function removeAccents(str) {
39
+ return safeString(str).normalize('NFD').replace(/[\u0300-\u036f]/g, '');
40
+ }
41
+
42
+ function applyLeetMap(str) {
43
+ let out = '';
44
+ for (const ch of str) {
45
+ out += (LEET_MAP[ch] !== undefined) ? LEET_MAP[ch] : ch;
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function wordsToNumbers(str) {
51
+ // Very simple conversion for single-word numbers in Indonesian (e.g., "tujuh" -> "7")
52
+ const tokens = str.split(/\s+/);
53
+ return tokens.map(t => {
54
+ const low = t.toLowerCase();
55
+ return NUMBER_WORDS[low] !== undefined ? NUMBER_WORDS[low] : t;
56
+ }).join(' ');
57
  }
58
 
59
  function normalizeText(text) {
60
+ const original = safeString(text);
61
+ let normalized = removeAccents(original);
62
+ normalized = normalized.toLowerCase();
63
+ normalized = applyLeetMap(normalized);
64
+ // keep letters, numbers and spaces only
65
+ normalized = normalized.replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
66
+ // map simple number words
67
+ normalized = wordsToNumbers(normalized);
68
+ console.log(`🔤 DEBUG normalizeText: "${original}" -> "${normalized}"`);
69
  return normalized;
70
  }
71
 
72
+ // Levenshtein distance for fuzzy matching
73
+ function levenshtein(a = '', b = '') {
74
+ const alen = a.length, blen = b.length;
75
+ if (alen === 0) return blen;
76
+ if (blen === 0) return alen;
77
+ const matrix = Array.from({ length: alen + 1 }, () => new Array(blen + 1));
78
+ for (let i = 0; i <= alen; i++) matrix[i][0] = i;
79
+ for (let j = 0; j <= blen; j++) matrix[0][j] = j;
80
+ for (let i = 1; i <= alen; i++) {
81
+ for (let j = 1; j <= blen; j++) {
82
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
83
+ matrix[i][j] = Math.min(
84
+ matrix[i - 1][j] + 1,
85
+ matrix[i][j - 1] + 1,
86
+ matrix[i - 1][j - 1] + cost
87
+ );
88
+ }
89
+ }
90
+ return matrix[alen][blen];
91
+ }
92
 
93
+ function similarity(a, b) {
94
+ a = safeString(a);
95
+ b = safeString(b);
96
+ if (a === b) return 1;
97
+ const dist = levenshtein(a, b);
98
+ const maxLen = Math.max(a.length, b.length);
99
+ return maxLen === 0 ? 1 : 1 - (dist / maxLen);
100
+ }
101
 
102
+ function tryEvaluateMathExpression(s) {
103
+ // allow simple expressions like "3+4", " 7 - 2 ", "2 * (3+1)"
104
+ try {
105
+ const cleaned = s.replace(/[^0-9+\-*/().\s]/g, '');
106
+ if (!/[0-9]/.test(cleaned)) return null;
107
+ // eslint-disable-next-line no-new-func
108
+ const val = Function(`"use strict"; return (${cleaned});`)();
109
+ if (typeof val === 'number' && isFinite(val)) return String(val);
110
+ return null;
111
+ } catch {
112
+ return null;
113
  }
114
+ }
115
 
116
+ // compare OCR value with expected soal (question) with multiple heuristics
117
+ function isValueMatch(value, soal, options = {}) {
118
+ const { fuzzyThreshold = 0.75 } = options;
119
+ console.log('🔍 DEBUG isValueMatch: Value="%s", Soal="%s"', value, soal);
120
+ if (!value && !soal) return false;
121
 
122
+ const vNorm = normalizeText(safeString(value));
123
+ const sNorm = normalizeText(safeString(soal));
124
+
125
+ // exact match
126
+ if (vNorm === sNorm) {
127
+ console.log('✅ DEBUG exact match');
128
  return true;
129
  }
130
 
131
+ // try math evaluation for both sides
132
+ const vMath = tryEvaluateMathExpression(vNorm);
133
+ const sMath = tryEvaluateMathExpression(sNorm);
134
+ if (vMath !== null && sMath !== null && vMath === sMath) {
135
+ console.log('✅ DEBUG math match:', vMath);
 
136
  return true;
137
  }
138
+ if (vMath !== null && sMath === null && vMath === sNorm) {
139
+ console.log('✅ DEBUG math->soal match:', vMath);
 
 
 
140
  return true;
141
  }
142
+ if (sMath !== null && vMath === null && sMath === vNorm) {
143
+ console.log('✅ DEBUG soal->math match:', sMath);
144
+ return true;
 
 
 
 
 
 
 
 
145
  }
146
 
147
+ // numeric comparison if either is numeric
148
+ const vNum = parseFloat(vNorm);
149
+ const sNum = parseFloat(sNorm);
150
+ if (!isNaN(vNum) && !isNaN(sNum) && Math.abs(vNum - sNum) < 1e-9) {
151
+ console.log('✅ DEBUG numeric equal');
152
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
 
154
 
155
+ // fuzzy text similarity
156
+ const sim = similarity(vNorm, sNorm);
157
+ console.log('📊 DEBUG Similarity:', sim);
158
+ if (sim >= fuzzyThreshold) {
159
+ console.log('✅ DEBUG fuzzy match (threshold=', fuzzyThreshold, ')');
160
+ return true;
 
 
 
 
 
 
 
 
 
 
161
  }
162
 
163
+ // partial match: one contains the other with decent length
164
+ if (vNorm && sNorm) {
165
+ if (vNorm.includes(sNorm) || sNorm.includes(vNorm)) {
166
+ const longer = Math.max(vNorm.length, sNorm.length);
167
+ if (longer >= 3) {
168
+ console.log('✅ DEBUG partial contains match');
169
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  }
171
  }
172
+ }
173
 
174
+ console.log(' DEBUG: No match found');
175
+ return false;
176
+ }
177
 
178
+ function mapAnswer(soalArray, jawaban, botIndex) {
179
+ console.log(`🤖 DEBUG mapAnswer: Bot ${botIndex}, Jawaban: "${jawaban}"`);
180
+ // jawaban can be object or string; normalize to string for now
181
+ if (jawaban && typeof jawaban === 'object' && jawaban.response) {
182
+ return jawaban.response;
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  }
184
+ return jawaban;
185
  }
186
 
187
+ module.exports = {
188
+ extractTextFromBuffer,
189
+ mapAnswer,
190
+ normalizeText,
191
+ isValueMatch,
192
+ levenshtein,
193
+ similarity
194
+ };