AsyncDDGS 0.1.0a1
Asynchronous DuckDuckGo Search API: A FastAPI service for async access to DuckDuckGo’s text, image, video, and news searches. Uses a custom aDDGS class with aiohttp and asyncio for concurrent queries. Supports advanced syntax and proxies.
Requires Python: >=3.9

# Async DuckDuckGo Search API

[![Build Status](https://github.com/Rah-Rah-Mitra/AsyncDDGS/actions/workflows/python-package.yml/badge.svg)](https://github.com/Rah-Rah-Mitra/AsyncDDGS/actions/workflows/python-package.yml)
[![PyPI version](https://badge.fury.io/py/asyncddgs.svg)](https://pypi.org/project/asyncddgs)

This FastAPI-based application provides a standalone interface to DuckDuckGo's core search functionalities, including text search, image search, video search, and news search. It uses asynchronous programming to efficiently query DuckDuckGo's search engine, offering a robust and scalable solution. The API is built on top of the `aDDGS` class from the `asyncddgs` module, which handles the asynchronous interactions with DuckDuckGo and can also be used directly in Python code for custom applications.

**Note:** This API is not affiliated with DuckDuckGo and is intended for educational purposes only. Users must comply with DuckDuckGo's Terms of Service when utilizing this API.

---

## Table of Contents
- **[Installation](#installation)**  
- **[Using the `aDDGS` Class](#using-the-addgs-class)**  
  - [Initialization and Configuration](#initialization-and-configuration)  
  - [Internal Methods](#internal-methods)  
  - [Search Methods](#search-methods)  
    - [Text Search](#text-search)  
    - [Image Search](#image-search)  
    - [Video Search](#video-search)  
    - [News Search](#news-search)  
  - [Full Example](#usage-example)  
- **[Running the API](#running-the-api)**  
  - [Local Deployment](#local-deployment)  
  - [API Endpoints](#api-endpoints)  
    - [Text Search (`/text`)](#text-search-text)  
    - [Image Search (`/images`)](#image-search-images)  
    - [Video Search (`/videos`)](#video-search-videos)  
    - [News Search (`/news`)](#news-search-news)  
  - [Advanced Search Syntax](#advanced-search-syntax)  
  - [Using Proxies](#using-proxies)  
- **[Technical Details](#technical-details)**  
  - [Implementation Details](#implementation-details)  
  - [Error Handling](#error-handling)  
  - [Docker Deployment](#docker-deployment)  
- **[Legal Information](#legal-information)**  
  - [Disclaimer](#disclaimer) 

---

## **Installation**

The `asyncddgs` module is published on PyPI and can be installed using pip. Python 3.9 or higher is required.

To install the module:

```bash
pip install asyncddgs
```

This command installs the `asyncddgs` module and its core dependencies, including `aiohttp`, `lxml`, and others necessary for the `aDDGS` class to function.

If you plan to run the FastAPI application, additional dependencies are required:

```bash
pip install fastapi uvicorn
```

- `fastapi`: High-performance web framework for the API.
- `uvicorn`: ASGI server to run FastAPI.

Alternatively, you can use the `aDDGS` class directly in your asynchronous Python code without the FastAPI app, requiring only the base `asyncddgs` installation.

**Verify Installation**:

```bash
pip list
```

Confirm that `asyncddgs`, and optionally `fastapi` and `uvicorn`, are installed.

---

## **Using the `aDDGS` Class**

The `aDDGS` class is the core of the `asyncddgs` module, providing asynchronous methods to perform searches on DuckDuckGo. It leverages `aiohttp` for HTTP requests and includes features like rate limiting, proxy support, and customizable headers, making it suitable for both standalone use and integration into applications like the FastAPI app.

### Initialization and Configuration

The `aDDGS` class is initialized with parameters that configure its behavior:

```python
from asyncddgs import aDDGS

async with aDDGS(
    headers=None,
    proxy=None,
    timeout=10,
    verify=True,
    enable_rate_limit=True,
    min_request_interval=20.0,
    delay_if_needed=0.75
) as ddgs:
    # Use the instance here
    pass
```

**Parameters**:

- **`headers`**: `dict[str, str] | None`  
  Custom headers for HTTP requests (e.g., `{"Accept": "application/json"}`). Defaults to a predefined User-Agent mimicking Chrome 100 if not specified.

- **`proxy`**: `str | None`  
  Proxy URL (e.g., `"http://user:pass@example.com:3128"`) for routing requests. Supports HTTP, HTTPS, and SOCKS5. Overrides the `aDDGS_PROXY` environment variable if set. Defaults to `None`.

- **`timeout`**: `int | None`  
  Total timeout in seconds for each HTTP request (connection and read). Defaults to 10 seconds.

- **`verify`**: `bool`  
  Whether to verify SSL certificates. Set to `False` for self-signed certificates or certain proxies (reduces security). Defaults to `True`.

- **`enable_rate_limit`**: `bool`  
  Enables rate limiting to avoid exceeding DuckDuckGo's request limits. Defaults to `True`.

- **`min_request_interval`**: `float`  
  Minimum interval in seconds between requests when rate limiting is enabled. Defaults to 20.0 seconds.

- **`delay_if_needed`**: `float`  
  Delay in seconds to sleep if the time since the last request is less than `min_request_interval`. Defaults to 0.75 seconds.

These parameters allow customization for specific use cases, such as adjusting request rates or routing through proxies.

### Configuration Options

The initialization parameters provide fine-grained control over the `aDDGS` instance:

- **Headers**: Customize the User-Agent or add metadata to mimic browsers or pass additional information.
- **Proxy**: Use HTTP, HTTPS, or SOCKS5 proxies for anonymity or to bypass restrictions. Example: `"socks5://user:pass@example.com:1080"`.
- **Timeout**: Adjust based on network conditions or expected response times.
- **SSL Verification**: Disable for testing with self-signed certificates, but use cautiously due to security risks.
- **Rate Limiting**: Toggle or adjust `enable_rate_limit`, `min_request_interval`, and `delay_if_needed` to balance speed and compliance with DuckDuckGo's policies.

### Internal Methods

The class includes internal methods that support its functionality:

- **`_sleep`**:  
  Manages rate limiting by sleeping asynchronously if requests are made too quickly, based on `min_request_interval` and `delay_if_needed`.

- **`_get_url`**:  
  A generic method to make HTTP requests (GET, POST, etc.) with customizable parameters (e.g., headers, data, proxy). Returns an `aiohttp.ClientResponse` object.

- **`_get_vqd`**:  
  Extracts the 'vqd' token required for image, video, and news searches by querying DuckDuckGo with the search keywords.

These methods are used internally by the search methods but are critical to understanding the class's operation.

### Search Methods

The `aDDGS` class provides four primary asynchronous search methods:

#### Text Search
The `text` method performs a text search on DuckDuckGo, returning web page titles, URLs, and snippets.

```python
async def text(
    keywords: str,
    region: str = "wt-wt",
    safesearch: str = "moderate",
    timelimit: str | None = None,
    backend: str = "auto",
    max_results: int | None = None,
    max_pages: int = 5
) -> list[dict[str, str]]
```


| **Parameter**  | **Type**       | **Description**                                                                 | **Possible Values**                                                                 | **Default**        |
|----------------|----------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------|
| `keywords`     | `str`          | Search query (required).                                                        | Any string (e.g., `"python"`, `"site:python.org"`)                                 | Required           |
| `region`       | `str`          | Region code for localized results.                                              | `"wt-wt"` (worldwide), `"us-en"`, `"uk-en"`, `"de-de"`, etc.                       | `"wt-wt"`          |
| `safesearch`   | `str`          | Filters explicit content.                                                       | `"on"`, `"moderate"`, `"off"`                                                      | `"moderate"`       |
| `timelimit`    | `str \| None`   | Limits results by publication time.                                             | `"d"` (day), `"w"` (week), `"m"` (month), `"y"` (year)                             | `None`             |
| `backend`      | `str`          | Specifies the DuckDuckGo backend to use.                                        | `"auto"` (randomly selects `"html"` or `"lite"`), `"html"`, `"lite"`                | `"auto"`           |
| `max_results`  | `int \| None`   | Maximum number of results to return.                                            | Any positive integer (e.g., `10`)                                                  | `None` (first page)|
| `max_pages`    | `int`          | Maximum number of result pages to fetch.                                        | Any positive integer (e.g., `5`)                                                   | `5`                |


- **Returns**: List of dictionaries with `title`, `href`, and `body`.

- **Behavior**: Supports two backends ("html" and "lite") for parsing results, with pagination and deduplication.

##### **Usage Example**

```python
import asyncio
from asyncddgs import aDDGS

async def main():
    async with aDDGS() as ddgs:
        # Search for "python programming" with specific parameters
        text_results = await ddgs.text(
            keywords="python programming",
            region="us-en",
            safesearch="off",
            timelimit="m",
            backend="html",
            max_results=5,
            max_pages=3
        )
        for result in text_results:
            print(f"Title: {result['title']}")
            print(f"URL: {result['href']}")
            print(f"Snippet: {result['body']}\n")

if __name__ == "__main__":
    asyncio.run(main())
```

#### Image Search
The `images` method searches for images with extensive filtering options.

```python
async def images(
    keywords: str,
    region: str = "wt-wt",
    safesearch: str = "moderate",
    timelimit: str | None = None,
    size: str | None = None,
    color: str | None = None,
    type_image: str | None = None,
    layout: str | None = None,
    license_image: str | None = None,
    max_results: int | None = None,
    max_pages: int = 5
) -> list[dict[str, str]]
```


| **Parameter**    | **Type**       | **Description**                                                                 | **Possible Values**                                                                 | **Default**        |
|------------------|----------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------|
| `keywords`       | `str`          | Search query (required).                                                        | Any string (e.g., `"sunset"`)                                                      | Required           |
| `region`         | `str`          | Region code for localized results.                                              | `"wt-wt"`, `"us-en"`, `"uk-en"`, etc.                                              | `"wt-wt"`          |
| `safesearch`     | `str`          | Filters explicit content.                                                       | `"on"`, `"moderate"`, `"off"`                                                      | `"moderate"`       |
| `timelimit`      | `str \| None`   | Limits results by time.                                                         | `"Day"`, `"Week"`, `"Month"`, `"Year"`                                             | `None`             |
| `size`           | `str \| None`   | Filters by image size.                                                          | `"Small"`, `"Medium"`, `"Large"`, `"Wallpaper"`                                    | `None`             |
| `color`          | `str \| None`   | Filters by image color.                                                         | `"color"`, `"Monochrome"`, `"Red"`, `"Orange"`, `"Yellow"`, `"Green"`, `"Blue"`, `"Purple"`, `"Pink"`, `"Brown"`, `"Black"`, `"Gray"`, `"Teal"`, `"White"` | `None` |
| `type_image`     | `str \| None`   | Filters by image type.                                                          | `"photo"`, `"clipart"`, `"gif"`, `"transparent"`, `"line"`                         | `None`             |
| `layout`         | `str \| None`   | Filters by image layout.                                                        | `"Square"`, `"Tall"`, `"Wide"`                                                     | `None`             |
| `license_image`  | `str \| None`   | Filters by image license.                                                       | `"any"`, `"Public"`, `"Share"`, `"ShareCommercially"`, `"Modify"`, `"ModifyCommercially"` | `None`       |
| `max_results`    | `int \| None`   | Maximum number of results.                                                      | Any positive integer (e.g., `10`)                                                  | `None`             |
| `max_pages`      | `int`          | Maximum pages to fetch.                                                         | Any positive integer (e.g., `5`)                                                   | `5`                |

- **Returns**: List of dictionaries with `title`, `image`, `thumbnail`, `url`, `height`, `width`, and `source`.

##### **Usage Example**

```python
import asyncio
from asyncddgs import aDDGS

async def main():
    async with aDDGS() as ddgs:
        # Search for "sunset" images with filters
        image_results = await ddgs.images(
            keywords="sunset",
            region="wt-wt",
            safesearch="moderate",
            timelimit="Week",
            size="Large",
            color="Orange",
            type_image="photo",
            layout="Wide",
            license_image="Share",
            max_results=4,
            max_pages=2
        )
        for result in image_results:
            print(f"Title: {result['title']}")
            print(f"Image URL: {result['image']}")
            print(f"Thumbnail: {result['thumbnail']}")
            print(f"Source: {result['source']}\n")

if __name__ == "__main__":
    asyncio.run(main())
```

#### Video Search
The `videos` method searches for videos with filters for resolution and duration.

```python
async def videos(
    keywords: str,
    region: str = "wt-wt",
    safesearch: str = "moderate",
    timelimit: str | None = None,
    resolution: str | None = None,
    duration: str | None = None,
    license_videos: str | None = None,
    max_results: int | None = None,
    max_pages: int = 8
) -> list[dict[str, str]]
```

| **Parameter**     | **Type**       | **Description**                                                                 | **Possible Values**                                                                 | **Default**        |
|-------------------|----------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------|
| `keywords`        | `str`          | Search query (required).                                                        | Any string (e.g., `"python tutorials"`)                                            | Required           |
| `region`          | `str`          | Region code for localized results.                                              | `"wt-wt"`, `"us-en"`, `"uk-en"`, etc.                                              | `"wt-wt"`          |
| `safesearch`      | `str`          | Filters explicit content.                                                       | `"on"`, `"moderate"`, `"off"`                                                      | `"moderate"`       |
| `timelimit`       | `str \| None`   | Limits results by time.                                                         | `"d"` (day), `"w"` (week), `"m"` (month)                                           | `None`             |
| `resolution`      | `str \| None`   | Filters by video resolution.                                                    | `"high"`, `"standard"`                                                             | `None`             |
| `duration`        | `str \| None`   | Filters by video duration.                                                      | `"short"`, `"medium"`, `"long"`                                                    | `None`             |
| `license_videos`  | `str \| None`   | Filters by video license.                                                       | `"creativeCommon"`, `"youtube"`                                                    | `None`             |
| `max_results`     | `int \| None`   | Maximum number of results.                                                      | Any positive integer (e.g., `5`)                                                   | `None`             |
| `max_pages`       | `int`          | Maximum pages to fetch.                                                         | Any positive integer (e.g., `8`)                                                   | `8`                |

- **Returns**: List of dictionaries with `content` (video URL), `title`, and additional fields.

##### **Usage Example**

```python
import asyncio
from asyncddgs import aDDGS

async def main():
    async with aDDGS() as ddgs:
        # Search for "python tutorials" videos with filters
        video_results = await ddgs.videos(
            keywords="python tutorials",
            region="uk-en",
            safesearch="on",
            timelimit="w",
            resolution="high",
            duration="medium",
            license_videos="youtube",
            max_results=3,
            max_pages=4
        )
        for result in video_results:
            print(f"Title: {result['title']}")
            print(f"Video URL: {result['content']}\n")

if __name__ == "__main__":
    asyncio.run(main())
```

#### News Search
The `news` method fetches news articles with filters for recency.

```python
async def news(
    keywords: str,
    region: str = "wt-wt",
    safesearch: str = "moderate",
    timelimit: str | None = None,
    max_results: int | None = None,
    max_pages: int = 5
) -> list[dict[str, str]]
```

| **Parameter**  | **Type**       | **Description**                                                                 | **Possible Values**                                                                 | **Default**        |
|----------------|----------------|---------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|--------------------|
| `keywords`     | `str`          | Search query (required).                                                        | Any string (e.g., `"technology"`)                                                  | Required           |
| `region`       | `str`          | Region code for localized results.                                              | `"wt-wt"`, `"us-en"`, `"uk-en"`, etc.                                              | `"wt-wt"`          |
| `safesearch`   | `str`          | Filters explicit content.                                                       | `"on"`, `"moderate"`, `"off"`                                                      | `"moderate"`       |
| `timelimit`    | `str \| None`   | Limits results by time.                                                         | `"d"` (day), `"w"` (week), `"m"` (month)                                           | `None`             |
| `max_results`  | `int \| None`   | Maximum number of results.                                                      | Any positive integer (e.g., `3`)                                                   | `None`             |
| `max_pages`    | `int`          | Maximum pages to fetch.                                                         | Any positive integer (e.g., `5`)                                                   | `5`                |

- **Returns**: List of dictionaries with `date` (ISO format), `title`, `body`, `url`, `image` (optional), and `source`.

**Common Features**: All methods are asynchronous, validate `keywords`, use rate limiting, and support pagination via `max_results` and `max_pages`.

##### **Usage Example**

```python
import asyncio
from asyncddgs import aDDGS

async def main():
    async with aDDGS() as ddgs:
        # Search for "technology" news with filters
        news_results = await ddgs.news(
            keywords="technology",
            region="us-en",
            safesearch="moderate",
            timelimit="d",
            max_results=2,
            max_pages=1
        )
        for result in news_results:
            print(f"Title: {result['title']}")
            print(f"Date: {result['date']}")
            print(f"URL: {result['url']}")
            print(f"Source: {result['source']}\n")

if __name__ == "__main__":
    asyncio.run(main())
```

### **Full Example**

Here’s how to use the `aDDGS` class directly:

```python
import asyncio
from asyncddgs import aDDGS

async def main():
    async with aDDGS(proxy="http://user:pass@proxy.example.com:3128") as ddgs:
        # Text search
        text_results = await ddgs.text("python programming", max_results=5)
        for result in text_results:
            print(f"Title: {result['title']}, URL: {result['href']}")

        # Image search
        image_results = await ddgs.images("sunset", size="Large", max_results=3)
        for result in image_results:
            print(f"Image: {result['image']}, Source: {result['source']}")

if __name__ == "__main__":
    asyncio.run(main())
```

This script demonstrates text and image searches with custom configuration (e.g., proxy). Adjust parameters as needed for other search types.

---

## **Running the API**

### Local Deployment
To launch the FastAPI application:

1. **Ensure Dependencies are Installed**:
   Complete the installation steps above.

2. **Start the FastAPI Application**:
   ```bash
   uvicorn app:app --host 0.0.0.0 --port 8080
   ```
   - `app:app`: Refers to the FastAPI instance in `app.py`.
   - `--host 0.0.0.0`: Allows external access.
   - `--port 8080`: Specifies the port.

3. **Access the API**:
   - Base URL: `http://localhost:8080`
   - Swagger UI: `http://localhost:8080/docs`

---

### API Endpoints

The API exposes four endpoints for searching DuckDuckGo:

- **POST /text**: Perform a text search.
- **POST /images**: Perform an image search.
- **POST /videos**: Perform a video search.
- **POST /news**: Perform a news search.

Each endpoint accepts a JSON payload with specific parameters, detailed below with request bodies, response formats, parameter explanations, and examples.

#### Text Search (`/text`)

**Endpoint**: `POST /text`  
**Description**: Queries DuckDuckGo for text-based search results, returning web page titles, URLs, and snippets. Supports advanced search syntax for precise queries.

**Request Body**:
```javascript
{
  "keywords": "string",          // Required: Search query
  "region": "string",            // Optional: Region code (default: "wt-wt")
  "safesearch": "string",        // Optional: Safe search level (default: "moderate")
  "timelimit": "string",         // Optional: Time filter (e.g., "d", "w", "m", "y")
  "backend": "string",           // Optional: Backend (default: "auto")
  "max_results": integer,        // Optional: Maximum number of results
  "max_pages": integer           // Optional: Maximum pages to fetch (default: 5)
}
```

**Response**:
```javascript
{
  "results": [
    {
      "title": "string",
      "href": "string",
      "body": "string"
    }
  ]
}
```

**Parameters**:
| Parameter     | Description                                                                 | Possible Values                                      | Default     |
|---------------|-----------------------------------------------------------------------------|-----------------------------------------------------|-------------|
| `keywords`    | Search query (required). Supports advanced syntax (see below).              | Any string (e.g., "python", "site:python.org")      | Required    |
| `region`      | Region code for localized results.                                         | "wt-wt" (worldwide), "us-en", "uk-en", "de-de", etc.| "wt-wt"     |
| `safesearch`  | Filters explicit content.                                                  | "on", "moderate", "off"                             | "moderate"  |
| `timelimit`   | Limits results by publication time.                                        | "d" (day), "w" (week), "m" (month), "y" (year)      | None        |
| `backend`     | Specifies the DuckDuckGo backend to use.                                   | "auto" (random), "html", "lite"                     | "auto"      |
| `max_results` | Maximum number of results to return.                                       | Any integer (e.g., 10)                              | None        |
| `max_pages`   | Maximum number of result pages to fetch (each page triggers a request).    | Any integer (e.g., 5)                               | 5           |

**Example Request**:
```bash
curl -X POST "http://localhost:8080/text" -H "Content-Type: application/json" -d '{
  "keywords": "python programming site:python.org",
  "max_results": 5
}'
```

**Example Response**:
```javascript
{
  "results": [
    {
      "title": "Python",
      "href": "https://www.python.org/",
      "body": "The official home of the Python Programming Language"
    },
    {
      "title": "Download Python",
      "href": "https://www.python.org/downloads/",
      "body": "Download the latest version of Python"
    }
  ]
}
```

**Implementation Notes**: The `text` endpoint uses either the "html" or "lite" backend, implemented in the standalone `aDDGS` class within `app.py`. If `backend` is "auto", it randomly selects between parsing full DuckDuckGo HTML pages (`https://html.duckduckgo.com/html`) or the lighter interface (`https://lite.duckduckgo.com/lite/`), returning deduplicated results.

---

#### Image Search (`/images`)

**Endpoint**: `POST /images`  
**Description**: Retrieves image results from DuckDuckGo, including metadata like size, color, and license filters.

**Request Body**:
```javascript
{
  "keywords": "string",          // Required: Search query
  "region": "string",            // Optional: Region code (default: "wt-wt")
  "safesearch": "string",        // Optional: Safe search level (default: "moderate")
  "timelimit": "string",         // Optional: Time filter (e.g., "Day", "Week")
  "size": "string",              // Optional: Image size filter
  "color": "string",             // Optional: Color filter
  "type_image": "string",        // Optional: Image type filter
  "layout": "string",            // Optional: Layout filter
  "license_image": "string",     // Optional: License filter
  "max_results": integer,        // Optional: Maximum number of results
  "max_pages": integer           // Optional: Maximum pages to fetch (default: 5)
}
```

**Response**:
```javascript
{
  "results": [
    {
      "title": "string",
      "image": "string",
      "thumbnail": "string",
      "url": "string",
      "height": integer,
      "width": integer,
      "source": "string"
    }
  ]
}
```

**Parameters**:
| Parameter       | Description                                      | Possible Values                                                                 | Default     |
|-----------------|--------------------------------------------------|--------------------------------------------------------------------------------|-------------|
| `keywords`      | Search query (required).                         | Any string (e.g., "sunset")                                                    | Required    |
| `region`        | Region code for localized results.               | "wt-wt", "us-en", "uk-en", etc.                                                | "wt-wt"     |
| `safesearch`    | Filters explicit content.                        | "on", "moderate", "off"                                                        | "moderate"  |
| `timelimit`     | Limits results by time.                          | "Day", "Week", "Month", "Year"                                                 | None        |
| `size`          | Filters by image size.                           | "Small", "Medium", "Large", "Wallpaper"                                        | None        |
| `color`         | Filters by image color.                          | "color", "Monochrome", "Red", "Orange", "Yellow", "Green", "Blue", "Purple", "Pink", "Brown", "Black", "Gray", "Teal", "White" | None |
| `type_image`    | Filters by image type.                           | "photo", "clipart", "gif", "transparent", "line"                               | None        |
| `layout`        | Filters by image layout.                         | "Square", "Tall", "Wide"                                                       | None        |
| `license_image` | Filters by image license.                        | "any", "Public", "Share", "ShareCommercially", "Modify", "ModifyCommercially"  | None        |
| `max_results`   | Maximum number of results.                       | Any integer (e.g., 10)                                                         | None        |
| `max_pages`     | Maximum pages to fetch.                          | Any integer (e.g., 5)                                                          | 5           |

**Example Request**:
```bash
curl -X POST "http://localhost:8080/images" -H "Content-Type: application/json" -d '{
  "keywords": "sunset",
  "max_results": 10,
  "size": "Large",
  "color": "Orange"
}'
```

**Example Response**:
```javascript
{
  "results": [
    {
      "title": "Sunset over the ocean",
      "image": "https://example.com/image.jpg",
      "thumbnail": "https://example.com/thumb.jpg",
      "url": "https://example.com",
      "height": 1080,
      "width": 1920,
      "source": "example.com"
    }
  ]
}
```

**Implementation Notes**: Uses the `images` method from the `aDDGS` class in `app.py`, querying `https://duckduckgo.com/i.js` with a `vqd` token. Results are deduplicated by image URL and paginated based on `max_results` or `max_pages`.

---

#### Video Search (`/videos`)

**Endpoint**: `POST /videos`  
**Description**: Searches for videos on DuckDuckGo, returning video URLs and titles with filters for resolution and duration.

**Request Body**:
```javascript
{
  "keywords": "string",          // Required: Search query
  "region": "string",            // Optional: Region code (default: "wt-wt")
  "safesearch": "string",        // Optional: Safe search level (default: "moderate")
  "timelimit": "string",         // Optional: Time filter (e.g., "d", "w", "m")
  "resolution": "string",        // Optional: Video resolution filter
  "duration": "string",          // Optional: Video duration filter
  "license_videos": "string",    // Optional: Video license filter
  "max_results": integer,        // Optional: Maximum number of results
  "max_pages": integer           // Optional: Maximum pages to fetch (default: 8)
}
```

**Response**:
```javascript
{
  "results": [
    {
      "content": "string",
      "title": "string"
    }
  ]
}
```

**Parameters**:
| Parameter       | Description                                      | Possible Values                              | Default     |
|-----------------|--------------------------------------------------|---------------------------------------------|-------------|
| `keywords`      | Search query (required).                         | Any string (e.g., "python tutorials")       | Required    |
| `region`        | Region code for localized results.               | "wt-wt", "us-en", "uk-en", etc.             | "wt-wt"     |
| `safesearch`    | Filters explicit content.                        | "on", "moderate", "off"                     | "moderate"  |
| `timelimit`     | Limits results by time.                          | "d" (day), "w" (week), "m" (month)          | None        |
| `resolution`    | Filters by video resolution.                     | "high", "standard"                          | None        |
| `duration`      | Filters by video duration.                       | "short", "medium", "long"                   | None        |
| `license_videos`| Filters by video license.                        | "creativeCommon", "youtube"                 | None        |
| `max_results`   | Maximum number of results.                       | Any integer (e.g., 5)                       | None        |
| `max_pages`     | Maximum pages to fetch.                          | Any integer (e.g., 8)                       | 8           |

**Example Request**:
```bash
curl -X POST "http://localhost:8080/videos" -H "Content-Type: application/json" -d '{
  "keywords": "python tutorials",
  "max_results": 5,
  "duration": "short"
}'
```

**Example Response**:
```javascript
{
  "results": [
    {
      "content": "https://www.youtube.com/watch?v=abc123",
      "title": "Python Basics in 5 Minutes"
    }
  ]
}
```

**Implementation Notes**: Uses the `videos` method from `aDDGS`, querying `https://duckduckgo.com/v.js`. Results are deduplicated by video URL (`content`), with pagination up to `max_pages` (default 8).

---

#### News Search (`/news`)

**Endpoint**: `POST /news`  
**Description**: Fetches news articles from DuckDuckGo, including publication dates, titles, and sources.

**Request Body**:
```javascript
{
  "keywords": "string",          // Required: Search query
  "region": "string",            // Optional: Region code (default: "wt-wt")
  "safesearch": "string",        // Optional: Safe search level (default: "moderate")
  "timelimit": "string",         // Optional: Time filter (e.g., "d", "w", "m")
  "max_results": integer,        // Optional: Maximum number of results
  "max_pages": integer           // Optional: Maximum pages to fetch (default: 5)
}
```

**Response**:
```javascript
{
  "results": [
    {
      "date": "string",  // ISO format
      "title": "string",
      "body": "string",
      "url": "string",
      "image": "string",
      "source": "string"
    }
  ]
}
```

**Parameters**:
| Parameter     | Description                                      | Possible Values                              | Default     |
|---------------|--------------------------------------------------|---------------------------------------------|-------------|
| `keywords`    | Search query (required).                         | Any string (e.g., "technology")             | Required    |
| `region`      | Region code for localized results.               | "wt-wt", "us-en", "uk-en", etc.             | "wt-wt"     |
| `safesearch`  | Filters explicit content.                        | "on", "moderate", "off"                     | "moderate"  |
| `timelimit`   | Limits results by time.                          | "d" (day), "w" (week), "m" (month)          | None        |
| `max_results` | Maximum number of results.                       | Any integer (e.g., 3)                       | None        |
| `max_pages`   | Maximum pages to fetch.                          | Any integer (e.g., 5)                       | 5           |

**Example Request**:
```bash
curl -X POST "http://localhost:8080/news" -H "Content-Type: application/json" -d '{
  "keywords": "technology",
  "max_results": 3,
  "timelimit": "d"
}'
```

**Example Response**:
```javascript
{
  "results": [
    {
      "date": "2025-03-30T12:00:00+00:00",
      "title": "New Tech Breakthrough",
      "body": "A summary of the article...",
      "url": "https://news.example.com/article",
      "image": "https://news.example.com/image.jpg",
      "source": "Tech News"
    }
  ]
}
```

**Implementation Notes**: Queries `https://duckduckgo.com/news.js` via the `news` method in `aDDGS`. Results include ISO-formatted dates and are deduplicated by URL.

---

### Advanced Search Syntax

The `keywords` parameter in the `/text` endpoint supports DuckDuckGo's advanced search operators for precise queries. Below is a comprehensive table of operators as of March 31, 2025:

| **Operator**            | **Example**                  | **Result**                                                                                   |
|-------------------------|------------------------------|---------------------------------------------------------------------------------------------|
| (space)                 | `cats dogs`                  | Results containing "cats" OR "dogs" (implicit OR)                                           |
| `"exact phrase"`        | `"cats and dogs"`            | Exact phrase match; falls back to related results if no exact match                         |
| `-exclude`              | `cats -dogs`                 | Results with "cats" but fewer or no "dogs"                                                  |
| `+include`              | `cats +dogs`                 | Results with "cats" and more emphasis on "dogs"                                             |
| `filetype:`             | `cats filetype:pdf`          | Results in specific file types (pdf, doc, docx, xls, xlsx, ppt, pptx, html)                 |
| `site:`                 | `dogs site:example.com`      | Results from a specific site (e.g., example.com)                                            |
| `-site:`                | `cats -site:example.com`     | Excludes results from a specific site                                                       |
| `intitle:`              | `intitle:dogs`               | Results with "dogs" in the page title                                                       |
| `inurl:`                | `inurl:cats`                 | Results with "cats" in the URL                                                              |
| `*` (wildcard)          | `cat*`                       | Matches variations (e.g., "cats", "caterpillar")                                            |
| `OR`                    | `cats OR dogs`               | Results containing either "cats" or "dogs" (case-sensitive operator)                        |
| `()` (grouping)         | `(cats dogs) -mice`          | Groups terms for complex queries; here, "cats" or "dogs" excluding "mice"                   |
| `intext:`               | `intext:programming`         | Results with "programming" in the body text                                                 |
| `allintitle:`           | `allintitle:cats dogs`       | All terms must appear in the title                                                          |
| `allinurl:`             | `allinurl:cats dogs`         | All terms must appear in the URL                                                            |
| `allintext:`            | `allintext:cats dogs`        | All terms must appear in the body text                                                      |
| `related:`              | `related:python.org`