Compare commits

...

23 Commits

Author SHA1 Message Date
Yonei aeeaaac576
Merge 3732b7fef5 into b07c0ae39f 2024-11-05 08:58:56 +01:00
Bnyro b07c0ae39f [fix] annas archive: crash when no thumbnail, differing results, paging 2024-11-01 12:49:33 +01:00
Markus Heiser 56e3d72a76 [fix] CI: remove target test.coverage from python's test matrix
The test.coverage cause a lot of failed CI jobs for reasons that cannot be
explained.  As we do not monitor the coverage anyway, it is superfluous to run
this job, especially as it only has a disruptive effect on the CI.

BTW and the CI action upload-artifact@v3 is deprecated [1]

[1] https://github.com/actions/upload-artifact?tab=readme-ov-file#actionsupload-artifact

Related: https://github.com/searxng/searxng/issues/3983
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-11-01 10:14:57 +01:00
searxng-bot cc148a76b0 [l10n] update translations from Weblate
a4cdaaa26 - 2024-10-30 - Juno Takano <jutty@users.noreply.translate.codeberg.org>
46bad3a79 - 2024-10-29 - saltsnorter <saltsnorter@users.noreply.translate.codeberg.org>
6a4096da9 - 2024-10-27 - Eryk Michalak <gnu.ewm@protonmail.com>
64815d956 - 2024-10-28 - ljansen <ljansen@users.noreply.translate.codeberg.org>
851ae554d - 2024-10-26 - return42 <return42@users.noreply.translate.codeberg.org>
24f16d5e3 - 2024-10-26 - return42 <return42@users.noreply.translate.codeberg.org>
8278d1cb9 - 2024-10-26 - Atul_Eterno <Atul_Eterno@users.noreply.translate.codeberg.org>
2024-11-01 08:30:38 +01:00
uply23333 fa108c140f [fix] google: display every result when keyword is contained in content field 2024-10-31 13:21:32 +01:00
Markus Heiser fa4dfd4efe [fix] favicons: msgspec.ValidationError: Expected `Path`, got `str` - at `$.favicons.cache.db_url`
Closes: https://github.com/searxng/searxng/issues/3975
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-10-29 18:22:22 +01:00
Markus Heiser b183e620d8 [refactor] engine: duckduckgo - https://html.duckduckgo.com/html
The entire source code of the duckduckgo engine has been reengineered and
purified.

1. DDG used the URL https://html.duckduckgo.com/html for no-JS requests whose
   response is also easier to parse than the previous
   https://lite.duckduckgo.com/lite/ URL

2. the bot detection of DDG has so far caused problems and often led to a
   CAPTCHA, this can be circumvented using `'Sec-Fetch-Mode'] = “navigate”`

Closes: https://github.com/searxng/searxng/issues/3927
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-10-29 14:56:27 +01:00
Markus Heiser f63f97c56c Revert "Fix for broken docker builds"
This reverts commit 4ef1c706f8.
2024-10-29 13:50:38 +01:00
Markus Heiser 163031c394 Revert "[fix] typo in Dockerfile"
This reverts commit 038a2ff6bd.
2024-10-29 13:50:38 +01:00
Markus Heiser 3e5621e1af [refactor] replace pydantic by msgspec
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-10-29 13:50:38 +01:00
return42 e392892578 [data] update searx.data - update_firefox_version.py 2024-10-29 09:30:40 +01:00
return42 68ed8245da [data] update searx.data - update_ahmia_blacklist.py 2024-10-29 09:29:58 +01:00
return42 2d748d1d74 [data] update searx.data - update_currencies.py 2024-10-29 09:29:18 +01:00
return42 2985ece0ca [data] update searx.data - update_wikidata_units.py 2024-10-29 09:28:58 +01:00
return42 adc38c5800 [data] update searx.data - update_engine_traits.py 2024-10-29 09:28:28 +01:00
return42 a084436ff4 [data] update searx.data - update_engine_descriptions.py 2024-10-29 09:17:30 +01:00
Markus Heiser b176323e89 [fix] calculator: use locale from UI (not from selected language)
Closes: https://github.com/searxng/searxng/issues/3956
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-10-28 15:53:57 +01:00
Markus Heiser da28f5280b [fix] limiter: don't hard code settings folder to /etc/searxng
The location of the local settings depends on environment ``SEARXNG_SETTINGS_PATH``
and can be different from ``/etc/searxng``.  Issue was reported on Matrix [1].

To get the location function ``searx.settings_loader.get_user_cfg_folder()``
should be used.

[1] https://matrix.to/#/!vxScbLNEAmRvOraXBn:matrix.org/$_eLS0JpE9oVEWsiGJkqJnWcFWEeZClIMGDK6cWv_Q4g?via=matrix.org&via=tchncs.de&via=envs.net

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2024-10-28 12:51:05 +01:00
dependabot[bot] 543ab92fde [upd] pypi: Bump pallets-sphinx-themes from 2.2.0 to 2.3.0
Bumps [pallets-sphinx-themes](https://github.com/pallets/pallets-sphinx-themes) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/pallets/pallets-sphinx-themes/releases)
- [Changelog](https://github.com/pallets/pallets-sphinx-themes/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/pallets-sphinx-themes/compare/2.2.0...2.3.0)

---
updated-dependencies:
- dependency-name: pallets-sphinx-themes
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-27 10:48:26 +01:00
y0nei 3732b7fef5
Merge branch 'master' of github.com:y0nei/searxng 2023-10-03 10:37:13 +02:00
y0nei bf7d5cd432
[fix] logs: Proper member name mapping for ValidLogLevels
`_member_names_` is not part of the Enum documented public API, instead
use `__members__`.
2023-10-03 10:32:34 +02:00
y0nei 7208feaf7e
Merge branch 'searxng:master' into master 2023-10-02 21:56:01 +00:00
y0nei 3c91f44cc6
[feat] logs: Settings option to set custom level 2023-10-02 23:54:16 +02:00
37 changed files with 3692 additions and 545 deletions

View File

@ -45,14 +45,6 @@ jobs:
make V=1 gecko.driver
- name: Run tests
run: make V=1 ci.test
- name: Test coverage
run: make V=1 test.coverage
- name: Store coverage result
uses: actions/upload-artifact@v3
with:
name: coverage-${{ matrix.python-version }}
path: coverage/
retention-days: 60
themes:
name: Themes

View File

@ -43,16 +43,9 @@ RUN apk add --no-cache -t build-dependencies \
tini \
uwsgi \
uwsgi-python3 \
brotli
# For 32bit arm architecture install pydantic from the alpine repos instead of requirements.txt
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "arm" ]; then \
apk add --no-cache py3-pydantic && pip install --no-cache --break-system-packages -r <(grep -v '^pydantic' requirements.txt); \
else \
pip install --no-cache --break-system-packages -r requirements.txt; \
fi
RUN apk del build-dependencies \
brotli \
&& pip3 install --break-system-packages --no-cache -r requirements.txt \
&& apk del build-dependencies \
&& rm -rf /root/.cache
COPY --chown=searxng:searxng dockerfiles ./dockerfiles

View File

@ -5,7 +5,7 @@ black==24.3.0
pylint==3.3.1
splinter==0.21.0
selenium==4.25.0
Pallets-Sphinx-Themes==2.2.0
Pallets-Sphinx-Themes==2.3.0
Sphinx==7.4.7
sphinx-issues==5.0.0
sphinx-jinja==2.0.2

View File

@ -16,6 +16,6 @@ redis==5.0.8
markdown-it-py==3.0.0
fasttext-predict==0.9.2.2
tomli==2.0.2; python_version < '3.11'
pydantic==2.9.2
msgspec==0.18.6
eval_type_backport; python_version < '3.9'
typer-slim==0.12.5

View File

@ -4,6 +4,7 @@
import sys
import os
from os.path import dirname, abspath
from enum import Enum
import logging
@ -11,14 +12,6 @@ import searx.unixthreadname
import searx.settings_loader
from searx.settings_defaults import settings_set_defaults
# Debug
LOG_FORMAT_DEBUG = '%(levelname)-7s %(name)-30.30s: %(message)s'
# Production
LOG_FORMAT_PROD = '%(asctime)-15s %(levelname)s:%(name)s: %(message)s'
LOG_LEVEL_PROD = logging.WARNING
searx_dir = abspath(dirname(__file__))
searx_parent_dir = abspath(dirname(dirname(__file__)))
settings, settings_load_message = searx.settings_loader.load_settings()
@ -26,6 +19,31 @@ settings, settings_load_message = searx.settings_loader.load_settings()
if settings is not None:
settings = settings_set_defaults(settings)
class ValidLogLevels(str, Enum):
"""Log levels for the application, levels should match ones from the logging
module.
"""
INFO = logging.INFO
WARN = logging.WARN
WARNING = logging.WARNING
ERROR = logging.ERROR
# Debug
LOG_FORMAT_DEBUG = '%(levelname)-7s %(name)-30.30s: %(message)s'
# Production
LOG_FORMAT_PROD = '%(asctime)-15s %(levelname)s:%(name)s: %(message)s'
searx_loglevel = settings['general']['log_level']
if searx_loglevel.upper() in list(ValidLogLevels.__members__):
LOG_LEVEL_PROD = ValidLogLevels[searx_loglevel.upper()].name
else:
LOG_LEVEL_PROD = logging.WARNING
_unset = object()
@ -95,6 +113,7 @@ else:
logging.root.setLevel(level=LOG_LEVEL_PROD)
logging.getLogger('werkzeug').setLevel(level=LOG_LEVEL_PROD)
logger = logging.getLogger('searx')
logger.setLevel(level=LOG_LEVEL_PROD)
logger.info(settings_load_message)
# log max_request_timeout

File diff suppressed because it is too large Load Diff

View File

@ -82,7 +82,7 @@
"af": "Albanese lek",
"ar": "ليك ألباني",
"bg": "Албански лек",
"ca": "lek",
"ca": "Lek (moneda)",
"cs": "Albánský lek",
"cy": "Lek",
"da": "Lek",
@ -383,6 +383,7 @@
"nl": "Azerbeidzjaanse manat",
"oc": "Manat",
"pa": "ਅਜ਼ਰਬਾਈਜਾਨੀ ਮਨਾਤ",
"pap": "Manat Azerbaijano",
"pl": "Manat azerski",
"pt": "Manat azeri",
"ro": "Manat azer",
@ -606,6 +607,7 @@
"pt": "Franco do Burúndi",
"ro": "franc burundez",
"ru": "бурундийский франк",
"sk": "Burundský frank",
"sl": "burundijski frank",
"sr": "бурундски франак",
"sv": "Burundisk franc",
@ -1327,6 +1329,7 @@
"pl": "escudo Zielonego Przylądka",
"pt": "escudo cabo-verdiano",
"ru": "Эскудо Кабо-Верде",
"sk": "Kapverdské escudo",
"sl": "zelenortski eskudo",
"sr": "зеленортски ескудо",
"sv": "Kapverdisk escudo",
@ -1405,6 +1408,7 @@
"pl": "frank Dżibuti",
"pt": "franco do Jibuti",
"ru": "Франк Джибути",
"sk": "Džibutský frank",
"sr": "џибутски франак",
"sv": "Djiboutisk franc",
"tr": "Cibuti frangı",
@ -1518,6 +1522,7 @@
"pt": "dinar argelino",
"ro": "Dinar algerian",
"ru": "алжирский динар",
"sk": "Alžírský dinár",
"sl": "alžirski dinar",
"sr": "алжирски динар",
"sv": "Algerisk dinar",
@ -1969,6 +1974,7 @@
"pl": "frank gwinejski",
"pt": "Franco da Guiné",
"ru": "Гвинейский франк",
"sk": "Guinejský frank",
"sl": "gvinejski frank",
"sr": "гвинејски франак",
"sv": "Guinesisk franc",
@ -2689,6 +2695,7 @@
"pt": "Franco comoriano",
"ro": "Franc comorian",
"ru": "Франк Комор",
"sk": "Komorský frank",
"sr": "коморски франак",
"sv": "Komoransk franc",
"tr": "Komor frangı",
@ -2986,6 +2993,7 @@
"pt": "rúpia do Sri Lanka",
"ru": "ланкийская рупия",
"si": "ශ්රී ලංකා රුපියල",
"sk": "Srílanská rupia",
"sl": "šrilanška rupija",
"sr": "шриланчанска рупија",
"sv": "Lankesisk rupie",
@ -3059,7 +3067,7 @@
"uk": "Лоті"
},
"LYD": {
"ar": "دينار ليبي",
"ar": "دينار ذهبي",
"bg": "Либийски динар",
"ca": "dinar libi",
"cs": "Libyjský dinár",
@ -3121,6 +3129,7 @@
"pt": "Dirham marroquino",
"ro": "Dirham marocan",
"ru": "марокканский дирхам",
"sk": "Marocký dirham",
"sl": "maroški dirham",
"sr": "марокански дирхам",
"sv": "Marockansk dirham",
@ -3140,6 +3149,7 @@
"et": "Moldova leu",
"fi": "Moldovan leu",
"fr": "leu moldave",
"gl": "leu moldovo",
"he": "לאו מולדובני",
"hr": "moldavski lej",
"hu": "moldován lej",
@ -3371,6 +3381,7 @@
"pl": "Ugija",
"pt": "Uguia",
"ru": "Мавританская угия",
"sk": "Mauritánska ukíjá",
"sr": "мауританска огија",
"sv": "Mauretansk ouguiya",
"tr": "Ugiya",
@ -3816,6 +3827,7 @@
"sl": "novozelandski dolar",
"sr": "новозеландски долар",
"sv": "Nyzeeländsk dollar",
"th": "ดอลลาร์นิวซีแลนด์",
"tr": "Yeni Zelanda doları",
"uk": "новозеландський долар",
"vi": "Đô la New Zealand"
@ -5386,12 +5398,14 @@
"ja": "スム",
"ko": "우즈베키스탄 숨",
"lt": "Uzbekijos sumas",
"lv": "Uzbekistānas soms",
"nl": "Oezbeekse sum",
"pa": "ਉਜ਼ਬੇਕਿਸਤਾਨੀ ਸੋਮ",
"pl": "Sum",
"pt": "som usbeque",
"ro": "Som uzbec",
"ru": "узбекский сум",
"sk": "Uzbecký som",
"sr": "узбекистански сом",
"sv": "Uzbekistansk som",
"tr": "Özbekistan somu",
@ -5645,7 +5659,7 @@
"eo": "specialaj rajtoj de enspezo",
"es": "Derechos Especiales de Giro",
"eu": "igorpen eskubide bereziak",
"fi": "Erityisnosto-oikeus",
"fi": "erityisnosto-oikeus",
"fr": "droits de tirage spéciaux",
"hr": "Posebna prava vučenja",
"hu": "különleges lehívási jog",
@ -5655,6 +5669,7 @@
"ko": "특별인출권",
"lt": "Specialiosios skolinimosi teisės",
"lv": "Speciālās aizņēmuma tiesības",
"ms": "hak pengeluaran khas",
"nl": "speciale trekkingsrechten",
"oc": "Drechs de tiratge Especials",
"pl": "specjalne prawa ciągnienia",
@ -5837,7 +5852,7 @@
"lt": "Randas",
"lv": "Dienvidāfrikas rands",
"ml": "സൗത്ത് ആഫ്രിക്കൻ റാൻഡ്",
"ms": "Rand",
"ms": "Rand Afrika Selatan",
"nl": "Zuid-Afrikaanse rand",
"oc": "Rand sudafrican",
"pl": "Rand",
@ -5900,6 +5915,7 @@
"ko": "짐바브웨 골드",
"nl": "Zimbabwe Gold",
"pl": "Złoto Zimbabwe",
"pt": "Ouro do Zimbábue",
"ru": "зимбабвийский золотой",
"sk": "zimbabwiansky zlatý",
"sl": "zimbabvejski gold",
@ -7817,6 +7833,7 @@
"eritrese nakfa": "ERN",
"erityinen nosto oikeus": "XDR",
"erityiset nosto oikeudet": "XDR",
"erityisnosto oikeudet": "XDR",
"erityisnosto oikeus": "XDR",
"ermeni dramı": "AMD",
"ermenistan dramı": "AMD",
@ -8372,6 +8389,8 @@
"haitský gourde": "HTG",
"haïtiaanse gourde": "HTG",
"hak penarikan khusus": "XDR",
"hak pengeluaran khas": "XDR",
"hak pengeluaran khusus": "XDR",
"halalas": "SAR",
"hegoafrikar rand": "ZAR",
"heller": "CZK",
@ -9115,6 +9134,7 @@
"leu da roménia": "RON",
"leu da romênia": "RON",
"leu de moldàvia": "MDL",
"leu de moldova": "MDL",
"leu moldau": "MDL",
"leu moldave": "MDL",
"leu moldavo": "MDL",
@ -9122,6 +9142,7 @@
"leu moldofa": "MDL",
"leu moldova": "MDL",
"leu moldovenesc": "MDL",
"leu moldovo": "MDL",
"leu romanès": "RON",
"leu romanés": "RON",
"leu romanian": "RON",
@ -9437,6 +9458,7 @@
"manat azerbaijandar": "AZN",
"manat azerbaijanês": "AZN",
"manat azerbaijano": "AZN",
"manat azerbaitjanés": "AZN",
"manat azerbaiyano": "AZN",
"manat azerbaïdjanais": "AZN",
"manat azerbejdżański": "AZN",
@ -9520,6 +9542,7 @@
"mauritanijska ouguja": "MRU",
"mauritanijska uguija": "MRU",
"mauritániai ouguiya": "MRU",
"mauritánska ukíjá": "MRU",
"mauritánská ukíjá": "MRU",
"mauritānijas oguja": "MRU",
"mauritiaanse roepee": "MUR",
@ -9985,6 +10008,7 @@
"ouguiya mauritana": "MRU",
"ouguiya mauritanien": "MRU",
"ouguiya mawritania": "MRU",
"ouro do zimbábue": "ZWG",
"örmény dram": "AMD",
"östkaribisk dollar": "XCD",
"özbekistan somu": "UZS",
@ -10796,6 +10820,7 @@
"salomona dolaro": "SBD",
"salomondollar": "SBD",
"salomonen dollar": "SBD",
"salomoninsaarten dollari": "SBD",
"salomonsaarten dollari": "SBD",
"salomonskootočni dolar": "SBD",
"salüng": "THB",
@ -11152,6 +11177,7 @@
"srilankansk rupee": "LKR",
"srilankanske rupee": "LKR",
"srí lanka i rúpia": "LKR",
"srílanská rupia": "LKR",
"srílanská rupie": "LKR",
"srpski dinar": "RSD",
"ssp": "SSP",
@ -11415,6 +11441,7 @@
"tengue": "KZT",
"tengue cazaque": "KZT",
"teňňe": "TMT",
"tetri": "GEL",
"thai baht": "THB",
"thai bát": "THB",
"thailandiar baht": "THB",
@ -11539,10 +11566,10 @@
"turkisk lira": "TRY",
"turkiska lira": "TRY",
"turkmeense manat": "TMT",
"turkmen manat": "TMT",
"turkmena manato": "TMT",
"turkmenistan manat": "TMT",
"turkmenistan new manat": "TMT",
"turkmenistani manat": "TMT",
"turkmenistani new manat": "TMT",
"turkmenistanin manat": "TMT",
"turkmenistansk manat": "TMT",
@ -11708,6 +11735,7 @@
"uzbekistano sumas": "UZS",
"uzbekistansk som": "UZS",
"uzbekistanski som": "UZS",
"uzbekistānas soms": "UZS",
"uzs": "UZS",
"új zélandi dollár": "NZD",
"ürdün dinarı": "JOD",
@ -11861,6 +11889,7 @@
"yuan cinese": "CNY",
"yuan renmimbi": "CNY",
"yuan renminbi": "CNY",
"yuan rmb": "CNY",
"yuans": "CNY",
"yuán chino": "CNY",
"z$": "ZWL",
@ -13734,6 +13763,7 @@
"دينار بحريني": "BHD",
"دينار تونسي": "TND",
"دينار جزائري": "DZD",
"دينار ذهبي": "LYD",
"دينار سوداني": "SDG",
"دينار صربي": "RSD",
"دينار عراقي": "IQD",
@ -14326,6 +14356,7 @@
"USD",
"TWD"
],
"ดอลลาร์นิวซีแลนด์": "NZD",
"ดอลลาร์บรูไน": "BND",
"ดอลลาร์สหรัฐ": "USD",
"ดอลลาร์สิงคโปร์": "SGD",
@ -14998,6 +15029,7 @@
"ボツワナ・プラ": "BWP",
"ボリバル・ソベラノ": "VES",
"ボリビアーノ": "BOB",
"ポンド・スターリング": "GBP",
"ポーランド・ズウォティ": [
"PLZ",
"PLN"
@ -15063,7 +15095,6 @@
"中華人民共和国の通貨": "CNY",
"中部アフリカcfaフラン": "XAF",
"人民元": "CNY",
"人民币": "CNY",
"人民幣": "CNY",
"元": [
"HKD",

File diff suppressed because one or more lines are too long

View File

@ -180,7 +180,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "prs-prs",
"fa": "fa-fa",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -188,14 +188,12 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -205,8 +203,6 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -214,7 +210,6 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -222,33 +217,22 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -256,13 +240,9 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant",
"zu": "zu-zu"
"zh_Hant": "zh-hant"
},
"regions": {
"am-ET": "am-et",
@ -478,14 +458,12 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -519,8 +497,6 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -529,23 +505,14 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -578,7 +545,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "prs-prs",
"fa": "fa-fa",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -586,14 +553,12 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -603,8 +568,6 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -612,7 +575,6 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -620,33 +582,22 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -654,13 +605,9 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant",
"zu": "zu-zu"
"zh_Hant": "zh-hant"
},
"regions": {
"am-ET": "am-et",
@ -876,14 +823,12 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -917,8 +862,6 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -927,23 +870,14 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -976,7 +910,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "prs-prs",
"fa": "fa-fa",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -984,14 +918,12 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -1001,8 +933,6 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -1010,7 +940,6 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -1018,33 +947,22 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -1052,13 +970,9 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant",
"zu": "zu-zu"
"zh_Hant": "zh-hant"
},
"regions": {
"am-ET": "am-et",
@ -1274,14 +1188,12 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -1315,8 +1227,6 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -1325,23 +1235,14 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "en-hk",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -1374,7 +1275,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "prs-prs",
"fa": "fa-fa",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -1382,14 +1283,12 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -1399,8 +1298,6 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -1408,7 +1305,6 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -1416,33 +1312,22 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -1450,13 +1335,9 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant",
"zu": "zu-zu"
"zh_Hant": "zh-hant"
},
"regions": {
"am-ET": "am-et",
@ -1672,14 +1553,12 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -1713,8 +1592,6 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -1723,23 +1600,14 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -1759,6 +1627,7 @@
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
@ -1769,7 +1638,6 @@
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ko": "ko",
"lt": "lt",
"lv": "lv",
@ -1860,6 +1728,7 @@
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
@ -1870,7 +1739,6 @@
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ko": "ko",
"lt": "lt",
"lv": "lv",
@ -1961,6 +1829,7 @@
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
@ -1971,7 +1840,6 @@
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ko": "ko",
"lt": "lt",
"lv": "lv",
@ -2062,6 +1930,7 @@
"el": "el",
"en-CA": "en-ca",
"en-GB": "en-gb",
"en-IN": "en-in",
"en-US": "en-us",
"es": "es",
"et": "et",
@ -2072,7 +1941,6 @@
"hu": "hu",
"id": "id",
"it": "it",
"ja-JP": "ja-jp",
"ko": "ko",
"lt": "lt",
"lv": "lv",
@ -7609,6 +7477,7 @@
"ami",
"an",
"ang",
"ann",
"anp",
"ar",
"arc",
@ -7727,6 +7596,7 @@
"hy",
"hyw",
"ia",
"iba",
"id",
"ie",
"ig",
@ -7815,6 +7685,7 @@
"no",
"nov",
"nqo",
"nr",
"nrm",
"nso",
"nv",
@ -7848,6 +7719,7 @@
"ro",
"roa-rup",
"roa-tara",
"rsk",
"ru",
"rue",
"rw",
@ -7886,6 +7758,7 @@
"ta",
"tay",
"tcy",
"tdd",
"te",
"tet",
"tg",

View File

@ -5,7 +5,7 @@
],
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
"versions": [
"130.0",
"129.0"
"132.0",
"131.0"
]
}

View File

@ -832,7 +832,7 @@
"Q104907390": {
"si_name": "Q182429",
"symbol": "nmi/h",
"to_si_factor": 0.514444
"to_si_factor": 0.5144444444444445
},
"Q104907398": {
"si_name": "Q215571",
@ -1336,7 +1336,7 @@
},
"Q106636307": {
"si_name": "Q80842107",
"symbol": "μS/cm",
"symbol": "μS/cm-1",
"to_si_factor": 0.0001
},
"Q106639711": {
@ -4142,7 +4142,7 @@
"Q23931103": {
"si_name": "Q25343",
"symbol": "nmi²",
"to_si_factor": 3434290.0120544
"to_si_factor": 3429904.0
},
"Q239830": {
"si_name": "Q3395194",

View File

@ -34,10 +34,10 @@ Implementations
"""
from typing import List, Dict, Any, Optional
from urllib.parse import quote
from urllib.parse import urlencode
from lxml import html
from searx.utils import extract_text, eval_xpath, eval_xpath_list
from searx.utils import extract_text, eval_xpath, eval_xpath_getindex, eval_xpath_list
from searx.enginelib.traits import EngineTraits
from searx.data import ENGINE_TRAITS
@ -53,7 +53,7 @@ about: Dict[str, Any] = {
# engine dependent config
categories: List[str] = ["files"]
paging: bool = False
paging: bool = True
# search-url
base_url: str = "https://annas-archive.org"
@ -99,9 +99,18 @@ def init(engine_settings=None): # pylint: disable=unused-argument
def request(query, params: Dict[str, Any]) -> Dict[str, Any]:
q = quote(query)
lang = traits.get_language(params["language"], traits.all_locale) # type: ignore
params["url"] = base_url + f"/search?lang={lang or ''}&content={aa_content}&ext={aa_ext}&sort={aa_sort}&q={q}"
args = {
'lang': lang,
'content': aa_content,
'ext': aa_ext,
'sort': aa_sort,
'q': query,
'page': params['pageno'],
}
# filter out None and empty values
filtered_args = dict((k, v) for k, v in args.items() if v)
params["url"] = f"{base_url}/search?{urlencode(filtered_args)}"
return params
@ -128,12 +137,12 @@ def response(resp) -> List[Dict[str, Optional[str]]]:
def _get_result(item):
return {
'template': 'paper.html',
'url': base_url + item.xpath('./@href')[0],
'url': base_url + extract_text(eval_xpath_getindex(item, './@href', 0)),
'title': extract_text(eval_xpath(item, './/h3/text()[1]')),
'publisher': extract_text(eval_xpath(item, './/div[contains(@class, "text-sm")]')),
'authors': [extract_text(eval_xpath(item, './/div[contains(@class, "italic")]'))],
'content': extract_text(eval_xpath(item, './/div[contains(@class, "text-xs")]')),
'thumbnail': item.xpath('.//img/@src')[0],
'thumbnail': extract_text(eval_xpath_getindex(item, './/img/@src', 0, default=None), allow_none=True),
}

View File

@ -18,7 +18,6 @@ from searx import (
)
from searx.utils import (
eval_xpath,
eval_xpath_getindex,
extract_text,
)
from searx.network import get # see https://github.com/searxng/searxng/issues/762
@ -54,31 +53,33 @@ paging = True
time_range_support = True
safesearch = True # user can't select but the results are filtered
url = 'https://lite.duckduckgo.com/lite/'
# url_ping = 'https://duckduckgo.com/t/sl_l'
url = "https://html.duckduckgo.com/html"
time_range_dict = {'day': 'd', 'week': 'w', 'month': 'm', 'year': 'y'}
form_data = {'v': 'l', 'api': 'd.js', 'o': 'json'}
__CACHE = []
def cache_vqd(query, value):
def _cache_key(data: dict):
return 'SearXNG_ddg_web_vqd' + redislib.secret_hash(f"{data['q']}//{data['kl']}")
def cache_vqd(data: dict, value):
"""Caches a ``vqd`` value from a query."""
c = redisdb.client()
if c:
logger.debug("cache vqd value: %s", value)
key = 'SearXNG_ddg_web_vqd' + redislib.secret_hash(query)
c.set(key, value, ex=600)
c.set(_cache_key(data), value, ex=600)
else:
logger.debug("MEM cache vqd value: %s", value)
if len(__CACHE) > 100: # cache vqd from last 100 queries
__CACHE.pop(0)
__CACHE.append((_cache_key(data), value))
def get_vqd(query):
"""Returns the ``vqd`` that fits to the *query*. If there is no ``vqd`` cached
(:py:obj:`cache_vqd`) the query is sent to DDG to get a vqd value from the
response.
.. hint::
If an empty string is returned there are no results for the ``query`` and
therefore no ``vqd`` value.
def get_vqd(data):
"""Returns the ``vqd`` that fits to the *query* (``data`` from HTTP POST).
DDG's bot detection is sensitive to the ``vqd`` value. For some search terms
(such as extremely long search terms that are often sent by bots), no ``vqd``
@ -106,28 +107,23 @@ def get_vqd(query):
- DuckDuckGo News: ``https://duckduckgo.com/news.js??q=...&vqd=...``
"""
key = _cache_key(data)
value = None
c = redisdb.client()
if c:
key = 'SearXNG_ddg_web_vqd' + redislib.secret_hash(query)
value = c.get(key)
if value or value == b'':
value = value.decode('utf-8')
logger.debug("re-use cached vqd value: %s", value)
logger.debug("re-use CACHED vqd value: %s", value)
return value
query_url = 'https://duckduckgo.com/?' + urlencode({'q': query})
res = get(query_url)
doc = lxml.html.fromstring(res.text)
for script in doc.xpath("//script[@type='text/javascript']"):
script = script.text
if 'vqd="' in script:
value = extr(script, 'vqd="', '"')
break
logger.debug("new vqd value: '%s'", value)
if value is not None:
cache_vqd(query, value)
return value
else:
for k, value in __CACHE:
if k == key:
logger.debug("MEM re-use CACHED vqd value: %s", value)
return value
return None
def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
@ -155,9 +151,10 @@ def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
.. hint::
`DDG-lite <https://lite.duckduckgo.com/lite>`__ does not offer a language
selection to the user, only a region can be selected by the user
(``eng_region`` from the example above). DDG-lite stores the selected
`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, only a region can be selected by the user (``eng_region``
from the example above). DDG-lite and *no Javascript* store the selected
region in a cookie::
params['cookies']['kl'] = eng_region # 'ar-es'
@ -241,10 +238,25 @@ def request(query, params):
query = quote_ddg_bangs(query)
# request needs a vqd argument
vqd = get_vqd(query)
if len(query) >= 500:
# DDG does not accept queries with more than 499 chars
params["url"] = None
return
# Advanced search syntax ends in CAPTCHA
# https://duckduckgo.com/duckduckgo-help-pages/results/syntax/
query = [
x.removeprefix("site:").removeprefix("intitle:").removeprefix("inurl:").removeprefix("filetype:")
for x in query.split()
]
eng_region = traits.get_region(params['searxng_locale'], traits.all_locale)
if eng_region == "wt-wt":
# https://html.duckduckgo.com/html sets an empty value for "all".
eng_region = ""
params['data']['kl'] = eng_region
params['cookies']['kl'] = eng_region
# eng_lang = get_ddg_lang(traits, params['searxng_locale'])
params['url'] = url
@ -252,54 +264,82 @@ def request(query, params):
params['data']['q'] = query
# The API is not documented, so we do some reverse engineering and emulate
# what https://lite.duckduckgo.com/lite/ does when you press "next Page"
# link again and again ..
# what https://html.duckduckgo.com/html does when you press "next Page" link
# again and again ..
params['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
params['data']['vqd'] = vqd
# initial page does not have an offset
params['headers']['Sec-Fetch-Dest'] = "document"
params['headers']['Sec-Fetch-Mode'] = "navigate" # at least this one is used by ddg's bot detection
params['headers']['Sec-Fetch-Site'] = "same-origin"
params['headers']['Sec-Fetch-User'] = "?1"
# Form of the initial search page does have empty values in the form
if params['pageno'] == 1:
params['data']['b'] = ""
params['data']['df'] = ''
if params['time_range'] in time_range_dict:
params['data']['df'] = time_range_dict[params['time_range']]
params['cookies']['df'] = time_range_dict[params['time_range']]
if params['pageno'] == 2:
# second page does have an offset of 20
offset = (params['pageno'] - 1) * 20
params['data']['s'] = offset
params['data']['dc'] = offset + 1
elif params['pageno'] > 2:
# third and following pages do have an offset of 20 + n*50
offset = 20 + (params['pageno'] - 2) * 50
params['data']['s'] = offset
params['data']['dc'] = offset + 1
# initial page does not have additional data in the input form
if params['pageno'] > 1:
# initial page does not have these additional data in the input form
params['data']['o'] = form_data.get('o', 'json')
params['data']['api'] = form_data.get('api', 'd.js')
params['data']['nextParams'] = form_data.get('nextParams', '')
params['data']['v'] = form_data.get('v', 'l')
params['headers']['Referer'] = 'https://lite.duckduckgo.com/'
params['headers']['Referer'] = url
params['data']['kl'] = eng_region
params['cookies']['kl'] = eng_region
# from here on no more params['data'] shuld be set, since this dict is
# needed to get a vqd value from the cache ..
params['data']['df'] = ''
if params['time_range'] in time_range_dict:
params['data']['df'] = time_range_dict[params['time_range']]
params['cookies']['df'] = time_range_dict[params['time_range']]
vqd = get_vqd(params['data'])
# Certain conditions must be met in order to call up one of the
# following pages ...
if vqd:
params['data']['vqd'] = vqd # follow up pages / requests needs a vqd argument
else:
# Don't try to call follow up pages without a vqd value. DDG
# recognizes this as a request from a bot. This lowers the
# reputation of the SearXNG IP and DDG starts to activate CAPTCHAs.
params["url"] = None
return
if params['searxng_locale'].startswith("zh"):
# Some locales (at least China) do not have a "next page" button and ddg
# will return a HTTP/2 403 Forbidden for a request of such a page.
params["url"] = None
return
logger.debug("param data: %s", params['data'])
logger.debug("param cookies: %s", params['cookies'])
return params
def detect_ddg_captcha(dom):
"""In case of CAPTCHA ddg open its own *not a Robot* dialog and is
not redirected to CAPTCHA page.
"""
if eval_xpath(dom, "//form[@id='challenge-form']"):
# set suspend time to zero is OK --> ddg does not block the IP
raise SearxEngineCaptchaException(suspended_time=0)
def is_ddg_captcha(dom):
"""In case of CAPTCHA ddg response its own *not a Robot* dialog and is not
redirected to a CAPTCHA page."""
return bool(eval_xpath(dom, "//form[@id='challenge-form']"))
def response(resp):
@ -309,37 +349,34 @@ def response(resp):
results = []
doc = lxml.html.fromstring(resp.text)
detect_ddg_captcha(doc)
result_table = eval_xpath(doc, '//html/body/form/div[@class="filters"]/table')
if is_ddg_captcha(doc):
# set suspend time to zero is OK --> ddg does not block the IP
raise SearxEngineCaptchaException(suspended_time=0, message=f"CAPTCHA ({resp.search_params['data'].get('kl')})")
if len(result_table) == 2:
# some locales (at least China) does not have a "next page" button and
# the layout of the HTML tables is different.
result_table = result_table[1]
elif not len(result_table) >= 3:
# no more results
return []
else:
result_table = result_table[2]
# update form data from response
form = eval_xpath(doc, '//html/body/form/div[@class="filters"]/table//input/..')
if len(form):
form = eval_xpath(doc, '//input[@name="vqd"]/..')
if len(form):
# some locales (at least China) does not have a "next page" button
form = form[0]
form_vqd = eval_xpath(form, '//input[@name="vqd"]/@value')[0]
form = form[0]
form_data['v'] = eval_xpath(form, '//input[@name="v"]/@value')[0]
form_data['api'] = eval_xpath(form, '//input[@name="api"]/@value')[0]
form_data['o'] = eval_xpath(form, '//input[@name="o"]/@value')[0]
logger.debug('form_data: %s', form_data)
cache_vqd(resp.search_params["data"], form_vqd)
tr_rows = eval_xpath(result_table, './/tr')
# In the last <tr> is the form of the 'previous/next page' links
tr_rows = tr_rows[:-1]
# just select "web-result" and ignore results of class "result--ad result--ad--small"
for div_result in eval_xpath(doc, '//div[@id="links"]/div[contains(@class, "web-result")]'):
len_tr_rows = len(tr_rows)
offset = 0
item = {}
title = eval_xpath(div_result, './/h2/a')
if not title:
# this is the "No results." item in the result list
continue
item["title"] = extract_text(title)
item["url"] = eval_xpath(div_result, './/h2/a/@href')[0]
item["content"] = extract_text(eval_xpath(div_result, './/a[contains(@class, "result__snippet")]')[0])
zero_click_info_xpath = '//html/body/form/div/table[2]/tr[2]/td/text()'
results.append(item)
zero_click_info_xpath = '//div[@id="zero_click_abstract"]'
zero_click = extract_text(eval_xpath(doc, zero_click_info_xpath)).strip()
if zero_click and "Your IP address is" not in zero_click and "Your user agent:" not in zero_click:
@ -352,33 +389,6 @@ def response(resp):
}
)
while len_tr_rows >= offset + 4:
# assemble table rows we need to scrap
tr_title = tr_rows[offset]
tr_content = tr_rows[offset + 1]
offset += 4
# ignore sponsored Adds <tr class="result-sponsored">
if tr_content.get('class') == 'result-sponsored':
continue
a_tag = eval_xpath_getindex(tr_title, './/td//a[@class="result-link"]', 0, None)
if a_tag is None:
continue
td_content = eval_xpath_getindex(tr_content, './/td[@class="result-snippet"]', 0, None)
if td_content is None:
continue
results.append(
{
'title': a_tag.text_content(),
'content': extract_text(td_content),
'url': a_tag.get('href'),
}
)
return results

View File

@ -62,7 +62,7 @@ filter_mapping = {0: 'off', 1: 'medium', 2: 'high'}
results_xpath = './/div[contains(@jscontroller, "SC7lYd")]'
title_xpath = './/a/h3[1]'
href_xpath = './/a[h3]/@href'
content_xpath = './/div[@data-sncf="1"]'
content_xpath = './/div[contains(@data-sncf, "1")]'
# Suggestions are links placed in a *card-section*, we extract only the text
# from the links not the links itself.

View File

@ -20,17 +20,17 @@
from __future__ import annotations
from typing import Literal
import os
import abc
import dataclasses
import hashlib
import logging
import pathlib
import sqlite3
import tempfile
import time
import typer
from pydantic import BaseModel
import msgspec
from searx import sqlitedb
from searx import logger
@ -90,7 +90,7 @@ def init(cfg: "FaviconCacheConfig"):
raise NotImplementedError(f"favicons db_type '{cfg.db_type}' is unknown")
class FaviconCacheConfig(BaseModel):
class FaviconCacheConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
"""Configuration of the favicon cache."""
db_type: Literal["sqlite", "mem"] = "sqlite"
@ -103,7 +103,7 @@ class FaviconCacheConfig(BaseModel):
:py:obj:`.cache.FaviconCacheMEM` (not recommended)
"""
db_url: pathlib.Path = pathlib.Path(tempfile.gettempdir()) / "faviconcache.db"
db_url: str = tempfile.gettempdir() + os.sep + "faviconcache.db"
"""URL of the SQLite DB, the path to the database file."""
HOLD_TIME: int = 60 * 60 * 24 * 30 # 30 days

View File

@ -4,9 +4,8 @@
from __future__ import annotations
import pathlib
from pydantic import BaseModel
import msgspec
from searx.compat import tomllib
from .cache import FaviconCacheConfig
from .proxy import FaviconProxyConfig
@ -19,7 +18,7 @@ TOML_CACHE_CFG: dict[str, "FaviconConfig"] = {}
DEFAULT_CFG_TOML_PATH = pathlib.Path(__file__).parent / "favicons.toml"
class FaviconConfig(BaseModel):
class FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
"""The class aggregates configurations of the favicon tools"""
cfg_schema: int
@ -28,10 +27,10 @@ class FaviconConfig(BaseModel):
By specifying a version, it is possible to ensure downward compatibility in
the event of future changes to the configuration schema"""
cache: FaviconCacheConfig = FaviconCacheConfig()
cache: FaviconCacheConfig = msgspec.field(default_factory=FaviconCacheConfig)
"""Setup of the :py:obj:`.cache.FaviconCacheConfig`."""
proxy: FaviconProxyConfig = FaviconProxyConfig()
proxy: FaviconProxyConfig = msgspec.field(default_factory=FaviconProxyConfig)
"""Setup of the :py:obj:`.proxy.FaviconProxyConfig`."""
@classmethod
@ -45,18 +44,22 @@ class FaviconConfig(BaseModel):
return cached
with cfg_file.open("rb") as f:
data = f.read()
cfg = tomllib.load(f)
cfg = cfg.get("favicons", cfg)
cfg = msgspec.toml.decode(data, type=_FaviconConfig)
schema = cfg.favicons.cfg_schema
if schema != CONFIG_SCHEMA:
raise ValueError(
f"config schema version {CONFIG_SCHEMA} is needed, version {schema} is given in {cfg_file}"
)
schema = cfg.get("cfg_schema")
if schema != CONFIG_SCHEMA:
raise ValueError(
f"config schema version {CONFIG_SCHEMA} is needed, version {schema} is given in {cfg_file}"
)
cfg = cfg.favicons
if use_cache and cached:
TOML_CACHE_CFG[str(cfg_file.resolve())] = cfg
cfg = cls(**cfg)
if use_cache and cached:
TOML_CACHE_CFG[str(cfg_file.resolve())] = cfg
return cfg
return cfg
class _FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
# wrapper struct for root object "favicons."
favicons: FaviconConfig

View File

@ -12,7 +12,7 @@ import urllib.parse
import flask
from httpx import HTTPError
from pydantic import BaseModel
import msgspec
from searx import get_setting
@ -41,7 +41,7 @@ def _initial_resolver_map():
return d
class FaviconProxyConfig(BaseModel):
class FaviconProxyConfig(msgspec.Struct):
"""Configuration of the favicon proxy."""
max_age: int = 60 * 60 * 24 * 7 # seven days
@ -59,7 +59,7 @@ class FaviconProxyConfig(BaseModel):
outgoing request of the resolver. By default, the value from
:ref:`outgoing.request_timeout <settings outgoing>` setting is used."""
resolver_map: dict[str, str] = _initial_resolver_map()
resolver_map: dict[str, str] = msgspec.field(default_factory=_initial_resolver_map)
"""The resolver_map is a key / value dictionary where the key is the name of
the resolver and the value is the fully qualifying name (fqn) of resolver's
function (the callable). The resolvers from the python module

View File

@ -128,9 +128,6 @@ _INSTALLED = False
LIMITER_CFG_SCHEMA = Path(__file__).parent / "limiter.toml"
"""Base configuration (schema) of the botdetection."""
LIMITER_CFG = Path('/etc/searxng/limiter.toml')
"""Local Limiter configuration."""
CFG_DEPRECATED = {
# "dummy.old.foo": "config 'dummy.old.foo' exists only for tests. Don't use it in your real project config."
}
@ -138,8 +135,12 @@ CFG_DEPRECATED = {
def get_cfg() -> config.Config:
global CFG # pylint: disable=global-statement
if CFG is None:
CFG = config.Config.from_toml(LIMITER_CFG_SCHEMA, LIMITER_CFG, CFG_DEPRECATED)
from . import settings_loader # pylint: disable=import-outside-toplevel
cfg_file = (settings_loader.get_user_cfg_folder() or Path("/etc/searxng")) / "limiter.toml"
CFG = config.Config.from_toml(LIMITER_CFG_SCHEMA, cfg_file, CFG_DEPRECATED)
return CFG

View File

@ -8,7 +8,8 @@ import operator
from multiprocessing import Process, Queue
from typing import Callable
import babel.numbers
import flask
import babel
from flask_babel import gettext
from searx.plugins import logger
@ -100,14 +101,17 @@ def post_search(_request, search):
# replace commonly used math operators with their proper Python operator
query = query.replace("x", "*").replace(":", "/")
# use UI language
ui_locale = babel.Locale.parse(flask.request.preferences.get_value('locale'), sep='-')
# parse the number system in a localized way
def _decimal(match: re.Match) -> str:
val = match.string[match.start() : match.end()]
val = babel.numbers.parse_decimal(val, search.search_query.locale, numbering_system="latn")
val = babel.numbers.parse_decimal(val, ui_locale, numbering_system="latn")
return str(val)
decimal = search.search_query.locale.number_symbols["latn"]["decimal"]
group = search.search_query.locale.number_symbols["latn"]["group"]
decimal = ui_locale.number_symbols["latn"]["decimal"]
group = ui_locale.number_symbols["latn"]["group"]
query = re.sub(f"[0-9]+[{decimal}|{group}][0-9]+[{decimal}|{group}]?[0-9]?", _decimal, query)
# only numbers and math operators are accepted
@ -121,6 +125,6 @@ def post_search(_request, search):
result = timeout_func(0.05, _eval_expr, query_py_formatted)
if result is None or result == "":
return True
result = babel.numbers.format_decimal(result, locale=search.search_query.locale)
result = babel.numbers.format_decimal(result, locale=ui_locale)
search.result_container.answers['calculate'] = {'answer': f"{search.search_query.query} = {result}"}
return True

View File

@ -1,6 +1,8 @@
general:
# Debug mode, only for development. Is overwritten by ${SEARXNG_DEBUG}
debug: false
# Application log level - leave blank to use the default 'WARNING' level
log_level: "warning"
# displayed name
instance_name: "SearXNG"
# For example: https://example.com/privacy

View File

@ -138,6 +138,7 @@ def apply_schema(settings, schema, path_list):
SCHEMA = {
'general': {
'debug': SettingsValue(bool, False, 'SEARXNG_DEBUG'),
'log_level': SettingsValue(str, 'warning'),
'instance_name': SettingsValue(str, 'SearXNG'),
'privacypolicy_url': SettingsValue((None, False, str), None),
'contact_url': SettingsValue((None, False, str), None),

View File

@ -39,7 +39,7 @@ msgstr ""
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-10-06 14:31+0000\n"
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
"Last-Translator: Atul_Eterno <Atul_Eterno@users.noreply.translate.codeberg."
"org>\n"
"Language-Team: Spanish <https://translate.codeberg.org/projects/searxng/"
@ -49,7 +49,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -155,7 +155,7 @@ msgstr "preguntas y respuestas"
#. CATEGORY_GROUPS['REPOS']
#: searx/searxng.msg
msgid "repos"
msgstr "repos"
msgstr "repositorios"
#. CATEGORY_GROUPS['SOFTWARE_WIKIS']
#: searx/searxng.msg

View File

@ -22,13 +22,14 @@
# MVDW-Java <MVDW-Java@users.noreply.translate.codeberg.org>, 2024.
# notlmutsaers <notlmutsaers@users.noreply.translate.codeberg.org>, 2024.
# return42 <return42@users.noreply.translate.codeberg.org>, 2024.
# ljansen <ljansen@users.noreply.translate.codeberg.org>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-10-15 12:18+0000\n"
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
"PO-Revision-Date: 2024-10-28 21:07+0000\n"
"Last-Translator: ljansen <ljansen@users.noreply.translate.codeberg.org>\n"
"Language-Team: Dutch <https://translate.codeberg.org/projects/searxng/"
"searxng/nl/>\n"
"Language: nl\n"
@ -36,7 +37,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -464,7 +465,7 @@ msgstr "Bereken {functions} van de opties"
#: searx/engines/mozhi.py:57
msgid "Synonyms"
msgstr ""
msgstr "Synoniemen"
#: searx/engines/openstreetmap.py:159
msgid "Get directions"
@ -1234,12 +1235,13 @@ msgid "Max time"
msgstr "Max. duur"
#: searx/templates/simple/preferences/favicon.html:2
#, fuzzy
msgid "Favicon Resolver"
msgstr ""
msgstr "favicon-resolver"
#: searx/templates/simple/preferences/favicon.html:15
msgid "Display favicons near search results"
msgstr ""
msgstr "Vertoon zoekresultaten naast favicons"
#: searx/templates/simple/preferences/footer.html:2
msgid ""

View File

@ -23,8 +23,8 @@ msgstr ""
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-10-15 12:18+0000\n"
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
"PO-Revision-Date: 2024-10-28 21:07+0000\n"
"Last-Translator: Eryk Michalak <gnu.ewm@protonmail.com>\n"
"Language-Team: Polish <https://translate.codeberg.org/projects/searxng/"
"searxng/pl/>\n"
"Language: pl\n"
@ -34,7 +34,7 @@ msgstr ""
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && ("
"n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && "
"n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -1230,7 +1230,7 @@ msgstr "Maksymalny czas"
#: searx/templates/simple/preferences/favicon.html:2
msgid "Favicon Resolver"
msgstr ""
msgstr "Pobieranie favikony"
#: searx/templates/simple/preferences/favicon.html:15
msgid "Display favicons near search results"

View File

@ -19,13 +19,14 @@
# gvlx <gvlx@users.noreply.translate.codeberg.org>, 2024.
# ds451 <ds451@users.noreply.translate.codeberg.org>, 2024.
# Pedro_Tresp <Pedro_Tresp@users.noreply.translate.codeberg.org>, 2024.
# saltsnorter <saltsnorter@users.noreply.translate.codeberg.org>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-10-13 23:26+0000\n"
"Last-Translator: Pedro_Tresp <Pedro_Tresp@users.noreply.translate.codeberg."
"PO-Revision-Date: 2024-10-29 05:54+0000\n"
"Last-Translator: saltsnorter <saltsnorter@users.noreply.translate.codeberg."
"org>\n"
"Language-Team: Portuguese <https://translate.codeberg.org/projects/searxng/"
"searxng/pt/>\n"
@ -34,7 +35,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -175,7 +176,7 @@ msgstr "escuro"
#. STYLE_NAMES['BLACK']
#: searx/searxng.msg
msgid "black"
msgstr ""
msgstr "preto"
#. BRAND_CUSTOM_LINKS['UPTIME']
#: searx/searxng.msg
@ -1228,11 +1229,11 @@ msgstr "Tempo máximo"
#: searx/templates/simple/preferences/favicon.html:2
msgid "Favicon Resolver"
msgstr ""
msgstr "Solucionador do Favicon"
#: searx/templates/simple/preferences/favicon.html:15
msgid "Display favicons near search results"
msgstr ""
msgstr "Monstra os favicons nos proximos os resultados"
#: searx/templates/simple/preferences/footer.html:2
msgid ""

View File

@ -30,13 +30,14 @@
# Pyrbor <Pyrbor@users.noreply.translate.codeberg.org>, 2024.
# rodgui <rodgui@users.noreply.translate.codeberg.org>, 2024.
# rafablog77 <rafablog77@users.noreply.translate.codeberg.org>, 2024.
# Juno Takano <jutty@users.noreply.translate.codeberg.org>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-10-15 12:18+0000\n"
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
"PO-Revision-Date: 2024-10-31 12:16+0000\n"
"Last-Translator: Juno Takano <jutty@users.noreply.translate.codeberg.org>\n"
"Language-Team: Portuguese (Brazil) <https://translate.codeberg.org/projects/"
"searxng/searxng/pt_BR/>\n"
"Language: pt_BR\n"
@ -44,7 +45,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.7.2\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -185,7 +186,7 @@ msgstr "escuro"
#. STYLE_NAMES['BLACK']
#: searx/searxng.msg
msgid "black"
msgstr ""
msgstr "preto"
#. BRAND_CUSTOM_LINKS['UPTIME']
#: searx/searxng.msg
@ -472,7 +473,7 @@ msgstr "Computar {functions} dos argumentos"
#: searx/engines/mozhi.py:57
msgid "Synonyms"
msgstr ""
msgstr "Sinônimos"
#: searx/engines/openstreetmap.py:159
msgid "Get directions"
@ -1243,7 +1244,7 @@ msgstr "Tempo máximo"
#: searx/templates/simple/preferences/favicon.html:2
msgid "Favicon Resolver"
msgstr ""
msgstr "Resolvedor de Favicons"
#: searx/templates/simple/preferences/favicon.html:15
msgid "Display favicons near search results"

View File

@ -12,19 +12,19 @@
# tvminh19 <tvminh19@users.noreply.translate.codeberg.org>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-08-07 01:02+0000\n"
"Last-Translator: tvminh19 <tvminh19@users.noreply.translate.codeberg.org>"
"\n"
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
"Language-Team: Vietnamese <https://translate.codeberg.org/projects/searxng/"
"searxng/vi/>\n"
"Language: vi\n"
"Language-Team: Vietnamese "
"<https://translate.codeberg.org/projects/searxng/searxng/vi/>\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -186,7 +186,7 @@ msgstr "Nhiệt độ trung bình."
#. WEATHER_TERMS['CLOUD COVER']
#: searx/engines/open_meteo.py:91 searx/searxng.msg
msgid "Cloud cover"
msgstr ""
msgstr "Mây che phủ"
#. WEATHER_TERMS['CONDITION']
#: searx/engines/duckduckgo_weather.py:45 searx/engines/wttr.py:51
@ -283,7 +283,7 @@ msgstr ""
#: searx/engines/duckduckgo_weather.py:58 searx/engines/open_meteo.py:86
#: searx/engines/wttr.py:62 searx/searxng.msg
msgid "Wind"
msgstr ""
msgstr "Gió"
#. SOCIAL_MEDIA_TERMS['SUBSCRIBERS']
#: searx/engines/lemmy.py:85 searx/searxng.msg
@ -1990,4 +1990,3 @@ msgstr "ẩn phim"
#~ msgid "Engines cannot retrieve results"
#~ msgstr "Các trình tìm kiếm không nhận được kết quả"

View File

@ -28,18 +28,19 @@
# hugoalh <hugoalh@users.noreply.translate.codeberg.org>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: searx\n"
"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
"PO-Revision-Date: 2024-08-12 04:00+0000\n"
"Last-Translator: hugoalh <hugoalh@users.noreply.translate.codeberg.org>\n"
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
"Language-Team: Chinese (Traditional Han script) <https://translate.codeberg."
"org/projects/searxng/searxng/zh_Hant/>\n"
"Language: zh_Hant_TW\n"
"Language-Team: Chinese (Traditional) "
"<https://translate.codeberg.org/projects/searxng/searxng/zh_Hant/>\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.8.1\n"
"Generated-By: Babel 2.16.0\n"
#. CONSTANT_NAMES['NO_SUBGROUPING']
@ -180,7 +181,7 @@ msgstr "黑暗"
#. STYLE_NAMES['BLACK']
#: searx/searxng.msg
msgid "black"
msgstr ""
msgstr "黑色"
#. BRAND_CUSTOM_LINKS['UPTIME']
#: searx/searxng.msg
@ -467,7 +468,7 @@ msgstr "計算 {functions} 參數"
#: searx/engines/mozhi.py:57
msgid "Synonyms"
msgstr ""
msgstr "同義詞"
#: searx/engines/openstreetmap.py:159
msgid "Get directions"
@ -942,7 +943,7 @@ msgstr "來自搜尋引擎的訊息"
#: searx/templates/simple/elements/engines_msg.html:7
msgid "seconds"
msgstr ""
msgstr ""
#: searx/templates/simple/elements/search_url.html:3
msgid "Search URL"
@ -1206,11 +1207,11 @@ msgstr "最大時間"
#: searx/templates/simple/preferences/favicon.html:2
msgid "Favicon Resolver"
msgstr ""
msgstr "網站圖標搜索器"
#: searx/templates/simple/preferences/favicon.html:15
msgid "Display favicons near search results"
msgstr ""
msgstr "在搜尋結果旁顯示網站圖標"
#: searx/templates/simple/preferences/footer.html:2
msgid ""
@ -1911,4 +1912,3 @@ msgstr "隱藏影片"
#~ msgid "Engines cannot retrieve results"
#~ msgstr "引擎無法擷取結果"

View File

@ -517,7 +517,7 @@ def pre_request():
preferences.parse_dict({"language": language})
logger.debug('set language %s (from browser)', preferences.get_value("language"))
# locale is defined neither in settings nor in preferences
# UI locale is defined neither in settings nor in preferences
# use browser headers
if not preferences.get_value("locale"):
locale = _get_browser_language(request, LOCALE_NAMES.keys())

View File

@ -1,9 +1,10 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring
from mock import Mock
import flask
from parameterized.parameterized import parameterized
from searx import plugins
from searx import preferences
from tests import SearxTestCase
from .test_utils import random_string
@ -11,63 +12,78 @@ from .test_plugins import get_search_mock
class PluginCalculator(SearxTestCase): # pylint: disable=missing-class-docstring
def setUp(self):
from searx import webapp # pylint: disable=import-outside-toplevel
self.webapp = webapp
self.store = plugins.PluginStore()
plugin = plugins.load_and_initialize_plugin('searx.plugins.calculator', False, (None, {}))
self.store.register(plugin)
self.preferences = preferences.Preferences(["simple"], ["general"], {}, self.store)
self.preferences.parse_dict({"locale": "en"})
def test_plugin_store_init(self):
self.assertEqual(1, len(self.store.plugins))
def test_single_page_number_true(self):
request = Mock(remote_addr='127.0.0.1')
search = get_search_mock(query=random_string(10), pageno=2)
result = self.store.call(self.store.plugins, 'post_search', request, search)
with self.webapp.app.test_request_context():
flask.request.preferences = self.preferences
search = get_search_mock(query=random_string(10), pageno=2)
result = self.store.call(self.store.plugins, 'post_search', flask.request, search)
self.assertTrue(result)
self.assertNotIn('calculate', search.result_container.answers)
def test_long_query_true(self):
request = Mock(remote_addr='127.0.0.1')
search = get_search_mock(query=random_string(101), pageno=1)
result = self.store.call(self.store.plugins, 'post_search', request, search)
with self.webapp.app.test_request_context():
flask.request.preferences = self.preferences
search = get_search_mock(query=random_string(101), pageno=1)
result = self.store.call(self.store.plugins, 'post_search', flask.request, search)
self.assertTrue(result)
self.assertNotIn('calculate', search.result_container.answers)
def test_alpha_true(self):
request = Mock(remote_addr='127.0.0.1')
search = get_search_mock(query=random_string(10), pageno=1)
result = self.store.call(self.store.plugins, 'post_search', request, search)
with self.webapp.app.test_request_context():
flask.request.preferences = self.preferences
search = get_search_mock(query=random_string(10), pageno=1)
result = self.store.call(self.store.plugins, 'post_search', flask.request, search)
self.assertTrue(result)
self.assertNotIn('calculate', search.result_container.answers)
@parameterized.expand(
[
("1+1", "2", "en-US"),
("1-1", "0", "en-US"),
("1*1", "1", "en-US"),
("1/1", "1", "en-US"),
("1**1", "1", "en-US"),
("1^1", "1", "en-US"),
("1,000.0+1,000.0", "2,000", "en-US"),
("1.0+1.0", "2", "en-US"),
("1.0-1.0", "0", "en-US"),
("1.0*1.0", "1", "en-US"),
("1.0/1.0", "1", "en-US"),
("1.0**1.0", "1", "en-US"),
("1.0^1.0", "1", "en-US"),
("1.000,0+1.000,0", "2.000", "de-DE"),
("1,0+1,0", "2", "de-DE"),
("1,0-1,0", "0", "de-DE"),
("1,0*1,0", "1", "de-DE"),
("1,0/1,0", "1", "de-DE"),
("1,0**1,0", "1", "de-DE"),
("1,0^1,0", "1", "de-DE"),
("1+1", "2", "en"),
("1-1", "0", "en"),
("1*1", "1", "en"),
("1/1", "1", "en"),
("1**1", "1", "en"),
("1^1", "1", "en"),
("1,000.0+1,000.0", "2,000", "en"),
("1.0+1.0", "2", "en"),
("1.0-1.0", "0", "en"),
("1.0*1.0", "1", "en"),
("1.0/1.0", "1", "en"),
("1.0**1.0", "1", "en"),
("1.0^1.0", "1", "en"),
("1.000,0+1.000,0", "2.000", "de"),
("1,0+1,0", "2", "de"),
("1,0-1,0", "0", "de"),
("1,0*1,0", "1", "de"),
("1,0/1,0", "1", "de"),
("1,0**1,0", "1", "de"),
("1,0^1,0", "1", "de"),
]
)
def test_localized_query(self, operation: str, contains_result: str, lang: str):
request = Mock(remote_addr='127.0.0.1')
search = get_search_mock(query=operation, lang=lang, pageno=1)
result = self.store.call(self.store.plugins, 'post_search', request, search)
with self.webapp.app.test_request_context():
self.preferences.parse_dict({"locale": lang})
flask.request.preferences = self.preferences
search = get_search_mock(query=operation, lang=lang, pageno=1)
result = self.store.call(self.store.plugins, 'post_search', flask.request, search)
self.assertTrue(result)
self.assertIn('calculate', search.result_container.answers)
self.assertIn(contains_result, search.result_container.answers['calculate']['answer'])
@ -78,8 +94,10 @@ class PluginCalculator(SearxTestCase): # pylint: disable=missing-class-docstrin
]
)
def test_invalid_operations(self, operation):
request = Mock(remote_addr='127.0.0.1')
search = get_search_mock(query=operation, pageno=1)
result = self.store.call(self.store.plugins, 'post_search', request, search)
with self.webapp.app.test_request_context():
flask.request.preferences = self.preferences
search = get_search_mock(query=operation, pageno=1)
result = self.store.call(self.store.plugins, 'post_search', flask.request, search)
self.assertTrue(result)
self.assertNotIn('calculate', search.result_container.answers)