GoogleSearch / server.js
NextAITeam's picture
Update server.js
d2ba5e3 verified
Raw
History Blame Contribute Delete
7.23 kB
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=هوش مصنوعی`);
});