Spaces:
Paused
Paused
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { extractTextFromImage, uploadImageToHosting } = require('./imageProcessor'); | |
| async function extractTextFromBuffer(imageBuffer) { | |
| try { | |
| console.log('🖼️ DEBUG extractTextFromBuffer: Membuat file temporary'); | |
| const tempPath = path.join(__dirname, `temp_${Date.now()}.jpg`); | |
| fs.writeFileSync(tempPath, imageBuffer); | |
| console.log('📁 DEBUG: File temporary dibuat:', tempPath); | |
| const result = await extractTextFromImage(tempPath); | |
| console.log('✅ DEBUG extractTextFromBuffer: Hasil ekstraksi:', result); | |
| fs.unlinkSync(tempPath); | |
| console.log('🧹 DEBUG: File temporary dihapus'); | |
| return result; | |
| } catch (error) { | |
| console.error('❌ DEBUG extractTextFromBuffer Error:', error.message); | |
| return { status: false, response: 'Gagal memproses gambar' }; | |
| } | |
| } | |
| function mapAnswer(soalArray, jawaban, botIndex) { | |
| console.log(`🤖 DEBUG mapAnswer: Bot ${botIndex}, Jawaban: "${jawaban}"`); | |
| return jawaban; | |
| } | |
| function normalizeText(text) { | |
| const normalized = text.toLowerCase().replace(/[^\w\s]/g, '').trim(); | |
| console.log(`🔤 DEBUG normalizeText: "${text}" -> "${normalized}"`); | |
| return normalized; | |
| } | |
| function isValueMatch(value, targetSoal) { | |
| console.log(`🔍 DEBUG isValueMatch: Value="${value}", Soal="${targetSoal}"`); | |
| const numberMap = { | |
| '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', | |
| '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine', '10': 'ten', | |
| 'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', | |
| 'five': '5', 'six': '6', 'seven': '7', 'eight': '8', 'nine': '9', 'ten': '10', | |
| 'I': '1', 'II': '2', 'III': '3', 'IV': '4', 'V': '5', | |
| 'VI': '6', 'VII': '7', 'VIII': '8', 'IX': '9', 'X': '10', | |
| 'i': '1', 'ii': '2', 'iii': '3', 'iv': '4', 'v': '5', | |
| 'vi': '6', 'vii': '7', 'viii': '8', 'ix': '9', 'x': '10', | |
| 'slx': '6', 's1x': '6', 'six': '6', | |
| 'f0ur': '4', 'f0r': '4', 'fuor': '4', | |
| 'f1ve': '5', 'fiv': '5', 'f1v': '5', | |
| 'e1ght': '8', 'elght': '8', 'eight': '8', | |
| 'n1ne': '9', 'n1n': '9', 'nne': '9', | |
| 'se7en': '7', 'sven': '7', 'seven': '7', | |
| 'thre': '3', 'tree': '3', 'thr33': '3', | |
| 'tw0': '2', 'to': '2', 'tw': '2', | |
| '0ne': '1', 'on': '1', 'oen': '1' | |
| }; | |
| const leetMap = { | |
| 'O': '0', 'o': '0', | |
| 'I': '1', 'i': '1', 'l': '1', | |
| 'Z': '2', 'z': '2', | |
| 'E': '3', 'e': '3', | |
| 'A': '4', 'a': '4', | |
| 'S': '5', 's': '5', | |
| 'G': '6', 'g': '6', | |
| 'T': '7', 't': '7', | |
| 'B': '8', 'b': '8', | |
| 'Q': '9', 'q': '9', | |
| 'U': '4', 'u': '4', | |
| 'R': '2', 'r': '2', | |
| 'N': '9', 'n': '9', | |
| 'V': '7', 'v': '7' | |
| }; | |
| const normalizedValue = normalizeText(value); | |
| const normalizedSoal = normalizeText(targetSoal); | |
| if (normalizedValue === normalizedSoal) { | |
| console.log('✅ DEBUG: Match exact normalized'); | |
| return true; | |
| } | |
| const convertLeet = (text) => { | |
| return text.split('').map(char => leetMap[char] || char).join(''); | |
| }; | |
| const leetValue = convertLeet(value); | |
| const leetSoal = convertLeet(targetSoal); | |
| console.log(`🔢 DEBUG Leet: Value="${leetValue}", Soal="${leetSoal}"`); | |
| if (leetValue === normalizedSoal) { | |
| console.log('✅ DEBUG: Match leet value -> normalized soal'); | |
| return true; | |
| } | |
| if (normalizedValue === leetSoal) { | |
| console.log('✅ DEBUG: Match normalized value -> leet soal'); | |
| return true; | |
| } | |
| if (leetValue === leetSoal) { | |
| console.log('✅ DEBUG: Match leet value -> leet soal'); | |
| return true; | |
| } | |
| const mappedValue = numberMap[normalizedValue] || numberMap[value] || normalizedValue; | |
| const mappedSoal = numberMap[normalizedSoal] || numberMap[targetSoal] || normalizedSoal; | |
| console.log(`🔄 DEBUG Number Map: Value="${mappedValue}", Soal="${mappedSoal}"`); | |
| if (mappedValue === normalizedSoal) { | |
| console.log('✅ DEBUG: Match mapped value -> normalized soal'); | |
| return true; | |
| } | |
| if (normalizedValue === mappedSoal) { | |
| console.log('✅ DEBUG: Match normalized value -> mapped soal'); | |
| return true; | |
| } | |
| if (mappedValue === mappedSoal) { | |
| console.log('✅ DEBUG: Match mapped value -> mapped soal'); | |
| return true; | |
| } | |
| const similarity = calculateSimilarity(normalizedValue, normalizedSoal); | |
| console.log(`📊 DEBUG Similarity: ${similarity}`); | |
| if (similarity >= 0.8) { | |
| console.log('✅ DEBUG: Match similarity >= 0.8'); | |
| return true; | |
| } | |
| try { | |
| const valueResult = evaluateSimpleMath(value); | |
| const soalResult = evaluateSimpleMath(targetSoal); | |
| console.log(`🧮 DEBUG Math: Value=${valueResult}, Soal=${soalResult}`); | |
| if (valueResult !== null && soalResult !== null && valueResult === soalResult) { | |
| console.log('✅ DEBUG: Match math evaluation'); | |
| return true; | |
| } | |
| } catch (e) { | |
| console.log('❌ DEBUG Math evaluation failed'); | |
| } | |
| console.log('❌ DEBUG: No match found'); | |
| return false; | |
| } | |
| function calculateSimilarity(str1, str2) { | |
| if (str1 === str2) return 1; | |
| if (str1.length === 0 || str2.length === 0) return 0; | |
| const longer = str1.length > str2.length ? str1 : str2; | |
| const shorter = str1.length > str2.length ? str2 : str1; | |
| if (longer.includes(shorter)) return shorter.length / longer.length; | |
| let matches = 0; | |
| for (let i = 0; i < shorter.length; i++) { | |
| if (shorter[i] === longer[i]) matches++; | |
| } | |
| return matches / longer.length; | |
| } | |
| function evaluateSimpleMath(expression) { | |
| if (!expression) return null; | |
| const cleanExpr = expression.toString().replace(/[^\d+\-*/.()]/g, ''); | |
| if (!cleanExpr) return null; | |
| try { | |
| if (cleanExpr.length > 10) return null; | |
| const result = Function(`"use strict"; return (${cleanExpr})`)(); | |
| return typeof result === 'number' ? result : null; | |
| } catch (e) { | |
| return null; | |
| } | |
| } | |
| function parseSoalText(text) { | |
| console.log(`📝 DEBUG parseSoalText: Input text: "${text}"`); | |
| const ignoreWords = [ | |
| 'hi', 'how', 'are', 'you', 'hello', 'hey', | |
| 'tentu', 'berikut', 'adalah', 'teks', 'dari', 'gambar', | |
| 'dipisahkan', 'sesuai', 'permintaan', 'anda', 'hanya', | |
| 'berikan', 'jangan', 'tambahkan', 'kata', 'apapun', 'seperti', | |
| 'atau', 'penjelasan', 'lain', 'saja' | |
| ]; | |
| const delimiters = /[.,:;\\/\s]+/; | |
| let parts = text.split(delimiters) | |
| .filter(part => part.trim() !== '') | |
| .filter(part => !ignoreWords.includes(part.toLowerCase())); | |
| if (parts.length === 0) { | |
| parts = text.split(/\s+/) | |
| .filter(part => part.trim() !== '') | |
| .filter(part => !ignoreWords.includes(part.toLowerCase())); | |
| } | |
| parts = parts.filter(part => part.length <= 3 || !isNaN(part)); | |
| parts = parts.slice(0, 3); | |
| console.log(`📝 DEBUG parseSoalText: Filtered parts (max 3):`, parts); | |
| return parts; | |
| } | |
| async function antibot(data) { | |
| console.log('🚀 DEBUG antibot: Memulai proses antibot'); | |
| console.log('📊 DEBUG: Data received - main:', data.main ? '✅' : '❌', 'bots:', data.bots?.length || 0); | |
| try { | |
| const { main, bots } = data; | |
| console.log('🖼️ DEBUG: Processing main image...'); | |
| const mainBuffer = Buffer.from(main, 'base64'); | |
| const mainText = await extractTextFromBuffer(mainBuffer); | |
| console.log('📄 DEBUG Main Text Result:', mainText); | |
| if (!mainText.status) { | |
| throw new Error('Gagal mengekstrak teks dari gambar utama: ' + mainText.response); | |
| } | |
| const soalArray = parseSoalText(mainText.response); | |
| console.log(`📋 DEBUG: Soal array:`, soalArray); | |
| if (soalArray.length === 0) { | |
| throw new Error('Tidak ada soal yang terdeteksi'); | |
| } | |
| const botResults = []; | |
| console.log(`🤖 DEBUG: Processing ${bots.length} bots...`); | |
| for (let i = 0; i < bots.length; i++) { | |
| const bot = bots[i]; | |
| console.log(`🤖 DEBUG: Processing bot ${i+1}/${bots.length} - ID: ${bot.id}`); | |
| try { | |
| const botBuffer = Buffer.from(bot.img, 'base64'); | |
| const botText = await extractTextFromBuffer(botBuffer); | |
| const mappedValue = mapAnswer(soalArray, botText.response, i); | |
| botResults.push({ | |
| id: bot.id, | |
| text: botText.response, | |
| value: mappedValue, | |
| normalized: normalizeText(botText.response) | |
| }); | |
| console.log(`✅ DEBUG Bot ${bot.id}:`, { | |
| text: botText.response, | |
| value: mappedValue, | |
| normalized: normalizeText(botText.response) | |
| }); | |
| } catch (error) { | |
| console.error(`❌ DEBUG Bot ${bot.id} Error:`, error.message); | |
| botResults.push({ | |
| id: bot.id, | |
| text: '', | |
| value: '', | |
| normalized: '', | |
| error: error.message | |
| }); | |
| } | |
| } | |
| console.log('🔍 DEBUG: Starting matching process...'); | |
| const result = []; | |
| const usedIds = new Set(); | |
| let successfulMatches = 0; | |
| // Step 1: Match setiap bot dengan soal yang tersedia | |
| const availableSoal = [...soalArray]; | |
| for (const bot of botResults) { | |
| let matchedSoal = null; | |
| let matchIndex = -1; | |
| if (bot.value && bot.value.trim() !== '') { | |
| for (let i = 0; i < availableSoal.length; i++) { | |
| if (isValueMatch(bot.value, availableSoal[i])) { | |
| matchedSoal = availableSoal[i]; | |
| matchIndex = i; | |
| successfulMatches++; | |
| console.log(`✅ DEBUG: Bot ${bot.id} matched with soal "${matchedSoal}"`); | |
| break; | |
| } | |
| } | |
| } | |
| if (matchedSoal) { | |
| result.push({ | |
| id: bot.id, | |
| soal: matchedSoal, | |
| matchType: 'exact' | |
| }); | |
| availableSoal.splice(matchIndex, 1); | |
| usedIds.add(bot.id); | |
| } else { | |
| result.push({ | |
| id: null, | |
| soal: '', | |
| matchType: 'none' | |
| }); | |
| } | |
| } | |
| console.log(`📊 DEBUG: Successful matches: ${successfulMatches}`); | |
| // Step 2: Jika minimal 2 match, isi bot yang belum match dengan soal tersisa | |
| if (successfulMatches >= 2) { | |
| console.log('✅ DEBUG: Minimal 2 match terpenuhi, mengisi bot yang belum match'); | |
| for (let i = 0; i < result.length; i++) { | |
| if (!result[i].id && availableSoal.length > 0) { | |
| const bot = botResults[i]; | |
| result[i].id = bot.id; | |
| result[i].soal = availableSoal.shift(); | |
| result[i].matchType = 'fallback'; | |
| console.log(`🔄 DEBUG: Bot ${bot.id} diisi dengan soal tersisa`); | |
| } | |
| } | |
| } | |
| // Step 3: Handle remaining cases | |
| for (let i = 0; i < result.length; i++) { | |
| if (!result[i].id) { | |
| if (successfulMatches >= 2) { | |
| result[i].id = botResults[i].id; | |
| result[i].matchType = 'qualified'; | |
| } else { | |
| result[i].id = 'invalid'; | |
| result[i].matchType = 'invalid'; | |
| } | |
| } | |
| } | |
| console.log('🎉 DEBUG: Process completed successfully'); | |
| console.log('📋 DEBUG Final Result:', result); | |
| return { | |
| success: true, | |
| data: { | |
| soal: soalArray, | |
| soalLeet: soalArray, | |
| botResults: botResults, | |
| result: result.map(r => ({ id: r.id })), | |
| debug: { | |
| parsedSoal: soalArray, | |
| matches: result.map(r => ({ | |
| id: r.id, | |
| matchType: r.matchType, | |
| soal: r.soal | |
| })), | |
| totalResults: result.length, | |
| successfulMatches: successfulMatches | |
| } | |
| } | |
| }; | |
| } catch (error) { | |
| console.error('💥 DEBUG antibot Error:', error.message); | |
| return { | |
| success: false, | |
| error: error.message, | |
| data: { | |
| soal: [], | |
| botResults: [], | |
| result: [] | |
| } | |
| }; | |
| } | |
| } | |
| module.exports = antibot; |