Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-duckduckgo-search for 
openSUSE:Factory checked in at 2025-04-30 19:05:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-duckduckgo-search (Old)
 and      /work/SRC/openSUSE:Factory/.python-duckduckgo-search.new.30101 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-duckduckgo-search"

Wed Apr 30 19:05:48 2025 rev:4 rq:1273675 version:8.0.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-duckduckgo-search/python-duckduckgo-search.changes
        2025-01-02 19:24:22.899797131 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-duckduckgo-search.new.30101/python-duckduckgo-search.changes
     2025-04-30 19:06:01.930453060 +0200
@@ -1,0 +2,71 @@
+Wed Apr 30 11:05:28 UTC 2025 - Felix Stegmeier <felix.stegme...@suse.com>
+
+- Update to 8.0.1:
+  * refactor: remove dead code
+  * refactor: remove dead code
+  * refactor: bump to primp=0.15.0
+
+- Update to 8.0.0:
+  * Chat moved to duckai package.
+  * feat(chat): remove chat
+  * fix(typing): fix typing in cli and tests
+
+- Update to 7.5.5:
+  * fix(chat): add _chat_xfe
+
+- Update to 7.5.4:
+  * fix(chat): x-vqd-hash-1 = ""
+
+- Update to 7.5.3:
+  * DDGS.chat: bugfix by @deedy5 in #294
+
+- Update to 7.5.2:
+  * fix(temp): don't set Client.headers
+  * Full Changelog: v7.5.1...v7.5.2
+
+- Update to 7.5.1:
+  * Bugfix DDGS.text() payload by @deedy5 in #291
+
+- Update to 7.5.0:
+  * chore: bump primp to v0.14.0
+  * tests: sleep 2 seconds between tests
+  * feat(chat): add mistral-small-3
+  * feat(chat): stream response
+  * feat(cli chat): stream response
+
+- Update to 7.4.5:
+  * Chat: bugfix ConversationLimitException by @deedy5 in #288
+
+- Update to 7.4.4:
+  * DDGS.chat: add mistral-small-3 by @deedy5 in #285
+  * fix(patch): patch only while sending request
+
+- Update to 7.4.3:
+  * feat: patch httpcore
+  * feat: improve ssl_context
+
+- Update to 7.4.2:
+  * Use httpx for requests by @deedy5 in #282
+  * Cli(chat): stream answer, add DDGS.chat_yield (response message generator) 
by @deedy5 in #283
+
+- Update to 7.3.2:
+  * DDGS.chat: add llama-3.3-70b model by @deedy5 in #281
+
+- Update to 7.3.1:
+  * DDGS.chat: add o3-mini model by @deedy5 in #280
+
+- Update to 7.3.0:
+  * clarify exceptions in README by @vpoulailleau in #275
+  * Drop Support for Python 3.8 by @deedy5 in #276
+  * Bump primp to v0.11.0
+  * Add _impersonates_os
+
+- Update to 7.2.1:
+  * Bump primp to v0.10.0
+  * DDGS._impersonates: add "firefox_128"
+
+- Update to 7.2.1:
+  * Bump primp to v0.10.0
+  * DDGS._impersonates: add "firefox_128"
+
+-------------------------------------------------------------------

Old:
----
  duckduckgo_search-7.1.1.tar.gz

New:
----
  duckduckgo_search-8.0.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-duckduckgo-search.spec ++++++
--- /var/tmp/diff_new_pack.mRj9wI/_old  2025-04-30 19:06:02.502476912 +0200
+++ /var/tmp/diff_new_pack.mRj9wI/_new  2025-04-30 19:06:02.502476912 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-duckduckgo-search
-Version:        7.1.1
+Version:        8.0.1
 Release:        0
 Summary:        Search using the DuckDuckGo.com search engine
 License:        MIT
@@ -31,17 +31,17 @@
 BuildRequires:  %{python_module wheel}
 BuildRequires:  python-rpm-macros
 # SECTION test requirements
-BuildRequires:  %{python_module click >= 8.1.7}
-BuildRequires:  %{python_module primp >= 0.6.3}
+BuildRequires:  %{python_module click >= 8.1.8}
+BuildRequires:  %{python_module primp >= 0.15.0}
 # /SECTION
 BuildRequires:  fdupes
-Requires:       python-click >= 8.1.7
-Requires:       python-primp >= 0.6.3
-Suggests:       python-lxml >= 5.2.2
-Suggests:       python-mypy >= 1.11.1
-Suggests:       python-pytest >= 8.3.1
-Suggests:       python-pytest-asyncio >= 0.23.8
-Suggests:       python-ruff >= 0.6.1
+Requires:       python-click >= 8.1.8
+Requires:       python-lxml >= 5.3.0
+Requires:       python-primp >= 0.15.0
+Suggests:       python-mypy >= 1.14.1
+Suggests:       python-pytest >= 8.3.4
+Suggests:       python-pytest-dependency >= 0.6.0
+Suggests:       python-ruff >= 0.9.2
 Requires(post): update-alternatives
 Requires(postun): update-alternatives
 BuildArch:      noarch

++++++ duckduckgo_search-7.1.1.tar.gz -> duckduckgo_search-8.0.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/PKG-INFO 
new/duckduckgo_search-8.0.1/PKG-INFO
--- old/duckduckgo_search-7.1.1/PKG-INFO        2024-12-27 00:22:58.106326300 
+0100
+++ new/duckduckgo_search-8.0.1/PKG-INFO        2025-04-17 14:37:28.462133000 
+0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: duckduckgo_search
-Version: 7.1.1
+Version: 8.0.1
 Summary: Search for words, documents, images, news, maps and text translation 
using the DuckDuckGo.com search engine.
 Author: deedy5
 License: MIT License
@@ -11,7 +11,6 @@
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
@@ -20,22 +19,25 @@
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/markdown
 License-File: LICENSE.md
-Requires-Dist: click>=8.1.7
-Requires-Dist: primp>=0.9.2
+Requires-Dist: click>=8.1.8
+Requires-Dist: primp>=0.15.0
 Requires-Dist: lxml>=5.3.0
 Provides-Extra: dev
-Requires-Dist: mypy>=1.13.0; extra == "dev"
+Requires-Dist: mypy>=1.14.1; extra == "dev"
 Requires-Dist: pytest>=8.3.4; extra == "dev"
 Requires-Dist: pytest-dependency>=0.6.0; extra == "dev"
-Requires-Dist: ruff>=0.8.3; extra == "dev"
+Requires-Dist: ruff>=0.9.2; extra == "dev"
+Dynamic: license-file
 
-![Python >= 3.8](https://img.shields.io/badge/python->=3.8-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search)](https://pepy.tech/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search/week)](https://pepy.tech/project/duckduckgo-search)
+![Python >= 3.9](https://img.shields.io/badge/python->=3.9-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 # Duckduckgo_search<a name="TOP"></a>
 
-AI chat and search for text, news, images and videos using the DuckDuckGo.com 
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search 
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
 
 ## Table of Contents
 * [Install](#install)
@@ -45,11 +47,10 @@
 * [DDGS class](#ddgs-class)
 * [Proxy](#proxy)
 * [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
 * [Disclaimer](#disclaimer)
 
 ## Install
@@ -64,8 +65,6 @@
 ```
 CLI examples:
 ```python3
-# AI chat
-ddgs chat
 # text search
 ddgs text -k "Assyrian siege of Jerusalem"
 # find and download pdf files via proxy
@@ -227,38 +226,24 @@
 
 ## Exceptions
 
+```python
+from duckduckgo_search.exceptions import (
+    ConversationLimitException,
+    DuckDuckGoSearchException,
+    RatelimitException,
+    TimeoutException,
+)
+```
+
 Exceptions:
 - `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
 - `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for 
exceeding API request rate limits.
 - `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API 
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) 
-> str:
-    """Initiates a chat session with DuckDuckGo AI.
-
-    Args:
-        keywords (str): The initial message or question to send to the AI.
-        model (str): The model to use: "gpt-4o-mini", "claude-3-haiku", 
"llama-3.1-70b", "mixtral-8x7b".
-            Defaults to "gpt-4o-mini".
-        timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
-    Returns:
-        str: The response from the AI.
-    """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator", 
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException, 
raised for conversation limit during API requests to AI endpoint.
 
 [Go To TOP](#TOP)
 
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
 
 ```python
 def text(
@@ -276,12 +261,10 @@
         region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
         safesearch: on, moderate, off. Defaults to "moderate".
         timelimit: d, w, m, y. Defaults to None.
-        backend: auto, api, html, lite. Defaults to auto.
+        backend: auto, html, lite. Defaults to auto.
             auto - try all backends in random order,
-            api - collect data from https://duckduckgo.com,
             html - collect data from https://html.duckduckgo.com,
-            lite - collect data from https://lite.duckduckgo.com,
-            ecosia - collect data from https://www.ecosia.com.
+            lite - collect data from https://lite.duckduckgo.com.
         max_results: max number of results. If None, returns results only from 
the first response. Defaults to None.
 
     Returns:
@@ -305,7 +288,7 @@
 
 [Go To TOP](#TOP)
 
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
 
 ```python
 def images(
@@ -372,7 +355,7 @@
 
 [Go To TOP](#TOP)
 
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
 
 ```python
 def videos(
@@ -439,7 +422,7 @@
 
 [Go To TOP](#TOP)
 
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
 
 ```python
 def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/README.md 
new/duckduckgo_search-8.0.1/README.md
--- old/duckduckgo_search-7.1.1/README.md       2024-12-27 00:22:48.000000000 
+0100
+++ new/duckduckgo_search-8.0.1/README.md       2025-04-17 14:37:17.000000000 
+0200
@@ -1,7 +1,9 @@
-![Python >= 3.8](https://img.shields.io/badge/python->=3.8-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search)](https://pepy.tech/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search/week)](https://pepy.tech/project/duckduckgo-search)
+![Python >= 3.9](https://img.shields.io/badge/python->=3.9-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 # Duckduckgo_search<a name="TOP"></a>
 
-AI chat and search for text, news, images and videos using the DuckDuckGo.com 
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search 
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
 
 ## Table of Contents
 * [Install](#install)
@@ -11,11 +13,10 @@
 * [DDGS class](#ddgs-class)
 * [Proxy](#proxy)
 * [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
 * [Disclaimer](#disclaimer)
 
 ## Install
@@ -30,8 +31,6 @@
 ```
 CLI examples:
 ```python3
-# AI chat
-ddgs chat
 # text search
 ddgs text -k "Assyrian siege of Jerusalem"
 # find and download pdf files via proxy
@@ -193,38 +192,24 @@
 
 ## Exceptions
 
+```python
+from duckduckgo_search.exceptions import (
+    ConversationLimitException,
+    DuckDuckGoSearchException,
+    RatelimitException,
+    TimeoutException,
+)
+```
+
 Exceptions:
 - `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
 - `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for 
exceeding API request rate limits.
 - `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API 
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) 
-> str:
-    """Initiates a chat session with DuckDuckGo AI.
-
-    Args:
-        keywords (str): The initial message or question to send to the AI.
-        model (str): The model to use: "gpt-4o-mini", "claude-3-haiku", 
"llama-3.1-70b", "mixtral-8x7b".
-            Defaults to "gpt-4o-mini".
-        timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
-    Returns:
-        str: The response from the AI.
-    """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator", 
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException, 
raised for conversation limit during API requests to AI endpoint.
 
 [Go To TOP](#TOP)
 
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
 
 ```python
 def text(
@@ -242,12 +227,10 @@
         region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
         safesearch: on, moderate, off. Defaults to "moderate".
         timelimit: d, w, m, y. Defaults to None.
-        backend: auto, api, html, lite. Defaults to auto.
+        backend: auto, html, lite. Defaults to auto.
             auto - try all backends in random order,
-            api - collect data from https://duckduckgo.com,
             html - collect data from https://html.duckduckgo.com,
-            lite - collect data from https://lite.duckduckgo.com,
-            ecosia - collect data from https://www.ecosia.com.
+            lite - collect data from https://lite.duckduckgo.com.
         max_results: max number of results. If None, returns results only from 
the first response. Defaults to None.
 
     Returns:
@@ -271,7 +254,7 @@
 
 [Go To TOP](#TOP)
 
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
 
 ```python
 def images(
@@ -338,7 +321,7 @@
 
 [Go To TOP](#TOP)
 
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
 
 ```python
 def videos(
@@ -405,7 +388,7 @@
 
 [Go To TOP](#TOP)
 
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
 
 ```python
 def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/duckduckgo_search/cli.py 
new/duckduckgo_search-8.0.1/duckduckgo_search/cli.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/cli.py        2024-12-27 
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/cli.py        2025-04-17 
14:37:17.000000000 +0200
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
 import csv
 import logging
 import os
-import sys
 from concurrent.futures import ThreadPoolExecutor, as_completed
 from datetime import datetime
 from pathlib import Path
@@ -11,7 +12,7 @@
 import primp
 
 from .duckduckgo_search import DDGS
-from .utils import _expand_proxy_tb_alias, json_dumps, json_loads
+from .utils import _expand_proxy_tb_alias, json_dumps
 from .version import __version__
 
 logger = logging.getLogger(__name__)
@@ -36,7 +37,7 @@
 }
 
 
-def _save_data(keywords, data, function_name, filename):
+def _save_data(keywords: str, data: list[dict[str, str]], function_name: str, 
filename: str | None) -> None:
     filename, ext = filename.rsplit(".", 1) if filename and 
filename.endswith((".csv", ".json")) else (None, filename)
     filename = filename if filename else 
f"{function_name}_{keywords}_{datetime.now():%Y%m%d_%H%M%S}"
     if ext == "csv":
@@ -45,12 +46,12 @@
         _save_json(f"{filename}.{ext}", data)
 
 
-def _save_json(jsonfile, data):
+def _save_json(jsonfile: str | Path, data: list[dict[str, str]]) -> None:
     with open(jsonfile, "w", encoding="utf-8") as file:
         file.write(json_dumps(data))
 
 
-def _save_csv(csvfile, data):
+def _save_csv(csvfile: str | Path, data: list[dict[str, str]]) -> None:
     with open(csvfile, "w", newline="", encoding="utf-8") as file:
         if data:
             headers = data[0].keys()
@@ -59,7 +60,7 @@
             writer.writerows(data)
 
 
-def _print_data(data):
+def _print_data(data: list[dict[str, str]]) -> None:
     if data:
         for i, e in enumerate(data, start=1):
             click.secho(f"{i}.\t    {'=' * 78}", bg="black", fg="white")
@@ -76,7 +77,7 @@
             input()
 
 
-def _sanitize_keywords(keywords):
+def _sanitize_keywords(keywords: str) -> str:
     keywords = (
         keywords.replace("filetype", "")
         .replace(":", "")
@@ -90,9 +91,11 @@
     return keywords
 
 
-def _download_file(url, dir_path, filename, proxy, verify):
+def _download_file(url: str, dir_path: str, filename: str, proxy: str | None, 
verify: bool) -> None:
     try:
-        resp = primp.Client(proxy=proxy, impersonate="chrome_131", timeout=10, 
verify=verify).get(url)
+        resp = primp.Client(proxy=proxy, impersonate="random", 
impersonate_os="random", timeout=10, verify=verify).get(
+            url
+        )
         if resp.status_code == 200:
             with open(os.path.join(dir_path, filename[:200]), "wb") as file:
                 file.write(resp.content)
@@ -100,7 +103,15 @@
         logger.debug(f"download_file url={url} {type(ex).__name__} {ex}")
 
 
-def _download_results(keywords, results, function_name, proxy=None, 
threads=None, verify=True, pathname=None):
+def _download_results(
+    keywords: str,
+    results: list[dict[str, str]],
+    function_name: str,
+    proxy: str | None = None,
+    threads: int | None = None,
+    verify: bool = True,
+    pathname: str | None = None,
+) -> None:
     path = pathname if pathname else 
f"{function_name}_{keywords}_{datetime.now():%Y%m%d_%H%M%S}"
     os.makedirs(path, exist_ok=True)
 
@@ -113,7 +124,7 @@
             f = executor.submit(_download_file, url, path, f"{i}_{filename}", 
proxy, verify)
             futures.append(f)
 
-        with click.progressbar(
+        with click.progressbar(  # type: ignore
             length=len(futures), label="Downloading", show_percent=True, 
show_pos=True, width=50
         ) as bar:
             for future in as_completed(futures):
@@ -122,12 +133,12 @@
 
 
 @click.group(chain=True)
-def cli():
+def cli() -> None:
     """duckduckgo_search CLI tool"""
     pass
 
 
-def safe_entry_point():
+def safe_entry_point() -> None:
     try:
         cli()
     except Exception as ex:
@@ -135,60 +146,12 @@
 
 
 @cli.command()
-def version():
+def version() -> str:
     print(__version__)
     return __version__
 
 
 @cli.command()
-@click.option("-l", "--load", is_flag=True, default=False, help="load the last 
conversation from the json cache")
-@click.option("-p", "--proxy", help="the proxy to send requests, example: 
socks5://127.0.0.1:9150")
-@click.option("-ml", "--multiline", is_flag=True, default=False, 
help="multi-line input")
-@click.option("-t", "--timeout", default=30, help="timeout value for the HTTP 
client")
-@click.option("-v", "--verify", default=True, help="verify SSL when making the 
request")
-@click.option(
-    "-m",
-    "--model",
-    prompt="""DuckDuckGo AI chat. Choose a model:
-[1]: gpt-4o-mini
-[2]: claude-3-haiku
-[3]: llama-3.1-70b
-[4]: mixtral-8x7b
-""",
-    type=click.Choice(["1", "2", "3", "4"]),
-    show_choices=False,
-    default="1",
-)
-def chat(load, proxy, multiline, timeout, verify, model):
-    """CLI function to perform an interactive AI chat using DuckDuckGo API."""
-    client = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify)
-    model = ["gpt-4o-mini", "claude-3-haiku", "llama-3.1-70b", 
"mixtral-8x7b"][int(model) - 1]
-
-    cache_file = "ddgs_chat_conversation.json"
-    if load and Path(cache_file).exists():
-        with open(cache_file) as f:
-            cache = json_loads(f.read())
-            client._chat_vqd = cache.get("vqd", None)
-            client._chat_messages = cache.get("messages", [])
-            client._chat_tokens_count = cache.get("tokens", 0)
-
-    while True:
-        print(f"{'-'*78}\nYou[{model=} tokens={client._chat_tokens_count}]: ", 
end="")
-        if multiline:
-            print(f"""[multiline, send message: ctrl+{"Z" if sys.platform == 
"win32" else "D"}]""")
-            user_input = sys.stdin.read()
-            print("...")
-        else:
-            user_input = input()
-        if user_input.strip():
-            resp_answer = client.chat(keywords=user_input, model=model, 
timeout=timeout)
-            click.secho(f"AI: {resp_answer}", fg="green")
-
-            cache = {"vqd": client._chat_vqd, "tokens": 
client._chat_tokens_count, "messages": client._chat_messages}
-            _save_json(cache_file, cache)
-
-
-@cli.command()
 @click.option("-k", "--keywords", required=True, help="text search, keywords 
for query")
 @click.option("-r", "--region", default="wt-wt", help="wt-wt, us-en, ru-ru, 
etc. -region https://duckduckgo.com/params";)
 @click.option("-s", "--safesearch", default="moderate", 
type=click.Choice(["on", "moderate", "off"]))
@@ -197,24 +160,24 @@
 @click.option("-o", "--output", help="csv, json or filename.csv|json (save the 
results to a csv or json file)")
 @click.option("-d", "--download", is_flag=True, default=False, help="download 
results. -dd to set custom directory")
 @click.option("-dd", "--download-directory", help="Specify custom download 
directory")
-@click.option("-b", "--backend", default="auto", type=click.Choice(["auto", 
"api", "html", "lite", "ecosia"]))
+@click.option("-b", "--backend", default="auto", type=click.Choice(["auto", 
"html", "lite"]))
 @click.option("-th", "--threads", default=10, help="download threads, 
default=10")
 @click.option("-p", "--proxy", help="the proxy to send requests, example: 
socks5://127.0.0.1:9150")
 @click.option("-v", "--verify", default=True, help="verify SSL when making the 
request")
 def text(
-    keywords,
-    region,
-    safesearch,
-    timelimit,
-    backend,
-    output,
-    download,
-    download_directory,
-    threads,
-    max_results,
-    proxy,
-    verify,
-):
+    keywords: str,
+    region: str,
+    safesearch: str,
+    timelimit: str | None,
+    backend: str,
+    output: str | None,
+    download: bool,
+    download_directory: str | None,
+    threads: int,
+    max_results: int | None,
+    proxy: str | None,
+    verify: bool,
+) -> None:
     """CLI function to perform a text search using DuckDuckGo API."""
     data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).text(
         keywords=keywords,
@@ -284,23 +247,23 @@
 @click.option("-p", "--proxy", help="the proxy to send requests, example: 
socks5://127.0.0.1:9150")
 @click.option("-v", "--verify", default=True, help="verify SSL when making the 
request")
 def images(
-    keywords,
-    region,
-    safesearch,
-    timelimit,
-    size,
-    color,
-    type_image,
-    layout,
-    license_image,
-    download,
-    download_directory,
-    threads,
-    max_results,
-    output,
-    proxy,
-    verify,
-):
+    keywords: str,
+    region: str,
+    safesearch: str,
+    timelimit: str | None,
+    size: str | None,
+    color: str | None,
+    type_image: str | None,
+    layout: str | None,
+    license_image: str | None,
+    download: bool,
+    download_directory: str | None,
+    threads: int,
+    max_results: int | None,
+    output: str | None,
+    proxy: str | None,
+    verify: bool,
+) -> None:
     """CLI function to perform a images search using DuckDuckGo API."""
     data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).images(
         keywords=keywords,
@@ -344,8 +307,18 @@
 @click.option("-p", "--proxy", help="the proxy to send requests, example: 
socks5://127.0.0.1:9150")
 @click.option("-v", "--verify", default=True, help="verify SSL when making the 
request")
 def videos(
-    keywords, region, safesearch, timelimit, resolution, duration, 
license_videos, max_results, output, proxy, verify
-):
+    keywords: str,
+    region: str,
+    safesearch: str,
+    timelimit: str | None,
+    resolution: str | None,
+    duration: str | None,
+    license_videos: str | None,
+    max_results: int | None,
+    output: str | None,
+    proxy: str | None,
+    verify: bool,
+) -> None:
     """CLI function to perform a videos search using DuckDuckGo API."""
     data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).videos(
         keywords=keywords,
@@ -373,7 +346,16 @@
 @click.option("-o", "--output", help="csv, json or filename.csv|json (save the 
results to a csv or json file)")
 @click.option("-p", "--proxy", help="the proxy to send requests, example: 
socks5://127.0.0.1:9150")
 @click.option("-v", "--verify", default=True, help="verify SSL when making the 
request")
-def news(keywords, region, safesearch, timelimit, max_results, output, proxy, 
verify):
+def news(
+    keywords: str,
+    region: str,
+    safesearch: str,
+    timelimit: str | None,
+    max_results: int | None,
+    output: str | None,
+    proxy: str | None,
+    verify: bool,
+) -> None:
     """CLI function to perform a news search using DuckDuckGo API."""
     data = DDGS(proxy=_expand_proxy_tb_alias(proxy), verify=verify).news(
         keywords=keywords, region=region, safesearch=safesearch, 
timelimit=timelimit, max_results=max_results
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/duckduckgo_search-7.1.1/duckduckgo_search/duckduckgo_search.py 
new/duckduckgo_search-8.0.1/duckduckgo_search/duckduckgo_search.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/duckduckgo_search.py  
2024-12-27 00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/duckduckgo_search.py  
2025-04-17 14:37:17.000000000 +0200
@@ -6,17 +6,17 @@
 from datetime import datetime, timezone
 from functools import cached_property
 from itertools import cycle
-from random import choice, shuffle
+from random import shuffle
 from time import sleep, time
 from types import TracebackType
-from typing import cast
+from typing import Any, Literal
 
-import primp  # type: ignore
+import primp
 from lxml.etree import _Element
 from lxml.html import HTMLParser as LHTMLParser
 from lxml.html import document_fromstring
 
-from .exceptions import ConversationLimitException, DuckDuckGoSearchException, 
RatelimitException, TimeoutException
+from .exceptions import DuckDuckGoSearchException, RatelimitException, 
TimeoutException
 from .utils import (
     _expand_proxy_tb_alias,
     _extract_vqd,
@@ -31,20 +31,6 @@
 class DDGS:
     """DuckDuckgo_search class to get search results from duckduckgo.com."""
 
-    _impersonates = (
-        "chrome_100", "chrome_101", "chrome_104", "chrome_105", "chrome_106", 
"chrome_107",
-        "chrome_108", "chrome_109", "chrome_114", "chrome_116", "chrome_117", 
"chrome_118",
-        "chrome_119", "chrome_120", "chrome_123", "chrome_124", "chrome_126", 
"chrome_127",
-        "chrome_128", "chrome_129", "chrome_130", "chrome_131",
-        "safari_ios_16.5", "safari_ios_17.2", "safari_ios_17.4.1", 
"safari_ios_18.1.1",
-        "safari_15.3", "safari_15.5", "safari_15.6.1", "safari_16", 
"safari_16.5",
-        "safari_17.0", "safari_17.2.1", "safari_17.4.1", "safari_17.5",
-        "safari_18", "safari_18.2",
-        "safari_ipad_18",
-        "edge_101", "edge_122", "edge_127", "edge_131",
-        "firefox_109", "firefox_133",
-    )  # fmt: skip
-
     def __init__(
         self,
         headers: dict[str, str] | None = None,
@@ -70,19 +56,18 @@
             self.proxy = proxies.get("http") or proxies.get("https") if 
isinstance(proxies, dict) else proxies
         self.headers = headers if headers else {}
         self.headers["Referer"] = "https://duckduckgo.com/";
+        self.timeout = timeout
         self.client = primp.Client(
-            headers=self.headers,
+            # headers=self.headers,
             proxy=self.proxy,
-            timeout=timeout,
+            timeout=self.timeout,
             cookie_store=True,
             referer=True,
-            impersonate=choice(self._impersonates),
+            impersonate="random",
+            impersonate_os="random",
             follow_redirects=False,
             verify=verify,
         )
-        self._chat_messages: list[dict[str, str]] = []
-        self._chat_tokens_count = 0
-        self._chat_vqd: str = ""
         self.sleep_timestamp = 0.0
 
     def __enter__(self) -> DDGS:
@@ -109,99 +94,45 @@
 
     def _get_url(
         self,
-        method: str,
+        method: Literal["GET", "HEAD", "OPTIONS", "DELETE", "POST", "PUT", 
"PATCH"],
         url: str,
         params: dict[str, str] | None = None,
         content: bytes | None = None,
-        data: dict[str, str] | bytes | None = None,
+        data: dict[str, str] | None = None,
+        headers: dict[str, str] | None = None,
         cookies: dict[str, str] | None = None,
-    ) -> bytes:
+        json: Any = None,
+        timeout: float | None = None,
+    ) -> Any:
         self._sleep()
         try:
-            resp = self.client.request(method, url, params=params, 
content=content, data=data, cookies=cookies)
+            resp = self.client.request(
+                method,
+                url,
+                params=params,
+                content=content,
+                data=data,
+                headers=headers,
+                cookies=cookies,
+                json=json,
+                timeout=timeout or self.timeout,
+            )
         except Exception as ex:
             if "time" in str(ex).lower():
                 raise TimeoutException(f"{url} {type(ex).__name__}: {ex}") 
from ex
             raise DuckDuckGoSearchException(f"{url} {type(ex).__name__}: 
{ex}") from ex
-        logger.debug(f"_get_url() {resp.url} {resp.status_code} 
{len(resp.content)}")
+        logger.debug(f"_get_url() {resp.url} {resp.status_code}")
         if resp.status_code == 200:
-            return cast(bytes, resp.content)
-        elif resp.status_code in (202, 301, 403):
+            return resp
+        elif resp.status_code in (202, 301, 403, 400, 429, 418):
             raise RatelimitException(f"{resp.url} {resp.status_code} 
Ratelimit")
         raise DuckDuckGoSearchException(f"{resp.url} return None. {params=} 
{content=} {data=}")
 
     def _get_vqd(self, keywords: str) -> str:
         """Get vqd value for a search query."""
-        resp_content = self._get_url("GET", "https://duckduckgo.com";, 
params={"q": keywords})
+        resp_content = self._get_url("GET", "https://duckduckgo.com";, 
params={"q": keywords}).content
         return _extract_vqd(resp_content, keywords)
 
-    def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 
30) -> str:
-        """Initiates a chat session with DuckDuckGo AI.
-
-        Args:
-            keywords (str): The initial message or question to send to the AI.
-            model (str): The model to use: "gpt-4o-mini", "claude-3-haiku", 
"llama-3.1-70b", "mixtral-8x7b".
-                Defaults to "gpt-4o-mini".
-            timeout (int): Timeout value for the HTTP client. Defaults to 20.
-
-        Returns:
-            str: The response from the AI.
-        """
-        models_deprecated = {
-            "gpt-3.5": "gpt-4o-mini",
-            "llama-3-70b": "llama-3.1-70b",
-        }
-        if model in models_deprecated:
-            logger.info(f"{model=} is deprecated, using 
{models_deprecated[model]}")
-            model = models_deprecated[model]
-        models = {
-            "claude-3-haiku": "claude-3-haiku-20240307",
-            "gpt-4o-mini": "gpt-4o-mini",
-            "llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
-            "mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1",
-        }
-        # vqd
-        if not self._chat_vqd:
-            resp = 
self.client.get("https://duckduckgo.com/duckchat/v1/status";, 
headers={"x-vqd-accept": "1"})
-            self._chat_vqd = resp.headers.get("x-vqd-4", "")
-
-        self._chat_messages.append({"role": "user", "content": keywords})
-        self._chat_tokens_count += len(keywords) // 4 if len(keywords) >= 4 
else 1  # approximate number of tokens
-
-        json_data = {
-            "model": models[model],
-            "messages": self._chat_messages,
-        }
-        resp = self.client.post(
-            "https://duckduckgo.com/duckchat/v1/chat";,
-            headers={"x-vqd-4": self._chat_vqd},
-            json=json_data,
-            timeout=timeout,
-        )
-        self._chat_vqd = resp.headers.get("x-vqd-4", "")
-
-        data = ",".join(x for line in 
resp.text.rstrip("[DONE]LIMT_CVRSA\n").split("data:") if (x := line.strip()))
-        data = json_loads("[" + data + "]")
-
-        results = []
-        for x in data:
-            if x.get("action") == "error":
-                err_message = x.get("type", "")
-                if x.get("status") == 429:
-                    raise (
-                        ConversationLimitException(err_message)
-                        if err_message == "ERR_CONVERSATION_LIMIT"
-                        else RatelimitException(err_message)
-                    )
-                raise DuckDuckGoSearchException(err_message)
-            elif message := x.get("message"):
-                results.append(message)
-        result = "".join(results)
-
-        self._chat_messages.append({"role": "assistant", "content": result})
-        self._chat_tokens_count += len(results)
-        return result
-
     def text(
         self,
         keywords: str,
@@ -218,11 +149,10 @@
             region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
             safesearch: on, moderate, off. Defaults to "moderate".
             timelimit: d, w, m, y. Defaults to None.
-            backend: auto, api, html, lite. Defaults to auto.
+            backend: auto, html, lite. Defaults to auto.
                 auto - try all backends in random order,
                 html - collect data from https://html.duckduckgo.com,
-                lite - collect data from https://lite.duckduckgo.com,
-                ecosia - collect data from https://www.ecosia.com.
+                lite - collect data from https://lite.duckduckgo.com.
             max_results: max number of results. If None, returns results only 
from the first response. Defaults to None.
 
         Returns:
@@ -233,10 +163,10 @@
             RatelimitException: Inherits from DuckDuckGoSearchException, 
raised for exceeding API request rate limits.
             TimeoutException: Inherits from DuckDuckGoSearchException, raised 
for API request timeouts.
         """
-        if backend == "api":
-            warnings.warn("'api' backend is deprecated, using backend='auto'", 
stacklevel=2)
+        if backend in ("api", "ecosia"):
+            warnings.warn(f"{backend=} is deprecated, using backend='auto'", 
stacklevel=2)
             backend = "auto"
-        backends = ["html", "lite", "ecosia"] if backend == "auto" else 
[backend]
+        backends = ["html", "lite"] if backend == "auto" else [backend]
         shuffle(backends)
 
         results, err = [], None
@@ -246,8 +176,6 @@
                     results = self._text_html(keywords, region, timelimit, 
max_results)
                 elif b == "lite":
                     results = self._text_lite(keywords, region, timelimit, 
max_results)
-                elif b == "ecosia":
-                    results = self._text_ecosia(keywords, region, safesearch, 
max_results)
                 return results
             except Exception as ex:
                 logger.info(f"Error to search using {b} backend: {ex}")
@@ -266,12 +194,8 @@
 
         payload = {
             "q": keywords,
-            "s": "0",
-            "o": "json",
-            "api": "d.js",
-            "vqd": "",
+            "b": "",
             "kl": region,
-            "bing_market": region,
         }
         if timelimit:
             payload["df"] = timelimit
@@ -280,7 +204,7 @@
         results: list[dict[str, str]] = []
 
         for _ in range(5):
-            resp_content = self._get_url("POST", 
"https://html.duckduckgo.com/html";, data=payload)
+            resp_content = self._get_url("POST", 
"https://html.duckduckgo.com/html";, data=payload).content
             if b"No  results." in resp_content:
                 return results
 
@@ -338,12 +262,7 @@
 
         payload = {
             "q": keywords,
-            "s": "0",
-            "o": "json",
-            "api": "d.js",
-            "vqd": "",
             "kl": region,
-            "bing_market": region,
         }
         if timelimit:
             payload["df"] = timelimit
@@ -352,7 +271,7 @@
         results: list[dict[str, str]] = []
 
         for _ in range(5):
-            resp_content = self._get_url("POST", 
"https://lite.duckduckgo.com/lite/";, data=payload)
+            resp_content = self._get_url("POST", 
"https://lite.duckduckgo.com/lite/";, data=payload).content
             if b"No more results." in resp_content:
                 return results
 
@@ -397,85 +316,15 @@
                             if max_results and len(results) >= max_results:
                                 return results
 
-            next_page_s = tree.xpath("//form[./input[contains(@value, 
'ext')]]/input[@name='s']/@value")
-            if not next_page_s or not max_results:
-                return results
-            elif isinstance(next_page_s, list):
-                payload["s"] = str(next_page_s[0])
-
-        return results
-
-    def _text_ecosia(
-        self,
-        keywords: str,
-        region: str = "wt-wt",
-        safesearch: str = "moderate",
-        max_results: int | None = None,
-    ) -> list[dict[str, str]]:
-        assert keywords, "keywords is mandatory"
-
-        payload = {
-            "q": keywords,
-        }
-        cookies = {
-            "a": "0",
-            "as": "0",
-            "cs": "1",
-            "dt": "pc",
-            "f": "y" if safesearch == "on" else "n" if safesearch == "off" 
else "i",
-            "fr": "0",
-            "fs": "1",
-            "l": "en",
-            "lt": f"{int(time() * 1000)}",
-            "mc": f"{region[3:]}-{region[:2]}",
-            "nf": "0",
-            "nt": "0",
-            "pz": "0",
-            "t": "6",
-            "tt": "",
-            "tu": "auto",
-            "wu": "auto",
-            "ma": "1",
-        }
-
-        cache = set()
-        results: list[dict[str, str]] = []
-
-        for _ in range(5):
-            resp_content = self._get_url("GET", 
"https://www.ecosia.org/search";, params=payload, cookies=cookies)
-            if b"Unfortunately we didn\xe2\x80\x99t find any results for" in 
resp_content:
-                return results
-
-            tree = document_fromstring(resp_content, self.parser)
-            elements = tree.xpath("//div[@class='result__body']")
-            if not isinstance(elements, list):
-                return results
-
-            for e in elements:
-                if isinstance(e, _Element):
-                    hrefxpath = 
e.xpath(".//div[@class='result__title']/a/@href")
-                    href = str(hrefxpath[0]) if hrefxpath and 
isinstance(hrefxpath, list) else None
-                    if href and href not in cache:
-                        cache.add(href)
-                        titlexpath = 
e.xpath(".//div[@class='result__title']/a/h2/text()")
-                        title = str(titlexpath[0]) if titlexpath and 
isinstance(titlexpath, list) else ""
-                        bodyxpath = 
e.xpath(".//div[@class='result__description']//text()")
-                        body = "".join(str(x) for x in bodyxpath) if bodyxpath 
and isinstance(bodyxpath, list) else ""
-                        results.append(
-                            {
-                                "title": _normalize(title.strip()),
-                                "href": _normalize_url(href),
-                                "body": _normalize(body.strip()),
-                            }
-                        )
-                        if max_results and len(results) >= max_results:
-                            return results
-
-            npx = tree.xpath("//div[contains(@class, 
'pagination')]//a[contains(@data-test-id, 'next')]/@href")
+            npx = tree.xpath("//form[./input[contains(@value, 'ext')]]")
             if not npx or not max_results:
                 return results
-            if isinstance(npx, list):
-                payload["p"] = str(npx[-1]).split("p=")[1].split("&")[0]
+            next_page = npx[-1] if isinstance(npx, list) else None
+            if isinstance(next_page, _Element):
+                names = next_page.xpath('.//input[@type="hidden"]/@name')
+                values = next_page.xpath('.//input[@type="hidden"]/@value')
+                if isinstance(names, list) and isinstance(values, list):
+                    payload = {str(n): str(v) for n, v in zip(names, values)}
 
         return results
 
@@ -543,7 +392,9 @@
         results: list[dict[str, str]] = []
 
         for _ in range(5):
-            resp_content = self._get_url("GET", "https://duckduckgo.com/i.js";, 
params=payload)
+            resp_content = self._get_url(
+                "GET", "https://duckduckgo.com/i.js";, params=payload, 
headers={"Referer": "https://duckduckgo.com/"}
+            ).content
             resp_json = json_loads(resp_content)
             page_data = resp_json.get("results", [])
 
@@ -623,7 +474,7 @@
         results: list[dict[str, str]] = []
 
         for _ in range(8):
-            resp_content = self._get_url("GET", "https://duckduckgo.com/v.js";, 
params=payload)
+            resp_content = self._get_url("GET", "https://duckduckgo.com/v.js";, 
params=payload).content
             resp_json = json_loads(resp_content)
             page_data = resp_json.get("results", [])
 
@@ -685,7 +536,7 @@
         results: list[dict[str, str]] = []
 
         for _ in range(5):
-            resp_content = self._get_url("GET", 
"https://duckduckgo.com/news.js";, params=payload)
+            resp_content = self._get_url("GET", 
"https://duckduckgo.com/news.js";, params=payload).content
             resp_json = json_loads(resp_content)
             page_data = resp_json.get("results", [])
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/duckduckgo_search/version.py 
new/duckduckgo_search-8.0.1/duckduckgo_search/version.py
--- old/duckduckgo_search-7.1.1/duckduckgo_search/version.py    2024-12-27 
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search/version.py    2025-04-17 
14:37:17.000000000 +0200
@@ -1 +1 @@
-__version__ = "7.1.1"
+__version__ = "8.0.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/PKG-INFO 
new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/PKG-INFO
--- old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/PKG-INFO     
2024-12-27 00:22:58.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/PKG-INFO     
2025-04-17 14:37:28.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: duckduckgo_search
-Version: 7.1.1
+Version: 8.0.1
 Summary: Search for words, documents, images, news, maps and text translation 
using the DuckDuckGo.com search engine.
 Author: deedy5
 License: MIT License
@@ -11,7 +11,6 @@
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
@@ -20,22 +19,25 @@
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Requires-Python: >=3.8
+Requires-Python: >=3.9
 Description-Content-Type: text/markdown
 License-File: LICENSE.md
-Requires-Dist: click>=8.1.7
-Requires-Dist: primp>=0.9.2
+Requires-Dist: click>=8.1.8
+Requires-Dist: primp>=0.15.0
 Requires-Dist: lxml>=5.3.0
 Provides-Extra: dev
-Requires-Dist: mypy>=1.13.0; extra == "dev"
+Requires-Dist: mypy>=1.14.1; extra == "dev"
 Requires-Dist: pytest>=8.3.4; extra == "dev"
 Requires-Dist: pytest-dependency>=0.6.0; extra == "dev"
-Requires-Dist: ruff>=0.8.3; extra == "dev"
+Requires-Dist: ruff>=0.9.2; extra == "dev"
+Dynamic: license-file
 
-![Python >= 3.8](https://img.shields.io/badge/python->=3.8-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search)](https://pepy.tech/project/duckduckgo-search)
 
[![Downloads](https://static.pepy.tech/badge/duckduckgo-search/week)](https://pepy.tech/project/duckduckgo-search)
+![Python >= 3.9](https://img.shields.io/badge/python->=3.9-red.svg) 
[![](https://badgen.net/github/release/deedy5/duckduckgo_search)](https://github.com/deedy5/duckduckgo_search/releases)
 
[![](https://badge.fury.io/py/duckduckgo-search.svg)](https://pypi.org/project/duckduckgo-search)
 # Duckduckgo_search<a name="TOP"></a>
 
-AI chat and search for text, news, images and videos using the DuckDuckGo.com 
search engine.
+Search for text, news, images and videos using the DuckDuckGo.com search 
engine.
+
+:bangbang: AI chat moved to [duckai](https://pypi.org/project/duckai) package
 
 ## Table of Contents
 * [Install](#install)
@@ -45,11 +47,10 @@
 * [DDGS class](#ddgs-class)
 * [Proxy](#proxy)
 * [Exceptions](#exceptions)
-* [1. chat() - AI chat](#1-chat---ai-chat)
-* [2. text() - text search](#2-text---text-search-by-duckduckgocom)
-* [3. images() - image search](#3-images---image-search-by-duckduckgocom)
-* [4. videos() - video search](#4-videos---video-search-by-duckduckgocom)
-* [5. news() - news search](#5-news---news-search-by-duckduckgocom)
+* [1. text() - text search](#2-text---text-search-by-duckduckgocom)
+* [2. images() - image search](#3-images---image-search-by-duckduckgocom)
+* [3. videos() - video search](#4-videos---video-search-by-duckduckgocom)
+* [4. news() - news search](#5-news---news-search-by-duckduckgocom)
 * [Disclaimer](#disclaimer)
 
 ## Install
@@ -64,8 +65,6 @@
 ```
 CLI examples:
 ```python3
-# AI chat
-ddgs chat
 # text search
 ddgs text -k "Assyrian siege of Jerusalem"
 # find and download pdf files via proxy
@@ -227,38 +226,24 @@
 
 ## Exceptions
 
+```python
+from duckduckgo_search.exceptions import (
+    ConversationLimitException,
+    DuckDuckGoSearchException,
+    RatelimitException,
+    TimeoutException,
+)
+```
+
 Exceptions:
 - `DuckDuckGoSearchException`: Base exception for duckduckgo_search errors.
 - `RatelimitException`: Inherits from DuckDuckGoSearchException, raised for 
exceeding API request rate limits.
 - `TimeoutException`: Inherits from DuckDuckGoSearchException, raised for API 
request timeouts.
-
-
-[Go To TOP](#TOP)
-
-## 1. chat() - AI chat
-
-```python
-def chat(self, keywords: str, model: str = "gpt-4o-mini", timeout: int = 30) 
-> str:
-    """Initiates a chat session with DuckDuckGo AI.
-
-    Args:
-        keywords (str): The initial message or question to send to the AI.
-        model (str): The model to use: "gpt-4o-mini", "claude-3-haiku", 
"llama-3.1-70b", "mixtral-8x7b".
-            Defaults to "gpt-4o-mini".
-        timeout (int): Timeout value for the HTTP client. Defaults to 30.
-
-    Returns:
-        str: The response from the AI.
-    """
-```
-***Example***
-```python
-results = DDGS().chat("summarize Daniel Defoe's The Consolidator", 
model='claude-3-haiku')
-```
+- `ConversationLimitException`: Inherits from DuckDuckGoSearchException, 
raised for conversation limit during API requests to AI endpoint.
 
 [Go To TOP](#TOP)
 
-## 2. text() - text search by duckduckgo.com
+## 1. text() - text search by duckduckgo.com
 
 ```python
 def text(
@@ -276,12 +261,10 @@
         region: wt-wt, us-en, uk-en, ru-ru, etc. Defaults to "wt-wt".
         safesearch: on, moderate, off. Defaults to "moderate".
         timelimit: d, w, m, y. Defaults to None.
-        backend: auto, api, html, lite. Defaults to auto.
+        backend: auto, html, lite. Defaults to auto.
             auto - try all backends in random order,
-            api - collect data from https://duckduckgo.com,
             html - collect data from https://html.duckduckgo.com,
-            lite - collect data from https://lite.duckduckgo.com,
-            ecosia - collect data from https://www.ecosia.com.
+            lite - collect data from https://lite.duckduckgo.com.
         max_results: max number of results. If None, returns results only from 
the first response. Defaults to None.
 
     Returns:
@@ -305,7 +288,7 @@
 
 [Go To TOP](#TOP)
 
-## 3. images() - image search by duckduckgo.com
+## 2. images() - image search by duckduckgo.com
 
 ```python
 def images(
@@ -372,7 +355,7 @@
 
 [Go To TOP](#TOP)
 
-## 4. videos() - video search by duckduckgo.com
+## 3. videos() - video search by duckduckgo.com
 
 ```python
 def videos(
@@ -439,7 +422,7 @@
 
 [Go To TOP](#TOP)
 
-## 5. news() - news search by duckduckgo.com
+## 4. news() - news search by duckduckgo.com
 
 ```python
 def news(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/requires.txt 
new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/requires.txt
--- old/duckduckgo_search-7.1.1/duckduckgo_search.egg-info/requires.txt 
2024-12-27 00:22:58.000000000 +0100
+++ new/duckduckgo_search-8.0.1/duckduckgo_search.egg-info/requires.txt 
2025-04-17 14:37:28.000000000 +0200
@@ -1,9 +1,9 @@
-click>=8.1.7
-primp>=0.9.2
+click>=8.1.8
+primp>=0.15.0
 lxml>=5.3.0
 
 [dev]
-mypy>=1.13.0
+mypy>=1.14.1
 pytest>=8.3.4
 pytest-dependency>=0.6.0
-ruff>=0.8.3
+ruff>=0.9.2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/pyproject.toml 
new/duckduckgo_search-8.0.1/pyproject.toml
--- old/duckduckgo_search-7.1.1/pyproject.toml  2024-12-27 00:22:48.000000000 
+0100
+++ new/duckduckgo_search-8.0.1/pyproject.toml  2025-04-17 14:37:17.000000000 
+0200
@@ -6,7 +6,7 @@
 name = "duckduckgo_search"
 description = "Search for words, documents, images, news, maps and text 
translation using the DuckDuckGo.com search engine."
 readme = "README.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
 license = {text = "MIT License"}
 keywords = ["python", "duckduckgo"]
 authors = [
@@ -18,7 +18,6 @@
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
     "Programming Language :: Python :: 3 :: Only",
-    "Programming Language :: Python :: 3.8",
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
@@ -29,8 +28,8 @@
     "Topic :: Software Development :: Libraries :: Python Modules",
 ]
 dependencies = [
-    "click>=8.1.7",
-    "primp>=0.9.2",
+    "click>=8.1.8",
+    "primp>=0.15.0",
     "lxml>=5.3.0",
 ]
 dynamic = ["version"]
@@ -46,10 +45,10 @@
 
 [project.optional-dependencies]
 dev = [
-    "mypy>=1.13.0",
+    "mypy>=1.14.1",
     "pytest>=8.3.4",
     "pytest-dependency>=0.6.0",
-    "ruff>=0.8.3",
+    "ruff>=0.9.2",
 ]
 
 [tool.ruff]
@@ -67,6 +66,6 @@
 ]
 
 [tool.mypy]
-python_version = "3.8"
+python_version = "3.9"
 strict = true
-exclude = ['cli\.py$', '__main__\.py$', "tests/", "build/"]
+exclude = ["build/"]
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/duckduckgo_search-7.1.1/tests/test_cli.py 
new/duckduckgo_search-8.0.1/tests/test_cli.py
--- old/duckduckgo_search-7.1.1/tests/test_cli.py       2024-12-27 
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/tests/test_cli.py       2025-04-17 
14:37:17.000000000 +0200
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
 import os
 import pathlib
 import shutil
 import time
+from pathlib import Path
 
 import pytest
 from click.testing import CliRunner
@@ -10,76 +13,71 @@
 from duckduckgo_search.cli import _download_results, _save_csv, _save_json, cli
 
 runner = CliRunner()
-TEXT_RESULTS = None
-IMAGES_RESULTS = None
+TEXT_RESULTS = []
+IMAGES_RESULTS = []
 
 @pytest.fixture(autouse=True)
-def pause_between_tests():
-    time.sleep(1)
+def pause_between_tests() -> None:
+    time.sleep(2)
 
 
-def test_version_command():
+def test_version_command() -> None:
     result = runner.invoke(cli, ["version"])
     assert result.output.strip() == __version__
 
 
-def test_chat_command():
-    result = runner.invoke(cli, ["chat"])
-    assert "chat" in result.output
-
-
-def test_text_command():
+def test_text_command() -> None:
     result = runner.invoke(cli, ["text", "-k", "python"])
     assert "title" in result.output
 
 
-def test_images_command():
+def test_images_command() -> None:
     result = runner.invoke(cli, ["images", "-k", "cat"])
     assert "title" in result.output
 
 
-def test_news_command():
+def test_news_command() -> None:
     result = runner.invoke(cli, ["news", "-k", "usa"])
     assert "title" in result.output
 
 
-def test_videos_command():
+def test_videos_command() -> None:
     result = runner.invoke(cli, ["videos", "-k", "dog"])
     assert "title" in result.output
 
 
 @pytest.mark.dependency()
-def test_get_text():
+def test_get_text() -> None:
     global TEXT_RESULTS
     TEXT_RESULTS = DDGS().text("test")
     assert TEXT_RESULTS
 
 
 @pytest.mark.dependency()
-def test_get_images():
+def test_get_images() -> None:
     global IMAGES_RESULTS
     IMAGES_RESULTS = DDGS().images("test")
     assert IMAGES_RESULTS
 
 
-@pytest.mark.dependency(depends=["test_get_data"])
-def test_save_csv(tmp_path):
+@pytest.mark.dependency(depends=["test_get_text"])
+def test_save_csv(tmp_path: Path) -> None:
     temp_file = tmp_path / "test_csv.csv"
-    _save_csv(temp_file, RESULTS)
+    _save_csv(temp_file, TEXT_RESULTS)
     assert temp_file.exists()
 
 
-@pytest.mark.dependency(depends=["test_get_data"])
-def test_save_json(tmp_path):
+@pytest.mark.dependency(depends=["test_get_text"])
+def test_save_json(tmp_path: Path) -> None:
     temp_file = tmp_path / "test_json.json"
-    _save_json(temp_file, RESULTS)
+    _save_json(temp_file, TEXT_RESULTS)
     assert temp_file.exists()
 
 
-@pytest.mark.dependency(depends=["test_get_data"])
-def test_text_download():
+@pytest.mark.dependency(depends=["test_get_text"])
+def test_text_download() -> None:
     pathname = pathlib.Path("text_downloads")
-    _download_results(test_text_download, TEXT_RESULTS, function_name="text", 
pathname=str(pathname))
+    _download_results(f"{test_text_download}", TEXT_RESULTS, 
function_name="text", pathname=str(pathname))
     assert pathname.is_dir() and pathname.iterdir()
     for file in pathname.iterdir():
         assert file.is_file()
@@ -87,9 +85,9 @@
 
 
 @pytest.mark.dependency(depends=["test_get_images"])
-def test_images_download():
+def test_images_download() -> None:
     pathname = pathlib.Path("images_downloads")
-    _download_results(test_images_download, IMAGES_RESULTS, 
function_name="images", pathname=str(pathname))
+    _download_results(f"{test_images_download}", IMAGES_RESULTS, 
function_name="images", pathname=str(pathname))
     assert pathname.is_dir() and pathname.iterdir()
     for file in pathname.iterdir():
         assert file.is_file()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/duckduckgo_search-7.1.1/tests/test_duckduckgo_search.py 
new/duckduckgo_search-8.0.1/tests/test_duckduckgo_search.py
--- old/duckduckgo_search-7.1.1/tests/test_duckduckgo_search.py 2024-12-27 
00:22:48.000000000 +0100
+++ new/duckduckgo_search-8.0.1/tests/test_duckduckgo_search.py 2025-04-17 
14:37:17.000000000 +0200
@@ -5,47 +5,36 @@
 
 
 @pytest.fixture(autouse=True)
-def pause_between_tests():
-    time.sleep(1)
+def pause_between_tests() -> None:
+    time.sleep(2)
 
 
-def test_context_manager():
+def test_context_manager() -> None:
     with DDGS() as ddgs:
         results = ddgs.news("cars", max_results=30)
         assert 20 <= len(results) <= 30
 
 
-@pytest.mark.parametrize("model", ["gpt-4o-mini", "claude-3-haiku", 
"llama-3.1-70b", "mixtral-8x7b"])
-def test_chat(model):
-    results = DDGS().chat("cat", model=model)
-    assert  len(results) >= 1
-
-
-def test_text_html():
+def test_text_html() -> None:
     results = DDGS().text("eagle", backend="html", region="br-pt", 
timelimit="y", max_results=20)
     assert 15 <= len(results) <= 20
 
 
-def test_text_lite():
+def test_text_lite() -> None:
     results = DDGS().text("dog", backend="lite", region="br-pt", 
timelimit="y", max_results=20)
     assert 15 <= len(results) <= 20
 
 
-def test_text_ecosia():
-    results = DDGS().text("cat", backend="ecosia", region="br-pt", 
safesearch="off", max_results=20)
-    assert 15 <= len(results) <= 20
-
-
-def test_images():
+def test_images() -> None:
     results = DDGS().images("flower", max_results=200)
     assert 85 <= len(results) <= 200
 
 
-def test_videos():
+def test_videos() -> None:
     results = DDGS().videos("sea", max_results=40)
     assert 30 <= len(results) <= 40
 
 
-def test_news():
+def test_news() -> None:
     results = DDGS().news("tesla", max_results=30)
     assert 20 <= len(results) <= 30

Reply via email to