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

Update endpoints/antibot.js

Browse files
Files changed (1) hide show
  1. endpoints/antibot.js +121 -91
endpoints/antibot.js CHANGED
@@ -3,13 +3,22 @@ const fs = require("fs");
3
  const path = require("path");
4
  const sharp = require("sharp");
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()
@@ -18,19 +27,9 @@ function leetize(str) {
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++) {
@@ -54,23 +53,23 @@ function similarity(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()
@@ -80,125 +79,156 @@ async function preprocessBot(b) {
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
- };
 
3
  const path = require("path");
4
  const sharp = require("sharp");
5
 
6
+ // Fungsi untuk mengkonversi data base64 menjadi Buffer
7
  const buf = b => Buffer.from(b.replace(/^data:image\/\w+;base64,/, ""), "base64");
8
 
9
+ // ==========================
10
+ // LEET MAP
11
+ // ==========================
12
  const LEET = {
13
  a:"4", e:"3", g:"9", i:"1", l:"1", o:"0", s:"5", t:"7", b:"8", z:"2",
14
  "@":"4", "$":"5", "&":"8"
15
  };
16
 
17
+ /**
18
+ * Mengubah string menjadi format "leet" untuk pencocokan yang lebih fleksibel.
19
+ * @param {string} str - String input.
20
+ * @returns {string} String yang sudah di-leetize.
21
+ */
22
  function leetize(str) {
23
  return str
24
  .toLowerCase()
 
27
  .join("");
28
  }
29
 
30
+ // ==========================
31
+ // LEVENSHTEIN (Fungsi untuk mengukur kemiripan string)
32
+ // ==========================
 
 
 
 
 
 
 
 
 
 
33
  function levenshtein(a, b) {
34
  const m = [];
35
  for (let i = 0; i <= a.length; i++) {
 
53
  return 1 - dist / Math.max(a.length, b.length);
54
  }
55
 
56
+ // ==========================
57
+ // OCR PREP (Pre-processing Gambar)
58
+ // ==========================
 
 
 
59
  async function preprocessSoal(b) {
60
+ // Preprocessing agresif untuk soal gelap: Grayscale, Normalize, Sharpen, Median, Threshold
61
  return sharp(b)
62
  .resize({ width: 1000 })
63
  .grayscale()
64
  .normalize()
65
+ .sharpen({ sigma: 3 })
66
+ .median(3)
67
+ .threshold(120) // Binerisasi gambar (hitam-putih murni)
68
  .toBuffer();
69
  }
70
 
71
  async function preprocessBot(b) {
72
+ // Preprocessing standar untuk gambar bot
73
  return sharp(b)
74
  .resize({ width: 800 })
75
  .grayscale()
 
79
  .toBuffer();
80
  }
81
 
82
+ /**
83
+ * Melakukan OCR pada buffer gambar.
84
+ * @param {Buffer} buf - Buffer gambar.
85
+ * @param {'soal'|'bot'} mode - Mode OCR.
86
+ * @returns {Promise<string>} Teks hasil OCR yang sudah di-trim.
87
+ */
88
  async function ocr(buf, mode = "soal") {
89
  const tmp = path.join(__dirname, "tmp_" + Date.now() + ".png");
90
  fs.writeFileSync(tmp, buf);
91
 
92
+ // Menambahkan simbol operasi matematika ke whitelist
93
+ const whitelist = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$&+-*/";
94
 
95
  try {
96
  const r = await Tesseract.recognize(tmp, "eng", {
97
  tessedit_char_whitelist: whitelist,
98
+ // Mode 6 untuk Soal (single block of text) karena gambar digabungkan
99
+ // Mode 8 untuk Bot (single word)
100
+ tessedit_pageseg_mode: mode === "soal" ? "6" : "8",
101
  tessedit_ocr_engine_mode: "1"
102
  });
103
  fs.unlinkSync(tmp);
104
+ // Hapus whitespace berlebih dari hasil OCR, biarkan simbol operasi
105
+ return r.data.text.trim().replace(/\s+/g, "");
106
+ } catch (error) {
107
  fs.unlinkSync(tmp);
108
+ console.error("OCR Error:", error);
109
  return "";
110
  }
111
  }
112
 
113
+ // ==========================
114
+ // MAIN FUNCTION (LOGIKA UTAMA)
115
+ // ==========================
116
  module.exports = async data => {
117
  try {
118
+ // 1. PROSES SOAL
119
  const soalImg = buf(data.main);
120
  const soalProcessed = await preprocessSoal(soalImg);
121
  const soalText = await ocr(soalProcessed, "soal");
122
 
123
+ const soalRaw = [];
124
+
125
+ // Regex untuk mencari pola operasi matematika atau angka
126
+ // Pola: (digit + operasi + digit) ATAU (angka/huruf yang panjangnya 1-3 karakter)
127
+ const regex = /(\d[\+\*x\/\-]\d|\d{1,3}(?:[.,]\d{1,3})?)/g;
128
+ let match;
129
+
130
+ // Ekstraksi 3 bagian soal menggunakan regex
131
+ while ((match = regex.exec(soalText)) !== null && soalRaw.length < 3) {
132
+ // Ganti 'x' (jika dikenali sebagai simbol kali) dengan '*'
133
+ soalRaw.push(match[0].replace(/x/g, '*'));
134
+ }
135
+
136
+ // Jika ekstraksi regex gagal menghasilkan 3 soal, coba pisahkan sisa teks
137
+ if (soalRaw.length < 3 && soalText) {
138
+ const extractedLength = soalRaw.map(s => s.length).reduce((a, b) => a + b, 0);
139
+ const remainingText = soalText.substring(extractedLength);
140
+ // Coba pisahkan sisa teks menjadi bagian-bagian yang lebih kecil
141
+ const parts = remainingText.match(/.{1,3}/g) || [];
142
+ parts.forEach(p => {
143
+ if (soalRaw.length < 3) soalRaw.push(p);
144
+ });
145
+ }
146
 
147
+ // Pastikan hasil array soal memiliki panjang 3 (diisi dengan string kosong jika kurang)
148
+ while (soalRaw.length < 3) {
149
+ soalRaw.push("");
150
+ }
151
+ soalRaw.length = 3;
152
+
153
+ const soalLeet = soalRaw.map(leetize); // Soal dalam format leet
154
+
155
+ // 2. PROSES BOT (HASIL JAWABAN)
156
  const botResults = [];
157
  for (const b of data.bots) {
158
  const d = buf(b.img);
159
  const p = await preprocessBot(d);
160
  const t = await ocr(p, "bot");
161
+
162
+ const clean = t.replace(/[^A-Za-z0-9@$&]/g, ""); // Bersihkan teks bot
163
+ const leet = leetize(clean);
164
+
165
  botResults.push({
166
  id: b.id,
167
+ text: clean,
168
+ value: leet
 
 
169
  });
170
  }
171
 
172
+ // 3. LOGIKA PENCERCOKAN
173
+ const result = new Array(soalLeet.length).fill(null);
174
+ const usedBots = new Set();
175
+ const botIds = botResults.map(b => b.id);
176
+
177
+ // Pass 1: Levenshtein Matching (Cari kecocokan terbaik dengan score >= 0.3)
178
+ for (let i = 0; i < soalLeet.length; i++) {
179
+ const s = soalLeet[i];
180
+ let bestBot = null;
181
+ let bestScore = -1;
182
+
183
+ for (const bot of botResults) {
184
+ if (usedBots.has(bot.id) || !bot.value) continue;
185
+
186
+ const score = similarity(s, bot.value);
187
+ if (score > bestScore && score >= 0.3) {
188
+ bestScore = score;
189
+ bestBot = bot.id;
190
+ }
191
  }
192
 
193
+ if (bestBot) {
194
+ usedBots.add(bestBot);
195
+ result[i] = bestBot;
196
  }
 
 
 
 
 
 
 
 
197
  }
198
 
199
+ // Pass 2: Auto-fill remaining null slots (Mengisi yang belum cocok dengan bot yang tersisa)
200
+ let unusedBotsIndex = 0;
201
  for (let i = 0; i < result.length; i++) {
202
+ if (result[i] === null) {
203
+ // Cari bot yang belum terpakai secara berurutan
204
+ while (unusedBotsIndex < botIds.length) {
205
+ const botId = botIds[unusedBotsIndex++];
206
+ if (!usedBots.has(botId)) {
207
+ result[i] = botId;
208
+ usedBots.add(botId);
209
+ break;
210
+ }
211
+ }
212
  }
 
213
  }
214
+ // Pastikan result hanya berisi 3 ID
215
+ result.length = 3;
216
 
217
+ // 4. KEMBALIKAN HASIL
218
  return {
219
+ soal: soalRaw,
220
+ soalLeet,
221
  botResults,
222
  result
223
  };
224
+
225
+ } catch (error) {
226
+ console.error("Main function error:", error);
227
  return {
228
  soal: [],
229
+ soalLeet: [],
230
  botResults: [],
231
  result: []
232
  };
233
  }
234
+ };