require('dotenv').config(); const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion } = require('@whiskeysockets/baileys'); const { Boom } = require('@hapi/boom'); const express = require('express'); const app = express(); const PORT = 3000; const qr = require('qr-image'); const fs = require('fs'); const mysql = require('mysql'); const axios = require('axios'); const pino = require('pino'); // -- ذاكرة مؤقتة لتخزين جلسات المستخدمين -- let userSessions = {}; // -- ذاكرة لتخزين عدد الطلبات والحد من المعدل -- let requestCounts = {}; // دالة للتحقق من الحد المسموح من الطلبات function checkRateLimit(phone, limit = 10, timeWindow = 60000) { const now = Date.now(); if (!requestCounts[phone]) { requestCounts[phone] = { count: 1, firstRequest: now, warned: false, violationCount: 0, blockUntil: 0, lastViolationTime: 0 }; return { allowed: true, shouldWarn: false }; } // التحقق إذا المستخدم ممنوع حالياً if (now < requestCounts[phone].blockUntil) { const remainingTime = Math.ceil((requestCounts[phone].blockUntil - now) / 1000 / 60); return { allowed: false, shouldWarn: false, blocked: true, remainingMinutes: remainingTime }; } const timePassed = now - requestCounts[phone].firstRequest; // إذا انتهت الفترة الزمنية، نبدأ عداد جديد if (timePassed > timeWindow) { requestCounts[phone] = { count: 1, firstRequest: now, warned: false, violationCount: requestCounts[phone].violationCount, blockUntil: requestCounts[phone].blockUntil, lastViolationTime: requestCounts[phone].lastViolationTime }; return { allowed: true, shouldWarn: false }; } // إذا تجاوز الحد المسموح if (requestCounts[phone].count >= limit) { // نزيد عدد المخالفات فقط إذا آخر مخالفة كانت في خلال 24 ساعة const lastViolationTime = requestCounts[phone].lastViolationTime || 0; const timeSinceLastViolation = now - lastViolationTime; // إذا كانت أول مخالفة أو آخر مخالفة من أكثر من 24 ساعة if (timeSinceLastViolation > 86400000) { // 24 ساعة requestCounts[phone].violationCount = 1; // نبدأ من الأول } else { requestCounts[phone].violationCount++; } // تحديث وقت آخر مخالفة requestCounts[phone].lastViolationTime = now; // تحديد مدة المنع حسب عدد المخالفات let blockDuration = 0; if (requestCounts[phone].violationCount === 1) { blockDuration = 60000; // 1 دقيقة } else if (requestCounts[phone].violationCount === 2) { blockDuration = 180000; // 3 دقائق } else { blockDuration = 3600000; // ساعة } requestCounts[phone].blockUntil = now + blockDuration; // نرسل تحذير مرة واحدة فقط const shouldWarn = !requestCounts[phone].warned; if (shouldWarn) { requestCounts[phone].warned = true; } return { allowed: false, shouldWarn, blocked: true, blockDuration: Math.round(blockDuration / 1000 / 60) // بالمدة بالدقائق (مقرب) }; } // زيادة العداد requestCounts[phone].count++; return { allowed: true, shouldWarn: false }; } // ========================================================= // 1. إعدادات قاعدة البيانات والتشخيص // ========================================================= const dbConfig = { host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME }; const dbConnection = mysql.createConnection(dbConfig); const logger = pino({ transport: { target: 'pino/file', options: { destination: './diagnostic_log.txt' } } }); dbConnection.connect(err => { if (err) { logger.error(`DB Connection failed: ${err.message}`); } else { logger.info('✅ تم الاتصال بقاعدة البيانات بنجاح.'); } }); // تنظيف الذاكرة periodically setInterval(() => { const now = Date.now(); let deletedCount = 0; for (const [phone, data] of Object.entries(requestCounts)) { // مسح البيانات إذا: // 1. انتهت مدة المنع من أكثر من ساعة // 2. أو مفيش نشاط من أكثر من 24 ساعة const lastActivity = Math.max(data.firstRequest, data.lastViolationTime || 0); const inactiveFor = now - lastActivity; if ((data.blockUntil > 0 && now > data.blockUntil + 3600000) || inactiveFor > 86400000) { delete requestCounts[phone]; deletedCount++; } } if (deletedCount > 0) { logger.info(`تم تنظيف ${deletedCount} مستخدم من ذاكرة Rate Limit`); } }, 30000); // كل 30 ثانية // ========================================================= // 3. الوظيفة الأساسية للاتصال بواتساب // ========================================================= let sock; async function connectToWhatsApp() { const { state, saveCreds } = await useMultiFileAuthState('baileys_auth_info'); const { version, isLatest } = await fetchLatestBaileysVersion(); logger.info(`Using WA v${version.join('.')}, is latest: ${isLatest}`); sock = makeWASocket({ auth: state, browser: ['Bot', 'Safari', '1.0.0'], logger: logger }); sock.ev.on('connection.update', (update) => { const { connection, lastDisconnect, qr: qrCodeData } = update; if (connection === 'close') { const shouldReconnect = new Boom(lastDisconnect?.error)?.output?.statusCode !== DisconnectReason.loggedOut; if (shouldReconnect) connectToWhatsApp(); } else if (connection === 'open') { logger.info('Opened connection.'); } if (qrCodeData) { fs.writeFileSync('qr_code.png', qr.imageSync(qrCodeData, { type: 'png' })); logger.info(`QR code generated.`); } }); sock.ev.on('creds.update', saveCreds); sock.ev.on('messages.upsert', async ({ messages, type }) => { if (type === 'notify') handleIncomingMessage(messages); }); } // ========================================================= // 4. معالجة الرسائل الواردة (تمت إعادة الهيكلة بالكامل) // ========================================================= async function handleIncomingMessage(messages) { const message = messages[0]; if (!message || message.key.fromMe) return; const senderPhone = message.key.remoteJid.replace('@s.whatsapp.net', ''); // التحقق من حد المعدل const rateLimitCheck = checkRateLimit(senderPhone); if (!rateLimitCheck.allowed) { // إذا كان ممنوع حالياً، نتجاهل الرسالة تماماً if (rateLimitCheck.blocked) { // بس بنبعت تحذير مرة واحدة فقط if (rateLimitCheck.shouldWarn) { let message = "⚠️ لقد تجاوزت الحد المسموح من الطلبات."; if (rateLimitCheck.blockDuration === 1) { message += " تم منعك لمدة دقيقة واحدة."; } else if (rateLimitCheck.blockDuration === 3) { message += " تم منعك لمدة 3 دقائق."; } else { message += " تم منعك لمدة ساعة."; } message += " لن يتم الرد على أي رسائل خلال هذه المدة."; await sendWhatsAppReply(senderPhone, message); } // بنرجع من غير ما نكمل معالجة الرسالة return; } // إذا كان مش ممنوع لكنه تعدى الحد (حالة نادرة) if (rateLimitCheck.shouldWarn) { await sendWhatsAppReply(senderPhone, "⚠️ لقد تجاوزت الحد المسموح من الطلبات."); } return; } let messageBody = (message.message?.conversation || message.message?.extendedTextMessage?.text || '').trim(); messageBody = convertArabicNumbers(messageBody); logger.info(`Received message from ${senderPhone}: "${messageBody}"`); if (!senderPhone || !messageBody) return; try { const mappingData = await getWhatsappFbMapping(senderPhone); if (!mappingData) { await sendWhatsAppContact(senderPhone, "❌ عذراً، هذا الرقم غير مسجل في نظامنا. يرجى التواصل مع الدعم الفني للتسجيل."); return; } const subscriptionStatus = await checkUserSubscription(mappingData.user_id); if (subscriptionStatus !== 'active') { await sendWhatsAppContact(senderPhone, "⚠️ لقد انتهت مدة اشتراكك! يرجى تجديد الاشتراك لاستخدام الخدمة."); return; } const pageInfo = await getFacebookPageInfo(mappingData.facebook_rx_fb_user_info_id, mappingData.page_info_table_id); if (!pageInfo) { await sendWhatsAppContact(senderPhone, "❌ لم يتم العثور على صفحة فيسبوك مرتبطة بحسابك. يرجى التأكد من إعدادات الحساب."); return; } const parts = messageBody.toLowerCase().split('|').map(p => p.trim()); const command = parts[0]; const session = userSessions[senderPhone]; // --- الموجه الرئيسي للأوامر (تمت إعادة بنائه بالكامل) --- // الأولوية 1: تحقق من الأوامر الرئيسية التي تبدأ أو تلغي جلسة if (parts.length === 1 && ['استعراض', 'تعديل', 'حذف', 'بحث'].includes(command)) { let posts; let introMessage = ''; let formatMessages = []; let sessionMode = command; if (command === 'بحث') { await sendWhatsAppReply(senderPhone, "للبحث في منشورات صفحتك خلال فترة زمنية محددة، تحتاج إلى استخدام الصيغة الصحيحة مع تحديد تاريخ البدء والانتهاء:"); await sendWhatsAppReply(senderPhone, "بحث | من: 2024-01-01 | إلى: 2024-01-31"); return; } else { sessionMode = (command === 'استعراض') ? 'تفعيل' : command; posts = (command === 'استعراض') ? await getLastFacebookPosts(pageInfo.page_id, pageInfo.page_access_token, 10) : await getActiveAutoReplyPosts(mappingData); introMessage = `📋 هذه هي آخر منشورات ${command !== 'استعراض' ? 'مُفعّلة' : ''}:\n\n`; if(command === 'استعراض') formatMessages.push("تفعيل | رقم المنشور | رد التعليق | الرسالة الخاصة"); if(command === 'تعديل') formatMessages.push("تعديل | رقم المنشور | الرد الجديد على التعليق | الرسالة الخاصة الجديدة"); if(command === 'حذف') formatMessages.push("حذف | رقم المنشور"); } if (!posts || posts.length === 0) { await sendWhatsAppReply(senderPhone, `لم نتمكن من العثور على أي منشورات تطابق طلبك.`); return; } userSessions[senderPhone] = { posts, mode: sessionMode, timestamp: Date.now() }; let postListMessage = introMessage; posts.forEach((post, index) => { const postText = post.message?.substring(0, 30).replace(/\n/g, ' ') + '...' || 'منشور بدون نص'; postListMessage += `${index + 1} - ${postText}\n`; }); await sendWhatsAppReply(senderPhone, postListMessage); // رسالة الشرح let instructionMessage = "لاستكمال العملية، انسخ نموذج الرسالة ⬇️ الموجود بالأسفل، وعدله بإضافة رقم المنشور المطلوب، ثم أعد إرساله لتنفيذ الأمر."; await sendWhatsAppReply(senderPhone, instructionMessage); // رسالة الصيغ الجاهزة للنسخ let formatMessage = ""; for(const format of formatMessages){ formatMessage += format + "\n"; } await sendWhatsAppReply(senderPhone, formatMessage); return; } // الأولوية 2: إذا لم يكن أمرًا رئيسيًا، تحقق إذا كانت ردًا في جلسة قائمة if (session) { const action = parts[0]; const postIndex = parseInt(parts[1], 10) - 1; const isValidAction = (session.mode === 'search_result') ? ['تفعيل', 'تعديل', 'حذف'].includes(action) : (action === session.mode); if (!isValidAction) { await sendWhatsAppReply(senderPhone, `⚠️ أمر غير متوقع أنت حالياً في عملية سابقة لم تكتمل. للخروج من العملية والبدء من جديد، اكتب كلمة: استعراض وبذلك يمكنك تفعيل الخدمة على منشور جديد.`); return; } if (isNaN(postIndex) || postIndex < 0 || postIndex >= session.posts.length) { await sendWhatsAppReply(senderPhone, "⚠️ الرقم الذي أدخلته غير صحيح. يرجى اختيار رقم من القائمة."); return; } const chosenPost = session.posts[postIndex]; if (action === 'حذف') { const deleteStatus = await deleteAutoReplyForPost(mappingData, chosenPost); await sendWhatsAppReply(senderPhone, deleteStatus === 'deleted' ? `🗑️ تم حذف التفعيل بنجاح.` : `❌ حدث خطأ فني.`); } else { const comment_reply = (parts[2] || '').trim(); const private_reply = (parts[3] || '').trim(); if (!comment_reply || !private_reply) { await sendWhatsAppReply(senderPhone, "⚠️ صيغة الرسالة غير كاملة. تأكد من إضافة رد التعليق والرسالة الخاصة."); return; } const parsedData = { comment_reply, private_reply }; const resultStatus = await createAllRecords(mappingData, parsedData, chosenPost, pageInfo); if (resultStatus === 'created' || resultStatus === 'updated') { await sendWhatsAppReply(senderPhone, `✅ تم ${resultStatus === 'updated' ? 'تعديل' : 'تفعيل'} الرد بنجاح.`); } else { await sendWhatsAppContact(senderPhone, "❌ حدث خطأ فني."); } } delete userSessions[senderPhone]; return; } // الأولوية 3: البحث بالتاريخ أو التفعيل السريع if (command === 'بحث' && parts.length === 3) { const startDate = parts[1].replace('من:', '').trim(); const endDate = parts[2].replace('إلى:', '').trim(); const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(startDate) || !dateRegex.test(endDate)) { await sendWhatsAppReply(senderPhone, "⚠️ صيغة التاريخ غير صحيحة. يرجى استخدام التنسيق: YYYY-MM-DD"); await sendWhatsAppReply(senderPhone, "بحث | من: 2024-01-01 | إلى: 2024-01-31"); return; } const since = Math.floor(new Date(startDate).getTime() / 1000); const untilDate = new Date(endDate); untilDate.setHours(23, 59, 59, 999); const until = Math.floor(untilDate.getTime() / 1000); const posts = await getLastFacebookPosts(pageInfo.page_id, pageInfo.page_access_token, 10, since, until); if (!posts || posts.length === 0) { await sendWhatsAppReply(senderPhone, `لم نتمكن من العثور على أي منشورات في الفترة المحددة.`); return; } userSessions[senderPhone] = { posts, mode: 'search_result', timestamp: Date.now() }; let introMessage = `📋 نتائج البحث من ${startDate} إلى ${endDate}:\n\nالآن يمكنك تفعيل، تعديل، أو حذف الرد على أي منشور من هذه القائمة.\n\n`; let postListMessage = introMessage; posts.forEach((post, index) => { const postText = post.message?.substring(0, 30).replace(/\n/g, ' ') + '...' || 'منشور بدون نص'; postListMessage += `${index + 1} - ${postText}\n`; }); await sendWhatsAppReply(senderPhone, postListMessage); await sendWhatsAppReply(senderPhone, "لاستكمال العملية، اختر أحد الخيارات التالية وانسخ الصيغة المناسبة:"); await sendWhatsAppReply(senderPhone, "تفعيل | رقم المنشور | رد التعليق | الرسالة الخاصة\nتعديل | رقم المنشور | الرد الجديد | الرسالة الجديدة\nحذف | رقم المنشور"); await sendWhatsAppReply(senderPhone, "أمثلة:\nتفعيل | 2 | شكراً لتعليقك | سنتواصل معك قريباً\nتعديل | 3 | نعتز بتعليقاتكم | سنرد خلال 24 ساعة\nحذف | 1"); return; } if (parts.length === 2) { // التفعيل السريع const parsedData = { comment_reply: parts[0], private_reply: parts[1] }; const lastPosts = await getLastFacebookPosts(pageInfo.page_id, pageInfo.page_access_token, 1); if (!lastPosts || lastPosts.length === 0) { await sendWhatsAppReply(senderPhone, "❌ لم يتم العثور على أي منشورات لتفعيل الرد عليها."); return; } const resultStatus = await createAllRecords(mappingData, parsedData, lastPosts[0], pageInfo); if (resultStatus === 'created' || resultStatus === 'updated') { const SNIPPET_WORDS = 12; const post = lastPosts[0]; const postText = (post.message || post.post_description || '').replace(/\n/g, ' ').trim(); const words = postText ? postText.split(/\s+/) : []; const snippet = words.length ? words.slice(0, SNIPPET_WORDS).join(' ') + (words.length > SNIPPET_WORDS ? '...' : '') : 'منشور بدون نص'; const postDateRaw = post.created_time || post.post_created_at || ''; const postDate = postDateRaw ? new Date(postDateRaw).toLocaleString() : ''; const actionWord = resultStatus === 'updated' ? 'تم تعديل' : 'تم تفعيل'; let reply = `✅ ${actionWord} الرد بنجاح على آخر منشور:\n\n"${snippet}"`; if (postDate) reply += `\n⏱️ تاريخ النشر: ${postDate}`; await sendWhatsAppReply(senderPhone, reply); } else { await sendWhatsAppContact(senderPhone, "❌ حدث خطأ فني."); } return; } // الأولوية 4: الرسالة الافتراضية النهائية await sendWhatsAppReply(senderPhone, `👋 أهلًا بك يا مسؤول صفحة "${pageInfo.page_name}"! لتفعيل الخدمة على آخر منشور: ⬇️ انسخ القالب أسفل الرسالة، اكتب فيه ردك على التعليق والرد في الخاص، ثم أعد إرساله ليتم التفعيل مباشرة. `); await sendWhatsAppReply(senderPhone, "رد التعليق | الرسالة الخاصة"); } catch (error) { logger.fatal(error, 'An unhandled error occurred:'); await sendWhatsAppContact(senderPhone, "❌ حدث خطأ عام وغير متوقع. يرجى المحاولة مرة أخرى لاحقاً."); } } // ========================================================= // 5. الدوال المساعدة // ========================================================= function convertArabicNumbers(str) { if (typeof str !== 'string') return str; return str.replace(/[٠-٩]/g, d => '٠١٢٣٤٥٦٧٨٩'.indexOf(d)); } function queryAsync(sql, values) { return new Promise((resolve, reject) => { dbConnection.query(sql.trim(), values, (error, results) => { if (error) { logger.error({ message: error.message, code: error.code, sql: error.sql }, 'SQL Error Details:'); return reject(error); } resolve(results); }); }); } async function checkUserSubscription(userId) { try { const sql = "SELECT expired_date FROM users WHERE id = ? LIMIT 1"; const results = await queryAsync(sql, [userId]); if (results.length > 0 && results[0].expired_date) { const expiryDate = new Date(results[0].expired_date); const today = new Date(); today.setHours(0, 0, 0, 0); return expiryDate < today ? 'expired' : 'active'; } return 'not_found'; } catch (error) { logger.error(error, `Error checking subscription for user ID: ${userId}`); return 'error'; } } async function getWhatsappFbMapping(phone) { const sql = "SELECT * FROM whatsapp_fb_mapping WHERE phone_number = ? AND is_active = TRUE LIMIT 1"; const results = await queryAsync(sql, [phone]); return results.length > 0 ? results[0] : null; } async function getFacebookPageInfo(facebookRxFbUserId, pageInfoTableId) { const sql = "SELECT id, page_id, page_access_token, page_name FROM facebook_rx_fb_page_info WHERE facebook_rx_fb_user_info_id = ? AND id = ? AND deleted = '0' LIMIT 1"; const results = await queryAsync(sql, [facebookRxFbUserId, pageInfoTableId]); return results.length > 0 ? results[0] : null; } async function getLastFacebookPosts(pageId, pageAccessToken, limit = 5) { const feedUrl = `https://graph.facebook.com/v19.0/${pageId}/feed?fields=id,created_time,message,permalink_url,full_picture,status_type&limit=${limit}&access_token=${encodeURIComponent(pageAccessToken)}`; try { const res = await axios.get(feedUrl); const data = res.data?.data || []; const items = data.map(p => { // حدد النوع بشكل تقريبي من status_type let type = "post"; if (p.status_type?.includes("video")) type = "video"; if (p.status_type?.includes("reel")) type = "reel"; // لو ظهر reel في المستقبل return { id: p.id, created_time: p.created_time, message: p.message || "", full_picture: p.full_picture || "", permalink_url: p.permalink_url || "", post_type: type }; }); return items; } catch (err) { logger.error(err.response?.data || err.message, 'getLastFacebookPosts error'); return []; } } async function getActiveAutoReplyPosts(mappingData) { const sql = "SELECT post_id as id, post_permalink, post_created_at, post_description as message, post_thumb as full_picture FROM facebook_ex_autoreply WHERE user_id = ? AND page_info_table_id = ? ORDER BY last_updated_at DESC LIMIT 10"; const values = [mappingData.user_id, mappingData.page_info_table_id]; try { const results = await queryAsync(sql, values); return results.length > 0 ? results : null; } catch (error) { logger.error(error, 'DB Error (getActiveAutoReplyPosts):'); return null; } } async function deleteAutoReplyForPost(mappingData, post) { try { const sql = "DELETE FROM facebook_ex_autoreply WHERE user_id = ? AND page_info_table_id = ? AND post_id = ?"; const values = [mappingData.user_id, mappingData.page_info_table_id, post.id]; const result = await queryAsync(sql, values); if (result.affectedRows > 0) { logger.info(`✅ تم حذف الرد الآلي للمنشور ID: ${post.id}`); return 'deleted'; } return 'not_found'; } catch (error) { logger.error(error, 'DB Error (deleteAutoReplyForPost):'); return 'failed'; } } function validateData(data) { return !!(data.comment_reply && data.private_reply); } async function createMessengerBotRecord(mappingData, parsedData, pageInfo) { try { const characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let postbackId = ''; for (let i = 0; i < 12; i++) { postbackId += characters[Math.floor(Math.random() * characters.length)]; } const botName = postbackId; const templateJson = JSON.stringify({ "1": { "recipient": { "id": "replace_id" }, "message": { "template_type": "text", "text": parsedData.private_reply, "typing_on_settings": "off", "delay_in_reply": "0" } } }); const sql = "INSERT INTO messenger_bot (user_id, page_id, fb_page_id, template_type, bot_type, keyword_type, keywords, message, conditions, message_condition_false, buttons, images, audio, video, file, status, bot_name, postback_id, is_template, broadcaster_labels, drip_campaign_id, remove_label_ids, remove_sequence_campaign_id, team_assign_role_id, team_assign_user_id, visual_flow_type, media_type, visual_flow_campaign_id, trigger_matching_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; const values = [mappingData.user_id, mappingData.page_info_table_id, pageInfo.page_id, 'text', 'generic', 'reply', '', templateJson, '[]', '', '[]', '[]', '', '', '', '1', botName, postbackId, '1', '', 0, '', 0, 0, 0, 'general', 'fb', 0, 'exact']; const result = await queryAsync(sql, values); logger.info(`Messenger bot insert successful, ID: ${result.insertId}`); return { messenger_bot_id: result.insertId, postback_id: postbackId }; } catch (error) { logger.error(error, 'DB Error (createMessengerBotRecord):'); return false; } } async function createMessengerBotPostback(mappingData, parsedData, pageInfo, messengerBotTableId, postbackId) { try { const botName = postbackId; const templateJson = JSON.stringify({ "1": { "recipient": { "id": "replace_id" }, "message": { "template_type": "text", "text": parsedData.private_reply, "typing_on_settings": "off", "delay_in_reply": '0' } } }); const sql = "INSERT INTO messenger_bot_postback (user_id, postback_id, page_id, use_status, status, messenger_bot_table_id, bot_name, is_template, template_jsoncode, template_name, template_for, template_id, inherit_from_template, broadcaster_labels, drip_campaign_id, remove_label_ids, remove_sequence_campaign_id, team_assign_role_id, team_assign_user_id, visual_flow_type, postback_type, media_type, visual_flow_campaign_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; const values = [mappingData.user_id, postbackId, mappingData.page_info_table_id, '1', '1', messengerBotTableId, botName, '1', templateJson, botName, 'reply_message', 0, '0', '', 0, '', 0, 0, 0, 'general', 'sub', 'fb', 0]; const result = await queryAsync(sql, values); logger.info(`Messenger bot postback insert successful, ID: ${result.insertId}`); return { success: true, postback_id_from_db: result.insertId }; } catch (error) { logger.error(error, 'DB Error (createMessengerBotPostback):'); return false; } } async function createAutoReplyRule(mappingData, parsedData, postData, pageInfo, postbackIdFromDb) { try { const now = new Date().toISOString().slice(0, 19).replace('T', ' '); const autoReplyText = JSON.stringify([{'comment_reply': parsedData.comment_reply, 'private_reply': postbackIdFromDb, 'image_link': '', 'video_link': ''}]); const checkSql = "SELECT id FROM facebook_ex_autoreply WHERE user_id = ? AND page_info_table_id = ? AND post_id = ?"; const checkValues = [mappingData.user_id, mappingData.page_info_table_id, postData.id]; const existingRule = await queryAsync(checkSql, checkValues); if (existingRule.length > 0) { const ruleId = existingRule[0].id; const updateSql = "UPDATE facebook_ex_autoreply SET auto_reply_text = ?, last_updated_at = ?, structured_message = 'yes' WHERE id = ?"; const updateValues = [autoReplyText, now, ruleId]; await queryAsync(updateSql, updateValues); logger.info(`✅ تم تحديث الرد في facebook_ex_autoreply بنجاح. ID: ${ruleId}`); return 'updated'; } else { const characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let campaignName = 'campaign_'; for (let i = 0; i < 8; i++) { campaignName += characters[Math.floor(Math.random() * characters.length)]; } const insertSql = "INSERT INTO facebook_ex_autoreply (facebook_rx_fb_user_info_id, auto_reply_campaign_name, user_id, page_info_table_id, page_name, post_id, post_permalink, post_created_at, post_description, post_thumb, reply_type, auto_like_comment, multiple_reply, comment_reply_enabled, nofilter_word_found_text, auto_reply_text, auto_private_reply_status, auto_private_reply_count, last_updated_at, last_reply_time, error_message, hide_comment_after_comment_reply, is_delete_offensive, offensive_words, private_message_offensive_words, hidden_comment_count, deleted_comment_count, auto_comment_reply_count, template_manager_table_id, broadcaster_labels, structured_message, trigger_matching_type, ai_reply_enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; const insertValues = [mappingData.facebook_rx_fb_user_info_id, campaignName, mappingData.user_id, mappingData.page_info_table_id, pageInfo.page_name, postData.id, postData.permalink_url || '', postData.created_time || now, postData.message || '', postData.full_picture || '', 'generic', 'yes', 'no', 'yes', '[{\"comment_reply\":\"\",\"private_reply\":\"\",\"image_link\":\"\",\"video_link\":\"\"}]', autoReplyText, '0', 0, now, '1970-01-01 00:00:00', '', 'no', 'hide', '', '', 0, 0, 0, 0, '', 'yes', 'exact', '0']; await queryAsync(insertSql, insertValues); logger.info(`✅ تم إدراج الرد في facebook_ex_autoreply بنجاح.`); return 'created'; } } catch (error) { logger.error(error, '❌ خطأ تفصيلي من قاعدة البيانات:'); return 'failed'; } } async function createAllRecords(mappingData, parsedData, postData, pageInfo) { try { const messengerBotResult = await createMessengerBotRecord(mappingData, parsedData, pageInfo); if (!messengerBotResult) { return 'failed'; } const postbackResult = await createMessengerBotPostback(mappingData, parsedData, pageInfo, messengerBotResult.messenger_bot_id, messengerBotResult.postback_id); if (!postbackResult) { return 'failed'; } const autoReplyStatus = await createAutoReplyRule(mappingData, parsedData, postData, pageInfo, postbackResult.postback_id_from_db); return autoReplyStatus; } catch (error) { logger.error(error, 'Error in createAllRecords:'); return 'failed'; } } async function sendWhatsAppReply(phone, message) { try { const fullPhoneJid = `${phone}@s.whatsapp.net`; await sock.sendMessage(fullPhoneJid, { text: message }); logger.info(`✅ WhatsApp reply sent to ${phone}`); } catch (error) { logger.error(error, `❌ Failed to send WhatsApp reply to ${phone}:`); } } async function sendWhatsAppContact(phone, message) { try { const fullPhoneJid = `${phone}@s.whatsapp.net`; const vcard = 'BEGIN:VCARD\n' + 'VERSION:3.0\n' + 'FN:الدعم الفني\n' + 'ORG:Your Company;\n' + 'TEL;type=CELL;type=VOICE;waid=905510923796:+90 551 092 3796\n' + 'END:VCARD'; await sock.sendMessage(fullPhoneJid, { text: message }); await sock.sendMessage(fullPhoneJid, { contacts: { displayName: 'الدعم الفني', contacts: [{ vcard }] } }); logger.info(`✅ WhatsApp contact sent to ${phone}`); } catch (error) { logger.error(error, `❌ Failed to send WhatsApp contact to ${phone}:`); } } // ========================================================= // 6. تشغيل الخادم // ========================================================= app.get('/', (req, res) => res.send('WhatsApp API is running.')); app.get('/qr', (req, res) => { try { const qrPath = './qr_code.png'; if (fs.existsSync(qrPath)) { res.sendFile('qr_code.png', { root: __dirname }); } else { res.status(404).send('QR code not available.'); } } catch (err) { res.status(500).send('Error loading QR code.'); } }); connectToWhatsApp(); app.listen(PORT, () => { logger.info(`Server is listening on port ${PORT}`); console.log(`Server is running on port ${PORT}`); });
Severity: Warning
Message: Cannot modify header information - headers already sent by (output started at /home/houo/htdocs/houo.uk/application/controllers/Home.php:1)
Filename: core/Common.php
Line Number: 570
The page you requested was not found.