| const fs = require('fs'); |
| const path = require('path'); |
| const { extractTextFromImage, uploadImageToHosting } = require('./imageProcessor'); |
|
|
| |
| const NUMBER_WORDS = { |
| 'nol': '0', 'satu': '1', 'dua': '2', 'tiga': '3', 'empat': '4', 'lima': '5', |
| 'enam': '6', 'tujuh': '7', 'delapan': '8', 'sembilan': '9', 'sepuluh': '10', |
| 'sebelas': '11', 'belas': '', 'puluh': '', 'ratus': '', 'ribu': '' |
| }; |
|
|
| |
| const LEET_MAP = { |
| '4': 'a', '@': 'a', '8': 'b', '3': 'e', '6': 'g', '1': 'i', '!': 'i', '0': 'o', |
| '5': 's', '$': 's', '7': 't', '+': 't', '2': 'z' |
| }; |
|
|
| function safeString(s) { |
| return (s || '').toString(); |
| } |
|
|
| async function extractTextFromBuffer(imageBuffer) { |
| try { |
| if (!imageBuffer) throw new Error('No image buffer provided'); |
| if (!Buffer.isBuffer(imageBuffer)) throw new Error('extractTextFromBuffer expects a Buffer'); |
|
|
| console.log('πΌοΈ DEBUG extractTextFromBuffer: Received buffer, size:', imageBuffer.length, 'bytes'); |
|
|
| const result = await extractTextFromImage(imageBuffer); |
| console.log('β
DEBUG extractTextFromBuffer: Hasil ekstraksi:', result); |
| return result; |
| } catch (error) { |
| console.error('β DEBUG extractTextFromBuffer Error:', error.message); |
| return { status: false, response: 'Gagal memproses gambar' }; |
| } |
| } |
|
|
| function removeAccents(str) { |
| return safeString(str).normalize('NFD').replace(/[\u0300-\u036f]/g, ''); |
| } |
|
|
| function applyLeetMap(str) { |
| let out = ''; |
| for (const ch of str) { |
| out += (LEET_MAP[ch] !== undefined) ? LEET_MAP[ch] : ch; |
| } |
| return out; |
| } |
|
|
| function wordsToNumbers(str) { |
| |
| const tokens = str.split(/\s+/); |
| return tokens.map(t => { |
| const low = t.toLowerCase(); |
| return NUMBER_WORDS[low] !== undefined ? NUMBER_WORDS[low] : t; |
| }).join(' '); |
| } |
|
|
| function normalizeText(text) { |
| const original = safeString(text); |
| let normalized = removeAccents(original); |
| normalized = normalized.toLowerCase(); |
| normalized = applyLeetMap(normalized); |
| |
| normalized = normalized.replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim(); |
| |
| normalized = wordsToNumbers(normalized); |
| console.log(`π€ DEBUG normalizeText: "${original}" -> "${normalized}"`); |
| return normalized; |
| } |
|
|
| |
| function levenshtein(a = '', b = '') { |
| const alen = a.length, blen = b.length; |
| if (alen === 0) return blen; |
| if (blen === 0) return alen; |
| const matrix = Array.from({ length: alen + 1 }, () => new Array(blen + 1)); |
| for (let i = 0; i <= alen; i++) matrix[i][0] = i; |
| for (let j = 0; j <= blen; j++) matrix[0][j] = j; |
| for (let i = 1; i <= alen; i++) { |
| for (let j = 1; j <= blen; j++) { |
| const cost = a[i - 1] === b[j - 1] ? 0 : 1; |
| matrix[i][j] = Math.min( |
| matrix[i - 1][j] + 1, |
| matrix[i][j - 1] + 1, |
| matrix[i - 1][j - 1] + cost |
| ); |
| } |
| } |
| return matrix[alen][blen]; |
| } |
|
|
| function similarity(a, b) { |
| a = safeString(a); |
| b = safeString(b); |
| if (a === b) return 1; |
| const dist = levenshtein(a, b); |
| const maxLen = Math.max(a.length, b.length); |
| return maxLen === 0 ? 1 : 1 - (dist / maxLen); |
| } |
|
|
| function tryEvaluateMathExpression(s) { |
| |
| try { |
| const cleaned = s.replace(/[^0-9+\-*/().\s]/g, ''); |
| if (!/[0-9]/.test(cleaned)) return null; |
| |
| const val = Function(`"use strict"; return (${cleaned});`)(); |
| if (typeof val === 'number' && isFinite(val)) return String(val); |
| return null; |
| } catch { |
| return null; |
| } |
| } |
|
|
| |
| function isValueMatch(value, soal, options = {}) { |
| const { fuzzyThreshold = 0.75 } = options; |
| console.log('π DEBUG isValueMatch: Value="%s", Soal="%s"', value, soal); |
| if (!value && !soal) return false; |
|
|
| const vNorm = normalizeText(safeString(value)); |
| const sNorm = normalizeText(safeString(soal)); |
|
|
| |
| if (vNorm === sNorm) { |
| console.log('β
DEBUG exact match'); |
| return true; |
| } |
|
|
| |
| const vMath = tryEvaluateMathExpression(vNorm); |
| const sMath = tryEvaluateMathExpression(sNorm); |
| if (vMath !== null && sMath !== null && vMath === sMath) { |
| console.log('β
DEBUG math match:', vMath); |
| return true; |
| } |
| if (vMath !== null && sMath === null && vMath === sNorm) { |
| console.log('β
DEBUG math->soal match:', vMath); |
| return true; |
| } |
| if (sMath !== null && vMath === null && sMath === vNorm) { |
| console.log('β
DEBUG soal->math match:', sMath); |
| return true; |
| } |
|
|
| |
| const vNum = parseFloat(vNorm); |
| const sNum = parseFloat(sNorm); |
| if (!isNaN(vNum) && !isNaN(sNum) && Math.abs(vNum - sNum) < 1e-9) { |
| console.log('β
DEBUG numeric equal'); |
| return true; |
| } |
|
|
| |
| const sim = similarity(vNorm, sNorm); |
| console.log('π DEBUG Similarity:', sim); |
| if (sim >= fuzzyThreshold) { |
| console.log('β
DEBUG fuzzy match (threshold=', fuzzyThreshold, ')'); |
| return true; |
| } |
|
|
| |
| if (vNorm && sNorm) { |
| if (vNorm.includes(sNorm) || sNorm.includes(vNorm)) { |
| const longer = Math.max(vNorm.length, sNorm.length); |
| if (longer >= 3) { |
| console.log('β
DEBUG partial contains match'); |
| return true; |
| } |
| } |
| } |
|
|
| console.log('β DEBUG: No match found'); |
| return false; |
| } |
|
|
| function mapAnswer(soalArray, jawaban, botIndex) { |
| console.log(`π€ DEBUG mapAnswer: Bot ${botIndex}, Jawaban: "${jawaban}"`); |
| |
| if (jawaban && typeof jawaban === 'object' && jawaban.response) { |
| return jawaban.response; |
| } |
| return jawaban; |
| } |
|
|
| |
| function countPairs(s1 = '', s2 = '') { |
| s1 = safeString(s1).toLowerCase().replace(/[^a-z]/g, ''); |
| s2 = safeString(s2).toLowerCase().replace(/[^a-z]/g, ''); |
| const n1 = s1.length, n2 = s2.length; |
| const freq1 = Array(26).fill(0); |
| const freq2 = Array(26).fill(0); |
| for (let i = 0; i < n1; i++) freq1[s1.charCodeAt(i) - 97] = (freq1[s1.charCodeAt(i) - 97] || 0) + 1; |
| for (let i = 0; i < n2; i++) freq2[s2.charCodeAt(i) - 97] = (freq2[s2.charCodeAt(i) - 97] || 0) + 1; |
| let count = 0; |
| for (let i = 0; i < 26; i++) count += Math.min(freq1[i], freq2[i]); |
| return count; |
| } |
|
|
| |
| module.exports = { |
| extractTextFromBuffer, |
| mapAnswer, |
| normalizeText, |
| isValueMatch, |
| levenshtein, |
| similarity, |
| NUMBER_WORDS, |
| LEET_MAP, |
| countPairs |
| }; |