""" وحدة البحث في الويب وقراءة الروابط - بحث عبر DuckDuckGo (بدون API key) - بحث في Wikipedia - استخراج محتوى الصفحات """ import asyncio import logging import re from typing import List, Dict from urllib.parse import quote_plus, urlparse import httpx logger = logging.getLogger(__name__) USER_AGENT = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36" ) async def search_duckduckgo(query: str, max_results: int = 8) -> List[Dict]: """ بحث في DuckDuckGo HTML (بدون API key). يعيد قائمة نتائج: [{"title", "url", "snippet"}] """ results: List[Dict] = [] async with httpx.AsyncClient(timeout=15, follow_redirects=True) as client: url = f"https://html.duckduckgo.com/html/?q={quote_plus(query)}" try: resp = await client.get(url, headers={"User-Agent": USER_AGENT}) if resp.status_code != 200: logger.warning(f"DDG returned {resp.status_code}") return results text = resp.text # استخراج النتائج بنمط بسيط # كل نتيجة في title # وsnippet في results_raw = re.findall( r']*class="result__a"[^>]*href="([^"]+)"[^>]*>(.+?)' r'.*?]*class="result__snippet"[^>]*>(.+?)', text, re.DOTALL, ) for url_raw, title_html, snippet_html in results_raw[:max_results]: # DuckDuckGo يضع الرابط بـ //duckduckgo.com/l/?uddg= if "uddg=" in url_raw: from urllib.parse import parse_qs, unquote qs = urlparse(url_raw).query params = parse_qs(qs) real_url = unquote(params.get("uddg", [url_raw])[0]) else: real_url = url_raw title = _strip_html(title_html).strip() snippet = _strip_html(snippet_html).strip() if title and real_url: results.append({ "title": title, "url": real_url, "snippet": snippet, }) except Exception as e: logger.error(f"DDG search failed: {e}") return results async def search_wikipedia(query: str, lang: str = "ar", max_results: int = 3) -> List[Dict]: """بحث في Wikipedia عبر API الرسمي""" results: List[Dict] = [] async with httpx.AsyncClient(timeout=15) as client: try: # بحث search_url = f"https://{lang}.wikipedia.org/w/api.php" params = { "action": "query", "list": "search", "srsearch": query, "srlimit": max_results, "format": "json", } resp = await client.get(search_url, params=params) if resp.status_code != 200: return results data = resp.json() for item in data.get("query", {}).get("search", []): title = item.get("title", "") snippet = _strip_html(item.get("snippet", "")).strip() page_url = f"https://{lang}.wikipedia.org/wiki/{quote_plus(title.replace(' ', '_'))}" results.append({ "title": title, "url": page_url, "snippet": snippet, }) except Exception as e: logger.error(f"Wikipedia search failed: {e}") return results async def fetch_url_content(url: str, max_chars: int = 5000) -> Dict: """ استخراج المحتوى النصي من صفحة ويب. يعيد {"url", "title", "content", "error" (optional)} """ result = {"url": url, "title": "", "content": "", "error": ""} async with httpx.AsyncClient( timeout=20, follow_redirects=True, headers={"User-Agent": USER_AGENT}, ) as client: try: resp = await client.get(url) if resp.status_code != 200: result["error"] = f"HTTP {resp.status_code}" return result html = resp.text # استخراج العنوان title_match = re.search(r"