Fourstore commited on
Commit
09f4b59
·
verified ·
1 Parent(s): de4aad6

Update endpoints/antibot.js

Browse files
Files changed (1) hide show
  1. endpoints/antibot.js +154 -208
endpoints/antibot.js CHANGED
@@ -5,254 +5,200 @@ const sharp = require("sharp");
5
 
6
  const buf = b => Buffer.from(b.replace(/^data:image\/\w+;base64,/, ""), "base64");
7
 
8
- // ==========================
9
- // FIXED LEET MAPPING
10
- // ==========================
11
- const LEET_MAP = {
12
- 'a': '4', 'e': '3', 'i': '1', 'o': '0', 's': '5', 't': '7', 'b': '8', 'g': '9', 'z': '2',
13
- 'A': '4', 'E': '3', 'I': '1', 'O': '0', 'S': '5', 'T': '7', 'B': '8', 'G': '9', 'Z': '2'
14
  };
15
 
16
- const ENGLISH_NUMBERS = {
17
- 'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5',
18
- 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9', 'ten': '10',
19
- 'on': '1', 'tw': '2', 'thre': '3', 'fou': '4', 'fiv': '5',
20
- 'si': '6', 'seve': '7', 'eigh': '8', 'nin': '9'
21
- };
22
-
23
- function cleanText(text) {
24
- return text.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
25
  }
26
 
27
- function leetConvert(text) {
28
- if (!text) return '';
29
-
30
- const clean = cleanText(text);
31
-
32
- // Check if it's an English number word
33
- if (ENGLISH_NUMBERS[clean]) {
34
- return ENGLISH_NUMBERS[clean];
35
- }
36
-
37
- // Apply leet mapping
38
- return clean.split('').map(char => LEET_MAP[char] || char).join('');
39
  }
40
 
41
- // ==========================
42
- // BETTER OCR PROCESSING
43
- // ==========================
44
- async function enhanceImage(imageBuffer, type = "soal") {
45
- try {
46
- let pipeline = sharp(imageBuffer);
47
-
48
- if (type === "soal") {
49
- return pipeline
50
- .resize({ width: 1000 })
51
- .grayscale()
52
- .linear(1.5, -40)
53
- .normalize()
54
- .sharpen({ sigma: 2 })
55
- .median(2)
56
- .threshold(128)
57
- .toBuffer();
58
- } else {
59
- return pipeline
60
- .resize({ width: 800 })
61
- .grayscale()
62
- .linear(1.3, -30)
63
- .normalize()
64
- .sharpen({ sigma: 1.5 })
65
- .median(1)
66
- .toBuffer();
67
  }
68
- } catch (error) {
69
- return imageBuffer;
70
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
 
73
- async function reliableOCR(imageBuffer, mode = "soal") {
 
 
 
 
 
74
  try {
75
- const config = {
76
- tessedit_char_whitelist: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
77
  tessedit_pageseg_mode: mode === "soal" ? "7" : "8",
78
  tessedit_ocr_engine_mode: "1"
79
- };
80
-
81
- const result = await Tesseract.recognize(imageBuffer, "eng", config);
82
- return result.data.text.trim();
83
- } catch (error) {
84
  return "";
85
  }
86
  }
87
 
88
- // ==========================
89
- // SIMPLE MATCHING ALGORITHM
90
- // ==========================
91
- function calculateMatch(soalText, botText) {
92
- if (!soalText || !botText) return 0;
93
-
94
- // Exact match
95
- if (soalText === botText) return 1.0;
96
-
97
- // Contains match
98
- if (botText.includes(soalText) || soalText.includes(botText)) return 0.8;
99
-
100
- // Length similarity
101
- const lengthDiff = Math.abs(soalText.length - botText.length);
102
- const maxLength = Math.max(soalText.length, botText.length);
103
- const lengthScore = 1 - (lengthDiff / maxLength);
104
-
105
- // Character match
106
- let matchCount = 0;
107
- const minLength = Math.min(soalText.length, botText.length);
108
- for (let i = 0; i < minLength; i++) {
109
- if (soalText[i] === botText[i]) matchCount++;
110
- }
111
- const charScore = matchCount / maxLength;
112
-
113
- return (lengthScore + charScore) / 2;
114
- }
115
-
116
- // ==========================
117
- // MAIN FUNCTION - IMPROVED
118
- // ==========================
119
- module.exports = async (data) => {
120
- console.log("🔄 PROCESSING...");
121
-
122
  try {
123
- // 1. PROCESS SOAL
124
- const soalBuffer = buf(data.main);
125
- const soalEnhanced = await enhanceImage(soalBuffer, "soal");
126
- const soalOCR = await reliableOCR(soalEnhanced, "soal");
127
-
128
- console.log("📝 RAW SOAL OCR:", soalOCR);
129
-
130
- // Split soal into words (assuming space separation)
131
- const soalWords = soalOCR.split(/\s+/).filter(w => w.length > 0).slice(0, 3);
132
-
133
- // If OCR failed to get 3 words, use fallback
134
- while (soalWords.length < 3) {
135
- soalWords.push(`word${soalWords.length + 1}`);
136
- }
137
-
138
- const soalLeet = soalWords.map(leetConvert);
139
-
140
- console.log("🔤 SOAL WORDS:", soalWords);
141
- console.log("🔢 SOAL LEET:", soalLeet);
142
-
143
- // 2. PROCESS BOTS
144
  const botResults = [];
145
- for (const bot of data.bots) {
146
- const botBuffer = buf(bot.img);
147
- const botEnhanced = await enhanceImage(botBuffer, "bot");
148
- const botOCR = await reliableOCR(botEnhanced, "bot");
149
- const botLeet = leetConvert(botOCR);
150
-
 
 
151
  botResults.push({
152
- id: bot.id,
153
- text: botOCR || `bot${bot.id}`,
154
- value: botLeet || `bot${bot.id}`
 
 
155
  });
156
  }
157
-
158
- console.log("🤖 BOT RESULTS:");
159
- botResults.forEach(bot => {
160
- console.log(` ${bot.id}: "${bot.text}" -> "${bot.value}"`);
161
- });
162
-
163
- // 3. SMART MATCHING
164
  const result = [];
165
- const usedBots = new Set();
166
-
167
- console.log("\n🎯 MATCHING:");
168
-
169
- // Try to match each soal word with bots
170
- for (let i = 0; i < soalLeet.length; i++) {
171
- const soal = soalLeet[i];
172
-
173
- console.log(`\n Soal ${i}: "${soal}"`);
174
-
175
- let bestMatch = null;
176
  let bestScore = 0;
177
-
178
- for (const bot of botResults) {
179
- if (usedBots.has(bot.id)) continue;
180
-
181
- const score = calculateMatch(soal, bot.value);
182
- console.log(` Bot ${bot.id} ("${bot.value}"): ${score.toFixed(2)}`);
183
-
184
- if (score > bestScore) {
 
 
 
 
 
 
 
185
  bestScore = score;
186
- bestMatch = bot.id;
187
  }
188
  }
189
-
190
- // Use threshold 0.3 for matching
191
- if (bestMatch && bestScore >= 0.3) {
192
- result.push(bestMatch);
193
- usedBots.add(bestMatch);
194
- console.log(` ✅ MATCHED: Bot ${bestMatch}`);
195
  } else {
196
  result.push(null);
197
- console.log(` ❌ NO MATCH`);
198
  }
199
  }
200
-
201
- // 4. FILL EMPTY SLOTS
202
- console.log("\n🔄 FILLING EMPTY SLOTS...");
203
-
204
- const unusedBots = botResults.filter(bot => !usedBots.has(bot.id));
205
-
206
  for (let i = 0; i < result.length; i++) {
207
  if (result[i] === null) {
208
- if (unusedBots.length > 0) {
209
- result[i] = unusedBots.shift().id;
210
- console.log(` Filled slot ${i} with bot ${result[i]}`);
211
- } else {
212
- // Use any bot that hasn't been used twice
213
- for (const bot of botResults) {
214
- const usageCount = result.filter(id => id === bot.id).length;
215
- if (usageCount < 2) {
216
- result[i] = bot.id;
217
- console.log(` Reused bot ${bot.id} for slot ${i}`);
218
- break;
219
- }
220
  }
221
  }
222
  }
223
  }
224
-
225
- // Final check - ensure all slots filled
226
- for (let i = 0; i < result.length; i++) {
227
- if (result[i] === null) {
228
- result[i] = botResults[0].id;
229
- console.log(` Emergency fill slot ${i} with bot ${result[i]}`);
230
- }
231
- }
232
-
233
- console.log("\n🎉 FINAL RESULT:", result);
234
-
235
  return {
236
- soal: soalWords,
237
- soalLeet: soalLeet,
238
- botResults: botResults,
239
- result: result
240
  };
241
-
242
- } catch (error) {
243
- console.error("💥 ERROR:", error);
244
-
245
- // Fallback: use first 3 bots
246
- const fallbackResult = data.bots.slice(0, 3).map(bot => bot.id);
247
- while (fallbackResult.length < 3) {
248
- fallbackResult.push(data.bots[0]?.id || 1);
249
- }
250
-
251
  return {
252
- soal: ["text1", "text2", "text3"],
253
- soalLeet: ["text1", "text2", "text3"],
254
- botResults: data.bots.map(bot => ({ id: bot.id, text: "text", value: "text" })),
255
- result: fallbackResult
256
  };
257
  }
258
  };
 
5
 
6
  const buf = b => Buffer.from(b.replace(/^data:image\/\w+;base64,/, ""), "base64");
7
 
8
+ const LEET = {
9
+ a:"4", e:"3", g:"9", i:"1", l:"1", o:"0", s:"5", t:"7", b:"8", z:"2",
10
+ "@":"4", "$":"5", "&":"8"
 
 
 
11
  };
12
 
13
+ function leetize(str) {
14
+ return str
15
+ .toLowerCase()
16
+ .split("")
17
+ .map(c => LEET[c] ?? c)
18
+ .join("");
 
 
 
19
  }
20
 
21
+ function toAlpha(str) {
22
+ return str
23
+ .toLowerCase()
24
+ .replace(/0/g,"o")
25
+ .replace(/1/g,"i")
26
+ .replace(/3/g,"e")
27
+ .replace(/4/g,"a")
28
+ .replace(/5/g,"s")
29
+ .replace(/7/g,"t")
30
+ .replace(/8/g,"b")
31
+ .replace(/9/g,"g");
 
32
  }
33
 
34
+ function levenshtein(a, b) {
35
+ const m = [];
36
+ for (let i = 0; i <= a.length; i++) {
37
+ m[i] = [i];
38
+ for (let j = 1; j <= b.length; j++) {
39
+ m[i][j] =
40
+ i === 0
41
+ ? j
42
+ : Math.min(
43
+ m[i - 1][j] + 1,
44
+ m[i][j - 1] + 1,
45
+ m[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
46
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
 
 
48
  }
49
+ return m[a.length][b.length];
50
+ }
51
+
52
+ function similarity(a, b) {
53
+ const dist = levenshtein(a, b);
54
+ return 1 - dist / Math.max(a.length, b.length);
55
+ }
56
+
57
+ function classify(text) {
58
+ if (/^[0-9]+$/.test(text)) return "digit";
59
+ if (/^[A-Za-z]+$/.test(text)) return "word";
60
+ return "mixed";
61
+ }
62
+
63
+ async function preprocessSoal(b) {
64
+ return sharp(b)
65
+ .resize({ width: 1000 })
66
+ .grayscale()
67
+ .normalize()
68
+ .sharpen({ sigma: 2 })
69
+ .median(2)
70
+ .toBuffer();
71
+ }
72
+
73
+ async function preprocessBot(b) {
74
+ return sharp(b)
75
+ .resize({ width: 800 })
76
+ .grayscale()
77
+ .normalize()
78
+ .sharpen({ sigma: 1.5 })
79
+ .median(1)
80
+ .toBuffer();
81
  }
82
 
83
+ async function ocr(buf, mode = "soal") {
84
+ const tmp = path.join(__dirname, "tmp_" + Date.now() + ".png");
85
+ fs.writeFileSync(tmp, buf);
86
+
87
+ const whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$&";
88
+
89
  try {
90
+ const r = await Tesseract.recognize(tmp, "eng", {
91
+ tessedit_char_whitelist: whitelist,
92
  tessedit_pageseg_mode: mode === "soal" ? "7" : "8",
93
  tessedit_ocr_engine_mode: "1"
94
+ });
95
+ fs.unlinkSync(tmp);
96
+ return r.data.text.trim();
97
+ } catch {
98
+ fs.unlinkSync(tmp);
99
  return "";
100
  }
101
  }
102
 
103
+ module.exports = async data => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  try {
105
+ const soalImg = buf(data.main);
106
+ const soalProcessed = await preprocessSoal(soalImg);
107
+ const soalText = await ocr(soalProcessed, "soal");
108
+
109
+ const rawSoal = soalText
110
+ .split(/\s+/)
111
+ .filter(x => x)
112
+ .slice(0, 3);
113
+
114
+ const soalInfo = rawSoal.map(s => {
115
+ const type = classify(s);
116
+ const clean = s.replace(/[^A-Za-z0-9]/g,"");
117
+ return {
118
+ raw: clean,
119
+ type,
120
+ leet: leetize(clean),
121
+ alpha: toAlpha(clean)
122
+ };
123
+ });
124
+
 
125
  const botResults = [];
126
+ for (const b of data.bots) {
127
+ const d = buf(b.img);
128
+ const p = await preprocessBot(d);
129
+ const t = await ocr(p, "bot");
130
+
131
+ const clean = t.replace(/[^A-Za-z0-9]/g,"");
132
+ const type = classify(clean);
133
+
134
  botResults.push({
135
+ id: b.id,
136
+ raw: clean,
137
+ type,
138
+ leet: leetize(clean),
139
+ alpha: toAlpha(clean)
140
  });
141
  }
142
+
 
 
 
 
 
 
143
  const result = [];
144
+ const used = new Set();
145
+
146
+ for (const s of soalInfo) {
147
+ let best = null;
 
 
 
 
 
 
 
148
  let bestScore = 0;
149
+
150
+ for (const b of botResults) {
151
+ if (used.has(b.id) || !b.raw) continue;
152
+
153
+ let score = 0;
154
+
155
+ if (s.type === "digit" && b.type === "digit") {
156
+ if (s.raw === b.raw) score = 1.0;
157
+ } else {
158
+ const sa = s.alpha;
159
+ const ba = b.alpha;
160
+ score = similarity(sa, ba);
161
+ }
162
+
163
+ if (score > bestScore && score >= 0.3) {
164
  bestScore = score;
165
+ best = b.id;
166
  }
167
  }
168
+
169
+ if (best) {
170
+ used.add(best);
171
+ result.push(best);
 
 
172
  } else {
173
  result.push(null);
 
174
  }
175
  }
176
+
 
 
 
 
 
177
  for (let i = 0; i < result.length; i++) {
178
  if (result[i] === null) {
179
+ for (const b of botResults) {
180
+ if (!used.has(b.id) && b.raw) {
181
+ result[i] = b.id;
182
+ used.add(b.id);
183
+ break;
 
 
 
 
 
 
 
184
  }
185
  }
186
  }
187
  }
188
+
 
 
 
 
 
 
 
 
 
 
189
  return {
190
+ soal: rawSoal,
191
+ soalInfo,
192
+ botResults,
193
+ result
194
  };
195
+
196
+ } catch {
 
 
 
 
 
 
 
 
197
  return {
198
+ soal: [],
199
+ soalInfo: [],
200
+ botResults: [],
201
+ result: []
202
  };
203
  }
204
  };