Initial commit with translated description
This commit is contained in:
211
scripts/searxng.py
Normal file
211
scripts/searxng.py
Normal file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = ["httpx", "rich"]
|
||||
# ///
|
||||
"""SearXNG CLI - Privacy-respecting metasearch via your local instance."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import warnings
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich import print as rprint
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# Suppress SSL warnings for local self-signed certificates
|
||||
warnings.filterwarnings('ignore', message='Unverified HTTPS request')
|
||||
|
||||
console = Console()
|
||||
SEARXNG_URL = os.getenv("SEARXNG_URL", "http://localhost:8080")
|
||||
|
||||
def search_searxng(
|
||||
query: str,
|
||||
limit: int = 10,
|
||||
category: str = "general",
|
||||
language: str = "auto",
|
||||
time_range: str = None,
|
||||
output_format: str = "table"
|
||||
) -> dict:
|
||||
"""
|
||||
Search using SearXNG instance.
|
||||
|
||||
Args:
|
||||
query: Search query string
|
||||
limit: Number of results to return
|
||||
category: Search category (general, images, news, videos, etc.)
|
||||
language: Language code (auto, en, de, fr, etc.)
|
||||
time_range: Time range filter (day, week, month, year)
|
||||
output_format: Output format (table, json)
|
||||
|
||||
Returns:
|
||||
Dict with search results
|
||||
"""
|
||||
params = {
|
||||
"q": query,
|
||||
"format": "json",
|
||||
"categories": category,
|
||||
}
|
||||
|
||||
if language != "auto":
|
||||
params["language"] = language
|
||||
|
||||
if time_range:
|
||||
params["time_range"] = time_range
|
||||
|
||||
try:
|
||||
# Disable SSL verification for local self-signed certs
|
||||
response = httpx.get(
|
||||
f"{SEARXNG_URL}/search",
|
||||
params=params,
|
||||
timeout=30,
|
||||
verify=False # For local self-signed certs
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Limit results
|
||||
if "results" in data:
|
||||
data["results"] = data["results"][:limit]
|
||||
|
||||
return data
|
||||
|
||||
except httpx.HTTPError as e:
|
||||
console.print(f"[red]Error connecting to SearXNG:[/red] {e}", file=sys.stderr)
|
||||
return {"error": str(e), "results": []}
|
||||
except Exception as e:
|
||||
console.print(f"[red]Unexpected error:[/red] {e}", file=sys.stderr)
|
||||
return {"error": str(e), "results": []}
|
||||
|
||||
|
||||
def display_results_table(data: dict, query: str):
|
||||
"""Display search results in a rich table."""
|
||||
results = data.get("results", [])
|
||||
|
||||
if not results:
|
||||
rprint(f"[yellow]No results found for:[/yellow] {query}")
|
||||
return
|
||||
|
||||
table = Table(title=f"SearXNG Search: {query}", show_lines=False)
|
||||
table.add_column("#", style="dim", width=3)
|
||||
table.add_column("Title", style="bold")
|
||||
table.add_column("URL", style="blue", width=50)
|
||||
table.add_column("Engines", style="green", width=20)
|
||||
|
||||
for i, result in enumerate(results, 1):
|
||||
title = result.get("title", "No title")[:70]
|
||||
url = result.get("url", "")[:45] + "..."
|
||||
engines = ", ".join(result.get("engines", []))[:18]
|
||||
|
||||
table.add_row(
|
||||
str(i),
|
||||
title,
|
||||
url,
|
||||
engines
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
# Show additional info
|
||||
if data.get("number_of_results"):
|
||||
rprint(f"\n[dim]Total results available: {data['number_of_results']}[/dim]")
|
||||
|
||||
# Show content snippets for top 3
|
||||
rprint("\n[bold]Top results:[/bold]")
|
||||
for i, result in enumerate(results[:3], 1):
|
||||
title = result.get("title", "No title")
|
||||
url = result.get("url", "")
|
||||
content = result.get("content", "")[:200]
|
||||
|
||||
rprint(f"\n[bold cyan]{i}. {title}[/bold cyan]")
|
||||
rprint(f" [blue]{url}[/blue]")
|
||||
if content:
|
||||
rprint(f" [dim]{content}...[/dim]")
|
||||
|
||||
|
||||
def display_results_json(data: dict):
|
||||
"""Display results in JSON format for programmatic use."""
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="SearXNG CLI - Search the web via your local SearXNG instance",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=f"""
|
||||
Examples:
|
||||
%(prog)s search "python asyncio"
|
||||
%(prog)s search "climate change" -n 20
|
||||
%(prog)s search "cute cats" --category images
|
||||
%(prog)s search "breaking news" --category news --time-range day
|
||||
%(prog)s search "rust tutorial" --format json
|
||||
|
||||
Environment:
|
||||
SEARXNG_URL: SearXNG instance URL (default: {SEARXNG_URL})
|
||||
"""
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Commands")
|
||||
|
||||
# Search command
|
||||
search_parser = subparsers.add_parser("search", help="Search the web")
|
||||
search_parser.add_argument("query", nargs="+", help="Search query")
|
||||
search_parser.add_argument(
|
||||
"-n", "--limit",
|
||||
type=int,
|
||||
default=10,
|
||||
help="Number of results (default: 10)"
|
||||
)
|
||||
search_parser.add_argument(
|
||||
"-c", "--category",
|
||||
default="general",
|
||||
choices=["general", "images", "videos", "news", "map", "music", "files", "it", "science"],
|
||||
help="Search category (default: general)"
|
||||
)
|
||||
search_parser.add_argument(
|
||||
"-l", "--language",
|
||||
default="auto",
|
||||
help="Language code (auto, en, de, fr, etc.)"
|
||||
)
|
||||
search_parser.add_argument(
|
||||
"-t", "--time-range",
|
||||
choices=["day", "week", "month", "year"],
|
||||
help="Time range filter"
|
||||
)
|
||||
search_parser.add_argument(
|
||||
"-f", "--format",
|
||||
choices=["table", "json"],
|
||||
default="table",
|
||||
help="Output format (default: table)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
if args.command == "search":
|
||||
query = " ".join(args.query)
|
||||
|
||||
data = search_searxng(
|
||||
query=query,
|
||||
limit=args.limit,
|
||||
category=args.category,
|
||||
language=args.language,
|
||||
time_range=args.time_range,
|
||||
output_format=args.format
|
||||
)
|
||||
|
||||
if args.format == "json":
|
||||
display_results_json(data)
|
||||
else:
|
||||
display_results_table(data, query)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user