
    iGP                     
   U d Z ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZ ddlmZ ddlmZ ddlmZmZ ddlmZ dd	lmZmZmZmZmZmZ ej8                  rdd
lmZ ddlm Z  ddddddZ!e"e#e#e$z  f   e%d<   ddgZ&e'e#   e%d<   dZ(e$e%d<   dZ)e$e%d<   dZ*e$e%d<   	 dZ+e#e%d<   	 dddddZ,e"e#e#f   e%d <   da-ee%d!<   	  e       Z.e#e%d"<   d#efd$Z/d%e#e0z  d&e#d'd(d#dfd)Z1d%e#d'd(d#e#fd*Z2	 dZd+ed,e#d-e#d#e#dz  fd.Z3d/d0d1d2d3d4d5d6d7d8d9d:d;Z4e"e#e#f   e%d<<   i d=d>d?d>d7d>d@d>dAd>dBd>dCd>dDd>dEdFdGd>dHd>dId>dJd>dKd>dLd>dMd>dNd>d>d>d>d>d>d>d1d1d1dO	Z5e"e#e#f   e%dP<   d%e#d#e#fdQZ6d%e#d'd(d#dfdRZ7dSefdTZ8dUdVd#efdWZ9dXefdYZ:y)[a  
DuckDuckGo WEB
~~~~~~~~~~~~~~

DDG's WEB search:

- DuckDuckGo WEB      : ``https://links.duckduckgo.com/d.js?q=..``  (HTTP GET)
- DuckDuckGo WEB no-AI: ``https://noai.duckduckgo.com/``            (HTTP GET)
- DuckDuckGo WEB html : ``https://html.duckduckgo.com/html``        (HTTP POST no-JS / form data)
- DuckDuckGo WEB lite : ``https://lite.duckduckgo.com/lite``        (HTTP POST no-JS / form data)

DDG's content search / see engine ``duckduckgo_extra.py``

- DuckDuckGo Images   : ``https://duckduckgo.com/i.js??q=...&vqd=...``
- DuckDuckGo Videos   : ``https://duckduckgo.com/v.js??q=...&vqd=...``
- DuckDuckGo News     : ``https://duckduckgo.com/news.js??q=...&vqd=...``

.. hint::

   For WEB searches and to determine the ``vqd`` value, DDG-html (no-JS) is
   used.

Special features of the no-JS services (DDG-lite & DDG-html):

- The no-JS clients receive a form that contains all the controlling parameters.
- When the form data is submitted, a real WEB browser sets the HTTP *Sec-Fetch*
  headers.

HTML ``<form>``, HTTP-Headers & DDG's bot Blocker:

  The HTTP User-Agent_ (see below) is generated by the WEB-client and are
  checked by DDG's bot blocker.

To simulate the behavior of a real browser session, it might be necessary to
evaluate additional headers.  For example, in the response from DDG, the
Referrer-Policy_ is always set to ``origin``.  A real browser would then include
the following header in the next request::

    Referer: https://html.duckduckgo.com/

The fields of the html-form are reverse-engineered from DDG-html and may be
subject to additional bot detection mechanisms and breaking changes in the
future.

Query field:

Intro page: https://html.duckduckgo.com/html/

- ``q`` (str): Search query string
- ``b`` (str): Beginning parameter - empty string for first page requests.  If a
  second page is requested, this field is not set!

Search options:

- ``kl`` (str): Keyboard language/region code (e.g. 'en-us' default: 'wt-wt')
- ``df`` (str): Time filter, maps to values like 'd' (day), 'w' (week), 'm' (month), 'y' (year)

The key/value pairs ``df`` and ``kl`` are additional saved in the cookies,
example::

    Cookie: kl=en-us; df=m

*next page* form fields:

- ``nextParams`` (str): Continuation parameters from previous page response,
  typically empty string.  Opposite of ``b``; this field is not set when
  requesting the first result page.

- ``api`` (str): API endpoint identifier, typically 'd.js'
- ``o`` (str): Output format, typically ``json``
- ``v`` (str): Typically ``l`` for subsequent pages


- ``dc`` (int): Display count - value equal to offset (s) + 1
- ``s`` (int): Search offset for pagination
- ``vqd`` (str): Validation query digest

General assumptions regarding DDG's bot blocker:

- Except ``Cookie: kl=..; df=..`` DDG does not use cookies in any of its
  services.

- DDG does not accept queries with more than 499 chars

- The ``vqd`` value ("Validation query digest") is needed to pass DDG's bot
  protection and is used by all request to DDG.

- The ``vqd`` value is generally not needed for the first query (intro); it is
  only required when additional pages are accessed (or when new content needs to
  be loaded for the query while scrolling).

- The second page (additional content) for a query cannot be requested without
  ``vqd``, as this would lead to an immediate blocking, since such a use-case
  does not exist in the process flows provided by DDG (and is a clear indication
  of a bot).

The following HTTP headers are being evaluated (and may possibly be responsible
for issues):

User-Agent_:
  The HTTP User-Agent is also involved in the formation of the vqd value, read
  `DuckDuckGo Bot Detection Research & Solution`_.  However, it is not checked
  whether the UA is a known header. However, it is possible that certain UA
  headers (such as curl) are filtered.

Sec-Fetch-Mode_:
  In the past, Sec-Fetch-Mode had to be set to 'navigate', otherwise there were
  problems with the bot blocker.. I don't know if DDG still evaluates this
  header today

Accept-Language_:
  DDG-Lite and DDG-HTML TRY to guess user's preferred language from the HTTP
  ``Accept-Language``.  Optional the user can select a region filter (but not a
  language).

In  DDG's bot blocker, the IP will be blocked (DDG does not have a client session!)

- As far as is known, it is possible to remove a un-blocked an IP by executing a
  DDG query in a real web browser over the blocked IP (at least that's my
  assumption).

  How exactly the blocking mechanism currently works is not fully known, and
  there were also changes to the bot blocker in the period of Q3/Q4 2025: in the
  past, the IP blocking was implemented as a 'sliding window' (unblock after
  about 1 hour without requests from this IP)

Terms / phrases that you keep coming across:

- ``d.js``, ``i.js``, ``v.js``, ``news.js`` are the endpoints of the DDG's web
  API through which additional content for a query can be requested (vqd
  required)

  The ``*.js`` endpoints return a JSON response and can therefore only be
  executed on a JS-capable client.

  The service at https://lite.duckduckgo.com/lite offers general WEB searches
  (no news, videos etc).  DDG-lite and DDG-html can be used by clients that do
  not support JS, aka *no-JS*.

  DDG-lite works a bit differently: here, ``d.js`` is not an endpoint but a
  field (``api=d.js``) in a form that is sent to DDG-lite.

- The request argument ``origin=funnel_home_website`` is often seen in the DDG
  services when the category is changed (e.g., from web search to news, images,
  or to the video category)

.. _DuckDuckGo Bot Detection Research & Solution:
   https://github.com/ggfevans/searxng/blob/mod-sidecar-harvester/docs/ddg-bot-detection-research.md

.. _Sec-Fetch-Mode:
   https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Mode>

.. _Referrer-Policy:
   https://developer.mozilla.org/docs/Web/HTTP/Reference/Headers/Referrer-Policy#directives

.. _Referer:
   https://developer.mozilla.org/de/docs/Web/HTTP/Reference/Headers/Referer

.. _User-Agent:
   https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent

.. _Accept-Language:
   https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language

    N)locales)EngineCache)EngineTraits)SearxEngineCaptchaException)EXTERNAL_BANGSget_node)EngineResults)ElementType
eval_xpatheval_xpath_getindexextrextract_textgen_useragent)SXNG_Response)OnlineParamsz!https://lite.duckduckgo.com/lite/Q12805FHTML)websitewikidata_iduse_official_apirequire_api_keyresultsaboutgeneralweb
categoriesTpagingtime_range_support
safesearchz!https://html.duckduckgo.com/html/ddg_urldwmy)dayweekmonthyeartime_range_dict_CACHE_HTTP_User_Agentreturnc                  0    t         t        d      a t         S )N
duckduckgo)r*   r        )/root/searxng/searx/engines/duckduckgo.py	get_cacher2      s    ~\*Mr0   queryvalueparamsr   c                 x    t               }|j                  |  d|d   d          }|j                  ||d       y )N//headers
User-Agenti  )keyr4   expire)r2   secret_hashset)r3   r4   r5   cacher:   s        r1   set_vqdr?      sA    KE


ugRy(9,(G'HI
JC	II#U4I0r0   c                     t               }|j                  |  d|d   d          }|j                  |      xs d}|rt        j	                  d|       |S )zReturns the ``vqd`` value that fits to the *query* (and HTTP User-Agent_
    header).

    :param query: the query term
    :param params: request parameters
    r7   r8   r9   )r:    z get_vqd: re-use cached value: %s)r2   r<   getloggerdebug)r3   r5   r>   r:   r4   s        r1   get_vqdrE      s]     KE


ugRy(9,(G'HI
JCs#)rE7?Lr0   
eng_traitssxng_localedefaultc                 l    | j                  ||      }| j                  d   j                  ||      xs dS )av  Get DuckDuckGo's language identifier from SearXNG's locale.

    .. hint::

       `DDG-lite <https://lite.duckduckgo.com/lite>`__ and the *no Javascript*
       page https://html.duckduckgo.com/html do not offer a language selection
       to the user.

    DDG defines its languages by a region code (:py:obj:`fetch_traits`).  To
    get region and language of a DDG service use:

    .. code: python

       eng_region = traits.get_region(params["searxng_locale"], traits.all_locale)
       eng_lang = get_ddg_lang(traits, params["searxng_locale"])

    It might confuse, but the ``l`` value of the cookie is what SearXNG calls
    the *region*:

    .. code:: python

        # !ddi paris :es-AR --> {'ad': 'es_AR', 'ah': 'ar-es', 'l': 'ar-es'}
        params['cookies']['ad'] = eng_lang
        params['cookies']['ah'] = eng_region
        params['cookies']['l'] = eng_region

    lang_regionN)get_languagecustomrB   )rF   rG   rH   langs       r1   get_ddg_langrN      s:    @ "..{GDD]+//TBJdJr0   zh_TWzh_HKskipca_ESid_IDnb_NOja_JPko_KRar_SAsl_SIth_THvi_VN)ztw-tzhzhk-tzhzct-cazes-cazid-enzno-nozjp-jpzkr-krzxa-arzsl-slzth-enzvn-enddg_reg_mapar_DZrJ   ar_JObn_INde_CHen_AUen_CAen_GBeo_XXeoes_ARes_CLes_COes_CRes_ECes_MXes_PEes_UY)	es_VEfr_CAfr_CHfr_BEnl_BEpt_BRod_INio_XXtokipona_XXddg_lang_mapc                     g }t        j                  d|       D ]N  }|j                         s|j                  d      rt	        t
        |dd       rd| d}|j                  |       P dj                  |      S )zMTo avoid a redirect, the !bang directives in the query string are
    quoted.z(\s+)!   N' )resplitstrip
startswithr   r   appendjoin)r3   _qvals      r1   quote_ddg_bangsr   Y  sr     Bxx%( yy{>>#8NCG#DcU!*C
		# 88B<r0   c                    t        |       dk\  rd |d<   y t        |       } t        j                  |d   t        j                        }|d   }t
        |d<   d|d<   d|d	<   d
|d<   d|d<   d|d<   |d   }|j                  d      s| d| d|j                          d|d<   |d   }| |d<   t        |d<   d|d<   |d   dk(  rd|d<   nxt        | |      }|r||d<   nt        dd|d    d |d    d!"      |d   j                  d#      rd |d<   y d|d$<   d%|d&<   d'|d(<   d)|d*<   d+|d   d,z
  d-z  z   }|dz   |d.<   ||d/<   |d0k(  rd0|d1<   n||d1<   ||d2   d1<   t        j                  t        |d3         d      }|r||d4<   ||d2   d4<   d5|d   d6<   t        |d   d<   t        j                  d7|d          t        j                  d8|d          t        j                  d9|d2          y ):Ni  urlsearxng_localer8   r9   documentzSec-Fetch-DestnavigatezSec-Fetch-Modezsame-originzSec-Fetch-Sitez?1zSec-Fetch-Userzhttps://html.duckduckgo.com/RefererzAccept-Language,-z;q=0.7dataqPOSTmethodpagenory   rA   b)r3   r5   vqdr   zVQD missed (page: z
, locale: )suspended_timemessagezh
nextParamszd.jsapijsonolv
         dcswt-wtklcookies
time_rangedfz!application/x-www-form-urlencodedzContent-Typezparam headers: %szparam data: %szparam cookies: %s)lenr   traits
get_region
all_localer+   rB   upperr    rE   r   r   r)   strrC   rD   )	r3   r5   
eng_regionr8   ui_langr   r   offsett_ranges	            r1   requestr   i  s   
5zSuE"E'' J YG -GL *G *G -G $G7GI%&G;;()(/y'!GMMO;LF%S!" &>DDIF5MF8h1S	 E&1DK . ,VH-=,>jP`IaHbbcd 
 "#..t4 !F5M\US	S	vh'!+r11aZT
S	W T
T
",y$"&&s6,+?'@"EGT
")y$(KF9n%#*F9i 
LL$fY&78
LL!6&>2
LL$fY&78r0   domc                 ,    t        t        | d            S )ziIn case of CAPTCHA ddg response its own *not a Robot* dialog and is not
    redirected to a CAPTCHA page.z//form[@id='challenge-form'])boolr   )r   s    r1   is_ddg_captchar     s     
3 >?@@r0   respr   c           
         t               }| j                  dk(  r|S t        j                  j	                  | j
                        }| j                  }t        |      r#t        dd|d   j                  d       d      t        |d      }t        |      rF|d   }t        |d	      d   }t        |d   d
         }t        |t        |      | j                         t        |d      D ]p  }t        |d      }t        |ddg       }	|j                  |j                   j#                  t%        |      xs dt        |d      d   t%        |	      xs d             r d}
t%        t        ||
            j'                         }|rCd|vr?d|vr;d|vr7|j                  |j                   j)                  |t        |dd                   |S )Ni/  r   z	CAPTCHA (r   r   r   r   z//input[@name="vqd"]/..z//input[@name="vqd"]/@valuer   )r3   r4   r5   z6//div[@id="links"]/div[contains(@class, "web-result")]z.//h2/az).//a[contains(@class, "result__snippet")]rA   z.//h2/a/@href)titler   contentz //div[@id="zero_click_abstract"]zYour IP address iszYour user agent:zURL Decoded:z(//div[@id="zero_click_abstract"]/a/@href)answerr   )r	   status_codelxmlhtml
fromstringtextsearch_paramsr   r   rB   r   r   r   r?   r   addtypes
MainResultr   r~   Answer)r   resdocr5   formform_vqdr   
div_result_title_contentzero_click_info_xpath
zero_clicks               r1   responser     s   
/C3

))

tyy
)CFc)iPVW]P^PbPbcgPhOiijDkllc45D 4yAwd$AB1EVF^C()h-%%	
 !&^_ 	

J	2&z3^`acefII  "6*0bz?;A>$X.4" ! 	
	
 ?j.CDEKKMJJ.j0*,II!'-WYZ[  	
 Jr0   engine_traitsc                    ddl m} ddlm} d| _         |dd      }|j
                  st        d      t        |j                  d	d
      }t        j                  |      }|j                         D ]  \  }}|dk(  rd| _        t        j                  |      }|dk(  r.|s*|j                  d      \  }	}
|
dz   |	j                         z   }	 t        j                   t"        j$                  j'                  |            }| j,                  j                  |      }|r||k7  rt+        d|d|d|       || j,                  |<    i | j.                  d<   t        |j                  dd      } ||      }|j                         D ]  \  }
}|
dk(  rt0        j                  |
|
      }|dk(  r(	 |dk(  rEt        j                   t"        j$                  j'                  |
            }|
| j.                  d   |<   st        j2                  t"        j$                  j'                  |            }| j4                  j                  |      }|r||
k7  rt+        d|d|d|
       |
| j4                  |<    y# t"        j(                  $ r t+        d|d|d|d       Y ,w xY w# t"        j(                  $ r t+        d|d|
d       Y Ew xY w)a  Fetch languages & regions from DuckDuckGo.

    SearXNG's ``all`` locale maps DuckDuckGo's "All regions" (``wt-wt``).
    DuckDuckGo's language "Browsers preferred language" (``wt_WT``) makes no
    sense in a SearXNG request since SearXNG's ``all`` will not add a
    ``Accept-Language`` HTTP header.  The value in ``engine_traits.all_locale``
    is ``wt-wt`` (the region).

    Beside regions DuckDuckGo also defines its languages by region codes.  By
    example these are the english languages in DuckDuckGo:

    - en_US
    - en_AU
    - en_CA
    - en_GB

    The function :py:obj:`get_ddg_lang` evaluates DuckDuckGo's language from
    SearXNG's locale.

    r   )rB   )js_obj_str_to_pythonr   z:https://duckduckgo.com/dist/util/u.7669f071a13a7daa57cb.js   )timeoutz+Response from DuckDuckGo regions is not OK.zregions:z,snippetLengthsrQ   r   _zERROR: z (z) -> z is unknown by babelzCONFLICT: babel z --> z, rJ   z
languages:z,regionswt_WTzERROR: language z) is unknown by babelN)searx.networkrB   searx.utilsr   r   okRuntimeErrorr   r   r   loadsitemsr[   r}   r   r   
region_tagbabelLocaleparseUnknownLocaleErrorprintregionsrL   rv   language_tag	languages)r   rB   r   r   js_coder   eng_tagnameregioneng_territoryeng_langsxng_tagconflictr   	babel_tags                  r1   fetch_traitsr   	  s   . "0  'M KUVWD77HII499j*;<Gjj!G  2g'.M$)V&-mmC&8#M8^m&9&9&;;F	))%,,*<*<V*DEH
 !((,,X67"8WUV*1h'128 +-M'499lJ7G 4W =I#//+ 5$w $$Xx8	
	M)"--ell.@.@.JK@H$$]3H=++ELL,>,>y,IJH !**..x88#8XVW,4)35% '' 	gvVW	D '' 	4RS	s+   2I2-A	J!72J!2(JJ!%K
	K
)en_US);__doc__r   r|   typingtr   	lxml.htmlr   searxr   searx.enginelibr   searx.enginelib.traitsr   searx.exceptionsr   searx.external_bangr   r   searx.result_typesr	   r   r
   r   r   r   r   r   TYPE_CHECKINGsearx.extended_typesr   searx.search.processorsr   r   dictr   r   __annotations__r   listr   r   r   r    r)   r*   r+   r2   intr?   rE   rN   r[   rv   r   r   r   r   r   r/   r0   r1   <module>r     s   dN  	     ' / 8 8 ,  ??24 3 tCtO  #E*
DI *  D 
D  ?2 2 +.sSRU"Vc3h V  & # '; 139 1S 1. 1T 1 	* "K"K"K "K 	4Z	"KL T#s(^ % ]%  ]%  ]	%  ]%  ]%  ]%  ]%  ]%  T%   ]!% " ]#% $ ]%% & ]'% ( ])% * ]+% , ]-% . ]/% 0 I% d38n %P3 3  ^93 ^9 ^94 ^9BA A5? 5} 5p`5 `5r0   