import express from 'express'; import * as cheerio from 'cheerio'; import { chromium } from 'playwright'; const app = express(); const PORT = 7860; const CACHE_TTL = 3600; const cache = new Map(); // ===== تابع تبدیل URL برتینا به لینک واقعی ===== function decodeBertinaUrl(bertinaUrl) { if (!bertinaUrl || !bertinaUrl.startsWith('/api/click')) { return bertinaUrl; } try { const url = new URL(bertinaUrl, 'https://search.bertina.ir'); const dest = url.searchParams.get('dest'); if (dest) { return Buffer.from(dest, 'base64').toString('utf-8'); } return bertinaUrl; } catch { return bertinaUrl; } } // ===== استخراج نتایج ارگانیک ===== async function searchBertinaOrganic(query) { const url = `https://search.bertina.ir/search?q=${encodeURIComponent(query)}`; console.log(`🔍 Fetching organic results from Bertina...`); try { const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, signal: AbortSignal.timeout(15000) }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const html = await response.text(); const $ = cheerio.load(html); const results = []; $('article.group.animate-fade-in').each((i, el) => { const isAd = $(el).attr('data-ad-id') !== undefined; if (isAd) return; const link = $(el).find('h3 a'); const title = link.text().trim(); const href = link.attr('href') || ''; const domain = $(el).find('cite.not-italic').text().trim(); const snippet = $(el).find('p.mt-1.text-sm').text().trim(); if (title && href) { // تبدیل URL به لینک واقعی const realUrl = decodeBertinaUrl(href); results.push({ title: title.slice(0, 200), url: realUrl, snippet: snippet.slice(0, 300), site: domain || new URL(realUrl).hostname.replace('www.', '') }); } }); console.log(`✅ Organic results: ${results.length}`); return results; } catch (error) { console.error(`❌ Organic fetch error:`, error.message); return []; } } // ===== استخراج پاسخ هوش مصنوعی ===== async function searchBertinaAI(query) { console.log(`🤖 Fetching AI answer with Playwright...`); let browser; try { browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); const page = await browser.newPage(); await page.goto(`https://search.bertina.ir/search?q=${encodeURIComponent(query)}`, { waitUntil: 'networkidle', timeout: 30000 }); // منتظر می‌مانیم تا بخش پاسخ هوش مصنوعی بارگذاری شود try { await page.waitForSelector('aside[aria-label="پاسخ هوش مصنوعی"]', { timeout: 10000 }); } catch (e) { console.log('⏭️ No AI answer section found'); return null; } // استخراج متن پاسخ با سلکتور دقیق‌تر // متن پاسخ داخل یک div با کلاس text-sm leading-relaxed قرار دارد const aiAnswer = await page.$eval( 'aside[aria-label="پاسخ هوش مصنوعی"] .text-sm.leading-relaxed', el => el.textContent.trim() ).catch(() => null); // اگر با سلکتور بالا پیدا نشد، از سلکتور جایگزین استفاده کن let aiAnswer2 = null; if (!aiAnswer || aiAnswer.length < 10) { aiAnswer2 = await page.$eval( 'aside[aria-label="پاسخ هوش مصنوعی"] .space-y-2', el => el.textContent.trim() ).catch(() => null); } const finalAnswer = aiAnswer || aiAnswer2; if (finalAnswer && finalAnswer.length > 10 && !finalAnswer.includes('در حال تفکر')) { console.log(`✅ AI answer extracted: ${finalAnswer.slice(0, 50)}...`); return finalAnswer; } console.log('⏭️ AI answer content is empty or still loading'); return null; } catch (error) { console.error(`❌ Playwright error:`, error.message); return null; } finally { if (browser) await browser.close(); } } // ===== جستجوی اصلی ===== async function search(query) { const cached = cache.get(query); if (cached && Date.now() - cached.timestamp < CACHE_TTL * 1000) { console.log(`✅ Cache hit for "${query}"`); return cached.results; } console.log(`🔍 Searching for: "${query}"`); // ۱. نتایج ارگانیک let results = await searchBertinaOrganic(query); // ۲. پاسخ هوش مصنوعی let aiAnswer = null; if (results.length > 0) { aiAnswer = await searchBertinaAI(query); } // ۳. اگر نتیجه‌ای نبود، DuckDuckGo if (results.length === 0) { console.log(`🔄 Falling back to DuckDuckGo...`); try { const ddg = await fetch( `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1` ); const data = await ddg.json(); results = (data.RelatedTopics || []) .filter(t => t.FirstURL && t.Text) .slice(0, 10) .map(t => ({ title: t.Text.split('-')[0]?.trim() || t.Text, url: t.FirstURL, snippet: t.Text, site: new URL(t.FirstURL).hostname.replace('www.', '') })); } catch (e) {} } const output = { query, results, ai_answer: aiAnswer, source: results.length > 0 ? 'bertina' : 'duckduckgo' }; cache.set(query, { results: output, timestamp: Date.now() }); return output; } // ===== مسیر جستجو ===== app.get('/search', async (req, res) => { const query = req.query.q; if (!query) { return res.status(400).json({ error: 'Missing query parameter' }); } try { const result = await search(query); res.json(result); } catch (error) { console.error(`❌ Error:`, error.message); res.status(500).json({ error: error.message, query, results: [], ai_answer: null }); } }); app.get('/', (req, res) => { res.json({ status: 'running', usage: '/search?q=YOUR_QUERY', note: 'Bertina search + AI answer + real URLs' }); }); app.listen(PORT, '0.0.0.0', () => { console.log(`🚀 Server running on port ${PORT}`); console.log(`📍 Test: http://localhost:${PORT}/search?q=هوش مصنوعی`); });