mirror of https://github.com/searxng/searxng.git
Compare commits
1 Commits
6afd4695af
...
e7170d3621
Author | SHA1 | Date |
---|---|---|
k2s | e7170d3621 |
|
@ -45,6 +45,14 @@ jobs:
|
||||||
make V=1 gecko.driver
|
make V=1 gecko.driver
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: make V=1 ci.test
|
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:
|
themes:
|
||||||
name: Themes
|
name: Themes
|
||||||
|
|
|
@ -338,7 +338,6 @@ valid-metaclass-classmethod-first-arg=mcs
|
||||||
|
|
||||||
# Maximum number of arguments for function / method
|
# Maximum number of arguments for function / method
|
||||||
max-args=8
|
max-args=8
|
||||||
max-positional-arguments=14
|
|
||||||
|
|
||||||
# Maximum number of attributes for a class (see R0902).
|
# Maximum number of attributes for a class (see R0902).
|
||||||
max-attributes=20
|
max-attributes=20
|
||||||
|
|
|
@ -2,14 +2,14 @@ mock==5.1.0
|
||||||
nose2[coverage_plugin]==0.15.1
|
nose2[coverage_plugin]==0.15.1
|
||||||
cov-core==1.15.0
|
cov-core==1.15.0
|
||||||
black==24.3.0
|
black==24.3.0
|
||||||
pylint==3.3.1
|
pylint==3.2.7
|
||||||
splinter==0.21.0
|
splinter==0.21.0
|
||||||
selenium==4.25.0
|
selenium==4.25.0
|
||||||
Pallets-Sphinx-Themes==2.3.0
|
Pallets-Sphinx-Themes==2.1.3
|
||||||
Sphinx==7.4.7
|
Sphinx==7.4.7
|
||||||
sphinx-issues==5.0.0
|
sphinx-issues==4.1.0
|
||||||
sphinx-jinja==2.0.2
|
sphinx-jinja==2.0.2
|
||||||
sphinx-tabs==3.4.7
|
sphinx-tabs==3.4.5
|
||||||
sphinxcontrib-programoutput==0.17
|
sphinxcontrib-programoutput==0.17
|
||||||
sphinx-autobuild==2024.10.3
|
sphinx-autobuild==2024.10.3
|
||||||
sphinx-notfound-page==1.0.4
|
sphinx-notfound-page==1.0.4
|
||||||
|
|
|
@ -9,13 +9,13 @@ python-dateutil==2.9.0.post0
|
||||||
pyyaml==6.0.2
|
pyyaml==6.0.2
|
||||||
httpx[http2]==0.24.1
|
httpx[http2]==0.24.1
|
||||||
Brotli==1.1.0
|
Brotli==1.1.0
|
||||||
uvloop==0.21.0
|
uvloop==0.20.0
|
||||||
httpx-socks[asyncio]==0.7.7
|
httpx-socks[asyncio]==0.7.7
|
||||||
setproctitle==1.3.3
|
setproctitle==1.3.3
|
||||||
redis==5.0.8
|
redis==5.0.8
|
||||||
markdown-it-py==3.0.0
|
markdown-it-py==3.0.0
|
||||||
fasttext-predict==0.9.2.2
|
fasttext-predict==0.9.2.2
|
||||||
tomli==2.0.2; python_version < '3.11'
|
pytomlpp==1.0.13; python_version < '3.11'
|
||||||
msgspec==0.18.6
|
pydantic==2.9.2
|
||||||
eval_type_backport; python_version < '3.9'
|
eval_type_backport; python_version < '3.9'
|
||||||
typer-slim==0.12.5
|
typer-slim==0.12.5
|
||||||
|
|
|
@ -14,7 +14,17 @@ import typing
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from ..compat import tomllib
|
try:
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
pytomlpp = None
|
||||||
|
USE_TOMLLIB = True
|
||||||
|
except ImportError:
|
||||||
|
import pytomlpp
|
||||||
|
|
||||||
|
tomllib = None
|
||||||
|
USE_TOMLLIB = False
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Config', 'UNSET', 'SchemaIssue']
|
__all__ = ['Config', 'UNSET', 'SchemaIssue']
|
||||||
|
|
||||||
|
@ -173,10 +183,19 @@ class Config:
|
||||||
|
|
||||||
|
|
||||||
def toml_load(file_name):
|
def toml_load(file_name):
|
||||||
|
if USE_TOMLLIB:
|
||||||
|
# Python >= 3.11
|
||||||
|
try:
|
||||||
|
with open(file_name, "rb") as f:
|
||||||
|
return tomllib.load(f)
|
||||||
|
except tomllib.TOMLDecodeError as exc:
|
||||||
|
msg = str(exc).replace('\t', '').replace('\n', ' ')
|
||||||
|
log.error("%s: %s", file_name, msg)
|
||||||
|
raise
|
||||||
|
# fallback to pytomlpp for Python < 3.11
|
||||||
try:
|
try:
|
||||||
with open(file_name, "rb") as f:
|
return pytomlpp.load(file_name)
|
||||||
return tomllib.load(f)
|
except pytomlpp.DecodeError as exc:
|
||||||
except tomllib.TOMLDecodeError as exc:
|
|
||||||
msg = str(exc).replace('\t', '').replace('\n', ' ')
|
msg = str(exc).replace('\t', '').replace('\n', ' ')
|
||||||
log.error("%s: %s", file_name, msg)
|
log.error("%s: %s", file_name, msg)
|
||||||
raise
|
raise
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -82,7 +82,7 @@
|
||||||
"af": "Albanese lek",
|
"af": "Albanese lek",
|
||||||
"ar": "ليك ألباني",
|
"ar": "ليك ألباني",
|
||||||
"bg": "Албански лек",
|
"bg": "Албански лек",
|
||||||
"ca": "Lek (moneda)",
|
"ca": "lek",
|
||||||
"cs": "Albánský lek",
|
"cs": "Albánský lek",
|
||||||
"cy": "Lek",
|
"cy": "Lek",
|
||||||
"da": "Lek",
|
"da": "Lek",
|
||||||
|
@ -383,7 +383,6 @@
|
||||||
"nl": "Azerbeidzjaanse manat",
|
"nl": "Azerbeidzjaanse manat",
|
||||||
"oc": "Manat",
|
"oc": "Manat",
|
||||||
"pa": "ਅਜ਼ਰਬਾਈਜਾਨੀ ਮਨਾਤ",
|
"pa": "ਅਜ਼ਰਬਾਈਜਾਨੀ ਮਨਾਤ",
|
||||||
"pap": "Manat Azerbaijano",
|
|
||||||
"pl": "Manat azerski",
|
"pl": "Manat azerski",
|
||||||
"pt": "Manat azeri",
|
"pt": "Manat azeri",
|
||||||
"ro": "Manat azer",
|
"ro": "Manat azer",
|
||||||
|
@ -607,7 +606,6 @@
|
||||||
"pt": "Franco do Burúndi",
|
"pt": "Franco do Burúndi",
|
||||||
"ro": "franc burundez",
|
"ro": "franc burundez",
|
||||||
"ru": "бурундийский франк",
|
"ru": "бурундийский франк",
|
||||||
"sk": "Burundský frank",
|
|
||||||
"sl": "burundijski frank",
|
"sl": "burundijski frank",
|
||||||
"sr": "бурундски франак",
|
"sr": "бурундски франак",
|
||||||
"sv": "Burundisk franc",
|
"sv": "Burundisk franc",
|
||||||
|
@ -1329,7 +1327,6 @@
|
||||||
"pl": "escudo Zielonego Przylądka",
|
"pl": "escudo Zielonego Przylądka",
|
||||||
"pt": "escudo cabo-verdiano",
|
"pt": "escudo cabo-verdiano",
|
||||||
"ru": "Эскудо Кабо-Верде",
|
"ru": "Эскудо Кабо-Верде",
|
||||||
"sk": "Kapverdské escudo",
|
|
||||||
"sl": "zelenortski eskudo",
|
"sl": "zelenortski eskudo",
|
||||||
"sr": "зеленортски ескудо",
|
"sr": "зеленортски ескудо",
|
||||||
"sv": "Kapverdisk escudo",
|
"sv": "Kapverdisk escudo",
|
||||||
|
@ -1408,7 +1405,6 @@
|
||||||
"pl": "frank Dżibuti",
|
"pl": "frank Dżibuti",
|
||||||
"pt": "franco do Jibuti",
|
"pt": "franco do Jibuti",
|
||||||
"ru": "Франк Джибути",
|
"ru": "Франк Джибути",
|
||||||
"sk": "Džibutský frank",
|
|
||||||
"sr": "џибутски франак",
|
"sr": "џибутски франак",
|
||||||
"sv": "Djiboutisk franc",
|
"sv": "Djiboutisk franc",
|
||||||
"tr": "Cibuti frangı",
|
"tr": "Cibuti frangı",
|
||||||
|
@ -1522,7 +1518,6 @@
|
||||||
"pt": "dinar argelino",
|
"pt": "dinar argelino",
|
||||||
"ro": "Dinar algerian",
|
"ro": "Dinar algerian",
|
||||||
"ru": "алжирский динар",
|
"ru": "алжирский динар",
|
||||||
"sk": "Alžírský dinár",
|
|
||||||
"sl": "alžirski dinar",
|
"sl": "alžirski dinar",
|
||||||
"sr": "алжирски динар",
|
"sr": "алжирски динар",
|
||||||
"sv": "Algerisk dinar",
|
"sv": "Algerisk dinar",
|
||||||
|
@ -1974,7 +1969,6 @@
|
||||||
"pl": "frank gwinejski",
|
"pl": "frank gwinejski",
|
||||||
"pt": "Franco da Guiné",
|
"pt": "Franco da Guiné",
|
||||||
"ru": "Гвинейский франк",
|
"ru": "Гвинейский франк",
|
||||||
"sk": "Guinejský frank",
|
|
||||||
"sl": "gvinejski frank",
|
"sl": "gvinejski frank",
|
||||||
"sr": "гвинејски франак",
|
"sr": "гвинејски франак",
|
||||||
"sv": "Guinesisk franc",
|
"sv": "Guinesisk franc",
|
||||||
|
@ -2695,7 +2689,6 @@
|
||||||
"pt": "Franco comoriano",
|
"pt": "Franco comoriano",
|
||||||
"ro": "Franc comorian",
|
"ro": "Franc comorian",
|
||||||
"ru": "Франк Комор",
|
"ru": "Франк Комор",
|
||||||
"sk": "Komorský frank",
|
|
||||||
"sr": "коморски франак",
|
"sr": "коморски франак",
|
||||||
"sv": "Komoransk franc",
|
"sv": "Komoransk franc",
|
||||||
"tr": "Komor frangı",
|
"tr": "Komor frangı",
|
||||||
|
@ -2993,7 +2986,6 @@
|
||||||
"pt": "rúpia do Sri Lanka",
|
"pt": "rúpia do Sri Lanka",
|
||||||
"ru": "ланкийская рупия",
|
"ru": "ланкийская рупия",
|
||||||
"si": "ශ්රී ලංකා රුපියල",
|
"si": "ශ්රී ලංකා රුපියල",
|
||||||
"sk": "Srílanská rupia",
|
|
||||||
"sl": "šrilanška rupija",
|
"sl": "šrilanška rupija",
|
||||||
"sr": "шриланчанска рупија",
|
"sr": "шриланчанска рупија",
|
||||||
"sv": "Lankesisk rupie",
|
"sv": "Lankesisk rupie",
|
||||||
|
@ -3067,7 +3059,7 @@
|
||||||
"uk": "Лоті"
|
"uk": "Лоті"
|
||||||
},
|
},
|
||||||
"LYD": {
|
"LYD": {
|
||||||
"ar": "دينار ذهبي",
|
"ar": "دينار ليبي",
|
||||||
"bg": "Либийски динар",
|
"bg": "Либийски динар",
|
||||||
"ca": "dinar libi",
|
"ca": "dinar libi",
|
||||||
"cs": "Libyjský dinár",
|
"cs": "Libyjský dinár",
|
||||||
|
@ -3129,7 +3121,6 @@
|
||||||
"pt": "Dirham marroquino",
|
"pt": "Dirham marroquino",
|
||||||
"ro": "Dirham marocan",
|
"ro": "Dirham marocan",
|
||||||
"ru": "марокканский дирхам",
|
"ru": "марокканский дирхам",
|
||||||
"sk": "Marocký dirham",
|
|
||||||
"sl": "maroški dirham",
|
"sl": "maroški dirham",
|
||||||
"sr": "марокански дирхам",
|
"sr": "марокански дирхам",
|
||||||
"sv": "Marockansk dirham",
|
"sv": "Marockansk dirham",
|
||||||
|
@ -3149,7 +3140,6 @@
|
||||||
"et": "Moldova leu",
|
"et": "Moldova leu",
|
||||||
"fi": "Moldovan leu",
|
"fi": "Moldovan leu",
|
||||||
"fr": "leu moldave",
|
"fr": "leu moldave",
|
||||||
"gl": "leu moldovo",
|
|
||||||
"he": "לאו מולדובני",
|
"he": "לאו מולדובני",
|
||||||
"hr": "moldavski lej",
|
"hr": "moldavski lej",
|
||||||
"hu": "moldován lej",
|
"hu": "moldován lej",
|
||||||
|
@ -3381,7 +3371,6 @@
|
||||||
"pl": "Ugija",
|
"pl": "Ugija",
|
||||||
"pt": "Uguia",
|
"pt": "Uguia",
|
||||||
"ru": "Мавританская угия",
|
"ru": "Мавританская угия",
|
||||||
"sk": "Mauritánska ukíjá",
|
|
||||||
"sr": "мауританска огија",
|
"sr": "мауританска огија",
|
||||||
"sv": "Mauretansk ouguiya",
|
"sv": "Mauretansk ouguiya",
|
||||||
"tr": "Ugiya",
|
"tr": "Ugiya",
|
||||||
|
@ -3827,7 +3816,6 @@
|
||||||
"sl": "novozelandski dolar",
|
"sl": "novozelandski dolar",
|
||||||
"sr": "новозеландски долар",
|
"sr": "новозеландски долар",
|
||||||
"sv": "Nyzeeländsk dollar",
|
"sv": "Nyzeeländsk dollar",
|
||||||
"th": "ดอลลาร์นิวซีแลนด์",
|
|
||||||
"tr": "Yeni Zelanda doları",
|
"tr": "Yeni Zelanda doları",
|
||||||
"uk": "новозеландський долар",
|
"uk": "новозеландський долар",
|
||||||
"vi": "Đô la New Zealand"
|
"vi": "Đô la New Zealand"
|
||||||
|
@ -5398,14 +5386,12 @@
|
||||||
"ja": "スム",
|
"ja": "スム",
|
||||||
"ko": "우즈베키스탄 숨",
|
"ko": "우즈베키스탄 숨",
|
||||||
"lt": "Uzbekijos sumas",
|
"lt": "Uzbekijos sumas",
|
||||||
"lv": "Uzbekistānas soms",
|
|
||||||
"nl": "Oezbeekse sum",
|
"nl": "Oezbeekse sum",
|
||||||
"pa": "ਉਜ਼ਬੇਕਿਸਤਾਨੀ ਸੋਮ",
|
"pa": "ਉਜ਼ਬੇਕਿਸਤਾਨੀ ਸੋਮ",
|
||||||
"pl": "Sum",
|
"pl": "Sum",
|
||||||
"pt": "som usbeque",
|
"pt": "som usbeque",
|
||||||
"ro": "Som uzbec",
|
"ro": "Som uzbec",
|
||||||
"ru": "узбекский сум",
|
"ru": "узбекский сум",
|
||||||
"sk": "Uzbecký som",
|
|
||||||
"sr": "узбекистански сом",
|
"sr": "узбекистански сом",
|
||||||
"sv": "Uzbekistansk som",
|
"sv": "Uzbekistansk som",
|
||||||
"tr": "Özbekistan somu",
|
"tr": "Özbekistan somu",
|
||||||
|
@ -5659,7 +5645,7 @@
|
||||||
"eo": "specialaj rajtoj de enspezo",
|
"eo": "specialaj rajtoj de enspezo",
|
||||||
"es": "Derechos Especiales de Giro",
|
"es": "Derechos Especiales de Giro",
|
||||||
"eu": "igorpen eskubide bereziak",
|
"eu": "igorpen eskubide bereziak",
|
||||||
"fi": "erityisnosto-oikeus",
|
"fi": "Erityisnosto-oikeus",
|
||||||
"fr": "droits de tirage spéciaux",
|
"fr": "droits de tirage spéciaux",
|
||||||
"hr": "Posebna prava vučenja",
|
"hr": "Posebna prava vučenja",
|
||||||
"hu": "különleges lehívási jog",
|
"hu": "különleges lehívási jog",
|
||||||
|
@ -5669,7 +5655,6 @@
|
||||||
"ko": "특별인출권",
|
"ko": "특별인출권",
|
||||||
"lt": "Specialiosios skolinimosi teisės",
|
"lt": "Specialiosios skolinimosi teisės",
|
||||||
"lv": "Speciālās aizņēmuma tiesības",
|
"lv": "Speciālās aizņēmuma tiesības",
|
||||||
"ms": "hak pengeluaran khas",
|
|
||||||
"nl": "speciale trekkingsrechten",
|
"nl": "speciale trekkingsrechten",
|
||||||
"oc": "Drechs de tiratge Especials",
|
"oc": "Drechs de tiratge Especials",
|
||||||
"pl": "specjalne prawa ciągnienia",
|
"pl": "specjalne prawa ciągnienia",
|
||||||
|
@ -5852,7 +5837,7 @@
|
||||||
"lt": "Randas",
|
"lt": "Randas",
|
||||||
"lv": "Dienvidāfrikas rands",
|
"lv": "Dienvidāfrikas rands",
|
||||||
"ml": "സൗത്ത് ആഫ്രിക്കൻ റാൻഡ്",
|
"ml": "സൗത്ത് ആഫ്രിക്കൻ റാൻഡ്",
|
||||||
"ms": "Rand Afrika Selatan",
|
"ms": "Rand",
|
||||||
"nl": "Zuid-Afrikaanse rand",
|
"nl": "Zuid-Afrikaanse rand",
|
||||||
"oc": "Rand sudafrican",
|
"oc": "Rand sudafrican",
|
||||||
"pl": "Rand",
|
"pl": "Rand",
|
||||||
|
@ -5915,7 +5900,6 @@
|
||||||
"ko": "짐바브웨 골드",
|
"ko": "짐바브웨 골드",
|
||||||
"nl": "Zimbabwe Gold",
|
"nl": "Zimbabwe Gold",
|
||||||
"pl": "Złoto Zimbabwe",
|
"pl": "Złoto Zimbabwe",
|
||||||
"pt": "Ouro do Zimbábue",
|
|
||||||
"ru": "зимбабвийский золотой",
|
"ru": "зимбабвийский золотой",
|
||||||
"sk": "zimbabwiansky zlatý",
|
"sk": "zimbabwiansky zlatý",
|
||||||
"sl": "zimbabvejski gold",
|
"sl": "zimbabvejski gold",
|
||||||
|
@ -7833,7 +7817,6 @@
|
||||||
"eritrese nakfa": "ERN",
|
"eritrese nakfa": "ERN",
|
||||||
"erityinen nosto oikeus": "XDR",
|
"erityinen nosto oikeus": "XDR",
|
||||||
"erityiset nosto oikeudet": "XDR",
|
"erityiset nosto oikeudet": "XDR",
|
||||||
"erityisnosto oikeudet": "XDR",
|
|
||||||
"erityisnosto oikeus": "XDR",
|
"erityisnosto oikeus": "XDR",
|
||||||
"ermeni dramı": "AMD",
|
"ermeni dramı": "AMD",
|
||||||
"ermenistan dramı": "AMD",
|
"ermenistan dramı": "AMD",
|
||||||
|
@ -8389,8 +8372,6 @@
|
||||||
"haitský gourde": "HTG",
|
"haitský gourde": "HTG",
|
||||||
"haïtiaanse gourde": "HTG",
|
"haïtiaanse gourde": "HTG",
|
||||||
"hak penarikan khusus": "XDR",
|
"hak penarikan khusus": "XDR",
|
||||||
"hak pengeluaran khas": "XDR",
|
|
||||||
"hak pengeluaran khusus": "XDR",
|
|
||||||
"halalas": "SAR",
|
"halalas": "SAR",
|
||||||
"hegoafrikar rand": "ZAR",
|
"hegoafrikar rand": "ZAR",
|
||||||
"heller": "CZK",
|
"heller": "CZK",
|
||||||
|
@ -9134,7 +9115,6 @@
|
||||||
"leu da roménia": "RON",
|
"leu da roménia": "RON",
|
||||||
"leu da romênia": "RON",
|
"leu da romênia": "RON",
|
||||||
"leu de moldàvia": "MDL",
|
"leu de moldàvia": "MDL",
|
||||||
"leu de moldova": "MDL",
|
|
||||||
"leu moldau": "MDL",
|
"leu moldau": "MDL",
|
||||||
"leu moldave": "MDL",
|
"leu moldave": "MDL",
|
||||||
"leu moldavo": "MDL",
|
"leu moldavo": "MDL",
|
||||||
|
@ -9142,7 +9122,6 @@
|
||||||
"leu moldofa": "MDL",
|
"leu moldofa": "MDL",
|
||||||
"leu moldova": "MDL",
|
"leu moldova": "MDL",
|
||||||
"leu moldovenesc": "MDL",
|
"leu moldovenesc": "MDL",
|
||||||
"leu moldovo": "MDL",
|
|
||||||
"leu romanès": "RON",
|
"leu romanès": "RON",
|
||||||
"leu romanés": "RON",
|
"leu romanés": "RON",
|
||||||
"leu romanian": "RON",
|
"leu romanian": "RON",
|
||||||
|
@ -9458,7 +9437,6 @@
|
||||||
"manat azerbaijandar": "AZN",
|
"manat azerbaijandar": "AZN",
|
||||||
"manat azerbaijanês": "AZN",
|
"manat azerbaijanês": "AZN",
|
||||||
"manat azerbaijano": "AZN",
|
"manat azerbaijano": "AZN",
|
||||||
"manat azerbaitjanés": "AZN",
|
|
||||||
"manat azerbaiyano": "AZN",
|
"manat azerbaiyano": "AZN",
|
||||||
"manat azerbaïdjanais": "AZN",
|
"manat azerbaïdjanais": "AZN",
|
||||||
"manat azerbejdżański": "AZN",
|
"manat azerbejdżański": "AZN",
|
||||||
|
@ -9542,7 +9520,6 @@
|
||||||
"mauritanijska ouguja": "MRU",
|
"mauritanijska ouguja": "MRU",
|
||||||
"mauritanijska uguija": "MRU",
|
"mauritanijska uguija": "MRU",
|
||||||
"mauritániai ouguiya": "MRU",
|
"mauritániai ouguiya": "MRU",
|
||||||
"mauritánska ukíjá": "MRU",
|
|
||||||
"mauritánská ukíjá": "MRU",
|
"mauritánská ukíjá": "MRU",
|
||||||
"mauritānijas oguja": "MRU",
|
"mauritānijas oguja": "MRU",
|
||||||
"mauritiaanse roepee": "MUR",
|
"mauritiaanse roepee": "MUR",
|
||||||
|
@ -10008,7 +9985,6 @@
|
||||||
"ouguiya mauritana": "MRU",
|
"ouguiya mauritana": "MRU",
|
||||||
"ouguiya mauritanien": "MRU",
|
"ouguiya mauritanien": "MRU",
|
||||||
"ouguiya mawritania": "MRU",
|
"ouguiya mawritania": "MRU",
|
||||||
"ouro do zimbábue": "ZWG",
|
|
||||||
"örmény dram": "AMD",
|
"örmény dram": "AMD",
|
||||||
"östkaribisk dollar": "XCD",
|
"östkaribisk dollar": "XCD",
|
||||||
"özbekistan somu": "UZS",
|
"özbekistan somu": "UZS",
|
||||||
|
@ -10820,7 +10796,6 @@
|
||||||
"salomona dolaro": "SBD",
|
"salomona dolaro": "SBD",
|
||||||
"salomondollar": "SBD",
|
"salomondollar": "SBD",
|
||||||
"salomonen dollar": "SBD",
|
"salomonen dollar": "SBD",
|
||||||
"salomoninsaarten dollari": "SBD",
|
|
||||||
"salomonsaarten dollari": "SBD",
|
"salomonsaarten dollari": "SBD",
|
||||||
"salomonskootočni dolar": "SBD",
|
"salomonskootočni dolar": "SBD",
|
||||||
"salüng": "THB",
|
"salüng": "THB",
|
||||||
|
@ -11177,7 +11152,6 @@
|
||||||
"srilankansk rupee": "LKR",
|
"srilankansk rupee": "LKR",
|
||||||
"srilankanske rupee": "LKR",
|
"srilankanske rupee": "LKR",
|
||||||
"srí lanka i rúpia": "LKR",
|
"srí lanka i rúpia": "LKR",
|
||||||
"srílanská rupia": "LKR",
|
|
||||||
"srílanská rupie": "LKR",
|
"srílanská rupie": "LKR",
|
||||||
"srpski dinar": "RSD",
|
"srpski dinar": "RSD",
|
||||||
"ssp": "SSP",
|
"ssp": "SSP",
|
||||||
|
@ -11441,7 +11415,6 @@
|
||||||
"tengue": "KZT",
|
"tengue": "KZT",
|
||||||
"tengue cazaque": "KZT",
|
"tengue cazaque": "KZT",
|
||||||
"teňňe": "TMT",
|
"teňňe": "TMT",
|
||||||
"tetri": "GEL",
|
|
||||||
"thai baht": "THB",
|
"thai baht": "THB",
|
||||||
"thai bát": "THB",
|
"thai bát": "THB",
|
||||||
"thailandiar baht": "THB",
|
"thailandiar baht": "THB",
|
||||||
|
@ -11566,10 +11539,10 @@
|
||||||
"turkisk lira": "TRY",
|
"turkisk lira": "TRY",
|
||||||
"turkiska lira": "TRY",
|
"turkiska lira": "TRY",
|
||||||
"turkmeense manat": "TMT",
|
"turkmeense manat": "TMT",
|
||||||
"turkmen manat": "TMT",
|
|
||||||
"turkmena manato": "TMT",
|
"turkmena manato": "TMT",
|
||||||
"turkmenistan manat": "TMT",
|
"turkmenistan manat": "TMT",
|
||||||
"turkmenistan new manat": "TMT",
|
"turkmenistan new manat": "TMT",
|
||||||
|
"turkmenistani manat": "TMT",
|
||||||
"turkmenistani new manat": "TMT",
|
"turkmenistani new manat": "TMT",
|
||||||
"turkmenistanin manat": "TMT",
|
"turkmenistanin manat": "TMT",
|
||||||
"turkmenistansk manat": "TMT",
|
"turkmenistansk manat": "TMT",
|
||||||
|
@ -11735,7 +11708,6 @@
|
||||||
"uzbekistano sumas": "UZS",
|
"uzbekistano sumas": "UZS",
|
||||||
"uzbekistansk som": "UZS",
|
"uzbekistansk som": "UZS",
|
||||||
"uzbekistanski som": "UZS",
|
"uzbekistanski som": "UZS",
|
||||||
"uzbekistānas soms": "UZS",
|
|
||||||
"uzs": "UZS",
|
"uzs": "UZS",
|
||||||
"új zélandi dollár": "NZD",
|
"új zélandi dollár": "NZD",
|
||||||
"ürdün dinarı": "JOD",
|
"ürdün dinarı": "JOD",
|
||||||
|
@ -11889,7 +11861,6 @@
|
||||||
"yuan cinese": "CNY",
|
"yuan cinese": "CNY",
|
||||||
"yuan renmimbi": "CNY",
|
"yuan renmimbi": "CNY",
|
||||||
"yuan renminbi": "CNY",
|
"yuan renminbi": "CNY",
|
||||||
"yuan rmb": "CNY",
|
|
||||||
"yuans": "CNY",
|
"yuans": "CNY",
|
||||||
"yuán chino": "CNY",
|
"yuán chino": "CNY",
|
||||||
"z$": "ZWL",
|
"z$": "ZWL",
|
||||||
|
@ -13763,7 +13734,6 @@
|
||||||
"دينار بحريني": "BHD",
|
"دينار بحريني": "BHD",
|
||||||
"دينار تونسي": "TND",
|
"دينار تونسي": "TND",
|
||||||
"دينار جزائري": "DZD",
|
"دينار جزائري": "DZD",
|
||||||
"دينار ذهبي": "LYD",
|
|
||||||
"دينار سوداني": "SDG",
|
"دينار سوداني": "SDG",
|
||||||
"دينار صربي": "RSD",
|
"دينار صربي": "RSD",
|
||||||
"دينار عراقي": "IQD",
|
"دينار عراقي": "IQD",
|
||||||
|
@ -14356,7 +14326,6 @@
|
||||||
"USD",
|
"USD",
|
||||||
"TWD"
|
"TWD"
|
||||||
],
|
],
|
||||||
"ดอลลาร์นิวซีแลนด์": "NZD",
|
|
||||||
"ดอลลาร์บรูไน": "BND",
|
"ดอลลาร์บรูไน": "BND",
|
||||||
"ดอลลาร์สหรัฐ": "USD",
|
"ดอลลาร์สหรัฐ": "USD",
|
||||||
"ดอลลาร์สิงคโปร์": "SGD",
|
"ดอลลาร์สิงคโปร์": "SGD",
|
||||||
|
@ -15029,7 +14998,6 @@
|
||||||
"ボツワナ・プラ": "BWP",
|
"ボツワナ・プラ": "BWP",
|
||||||
"ボリバル・ソベラノ": "VES",
|
"ボリバル・ソベラノ": "VES",
|
||||||
"ボリビアーノ": "BOB",
|
"ボリビアーノ": "BOB",
|
||||||
"ポンド・スターリング": "GBP",
|
|
||||||
"ポーランド・ズウォティ": [
|
"ポーランド・ズウォティ": [
|
||||||
"PLZ",
|
"PLZ",
|
||||||
"PLN"
|
"PLN"
|
||||||
|
@ -15095,6 +15063,7 @@
|
||||||
"中華人民共和国の通貨": "CNY",
|
"中華人民共和国の通貨": "CNY",
|
||||||
"中部アフリカcfaフラン": "XAF",
|
"中部アフリカcfaフラン": "XAF",
|
||||||
"人民元": "CNY",
|
"人民元": "CNY",
|
||||||
|
"人民币": "CNY",
|
||||||
"人民幣": "CNY",
|
"人民幣": "CNY",
|
||||||
"元": [
|
"元": [
|
||||||
"HKD",
|
"HKD",
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,7 @@
|
||||||
],
|
],
|
||||||
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
|
"ua": "Mozilla/5.0 ({os}; rv:{version}) Gecko/20100101 Firefox/{version}",
|
||||||
"versions": [
|
"versions": [
|
||||||
"132.0",
|
"130.0",
|
||||||
"131.0"
|
"129.0"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -832,7 +832,7 @@
|
||||||
"Q104907390": {
|
"Q104907390": {
|
||||||
"si_name": "Q182429",
|
"si_name": "Q182429",
|
||||||
"symbol": "nmi/h",
|
"symbol": "nmi/h",
|
||||||
"to_si_factor": 0.5144444444444445
|
"to_si_factor": 0.514444
|
||||||
},
|
},
|
||||||
"Q104907398": {
|
"Q104907398": {
|
||||||
"si_name": "Q215571",
|
"si_name": "Q215571",
|
||||||
|
@ -1336,7 +1336,7 @@
|
||||||
},
|
},
|
||||||
"Q106636307": {
|
"Q106636307": {
|
||||||
"si_name": "Q80842107",
|
"si_name": "Q80842107",
|
||||||
"symbol": "μS/cm-1",
|
"symbol": "μS/cm",
|
||||||
"to_si_factor": 0.0001
|
"to_si_factor": 0.0001
|
||||||
},
|
},
|
||||||
"Q106639711": {
|
"Q106639711": {
|
||||||
|
@ -4142,7 +4142,7 @@
|
||||||
"Q23931103": {
|
"Q23931103": {
|
||||||
"si_name": "Q25343",
|
"si_name": "Q25343",
|
||||||
"symbol": "nmi²",
|
"symbol": "nmi²",
|
||||||
"to_si_factor": 3429904.0
|
"to_si_factor": 3434290.0120544
|
||||||
},
|
},
|
||||||
"Q239830": {
|
"Q239830": {
|
||||||
"si_name": "Q3395194",
|
"si_name": "Q3395194",
|
||||||
|
|
|
@ -34,10 +34,10 @@ Implementations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import quote
|
||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from searx.utils import extract_text, eval_xpath, eval_xpath_getindex, eval_xpath_list
|
from searx.utils import extract_text, eval_xpath, eval_xpath_list
|
||||||
from searx.enginelib.traits import EngineTraits
|
from searx.enginelib.traits import EngineTraits
|
||||||
from searx.data import ENGINE_TRAITS
|
from searx.data import ENGINE_TRAITS
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ about: Dict[str, Any] = {
|
||||||
|
|
||||||
# engine dependent config
|
# engine dependent config
|
||||||
categories: List[str] = ["files"]
|
categories: List[str] = ["files"]
|
||||||
paging: bool = True
|
paging: bool = False
|
||||||
|
|
||||||
# search-url
|
# search-url
|
||||||
base_url: str = "https://annas-archive.org"
|
base_url: str = "https://annas-archive.org"
|
||||||
|
@ -99,18 +99,9 @@ def init(engine_settings=None): # pylint: disable=unused-argument
|
||||||
|
|
||||||
|
|
||||||
def request(query, params: Dict[str, Any]) -> Dict[str, Any]:
|
def request(query, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
q = quote(query)
|
||||||
lang = traits.get_language(params["language"], traits.all_locale) # type: ignore
|
lang = traits.get_language(params["language"], traits.all_locale) # type: ignore
|
||||||
args = {
|
params["url"] = base_url + f"/search?lang={lang or ''}&content={aa_content}&ext={aa_ext}&sort={aa_sort}&q={q}"
|
||||||
'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
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,12 +128,12 @@ def response(resp) -> List[Dict[str, Optional[str]]]:
|
||||||
def _get_result(item):
|
def _get_result(item):
|
||||||
return {
|
return {
|
||||||
'template': 'paper.html',
|
'template': 'paper.html',
|
||||||
'url': base_url + extract_text(eval_xpath_getindex(item, './@href', 0)),
|
'url': base_url + item.xpath('./@href')[0],
|
||||||
'title': extract_text(eval_xpath(item, './/h3/text()[1]')),
|
'title': extract_text(eval_xpath(item, './/h3/text()[1]')),
|
||||||
'publisher': extract_text(eval_xpath(item, './/div[contains(@class, "text-sm")]')),
|
'publisher': extract_text(eval_xpath(item, './/div[contains(@class, "text-sm")]')),
|
||||||
'authors': [extract_text(eval_xpath(item, './/div[contains(@class, "italic")]'))],
|
'authors': [extract_text(eval_xpath(item, './/div[contains(@class, "italic")]'))],
|
||||||
'content': extract_text(eval_xpath(item, './/div[contains(@class, "text-xs")]')),
|
'content': extract_text(eval_xpath(item, './/div[contains(@class, "text-xs")]')),
|
||||||
'thumbnail': extract_text(eval_xpath_getindex(item, './/img/@src', 0, default=None), allow_none=True),
|
'thumbnail': item.xpath('.//img/@src')[0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,13 @@ from searx import (
|
||||||
)
|
)
|
||||||
from searx.utils import (
|
from searx.utils import (
|
||||||
eval_xpath,
|
eval_xpath,
|
||||||
|
eval_xpath_getindex,
|
||||||
extract_text,
|
extract_text,
|
||||||
)
|
)
|
||||||
from searx.network import get # see https://github.com/searxng/searxng/issues/762
|
from searx.network import get # see https://github.com/searxng/searxng/issues/762
|
||||||
from searx import redisdb
|
from searx import redisdb
|
||||||
from searx.enginelib.traits import EngineTraits
|
from searx.enginelib.traits import EngineTraits
|
||||||
from searx.utils import extr
|
from searx.utils import extr
|
||||||
from searx.exceptions import SearxEngineCaptchaException
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import logging
|
import logging
|
||||||
|
@ -53,33 +53,31 @@ paging = True
|
||||||
time_range_support = True
|
time_range_support = True
|
||||||
safesearch = True # user can't select but the results are filtered
|
safesearch = True # user can't select but the results are filtered
|
||||||
|
|
||||||
url = "https://html.duckduckgo.com/html"
|
url = 'https://lite.duckduckgo.com/lite/'
|
||||||
|
# url_ping = 'https://duckduckgo.com/t/sl_l'
|
||||||
|
|
||||||
time_range_dict = {'day': 'd', 'week': 'w', 'month': 'm', 'year': 'y'}
|
time_range_dict = {'day': 'd', 'week': 'w', 'month': 'm', 'year': 'y'}
|
||||||
form_data = {'v': 'l', 'api': 'd.js', 'o': 'json'}
|
form_data = {'v': 'l', 'api': 'd.js', 'o': 'json'}
|
||||||
__CACHE = []
|
|
||||||
|
|
||||||
|
|
||||||
def _cache_key(data: dict):
|
def cache_vqd(query, value):
|
||||||
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."""
|
"""Caches a ``vqd`` value from a query."""
|
||||||
c = redisdb.client()
|
c = redisdb.client()
|
||||||
if c:
|
if c:
|
||||||
logger.debug("cache vqd value: %s", value)
|
logger.debug("cache vqd value: %s", value)
|
||||||
c.set(_cache_key(data), value, ex=600)
|
key = 'SearXNG_ddg_web_vqd' + redislib.secret_hash(query)
|
||||||
|
c.set(key, 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(data):
|
def get_vqd(query):
|
||||||
"""Returns the ``vqd`` that fits to the *query* (``data`` from HTTP POST).
|
"""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.
|
||||||
|
|
||||||
DDG's bot detection is sensitive to the ``vqd`` value. For some search terms
|
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``
|
(such as extremely long search terms that are often sent by bots), no ``vqd``
|
||||||
|
@ -107,23 +105,28 @@ def get_vqd(data):
|
||||||
- DuckDuckGo News: ``https://duckduckgo.com/news.js??q=...&vqd=...``
|
- DuckDuckGo News: ``https://duckduckgo.com/news.js??q=...&vqd=...``
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = _cache_key(data)
|
|
||||||
value = None
|
value = None
|
||||||
c = redisdb.client()
|
c = redisdb.client()
|
||||||
if c:
|
if c:
|
||||||
|
key = 'SearXNG_ddg_web_vqd' + redislib.secret_hash(query)
|
||||||
value = c.get(key)
|
value = c.get(key)
|
||||||
if value or value == b'':
|
if value or value == b'':
|
||||||
value = value.decode('utf-8')
|
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
|
return value
|
||||||
|
|
||||||
else:
|
query_url = 'https://duckduckgo.com/?' + urlencode({'q': query})
|
||||||
for k, value in __CACHE:
|
res = get(query_url)
|
||||||
if k == key:
|
doc = lxml.html.fromstring(res.text)
|
||||||
logger.debug("MEM re-use CACHED vqd value: %s", value)
|
for script in doc.xpath("//script[@type='text/javascript']"):
|
||||||
return value
|
script = script.text
|
||||||
return None
|
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
|
||||||
|
|
||||||
|
|
||||||
def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
|
def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
|
||||||
|
@ -151,10 +154,9 @@ def get_ddg_lang(eng_traits: EngineTraits, sxng_locale, default='en_US'):
|
||||||
|
|
||||||
.. hint::
|
.. hint::
|
||||||
|
|
||||||
`DDG-lite <https://lite.duckduckgo.com/lite>`__ and the *no Javascript*
|
`DDG-lite <https://lite.duckduckgo.com/lite>`__ does not offer a language
|
||||||
page https://html.duckduckgo.com/html do not offer a language selection
|
selection to the user, only a region can be selected by the user
|
||||||
to the user, only a region can be selected by the user (``eng_region``
|
(``eng_region`` from the example above). DDG-lite stores the selected
|
||||||
from the example above). DDG-lite and *no Javascript* store the selected
|
|
||||||
region in a cookie::
|
region in a cookie::
|
||||||
|
|
||||||
params['cookies']['kl'] = eng_region # 'ar-es'
|
params['cookies']['kl'] = eng_region # 'ar-es'
|
||||||
|
@ -238,25 +240,10 @@ def request(query, params):
|
||||||
|
|
||||||
query = quote_ddg_bangs(query)
|
query = quote_ddg_bangs(query)
|
||||||
|
|
||||||
if len(query) >= 500:
|
# request needs a vqd argument
|
||||||
# DDG does not accept queries with more than 499 chars
|
vqd = get_vqd(query)
|
||||||
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)
|
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'])
|
# eng_lang = get_ddg_lang(traits, params['searxng_locale'])
|
||||||
|
|
||||||
params['url'] = url
|
params['url'] = url
|
||||||
|
@ -264,82 +251,45 @@ def request(query, params):
|
||||||
params['data']['q'] = query
|
params['data']['q'] = query
|
||||||
|
|
||||||
# The API is not documented, so we do some reverse engineering and emulate
|
# The API is not documented, so we do some reverse engineering and emulate
|
||||||
# what https://html.duckduckgo.com/html does when you press "next Page" link
|
# what https://lite.duckduckgo.com/lite/ does when you press "next Page"
|
||||||
# again and again ..
|
# link again and again ..
|
||||||
|
|
||||||
params['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
|
params['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
|
params['data']['vqd'] = vqd
|
||||||
|
|
||||||
params['headers']['Sec-Fetch-Dest'] = "document"
|
# initial page does not have an offset
|
||||||
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:
|
if params['pageno'] == 2:
|
||||||
|
|
||||||
# second page does have an offset of 20
|
# second page does have an offset of 20
|
||||||
offset = (params['pageno'] - 1) * 20
|
offset = (params['pageno'] - 1) * 20
|
||||||
params['data']['s'] = offset
|
params['data']['s'] = offset
|
||||||
params['data']['dc'] = offset + 1
|
params['data']['dc'] = offset + 1
|
||||||
|
|
||||||
elif params['pageno'] > 2:
|
elif params['pageno'] > 2:
|
||||||
|
|
||||||
# third and following pages do have an offset of 20 + n*50
|
# third and following pages do have an offset of 20 + n*50
|
||||||
offset = 20 + (params['pageno'] - 2) * 50
|
offset = 20 + (params['pageno'] - 2) * 50
|
||||||
params['data']['s'] = offset
|
params['data']['s'] = offset
|
||||||
params['data']['dc'] = offset + 1
|
params['data']['dc'] = offset + 1
|
||||||
|
|
||||||
|
# initial page does not have additional data in the input form
|
||||||
if params['pageno'] > 1:
|
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']['o'] = form_data.get('o', 'json')
|
||||||
params['data']['api'] = form_data.get('api', 'd.js')
|
params['data']['api'] = form_data.get('api', 'd.js')
|
||||||
params['data']['nextParams'] = form_data.get('nextParams', '')
|
params['data']['nextParams'] = form_data.get('nextParams', '')
|
||||||
params['data']['v'] = form_data.get('v', 'l')
|
params['data']['v'] = form_data.get('v', 'l')
|
||||||
params['headers']['Referer'] = url
|
params['headers']['Referer'] = 'https://lite.duckduckgo.com/'
|
||||||
|
|
||||||
# from here on no more params['data'] shuld be set, since this dict is
|
params['data']['kl'] = eng_region
|
||||||
# needed to get a vqd value from the cache ..
|
params['cookies']['kl'] = eng_region
|
||||||
|
|
||||||
vqd = get_vqd(params['data'])
|
params['data']['df'] = ''
|
||||||
|
if params['time_range'] in time_range_dict:
|
||||||
# Certain conditions must be met in order to call up one of the
|
params['data']['df'] = time_range_dict[params['time_range']]
|
||||||
# following pages ...
|
params['cookies']['df'] = time_range_dict[params['time_range']]
|
||||||
|
|
||||||
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 data: %s", params['data'])
|
||||||
logger.debug("param cookies: %s", params['cookies'])
|
logger.debug("param cookies: %s", params['cookies'])
|
||||||
|
return params
|
||||||
|
|
||||||
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):
|
def response(resp):
|
||||||
|
@ -350,33 +300,35 @@ def response(resp):
|
||||||
results = []
|
results = []
|
||||||
doc = lxml.html.fromstring(resp.text)
|
doc = lxml.html.fromstring(resp.text)
|
||||||
|
|
||||||
if is_ddg_captcha(doc):
|
result_table = eval_xpath(doc, '//html/body/form/div[@class="filters"]/table')
|
||||||
# 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')})")
|
|
||||||
|
|
||||||
form = eval_xpath(doc, '//input[@name="vqd"]/..')
|
if len(result_table) == 2:
|
||||||
if len(form):
|
# some locales (at least China) does not have a "next page" button and
|
||||||
# some locales (at least China) does not have a "next page" button
|
# the layout of the HTML tables is different.
|
||||||
form = form[0]
|
result_table = result_table[1]
|
||||||
form_vqd = eval_xpath(form, '//input[@name="vqd"]/@value')[0]
|
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):
|
||||||
|
|
||||||
cache_vqd(resp.search_params["data"], form_vqd)
|
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)
|
||||||
|
|
||||||
# just select "web-result" and ignore results of class "result--ad result--ad--small"
|
tr_rows = eval_xpath(result_table, './/tr')
|
||||||
for div_result in eval_xpath(doc, '//div[@id="links"]/div[contains(@class, "web-result")]'):
|
# In the last <tr> is the form of the 'previous/next page' links
|
||||||
|
tr_rows = tr_rows[:-1]
|
||||||
|
|
||||||
item = {}
|
len_tr_rows = len(tr_rows)
|
||||||
title = eval_xpath(div_result, './/h2/a')
|
offset = 0
|
||||||
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])
|
|
||||||
|
|
||||||
results.append(item)
|
zero_click_info_xpath = '//html/body/form/div/table[2]/tr[2]/td/text()'
|
||||||
|
|
||||||
zero_click_info_xpath = '//div[@id="zero_click_abstract"]'
|
|
||||||
zero_click = extract_text(eval_xpath(doc, zero_click_info_xpath)).strip()
|
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:
|
if zero_click and "Your IP address is" not in zero_click and "Your user agent:" not in zero_click:
|
||||||
|
@ -389,6 +341,33 @@ 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
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ filter_mapping = {0: 'off', 1: 'medium', 2: 'high'}
|
||||||
results_xpath = './/div[contains(@jscontroller, "SC7lYd")]'
|
results_xpath = './/div[contains(@jscontroller, "SC7lYd")]'
|
||||||
title_xpath = './/a/h3[1]'
|
title_xpath = './/a/h3[1]'
|
||||||
href_xpath = './/a[h3]/@href'
|
href_xpath = './/a[h3]/@href'
|
||||||
content_xpath = './/div[contains(@data-sncf, "1")]'
|
content_xpath = './/div[@data-sncf="1"]'
|
||||||
|
|
||||||
# Suggestions are links placed in a *card-section*, we extract only the text
|
# Suggestions are links placed in a *card-section*, we extract only the text
|
||||||
# from the links not the links itself.
|
# from the links not the links itself.
|
||||||
|
|
|
@ -57,11 +57,7 @@ def request(query, params):
|
||||||
|
|
||||||
if params['time_range']:
|
if params['time_range']:
|
||||||
search_type = 'search_by_date'
|
search_type = 'search_by_date'
|
||||||
timestamp = (
|
timestamp = (datetime.now() - relativedelta(**{f"{params['time_range']}s": 1})).timestamp()
|
||||||
# pylint: disable=unexpected-keyword-arg
|
|
||||||
datetime.now()
|
|
||||||
- relativedelta(**{f"{params['time_range']}s": 1}) # type: ignore
|
|
||||||
).timestamp()
|
|
||||||
query_params["numericFilters"] = f"created_at_i>{timestamp}"
|
query_params["numericFilters"] = f"created_at_i>{timestamp}"
|
||||||
|
|
||||||
params["url"] = f"{base_url}/{search_type}?{urlencode(query_params)}"
|
params["url"] = f"{base_url}/{search_type}?{urlencode(query_params)}"
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""Mojeek (general, images, news)"""
|
"""Mojeek (general, images, news)"""
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
from lxml import html
|
from lxml import html
|
||||||
|
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from searx.utils import eval_xpath, eval_xpath_list, extract_text
|
from searx.utils import eval_xpath, eval_xpath_list, extract_text
|
||||||
from searx.enginelib.traits import EngineTraits
|
|
||||||
|
|
||||||
about = {
|
about = {
|
||||||
'website': 'https://mojeek.com',
|
'website': 'https://mojeek.com',
|
||||||
|
@ -45,18 +42,6 @@ news_url_xpath = './/h2/a/@href'
|
||||||
news_title_xpath = './/h2/a'
|
news_title_xpath = './/h2/a'
|
||||||
news_content_xpath = './/p[@class="s"]'
|
news_content_xpath = './/p[@class="s"]'
|
||||||
|
|
||||||
language_param = 'lb'
|
|
||||||
region_param = 'arc'
|
|
||||||
|
|
||||||
_delta_kwargs = {'day': 'days', 'week': 'weeks', 'month': 'months', 'year': 'years'}
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger()
|
|
||||||
|
|
||||||
traits: EngineTraits
|
|
||||||
|
|
||||||
|
|
||||||
def init(_):
|
def init(_):
|
||||||
if search_type not in ('', 'images', 'news'):
|
if search_type not in ('', 'images', 'news'):
|
||||||
|
@ -68,16 +53,13 @@ def request(query, params):
|
||||||
'q': query,
|
'q': query,
|
||||||
'safe': min(params['safesearch'], 1),
|
'safe': min(params['safesearch'], 1),
|
||||||
'fmt': search_type,
|
'fmt': search_type,
|
||||||
language_param: traits.get_language(params['searxng_locale'], traits.custom['language_all']),
|
|
||||||
region_param: traits.get_region(params['searxng_locale'], traits.custom['region_all']),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if search_type == '':
|
if search_type == '':
|
||||||
args['s'] = 10 * (params['pageno'] - 1)
|
args['s'] = 10 * (params['pageno'] - 1)
|
||||||
|
|
||||||
if params['time_range'] and search_type != 'images':
|
if params['time_range'] and search_type != 'images':
|
||||||
kwargs = {_delta_kwargs[params['time_range']]: 1}
|
args["since"] = (datetime.now() - relativedelta(**{f"{params['time_range']}s": 1})).strftime("%Y%m%d")
|
||||||
args["since"] = (datetime.now() - relativedelta(**kwargs)).strftime("%Y%m%d") # type: ignore
|
|
||||||
logger.debug(args["since"])
|
logger.debug(args["since"])
|
||||||
|
|
||||||
params['url'] = f"{base_url}/search?{urlencode(args)}"
|
params['url'] = f"{base_url}/search?{urlencode(args)}"
|
||||||
|
@ -112,7 +94,7 @@ def _image_results(dom):
|
||||||
'template': 'images.html',
|
'template': 'images.html',
|
||||||
'url': extract_text(eval_xpath(result, image_url_xpath)),
|
'url': extract_text(eval_xpath(result, image_url_xpath)),
|
||||||
'title': extract_text(eval_xpath(result, image_title_xpath)),
|
'title': extract_text(eval_xpath(result, image_title_xpath)),
|
||||||
'img_src': base_url + extract_text(eval_xpath(result, image_img_src_xpath)), # type: ignore
|
'img_src': base_url + extract_text(eval_xpath(result, image_img_src_xpath)),
|
||||||
'content': '',
|
'content': '',
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -148,31 +130,3 @@ def response(resp):
|
||||||
return _news_results(dom)
|
return _news_results(dom)
|
||||||
|
|
||||||
raise ValueError(f"Invalid search type {search_type}")
|
raise ValueError(f"Invalid search type {search_type}")
|
||||||
|
|
||||||
|
|
||||||
def fetch_traits(engine_traits: EngineTraits):
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from searx import network
|
|
||||||
from searx.locales import get_official_locales, region_tag
|
|
||||||
from babel import Locale, UnknownLocaleError
|
|
||||||
import contextlib
|
|
||||||
|
|
||||||
resp = network.get(base_url + "/preferences", headers={'Accept-Language': 'en-US,en;q=0.5'})
|
|
||||||
dom = html.fromstring(resp.text) # type: ignore
|
|
||||||
|
|
||||||
languages = eval_xpath_list(dom, f'//select[@name="{language_param}"]/option/@value')
|
|
||||||
|
|
||||||
engine_traits.custom['language_all'] = languages[0]
|
|
||||||
|
|
||||||
for code in languages[1:]:
|
|
||||||
with contextlib.suppress(UnknownLocaleError):
|
|
||||||
locale = Locale(code)
|
|
||||||
engine_traits.languages[locale.language] = code
|
|
||||||
|
|
||||||
regions = eval_xpath_list(dom, f'//select[@name="{region_param}"]/option/@value')
|
|
||||||
|
|
||||||
engine_traits.custom['region_all'] = regions[1]
|
|
||||||
|
|
||||||
for code in regions[2:]:
|
|
||||||
for locale in get_official_locales(code, engine_traits.languages):
|
|
||||||
engine_traits.regions[region_tag(locale)] = code
|
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
"""Open library (books)
|
|
||||||
"""
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
import re
|
|
||||||
|
|
||||||
from dateutil import parser
|
|
||||||
|
|
||||||
about = {
|
|
||||||
'website': 'https://openlibrary.org',
|
|
||||||
'wikidata_id': 'Q1201876',
|
|
||||||
'require_api_key': False,
|
|
||||||
'use_official_api': False,
|
|
||||||
'official_api_documentation': 'https://openlibrary.org/developers/api',
|
|
||||||
}
|
|
||||||
|
|
||||||
paging = True
|
|
||||||
categories = []
|
|
||||||
|
|
||||||
base_url = "https://openlibrary.org"
|
|
||||||
results_per_page = 10
|
|
||||||
|
|
||||||
|
|
||||||
def request(query, params):
|
|
||||||
args = {
|
|
||||||
'q': query,
|
|
||||||
'page': params['pageno'],
|
|
||||||
'limit': results_per_page,
|
|
||||||
}
|
|
||||||
params['url'] = f"{base_url}/search.json?{urlencode(args)}"
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_date(date):
|
|
||||||
try:
|
|
||||||
return parser.parse(date)
|
|
||||||
except parser.ParserError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def response(resp):
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for item in resp.json().get("docs", []):
|
|
||||||
cover = None
|
|
||||||
if 'lending_identifier_s' in item:
|
|
||||||
cover = f"https://archive.org/services/img/{item['lending_identifier_s']}"
|
|
||||||
|
|
||||||
published = item.get('publish_date')
|
|
||||||
if published:
|
|
||||||
published_dates = [date for date in map(_parse_date, published) if date]
|
|
||||||
if published_dates:
|
|
||||||
published = min(published_dates)
|
|
||||||
|
|
||||||
if not published:
|
|
||||||
published = parser.parse(str(item.get('first_published_year')))
|
|
||||||
|
|
||||||
result = {
|
|
||||||
'template': 'paper.html',
|
|
||||||
'url': f"{base_url}{item['key']}",
|
|
||||||
'title': item['title'],
|
|
||||||
'content': re.sub(r"\{|\}", "", item['first_sentence'][0]) if item.get('first_sentence') else '',
|
|
||||||
'isbn': item.get('isbn', [])[:5],
|
|
||||||
'authors': item.get('author_name', []),
|
|
||||||
'thumbnail': cover,
|
|
||||||
'publishedDate': published,
|
|
||||||
'tags': item.get('subject', [])[:10] + item.get('place', [])[:10],
|
|
||||||
}
|
|
||||||
results.append(result)
|
|
||||||
|
|
||||||
return results
|
|
|
@ -1,7 +1,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
"""Exception types raised by SearXNG modules.
|
"""Exception types raised by SearXNG modules.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ class SearxEngineAccessDeniedException(SearxEngineResponseException):
|
||||||
"""This settings contains the default suspended time (default 86400 sec / 1
|
"""This settings contains the default suspended time (default 86400 sec / 1
|
||||||
day)."""
|
day)."""
|
||||||
|
|
||||||
def __init__(self, suspended_time: int | None = None, message: str = 'Access denied'):
|
def __init__(self, suspended_time: int = None, message: str = 'Access denied'):
|
||||||
"""Generic exception to raise when an engine denies access to the results.
|
"""Generic exception to raise when an engine denies access to the results.
|
||||||
|
|
||||||
:param suspended_time: How long the engine is going to be suspended in
|
:param suspended_time: How long the engine is going to be suspended in
|
||||||
|
@ -71,13 +70,12 @@ class SearxEngineAccessDeniedException(SearxEngineResponseException):
|
||||||
:param message: Internal message. Defaults to ``Access denied``
|
:param message: Internal message. Defaults to ``Access denied``
|
||||||
:type message: str
|
:type message: str
|
||||||
"""
|
"""
|
||||||
if suspended_time is None:
|
suspended_time = suspended_time or self._get_default_suspended_time()
|
||||||
suspended_time = self._get_default_suspended_time()
|
|
||||||
super().__init__(message + ', suspended_time=' + str(suspended_time))
|
super().__init__(message + ', suspended_time=' + str(suspended_time))
|
||||||
self.suspended_time = suspended_time
|
self.suspended_time = suspended_time
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
def _get_default_suspended_time(self) -> int:
|
def _get_default_suspended_time(self):
|
||||||
from searx import get_setting # pylint: disable=C0415
|
from searx import get_setting # pylint: disable=C0415
|
||||||
|
|
||||||
return get_setting(self.SUSPEND_TIME_SETTING)
|
return get_setting(self.SUSPEND_TIME_SETTING)
|
||||||
|
@ -90,7 +88,7 @@ class SearxEngineCaptchaException(SearxEngineAccessDeniedException):
|
||||||
"""This settings contains the default suspended time (default 86400 sec / 1
|
"""This settings contains the default suspended time (default 86400 sec / 1
|
||||||
day)."""
|
day)."""
|
||||||
|
|
||||||
def __init__(self, suspended_time: int | None = None, message='CAPTCHA'):
|
def __init__(self, suspended_time=None, message='CAPTCHA'):
|
||||||
super().__init__(message=message, suspended_time=suspended_time)
|
super().__init__(message=message, suspended_time=suspended_time)
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,7 +102,7 @@ class SearxEngineTooManyRequestsException(SearxEngineAccessDeniedException):
|
||||||
"""This settings contains the default suspended time (default 3660 sec / 1
|
"""This settings contains the default suspended time (default 3660 sec / 1
|
||||||
hour)."""
|
hour)."""
|
||||||
|
|
||||||
def __init__(self, suspended_time: int | None = None, message='Too many request'):
|
def __init__(self, suspended_time=None, message='Too many request'):
|
||||||
super().__init__(message=message, suspended_time=suspended_time)
|
super().__init__(message=message, suspended_time=suspended_time)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,8 @@ def init():
|
||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
from . import config, cache, proxy
|
from . import config, cache, proxy
|
||||||
from .. import settings_loader
|
|
||||||
|
|
||||||
cfg_file = (settings_loader.get_user_cfg_folder() or pathlib.Path("/etc/searxng")) / "favicons.toml"
|
cfg_file = pathlib.Path("/etc/searxng/favicons.toml")
|
||||||
if not cfg_file.exists():
|
if not cfg_file.exists():
|
||||||
if is_active():
|
if is_active():
|
||||||
logger.error(f"missing favicon config: {cfg_file}")
|
logger.error(f"missing favicon config: {cfg_file}")
|
||||||
|
@ -35,4 +34,4 @@ def init():
|
||||||
cache.init(cfg.cache)
|
cache.init(cfg.cache)
|
||||||
proxy.init(cfg.proxy)
|
proxy.init(cfg.proxy)
|
||||||
|
|
||||||
del cache, config, proxy, cfg, settings_loader
|
del cache, config, proxy, cfg
|
||||||
|
|
|
@ -20,17 +20,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
import os
|
|
||||||
import abc
|
import abc
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import pathlib
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
import msgspec
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from searx import sqlitedb
|
from searx import sqlitedb
|
||||||
from searx import logger
|
from searx import logger
|
||||||
|
@ -90,7 +90,7 @@ def init(cfg: "FaviconCacheConfig"):
|
||||||
raise NotImplementedError(f"favicons db_type '{cfg.db_type}' is unknown")
|
raise NotImplementedError(f"favicons db_type '{cfg.db_type}' is unknown")
|
||||||
|
|
||||||
|
|
||||||
class FaviconCacheConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
|
class FaviconCacheConfig(BaseModel):
|
||||||
"""Configuration of the favicon cache."""
|
"""Configuration of the favicon cache."""
|
||||||
|
|
||||||
db_type: Literal["sqlite", "mem"] = "sqlite"
|
db_type: Literal["sqlite", "mem"] = "sqlite"
|
||||||
|
@ -103,7 +103,7 @@ class FaviconCacheConfig(msgspec.Struct): # pylint: disable=too-few-public-meth
|
||||||
:py:obj:`.cache.FaviconCacheMEM` (not recommended)
|
:py:obj:`.cache.FaviconCacheMEM` (not recommended)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
db_url: str = tempfile.gettempdir() + os.sep + "faviconcache.db"
|
db_url: pathlib.Path = pathlib.Path(tempfile.gettempdir()) / "faviconcache.db"
|
||||||
"""URL of the SQLite DB, the path to the database file."""
|
"""URL of the SQLite DB, the path to the database file."""
|
||||||
|
|
||||||
HOLD_TIME: int = 60 * 60 * 24 * 30 # 30 days
|
HOLD_TIME: int = 60 * 60 * 24 * 30 # 30 days
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import msgspec
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from searx.compat import tomllib
|
||||||
from .cache import FaviconCacheConfig
|
from .cache import FaviconCacheConfig
|
||||||
from .proxy import FaviconProxyConfig
|
from .proxy import FaviconProxyConfig
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ TOML_CACHE_CFG: dict[str, "FaviconConfig"] = {}
|
||||||
DEFAULT_CFG_TOML_PATH = pathlib.Path(__file__).parent / "favicons.toml"
|
DEFAULT_CFG_TOML_PATH = pathlib.Path(__file__).parent / "favicons.toml"
|
||||||
|
|
||||||
|
|
||||||
class FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
|
class FaviconConfig(BaseModel):
|
||||||
"""The class aggregates configurations of the favicon tools"""
|
"""The class aggregates configurations of the favicon tools"""
|
||||||
|
|
||||||
cfg_schema: int
|
cfg_schema: int
|
||||||
|
@ -27,10 +28,10 @@ class FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
|
||||||
By specifying a version, it is possible to ensure downward compatibility in
|
By specifying a version, it is possible to ensure downward compatibility in
|
||||||
the event of future changes to the configuration schema"""
|
the event of future changes to the configuration schema"""
|
||||||
|
|
||||||
cache: FaviconCacheConfig = msgspec.field(default_factory=FaviconCacheConfig)
|
cache: FaviconCacheConfig = FaviconCacheConfig()
|
||||||
"""Setup of the :py:obj:`.cache.FaviconCacheConfig`."""
|
"""Setup of the :py:obj:`.cache.FaviconCacheConfig`."""
|
||||||
|
|
||||||
proxy: FaviconProxyConfig = msgspec.field(default_factory=FaviconProxyConfig)
|
proxy: FaviconProxyConfig = FaviconProxyConfig()
|
||||||
"""Setup of the :py:obj:`.proxy.FaviconProxyConfig`."""
|
"""Setup of the :py:obj:`.proxy.FaviconProxyConfig`."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -44,22 +45,18 @@ class FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
with cfg_file.open("rb") as f:
|
with cfg_file.open("rb") as f:
|
||||||
data = f.read()
|
|
||||||
|
|
||||||
cfg = msgspec.toml.decode(data, type=_FaviconConfig)
|
cfg = tomllib.load(f)
|
||||||
schema = cfg.favicons.cfg_schema
|
cfg = cfg.get("favicons", cfg)
|
||||||
if schema != CONFIG_SCHEMA:
|
|
||||||
raise ValueError(
|
|
||||||
f"config schema version {CONFIG_SCHEMA} is needed, version {schema} is given in {cfg_file}"
|
|
||||||
)
|
|
||||||
|
|
||||||
cfg = cfg.favicons
|
schema = cfg.get("cfg_schema")
|
||||||
if use_cache and cached:
|
if schema != CONFIG_SCHEMA:
|
||||||
TOML_CACHE_CFG[str(cfg_file.resolve())] = cfg
|
raise ValueError(
|
||||||
|
f"config schema version {CONFIG_SCHEMA} is needed, version {schema} is given in {cfg_file}"
|
||||||
|
)
|
||||||
|
|
||||||
return cfg
|
cfg = cls(**cfg)
|
||||||
|
if use_cache and cached:
|
||||||
|
TOML_CACHE_CFG[str(cfg_file.resolve())] = cfg
|
||||||
|
|
||||||
|
return cfg
|
||||||
class _FaviconConfig(msgspec.Struct): # pylint: disable=too-few-public-methods
|
|
||||||
# wrapper struct for root object "favicons."
|
|
||||||
favicons: FaviconConfig
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import urllib.parse
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from httpx import HTTPError
|
from httpx import HTTPError
|
||||||
import msgspec
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from searx import get_setting
|
from searx import get_setting
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ def _initial_resolver_map():
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
class FaviconProxyConfig(msgspec.Struct):
|
class FaviconProxyConfig(BaseModel):
|
||||||
"""Configuration of the favicon proxy."""
|
"""Configuration of the favicon proxy."""
|
||||||
|
|
||||||
max_age: int = 60 * 60 * 24 * 7 # seven days
|
max_age: int = 60 * 60 * 24 * 7 # seven days
|
||||||
|
@ -59,7 +59,7 @@ class FaviconProxyConfig(msgspec.Struct):
|
||||||
outgoing request of the resolver. By default, the value from
|
outgoing request of the resolver. By default, the value from
|
||||||
:ref:`outgoing.request_timeout <settings outgoing>` setting is used."""
|
:ref:`outgoing.request_timeout <settings outgoing>` setting is used."""
|
||||||
|
|
||||||
resolver_map: dict[str, str] = msgspec.field(default_factory=_initial_resolver_map)
|
resolver_map: dict[str, str] = _initial_resolver_map()
|
||||||
"""The resolver_map is a key / value dictionary where the key is the name of
|
"""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
|
the resolver and the value is the fully qualifying name (fqn) of resolver's
|
||||||
function (the callable). The resolvers from the python module
|
function (the callable). The resolvers from the python module
|
||||||
|
|
|
@ -128,6 +128,9 @@ _INSTALLED = False
|
||||||
LIMITER_CFG_SCHEMA = Path(__file__).parent / "limiter.toml"
|
LIMITER_CFG_SCHEMA = Path(__file__).parent / "limiter.toml"
|
||||||
"""Base configuration (schema) of the botdetection."""
|
"""Base configuration (schema) of the botdetection."""
|
||||||
|
|
||||||
|
LIMITER_CFG = Path('/etc/searxng/limiter.toml')
|
||||||
|
"""Local Limiter configuration."""
|
||||||
|
|
||||||
CFG_DEPRECATED = {
|
CFG_DEPRECATED = {
|
||||||
# "dummy.old.foo": "config 'dummy.old.foo' exists only for tests. Don't use it in your real project config."
|
# "dummy.old.foo": "config 'dummy.old.foo' exists only for tests. Don't use it in your real project config."
|
||||||
}
|
}
|
||||||
|
@ -135,12 +138,8 @@ CFG_DEPRECATED = {
|
||||||
|
|
||||||
def get_cfg() -> config.Config:
|
def get_cfg() -> config.Config:
|
||||||
global CFG # pylint: disable=global-statement
|
global CFG # pylint: disable=global-statement
|
||||||
|
|
||||||
if CFG is None:
|
if CFG is None:
|
||||||
from . import settings_loader # pylint: disable=import-outside-toplevel
|
CFG = config.Config.from_toml(LIMITER_CFG_SCHEMA, LIMITER_CFG, CFG_DEPRECATED)
|
||||||
|
|
||||||
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
|
return CFG
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,9 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import re
|
|
||||||
import operator
|
import operator
|
||||||
from multiprocessing import Process, Queue
|
from multiprocessing import Process, Queue
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import flask
|
|
||||||
import babel
|
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
|
|
||||||
from searx.plugins import logger
|
from searx.plugins import logger
|
||||||
|
@ -23,7 +19,7 @@ plugin_id = 'calculator'
|
||||||
|
|
||||||
logger = logger.getChild(plugin_id)
|
logger = logger.getChild(plugin_id)
|
||||||
|
|
||||||
operators: dict[type, Callable] = {
|
operators = {
|
||||||
ast.Add: operator.add,
|
ast.Add: operator.add,
|
||||||
ast.Sub: operator.sub,
|
ast.Sub: operator.sub,
|
||||||
ast.Mult: operator.mul,
|
ast.Mult: operator.mul,
|
||||||
|
@ -43,15 +39,11 @@ def _eval_expr(expr):
|
||||||
>>> _eval_expr('1 + 2*3**(4^5) / (6 + -7)')
|
>>> _eval_expr('1 + 2*3**(4^5) / (6 + -7)')
|
||||||
-5.0
|
-5.0
|
||||||
"""
|
"""
|
||||||
try:
|
return _eval(ast.parse(expr, mode='eval').body)
|
||||||
return _eval(ast.parse(expr, mode='eval').body)
|
|
||||||
except ZeroDivisionError:
|
|
||||||
# This is undefined
|
|
||||||
return ""
|
|
||||||
|
|
||||||
|
|
||||||
def _eval(node):
|
def _eval(node):
|
||||||
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
|
if isinstance(node, ast.Constant) and isinstance(node.value, int):
|
||||||
return node.value
|
return node.value
|
||||||
|
|
||||||
if isinstance(node, ast.BinOp):
|
if isinstance(node, ast.BinOp):
|
||||||
|
@ -101,19 +93,6 @@ def post_search(_request, search):
|
||||||
# replace commonly used math operators with their proper Python operator
|
# replace commonly used math operators with their proper Python operator
|
||||||
query = query.replace("x", "*").replace(":", "/")
|
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, ui_locale, numbering_system="latn")
|
|
||||||
return str(val)
|
|
||||||
|
|
||||||
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
|
# only numbers and math operators are accepted
|
||||||
if any(str.isalpha(c) for c in query):
|
if any(str.isalpha(c) for c in query):
|
||||||
return True
|
return True
|
||||||
|
@ -123,8 +102,10 @@ def post_search(_request, search):
|
||||||
|
|
||||||
# Prevent the runtime from being longer than 50 ms
|
# Prevent the runtime from being longer than 50 ms
|
||||||
result = timeout_func(0.05, _eval_expr, query_py_formatted)
|
result = timeout_func(0.05, _eval_expr, query_py_formatted)
|
||||||
if result is None or result == "":
|
if result is None:
|
||||||
return True
|
return True
|
||||||
result = babel.numbers.format_decimal(result, locale=ui_locale)
|
result = str(result)
|
||||||
search.result_container.answers['calculate'] = {'answer': f"{search.search_query.query} = {result}"}
|
|
||||||
|
if result != query:
|
||||||
|
search.result_container.answers['calculate'] = {'answer': f"{query} = {result}"}
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -23,7 +23,7 @@ def name_to_iso4217(name):
|
||||||
currency = CURRENCIES['names'].get(name, [name])
|
currency = CURRENCIES['names'].get(name, [name])
|
||||||
if isinstance(currency, str):
|
if isinstance(currency, str):
|
||||||
return currency
|
return currency
|
||||||
return currency[-1]
|
return currency[0]
|
||||||
|
|
||||||
|
|
||||||
def iso4217_to_name(iso4217, language):
|
def iso4217_to_name(iso4217, language):
|
||||||
|
|
|
@ -1289,12 +1289,6 @@ engines:
|
||||||
require_api_key: false
|
require_api_key: false
|
||||||
results: JSON
|
results: JSON
|
||||||
|
|
||||||
- name: openlibrary
|
|
||||||
engine: openlibrary
|
|
||||||
shortcut: ol
|
|
||||||
timeout: 5
|
|
||||||
disabled: true
|
|
||||||
|
|
||||||
- name: openmeteo
|
- name: openmeteo
|
||||||
engine: open_meteo
|
engine: open_meteo
|
||||||
shortcut: om
|
shortcut: om
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -277,7 +277,7 @@
|
||||||
@results-margin: 0.125rem;
|
@results-margin: 0.125rem;
|
||||||
@result-padding: 1rem;
|
@result-padding: 1rem;
|
||||||
@results-image-row-height: 12rem;
|
@results-image-row-height: 12rem;
|
||||||
@results-image-row-height-phone: 10rem;
|
@results-image-row-height-phone: 6rem;
|
||||||
@search-width: 44rem;
|
@search-width: 44rem;
|
||||||
// heigh of #search, see detail.less
|
// heigh of #search, see detail.less
|
||||||
@search-height: 7.6rem;
|
@search-height: 7.6rem;
|
||||||
|
|
|
@ -380,9 +380,9 @@ html.no-js #clear_search.hide_if_nojs {
|
||||||
}
|
}
|
||||||
|
|
||||||
.favicon img {
|
.favicon img {
|
||||||
height: 1.5rem;
|
height: 1.8rem;
|
||||||
width: 1.5rem;
|
width: 1.8rem;
|
||||||
border-radius: 10%;
|
border-radius: 20%;
|
||||||
background-color: var(--color-favicon-background-color);
|
background-color: var(--color-favicon-background-color);
|
||||||
border: 1px solid var(--color-favicon-border-color);
|
border: 1px solid var(--color-favicon-border-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -456,7 +456,6 @@ article[data-vim-selected].category-social {
|
||||||
margin: 0.25rem;
|
margin: 0.25rem;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
height: @results-image-row-height;
|
height: @results-image-row-height;
|
||||||
width: unset;
|
|
||||||
|
|
||||||
& > a {
|
& > a {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1108,7 +1107,6 @@ summary.title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: @results-image-row-height-phone;
|
height: @results-image-row-height-phone;
|
||||||
background: var(--color-base-background-mobile);
|
background: var(--color-base-background-mobile);
|
||||||
width: unset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.infobox {
|
.infobox {
|
||||||
|
|
|
@ -16,9 +16,9 @@ sxng_locales = (
|
||||||
('bg', 'Български', '', 'Bulgarian', '\U0001f310'),
|
('bg', 'Български', '', 'Bulgarian', '\U0001f310'),
|
||||||
('bg-BG', 'Български', 'България', 'Bulgarian', '\U0001f1e7\U0001f1ec'),
|
('bg-BG', 'Български', 'България', 'Bulgarian', '\U0001f1e7\U0001f1ec'),
|
||||||
('ca', 'Català', '', 'Catalan', '\U0001f310'),
|
('ca', 'Català', '', 'Catalan', '\U0001f310'),
|
||||||
|
('ca-ES', 'Català', 'Espanya', 'Catalan', '\U0001f1ea\U0001f1f8'),
|
||||||
('cs', 'Čeština', '', 'Czech', '\U0001f310'),
|
('cs', 'Čeština', '', 'Czech', '\U0001f310'),
|
||||||
('cs-CZ', 'Čeština', 'Česko', 'Czech', '\U0001f1e8\U0001f1ff'),
|
('cs-CZ', 'Čeština', 'Česko', 'Czech', '\U0001f1e8\U0001f1ff'),
|
||||||
('cy', 'Cymraeg', '', 'Welsh', '\U0001f310'),
|
|
||||||
('da', 'Dansk', '', 'Danish', '\U0001f310'),
|
('da', 'Dansk', '', 'Danish', '\U0001f310'),
|
||||||
('da-DK', 'Dansk', 'Danmark', 'Danish', '\U0001f1e9\U0001f1f0'),
|
('da-DK', 'Dansk', 'Danmark', 'Danish', '\U0001f1e9\U0001f1f0'),
|
||||||
('de', 'Deutsch', '', 'German', '\U0001f310'),
|
('de', 'Deutsch', '', 'German', '\U0001f310'),
|
||||||
|
@ -56,8 +56,6 @@ sxng_locales = (
|
||||||
('fr-CA', 'Français', 'Canada', 'French', '\U0001f1e8\U0001f1e6'),
|
('fr-CA', 'Français', 'Canada', 'French', '\U0001f1e8\U0001f1e6'),
|
||||||
('fr-CH', 'Français', 'Suisse', 'French', '\U0001f1e8\U0001f1ed'),
|
('fr-CH', 'Français', 'Suisse', 'French', '\U0001f1e8\U0001f1ed'),
|
||||||
('fr-FR', 'Français', 'France', 'French', '\U0001f1eb\U0001f1f7'),
|
('fr-FR', 'Français', 'France', 'French', '\U0001f1eb\U0001f1f7'),
|
||||||
('ga', 'Gaeilge', '', 'Irish', '\U0001f310'),
|
|
||||||
('gd', 'Gàidhlig', '', 'Scottish Gaelic', '\U0001f310'),
|
|
||||||
('gl', 'Galego', '', 'Galician', '\U0001f310'),
|
('gl', 'Galego', '', 'Galician', '\U0001f310'),
|
||||||
('he', 'עברית', '', 'Hebrew', '\U0001f1ee\U0001f1f1'),
|
('he', 'עברית', '', 'Hebrew', '\U0001f1ee\U0001f1f1'),
|
||||||
('hi', 'हिन्दी', '', 'Hindi', '\U0001f310'),
|
('hi', 'हिन्दी', '', 'Hindi', '\U0001f310'),
|
||||||
|
@ -94,7 +92,6 @@ sxng_locales = (
|
||||||
('ru-RU', 'Русский', 'Россия', 'Russian', '\U0001f1f7\U0001f1fa'),
|
('ru-RU', 'Русский', 'Россия', 'Russian', '\U0001f1f7\U0001f1fa'),
|
||||||
('sk', 'Slovenčina', '', 'Slovak', '\U0001f310'),
|
('sk', 'Slovenčina', '', 'Slovak', '\U0001f310'),
|
||||||
('sl', 'Slovenščina', '', 'Slovenian', '\U0001f310'),
|
('sl', 'Slovenščina', '', 'Slovenian', '\U0001f310'),
|
||||||
('sq', 'Shqip', '', 'Albanian', '\U0001f310'),
|
|
||||||
('sv', 'Svenska', '', 'Swedish', '\U0001f310'),
|
('sv', 'Svenska', '', 'Swedish', '\U0001f310'),
|
||||||
('sv-SE', 'Svenska', 'Sverige', 'Swedish', '\U0001f1f8\U0001f1ea'),
|
('sv-SE', 'Svenska', 'Sverige', 'Swedish', '\U0001f1f8\U0001f1ea'),
|
||||||
('ta', 'தமிழ்', '', 'Tamil', '\U0001f310'),
|
('ta', 'தமிழ்', '', 'Tamil', '\U0001f310'),
|
||||||
|
@ -103,8 +100,10 @@ sxng_locales = (
|
||||||
('tr', 'Türkçe', '', 'Turkish', '\U0001f310'),
|
('tr', 'Türkçe', '', 'Turkish', '\U0001f310'),
|
||||||
('tr-TR', 'Türkçe', 'Türkiye', 'Turkish', '\U0001f1f9\U0001f1f7'),
|
('tr-TR', 'Türkçe', 'Türkiye', 'Turkish', '\U0001f1f9\U0001f1f7'),
|
||||||
('uk', 'Українська', '', 'Ukrainian', '\U0001f310'),
|
('uk', 'Українська', '', 'Ukrainian', '\U0001f310'),
|
||||||
|
('uk-UA', 'Українська', 'Україна', 'Ukrainian', '\U0001f1fa\U0001f1e6'),
|
||||||
('ur', 'اردو', '', 'Urdu', '\U0001f310'),
|
('ur', 'اردو', '', 'Urdu', '\U0001f310'),
|
||||||
('vi', 'Tiếng Việt', '', 'Vietnamese', '\U0001f310'),
|
('vi', 'Tiếng Việt', '', 'Vietnamese', '\U0001f310'),
|
||||||
|
('vi-VN', 'Tiếng Việt', 'Việt Nam', 'Vietnamese', '\U0001f1fb\U0001f1f3'),
|
||||||
('zh', '中文', '', 'Chinese', '\U0001f310'),
|
('zh', '中文', '', 'Chinese', '\U0001f310'),
|
||||||
('zh-CN', '中文', '中国', 'Chinese', '\U0001f1e8\U0001f1f3'),
|
('zh-CN', '中文', '中国', 'Chinese', '\U0001f1e8\U0001f1f3'),
|
||||||
('zh-HK', '中文', '中國香港特別行政區', 'Chinese', '\U0001f1ed\U0001f1f0'),
|
('zh-HK', '中文', '中國香港特別行政區', 'Chinese', '\U0001f1ed\U0001f1f0'),
|
||||||
|
|
Binary file not shown.
|
@ -12,23 +12,20 @@
|
||||||
# Salif Mehmed <mail@salif.eu>, 2023, 2024.
|
# Salif Mehmed <mail@salif.eu>, 2023, 2024.
|
||||||
# return42 <return42@users.noreply.translate.codeberg.org>, 2024.
|
# return42 <return42@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# krlsk <krlsk@users.noreply.translate.codeberg.org>, 2024.
|
# krlsk <krlsk@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# stoychevww <stoychevww@users.noreply.translate.codeberg.org>, 2024.
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-13 03:30+0000\n"
|
"PO-Revision-Date: 2024-05-25 08:18+0000\n"
|
||||||
"Last-Translator: stoychevww <stoychevww@users.noreply.translate.codeberg.org>"
|
"Last-Translator: krlsk <krlsk@users.noreply.translate.codeberg.org>\n"
|
||||||
"\n"
|
|
||||||
"Language-Team: Bulgarian <https://translate.codeberg.org/projects/searxng/"
|
|
||||||
"searxng/bg/>\n"
|
|
||||||
"Language: bg\n"
|
"Language: bg\n"
|
||||||
|
"Language-Team: Bulgarian "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/bg/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
|
||||||
"X-Generator: Weblate 5.7.2\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -169,7 +166,7 @@ msgstr "тъмен"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "черно"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -337,17 +334,17 @@ msgstr "Автор"
|
||||||
#. SOCIAL_MEDIA_TERMS['THREAD OPEN']
|
#. SOCIAL_MEDIA_TERMS['THREAD OPEN']
|
||||||
#: searx/engines/discourse.py:149 searx/searxng.msg
|
#: searx/engines/discourse.py:149 searx/searxng.msg
|
||||||
msgid "open"
|
msgid "open"
|
||||||
msgstr "отворено"
|
msgstr ""
|
||||||
|
|
||||||
#. SOCIAL_MEDIA_TERMS['THREAD CLOSED']
|
#. SOCIAL_MEDIA_TERMS['THREAD CLOSED']
|
||||||
#: searx/engines/discourse.py:149 searx/searxng.msg
|
#: searx/engines/discourse.py:149 searx/searxng.msg
|
||||||
msgid "closed"
|
msgid "closed"
|
||||||
msgstr "Затворено"
|
msgstr ""
|
||||||
|
|
||||||
#. SOCIAL_MEDIA_TERMS['THREAD ANSWERED']
|
#. SOCIAL_MEDIA_TERMS['THREAD ANSWERED']
|
||||||
#: searx/engines/discourse.py:160 searx/searxng.msg
|
#: searx/engines/discourse.py:160 searx/searxng.msg
|
||||||
msgid "answered"
|
msgid "answered"
|
||||||
msgstr "Отговорено"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/webapp.py:332
|
#: searx/webapp.py:332
|
||||||
msgid "No item found"
|
msgid "No item found"
|
||||||
|
@ -456,7 +453,7 @@ msgstr "Изчислете {functions} на аргументите"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Синоними"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -1978,3 +1975,4 @@ msgstr "скрий видеото"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Търсачките не можаха да намерят резултати"
|
#~ msgstr "Търсачките не можаха да намерят резултати"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -39,7 +39,7 @@ msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
|
"PO-Revision-Date: 2024-10-06 14:31+0000\n"
|
||||||
"Last-Translator: Atul_Eterno <Atul_Eterno@users.noreply.translate.codeberg."
|
"Last-Translator: Atul_Eterno <Atul_Eterno@users.noreply.translate.codeberg."
|
||||||
"org>\n"
|
"org>\n"
|
||||||
"Language-Team: Spanish <https://translate.codeberg.org/projects/searxng/"
|
"Language-Team: Spanish <https://translate.codeberg.org/projects/searxng/"
|
||||||
|
@ -49,7 +49,7 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 5.8.1\n"
|
"X-Generator: Weblate 5.7.2\n"
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -155,7 +155,7 @@ msgstr "preguntas y respuestas"
|
||||||
#. CATEGORY_GROUPS['REPOS']
|
#. CATEGORY_GROUPS['REPOS']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "repos"
|
msgid "repos"
|
||||||
msgstr "repositorios"
|
msgstr "repos"
|
||||||
|
|
||||||
#. CATEGORY_GROUPS['SOFTWARE_WIKIS']
|
#. CATEGORY_GROUPS['SOFTWARE_WIKIS']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
|
Binary file not shown.
|
@ -17,7 +17,7 @@ msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-21 20:07+0000\n"
|
"PO-Revision-Date: 2024-10-05 08:07+0000\n"
|
||||||
"Last-Translator: Priit Jõerüüt <jrtcdbrg@users.noreply.translate.codeberg."
|
"Last-Translator: Priit Jõerüüt <jrtcdbrg@users.noreply.translate.codeberg."
|
||||||
"org>\n"
|
"org>\n"
|
||||||
"Language-Team: Estonian <https://translate.codeberg.org/projects/searxng/"
|
"Language-Team: Estonian <https://translate.codeberg.org/projects/searxng/"
|
||||||
|
@ -1155,8 +1155,8 @@ msgid ""
|
||||||
"Note: specifying custom settings in the search URL can reduce privacy by "
|
"Note: specifying custom settings in the search URL can reduce privacy by "
|
||||||
"leaking data to the clicked result sites."
|
"leaking data to the clicked result sites."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Märkus: lekitades andmed klõpsatud tulemuste saitidele võib täpsemate "
|
"Märkus: täpsemate seadete määramine otsingu URLis võib vähendada "
|
||||||
"seadete määramine otsingu URLis vähendada privaatsust."
|
"privaatsust, lekitades andmed klõpsatud tulemuste saitidele."
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/cookies.html:35
|
#: searx/templates/simple/preferences/cookies.html:35
|
||||||
msgid "URL to restore your preferences in another browser"
|
msgid "URL to restore your preferences in another browser"
|
||||||
|
|
Binary file not shown.
|
@ -25,22 +25,20 @@
|
||||||
# wags07 <wags07@users.noreply.translate.codeberg.org>, 2024.
|
# wags07 <wags07@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# Aeris1One <Aeris1One@users.noreply.translate.codeberg.org>, 2024.
|
# Aeris1One <Aeris1One@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# kratos <kratos@users.noreply.translate.codeberg.org>, 2024.
|
# kratos <kratos@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# hemie143 <hemie143@users.noreply.translate.codeberg.org>, 2024.
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-18 07:22+0000\n"
|
"PO-Revision-Date: 2024-09-24 19:18+0000\n"
|
||||||
"Last-Translator: hemie143 <hemie143@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: kratos <kratos@users.noreply.translate.codeberg.org>\n"
|
||||||
"Language-Team: French <https://translate.codeberg.org/projects/searxng/"
|
|
||||||
"searxng/fr/>\n"
|
|
||||||
"Language: fr\n"
|
"Language: fr\n"
|
||||||
|
"Language-Team: French "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/fr/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
|
||||||
"X-Generator: Weblate 5.7.2\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -181,7 +179,7 @@ msgstr "sombre"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "noir"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -468,7 +466,7 @@ msgstr "Calcule les {functions} des arguments"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Synonymes"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -1237,11 +1235,11 @@ msgstr "Temps max"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "Résolveur de Favicon"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Affiche les favicons à côté des résultats de recherche"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -2015,3 +2013,4 @@ msgstr "cacher la vidéo"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Les moteurs ne peuvent pas récupérer de résultats"
|
#~ msgstr "Les moteurs ne peuvent pas récupérer de résultats"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -22,22 +22,21 @@
|
||||||
# MVDW-Java <MVDW-Java@users.noreply.translate.codeberg.org>, 2024.
|
# MVDW-Java <MVDW-Java@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# notlmutsaers <notlmutsaers@users.noreply.translate.codeberg.org>, 2024.
|
# notlmutsaers <notlmutsaers@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# return42 <return42@users.noreply.translate.codeberg.org>, 2024.
|
# return42 <return42@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# ljansen <ljansen@users.noreply.translate.codeberg.org>, 2024.
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-28 21:07+0000\n"
|
"PO-Revision-Date: 2024-09-05 06:18+0000\n"
|
||||||
"Last-Translator: ljansen <ljansen@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>"
|
||||||
"Language-Team: Dutch <https://translate.codeberg.org/projects/searxng/"
|
"\n"
|
||||||
"searxng/nl/>\n"
|
|
||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
|
"Language-Team: Dutch "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/nl/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
|
||||||
"X-Generator: Weblate 5.8.1\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -178,7 +177,7 @@ msgstr "donker"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "zwart"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -465,7 +464,7 @@ msgstr "Bereken {functions} van de opties"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Synoniemen"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -1235,13 +1234,12 @@ msgid "Max time"
|
||||||
msgstr "Max. duur"
|
msgstr "Max. duur"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
#, fuzzy
|
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "favicon-resolver"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Vertoon zoekresultaten naast favicons"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -2006,3 +2004,4 @@ msgstr "verberg video"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Zoekmachines konden geen resultaten ophalen"
|
#~ msgstr "Zoekmachines konden geen resultaten ophalen"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -23,8 +23,8 @@ msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-28 21:07+0000\n"
|
"PO-Revision-Date: 2024-10-08 13:41+0000\n"
|
||||||
"Last-Translator: Eryk Michalak <gnu.ewm@protonmail.com>\n"
|
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
|
||||||
"Language-Team: Polish <https://translate.codeberg.org/projects/searxng/"
|
"Language-Team: Polish <https://translate.codeberg.org/projects/searxng/"
|
||||||
"searxng/pl/>\n"
|
"searxng/pl/>\n"
|
||||||
"Language: pl\n"
|
"Language: pl\n"
|
||||||
|
@ -34,7 +34,7 @@ msgstr ""
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && ("
|
"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%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"
|
"n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||||
"X-Generator: Weblate 5.8.1\n"
|
"X-Generator: Weblate 5.7.2\n"
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -1230,11 +1230,11 @@ msgstr "Maksymalny czas"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "Pobieranie favikony"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Wyświetlanie faviconów obok wyników wyszukiwania"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
|
Binary file not shown.
|
@ -18,24 +18,21 @@
|
||||||
# diodio <diodio@users.noreply.translate.codeberg.org>, 2024.
|
# diodio <diodio@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# gvlx <gvlx@users.noreply.translate.codeberg.org>, 2024.
|
# gvlx <gvlx@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# ds451 <ds451@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 ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-29 05:54+0000\n"
|
"PO-Revision-Date: 2024-09-05 06:18+0000\n"
|
||||||
"Last-Translator: saltsnorter <saltsnorter@users.noreply.translate.codeberg."
|
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>"
|
||||||
"org>\n"
|
"\n"
|
||||||
"Language-Team: Portuguese <https://translate.codeberg.org/projects/searxng/"
|
|
||||||
"searxng/pt/>\n"
|
|
||||||
"Language: pt\n"
|
"Language: pt\n"
|
||||||
|
"Language-Team: Portuguese "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/pt/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"X-Generator: Weblate 5.8.1\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -176,7 +173,7 @@ msgstr "escuro"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "preto"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -463,7 +460,7 @@ msgstr "Calcular {functions} dos argumentos"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Sinônimos"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -698,7 +695,7 @@ msgstr "Comprimento"
|
||||||
|
|
||||||
#: searx/templates/simple/macros.html:41
|
#: searx/templates/simple/macros.html:41
|
||||||
msgid "Views"
|
msgid "Views"
|
||||||
msgstr "Viazualisações"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/macros.html:42
|
#: searx/templates/simple/macros.html:42
|
||||||
#: searx/templates/simple/result_templates/files.html:34
|
#: searx/templates/simple/result_templates/files.html:34
|
||||||
|
@ -1229,11 +1226,11 @@ msgstr "Tempo máximo"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "Solucionador do Favicon"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Monstra os favicons nos proximos os resultados"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -2000,3 +1997,4 @@ msgstr "esconder vídeo"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Mecanismos não podem recuperar resultados"
|
#~ msgstr "Mecanismos não podem recuperar resultados"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -30,22 +30,21 @@
|
||||||
# Pyrbor <Pyrbor@users.noreply.translate.codeberg.org>, 2024.
|
# Pyrbor <Pyrbor@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# rodgui <rodgui@users.noreply.translate.codeberg.org>, 2024.
|
# rodgui <rodgui@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# rafablog77 <rafablog77@users.noreply.translate.codeberg.org>, 2024.
|
# rafablog77 <rafablog77@users.noreply.translate.codeberg.org>, 2024.
|
||||||
# Juno Takano <jutty@users.noreply.translate.codeberg.org>, 2024.
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-31 12:16+0000\n"
|
"PO-Revision-Date: 2024-09-05 06:18+0000\n"
|
||||||
"Last-Translator: Juno Takano <jutty@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>"
|
||||||
"Language-Team: Portuguese (Brazil) <https://translate.codeberg.org/projects/"
|
"\n"
|
||||||
"searxng/searxng/pt_BR/>\n"
|
|
||||||
"Language: pt_BR\n"
|
"Language: pt_BR\n"
|
||||||
|
"Language-Team: Portuguese (Brazil) "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/pt_BR/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
|
||||||
"X-Generator: Weblate 5.8.1\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -186,7 +185,7 @@ msgstr "escuro"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "preto"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -473,7 +472,7 @@ msgstr "Computar {functions} dos argumentos"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Sinônimos"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -1244,11 +1243,11 @@ msgstr "Tempo máximo"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "Resolvedor de Favicons"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Exibir favicons próximo aos resultados da pesquisa"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -2019,3 +2018,4 @@ msgstr "ocultar vídeo"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Os motores de busca não conseguiram obter resultados"
|
#~ msgstr "Os motores de busca não conseguiram obter resultados"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -24,19 +24,19 @@
|
||||||
# ulsaa <ulsaa@users.noreply.translate.codeberg.org>, 2024.
|
# ulsaa <ulsaa@users.noreply.translate.codeberg.org>, 2024.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-15 12:18+0000\n"
|
"PO-Revision-Date: 2024-09-05 06:18+0000\n"
|
||||||
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>"
|
||||||
"Language-Team: Turkish <https://translate.codeberg.org/projects/searxng/"
|
"\n"
|
||||||
"searxng/tr/>\n"
|
|
||||||
"Language: tr\n"
|
"Language: tr\n"
|
||||||
|
"Language-Team: Turkish "
|
||||||
|
"<https://translate.codeberg.org/projects/searxng/searxng/tr/>\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
"X-Generator: Weblate 5.7.2\n"
|
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -177,7 +177,7 @@ msgstr "karanlık"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "siyah"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -464,7 +464,7 @@ msgstr "Bağımsız değişkenlerin {functions} değerini hesapla"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "Eş Anlamlılar"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -1231,11 +1231,11 @@ msgstr "En fazla zaman"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "Favicon Çözümleyicisi"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "Arama sonuçlarının yanında favsimgelerini göster"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -1990,3 +1990,4 @@ msgstr "görüntüyü gizle"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Motorlar sonuçları alamıyor"
|
#~ msgstr "Motorlar sonuçları alamıyor"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -12,19 +12,19 @@
|
||||||
# tvminh19 <tvminh19@users.noreply.translate.codeberg.org>, 2024.
|
# tvminh19 <tvminh19@users.noreply.translate.codeberg.org>, 2024.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
|
"PO-Revision-Date: 2024-08-07 01:02+0000\n"
|
||||||
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: tvminh19 <tvminh19@users.noreply.translate.codeberg.org>"
|
||||||
"Language-Team: Vietnamese <https://translate.codeberg.org/projects/searxng/"
|
"\n"
|
||||||
"searxng/vi/>\n"
|
|
||||||
"Language: 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"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -186,7 +186,7 @@ msgstr "Nhiệt độ trung bình."
|
||||||
#. WEATHER_TERMS['CLOUD COVER']
|
#. WEATHER_TERMS['CLOUD COVER']
|
||||||
#: searx/engines/open_meteo.py:91 searx/searxng.msg
|
#: searx/engines/open_meteo.py:91 searx/searxng.msg
|
||||||
msgid "Cloud cover"
|
msgid "Cloud cover"
|
||||||
msgstr "Mây che phủ"
|
msgstr ""
|
||||||
|
|
||||||
#. WEATHER_TERMS['CONDITION']
|
#. WEATHER_TERMS['CONDITION']
|
||||||
#: searx/engines/duckduckgo_weather.py:45 searx/engines/wttr.py:51
|
#: 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/duckduckgo_weather.py:58 searx/engines/open_meteo.py:86
|
||||||
#: searx/engines/wttr.py:62 searx/searxng.msg
|
#: searx/engines/wttr.py:62 searx/searxng.msg
|
||||||
msgid "Wind"
|
msgid "Wind"
|
||||||
msgstr "Gió"
|
msgstr ""
|
||||||
|
|
||||||
#. SOCIAL_MEDIA_TERMS['SUBSCRIBERS']
|
#. SOCIAL_MEDIA_TERMS['SUBSCRIBERS']
|
||||||
#: searx/engines/lemmy.py:85 searx/searxng.msg
|
#: searx/engines/lemmy.py:85 searx/searxng.msg
|
||||||
|
@ -1990,3 +1990,4 @@ msgstr "ẩn phim"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "Các trình tìm kiếm không nhận được kết quả"
|
#~ msgstr "Các trình tìm kiếm không nhận được kết quả"
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -28,19 +28,18 @@
|
||||||
# hugoalh <hugoalh@users.noreply.translate.codeberg.org>, 2024.
|
# hugoalh <hugoalh@users.noreply.translate.codeberg.org>, 2024.
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: searx\n"
|
"Project-Id-Version: searx\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
"POT-Creation-Date: 2024-10-05 06:24+0000\n"
|
||||||
"PO-Revision-Date: 2024-10-26 21:13+0000\n"
|
"PO-Revision-Date: 2024-08-12 04:00+0000\n"
|
||||||
"Last-Translator: return42 <return42@users.noreply.translate.codeberg.org>\n"
|
"Last-Translator: hugoalh <hugoalh@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: 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"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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"
|
"Generated-By: Babel 2.16.0\n"
|
||||||
|
|
||||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||||
|
@ -181,7 +180,7 @@ msgstr "黑暗"
|
||||||
#. STYLE_NAMES['BLACK']
|
#. STYLE_NAMES['BLACK']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
msgid "black"
|
msgid "black"
|
||||||
msgstr "黑色"
|
msgstr ""
|
||||||
|
|
||||||
#. BRAND_CUSTOM_LINKS['UPTIME']
|
#. BRAND_CUSTOM_LINKS['UPTIME']
|
||||||
#: searx/searxng.msg
|
#: searx/searxng.msg
|
||||||
|
@ -468,7 +467,7 @@ msgstr "計算 {functions} 參數"
|
||||||
|
|
||||||
#: searx/engines/mozhi.py:57
|
#: searx/engines/mozhi.py:57
|
||||||
msgid "Synonyms"
|
msgid "Synonyms"
|
||||||
msgstr "同義詞"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/engines/openstreetmap.py:159
|
#: searx/engines/openstreetmap.py:159
|
||||||
msgid "Get directions"
|
msgid "Get directions"
|
||||||
|
@ -943,7 +942,7 @@ msgstr "來自搜尋引擎的訊息"
|
||||||
|
|
||||||
#: searx/templates/simple/elements/engines_msg.html:7
|
#: searx/templates/simple/elements/engines_msg.html:7
|
||||||
msgid "seconds"
|
msgid "seconds"
|
||||||
msgstr "秒"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/elements/search_url.html:3
|
#: searx/templates/simple/elements/search_url.html:3
|
||||||
msgid "Search URL"
|
msgid "Search URL"
|
||||||
|
@ -1207,11 +1206,11 @@ msgstr "最大時間"
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:2
|
#: searx/templates/simple/preferences/favicon.html:2
|
||||||
msgid "Favicon Resolver"
|
msgid "Favicon Resolver"
|
||||||
msgstr "網站圖標搜索器"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/favicon.html:15
|
#: searx/templates/simple/preferences/favicon.html:15
|
||||||
msgid "Display favicons near search results"
|
msgid "Display favicons near search results"
|
||||||
msgstr "在搜尋結果旁顯示網站圖標"
|
msgstr ""
|
||||||
|
|
||||||
#: searx/templates/simple/preferences/footer.html:2
|
#: searx/templates/simple/preferences/footer.html:2
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -1912,3 +1911,4 @@ msgstr "隱藏影片"
|
||||||
|
|
||||||
#~ msgid "Engines cannot retrieve results"
|
#~ msgid "Engines cannot retrieve results"
|
||||||
#~ msgstr "引擎無法擷取結果"
|
#~ msgstr "引擎無法擷取結果"
|
||||||
|
|
||||||
|
|
|
@ -517,7 +517,7 @@ def pre_request():
|
||||||
preferences.parse_dict({"language": language})
|
preferences.parse_dict({"language": language})
|
||||||
logger.debug('set language %s (from browser)', preferences.get_value("language"))
|
logger.debug('set language %s (from browser)', preferences.get_value("language"))
|
||||||
|
|
||||||
# UI locale is defined neither in settings nor in preferences
|
# locale is defined neither in settings nor in preferences
|
||||||
# use browser headers
|
# use browser headers
|
||||||
if not preferences.get_value("locale"):
|
if not preferences.get_value("locale"):
|
||||||
locale = _get_browser_language(request, LOCALE_NAMES.keys())
|
locale = _get_browser_language(request, LOCALE_NAMES.keys())
|
||||||
|
|
|
@ -101,7 +101,7 @@ def fetch_traits_map():
|
||||||
def filter_locales(traits_map: EngineTraitsMap):
|
def filter_locales(traits_map: EngineTraitsMap):
|
||||||
"""Filter language & region tags by a threshold."""
|
"""Filter language & region tags by a threshold."""
|
||||||
|
|
||||||
min_eng_per_region = 18
|
min_eng_per_region = 15
|
||||||
min_eng_per_lang = 20
|
min_eng_per_lang = 20
|
||||||
|
|
||||||
_ = {}
|
_ = {}
|
||||||
|
|
|
@ -32,10 +32,12 @@ class TestLocales(SearxTestCase):
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand(
|
||||||
[
|
[
|
||||||
|
('ca-es', 'ca-ES'),
|
||||||
('de-at', 'de-AT'),
|
('de-at', 'de-AT'),
|
||||||
('de-de', 'de-DE'),
|
('de-de', 'de-DE'),
|
||||||
('en-UK', 'en-GB'),
|
('en-UK', 'en-GB'),
|
||||||
('fr-be', 'fr-BE'),
|
('fr-be', 'fr-BE'),
|
||||||
|
('fr-be', 'fr-BE'),
|
||||||
('fr-ca', 'fr-CA'),
|
('fr-ca', 'fr-CA'),
|
||||||
('fr-ch', 'fr-CH'),
|
('fr-ch', 'fr-CH'),
|
||||||
('zh-cn', 'zh-CN'),
|
('zh-cn', 'zh-CN'),
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# pylint: disable=missing-module-docstring
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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):
|
|
||||||
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):
|
|
||||||
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):
|
|
||||||
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"),
|
|
||||||
("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):
|
|
||||||
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'])
|
|
||||||
|
|
||||||
@parameterized.expand(
|
|
||||||
[
|
|
||||||
"1/0",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_invalid_operations(self, operation):
|
|
||||||
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)
|
|
|
@ -1,51 +0,0 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# pylint: disable=missing-module-docstring, invalid-name
|
|
||||||
|
|
||||||
from mock import Mock
|
|
||||||
from parameterized.parameterized import parameterized
|
|
||||||
from searx import plugins
|
|
||||||
from tests import SearxTestCase
|
|
||||||
|
|
||||||
from .test_plugins import get_search_mock
|
|
||||||
|
|
||||||
|
|
||||||
class PluginHashTest(SearxTestCase): # pylint: disable=missing-class-docstring
|
|
||||||
def setUp(self):
|
|
||||||
self.store = plugins.PluginStore()
|
|
||||||
plugin = plugins.load_and_initialize_plugin('searx.plugins.hash_plugin', False, (None, {}))
|
|
||||||
self.store.register(plugin)
|
|
||||||
|
|
||||||
def test_plugin_store_init(self):
|
|
||||||
self.assertEqual(1, len(self.store.plugins))
|
|
||||||
|
|
||||||
@parameterized.expand(
|
|
||||||
[
|
|
||||||
('md5 test', 'md5 hash digest: 098f6bcd4621d373cade4e832627b4f6'),
|
|
||||||
('sha1 test', 'sha1 hash digest: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'),
|
|
||||||
('sha224 test', 'sha224 hash digest: 90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809'),
|
|
||||||
('sha256 test', 'sha256 hash digest: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'),
|
|
||||||
(
|
|
||||||
'sha384 test',
|
|
||||||
'sha384 hash digest: 768412320f7b0aa5812fce428dc4706b3c'
|
|
||||||
'ae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf1'
|
|
||||||
'7a0a9',
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'sha512 test',
|
|
||||||
'sha512 hash digest: ee26b0dd4af7e749aa1a8ee3c10ae9923f6'
|
|
||||||
'18980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5'
|
|
||||||
'fa9ad8e6f57f50028a8ff',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_hash_digest_new(self, query: str, hash_str: str):
|
|
||||||
request = Mock(remote_addr='127.0.0.1')
|
|
||||||
search = get_search_mock(query=query, pageno=1)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertIn(hash_str, search.result_container.answers['hash']['answer'])
|
|
||||||
|
|
||||||
def test_md5_bytes_no_answer(self):
|
|
||||||
request = Mock(remote_addr='127.0.0.1')
|
|
||||||
search = get_search_mock(query=b'md5 test', pageno=2)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertNotIn('hash', search.result_container.answers)
|
|
|
@ -1,65 +0,0 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# pylint: disable=missing-module-docstring, invalid-name
|
|
||||||
|
|
||||||
from mock import Mock
|
|
||||||
from parameterized.parameterized import parameterized
|
|
||||||
|
|
||||||
from searx import (
|
|
||||||
plugins,
|
|
||||||
limiter,
|
|
||||||
botdetection,
|
|
||||||
)
|
|
||||||
from tests import SearxTestCase
|
|
||||||
from .test_plugins import get_search_mock
|
|
||||||
|
|
||||||
|
|
||||||
class PluginIPSelfInfo(SearxTestCase): # pylint: disable=missing-class-docstring
|
|
||||||
def setUp(self):
|
|
||||||
plugin = plugins.load_and_initialize_plugin('searx.plugins.self_info', False, (None, {}))
|
|
||||||
self.store = plugins.PluginStore()
|
|
||||||
self.store.register(plugin)
|
|
||||||
cfg = limiter.get_cfg()
|
|
||||||
botdetection.init(cfg, None)
|
|
||||||
|
|
||||||
def test_plugin_store_init(self):
|
|
||||||
self.assertEqual(1, len(self.store.plugins))
|
|
||||||
|
|
||||||
def test_ip_in_answer(self):
|
|
||||||
request = Mock()
|
|
||||||
request.remote_addr = '127.0.0.1'
|
|
||||||
request.headers = {'X-Forwarded-For': '1.2.3.4, 127.0.0.1', 'X-Real-IP': '127.0.0.1'}
|
|
||||||
search = get_search_mock(query='ip', pageno=1)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertIn('127.0.0.1', search.result_container.answers["ip"]["answer"])
|
|
||||||
|
|
||||||
def test_ip_not_in_answer(self):
|
|
||||||
request = Mock()
|
|
||||||
request.remote_addr = '127.0.0.1'
|
|
||||||
request.headers = {'X-Forwarded-For': '1.2.3.4, 127.0.0.1', 'X-Real-IP': '127.0.0.1'}
|
|
||||||
search = get_search_mock(query='ip', pageno=2)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertNotIn('ip', search.result_container.answers)
|
|
||||||
|
|
||||||
@parameterized.expand(
|
|
||||||
[
|
|
||||||
'user-agent',
|
|
||||||
'What is my User-Agent?',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_user_agent_in_answer(self, query: str):
|
|
||||||
request = Mock(user_agent=Mock(string='Mock'))
|
|
||||||
search = get_search_mock(query=query, pageno=1)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertIn('Mock', search.result_container.answers["user-agent"]["answer"])
|
|
||||||
|
|
||||||
@parameterized.expand(
|
|
||||||
[
|
|
||||||
'user-agent',
|
|
||||||
'What is my User-Agent?',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def test_user_agent_not_in_answer(self, query: str):
|
|
||||||
request = Mock(user_agent=Mock(string='Mock'))
|
|
||||||
search = get_search_mock(query=query, pageno=2)
|
|
||||||
self.store.call(self.store.plugins, 'post_search', request, search)
|
|
||||||
self.assertNotIn('user-agent', search.result_container.answers)
|
|
|
@ -1,15 +1,19 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# pylint: disable=missing-module-docstring
|
# pylint: disable=missing-module-docstring
|
||||||
|
|
||||||
import babel
|
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
from searx import plugins
|
from parameterized.parameterized import parameterized
|
||||||
|
|
||||||
|
from searx import (
|
||||||
|
plugins,
|
||||||
|
limiter,
|
||||||
|
botdetection,
|
||||||
|
)
|
||||||
|
|
||||||
from tests import SearxTestCase
|
from tests import SearxTestCase
|
||||||
|
|
||||||
|
|
||||||
def get_search_mock(query, **kwargs):
|
def get_search_mock(query, **kwargs):
|
||||||
lang = kwargs.get("lang", "en-US")
|
|
||||||
kwargs["locale"] = babel.Locale.parse(lang, sep="-")
|
|
||||||
return Mock(search_query=Mock(query=query, **kwargs), result_container=Mock(answers={}))
|
return Mock(search_query=Mock(query=query, **kwargs), result_container=Mock(answers={}))
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,3 +52,97 @@ class PluginStoreTest(SearxTestCase): # pylint: disable=missing-class-docstring
|
||||||
request = Mock()
|
request = Mock()
|
||||||
store.call([testplugin], 'asdf', request, Mock())
|
store.call([testplugin], 'asdf', request, Mock())
|
||||||
self.assertTrue(getattr(testplugin, 'asdf').called) # pylint: disable=E1101
|
self.assertTrue(getattr(testplugin, 'asdf').called) # pylint: disable=E1101
|
||||||
|
|
||||||
|
|
||||||
|
class PluginIPSelfInfo(SearxTestCase): # pylint: disable=missing-class-docstring
|
||||||
|
def setUp(self):
|
||||||
|
plugin = plugins.load_and_initialize_plugin('searx.plugins.self_info', False, (None, {}))
|
||||||
|
self.store = plugins.PluginStore()
|
||||||
|
self.store.register(plugin)
|
||||||
|
cfg = limiter.get_cfg()
|
||||||
|
botdetection.init(cfg, None)
|
||||||
|
|
||||||
|
def test_plugin_store_init(self):
|
||||||
|
self.assertEqual(1, len(self.store.plugins))
|
||||||
|
|
||||||
|
def test_ip_in_answer(self):
|
||||||
|
request = Mock()
|
||||||
|
request.remote_addr = '127.0.0.1'
|
||||||
|
request.headers = {'X-Forwarded-For': '1.2.3.4, 127.0.0.1', 'X-Real-IP': '127.0.0.1'}
|
||||||
|
search = get_search_mock(query='ip', pageno=1)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertIn('127.0.0.1', search.result_container.answers["ip"]["answer"])
|
||||||
|
|
||||||
|
def test_ip_not_in_answer(self):
|
||||||
|
request = Mock()
|
||||||
|
request.remote_addr = '127.0.0.1'
|
||||||
|
request.headers = {'X-Forwarded-For': '1.2.3.4, 127.0.0.1', 'X-Real-IP': '127.0.0.1'}
|
||||||
|
search = get_search_mock(query='ip', pageno=2)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertNotIn('ip', search.result_container.answers)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
'user-agent',
|
||||||
|
'What is my User-Agent?',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_user_agent_in_answer(self, query: str):
|
||||||
|
request = Mock(user_agent=Mock(string='Mock'))
|
||||||
|
search = get_search_mock(query=query, pageno=1)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertIn('Mock', search.result_container.answers["user-agent"]["answer"])
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
'user-agent',
|
||||||
|
'What is my User-Agent?',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_user_agent_not_in_answer(self, query: str):
|
||||||
|
request = Mock(user_agent=Mock(string='Mock'))
|
||||||
|
search = get_search_mock(query=query, pageno=2)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertNotIn('user-agent', search.result_container.answers)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginHashTest(SearxTestCase): # pylint: disable=missing-class-docstring
|
||||||
|
def setUp(self):
|
||||||
|
self.store = plugins.PluginStore()
|
||||||
|
plugin = plugins.load_and_initialize_plugin('searx.plugins.hash_plugin', False, (None, {}))
|
||||||
|
self.store.register(plugin)
|
||||||
|
|
||||||
|
def test_plugin_store_init(self):
|
||||||
|
self.assertEqual(1, len(self.store.plugins))
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
('md5 test', 'md5 hash digest: 098f6bcd4621d373cade4e832627b4f6'),
|
||||||
|
('sha1 test', 'sha1 hash digest: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'),
|
||||||
|
('sha224 test', 'sha224 hash digest: 90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809'),
|
||||||
|
('sha256 test', 'sha256 hash digest: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'),
|
||||||
|
(
|
||||||
|
'sha384 test',
|
||||||
|
'sha384 hash digest: 768412320f7b0aa5812fce428dc4706b3c'
|
||||||
|
'ae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf1'
|
||||||
|
'7a0a9',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'sha512 test',
|
||||||
|
'sha512 hash digest: ee26b0dd4af7e749aa1a8ee3c10ae9923f6'
|
||||||
|
'18980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5'
|
||||||
|
'fa9ad8e6f57f50028a8ff',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_hash_digest_new(self, query: str, hash_str: str):
|
||||||
|
request = Mock(remote_addr='127.0.0.1')
|
||||||
|
search = get_search_mock(query=query, pageno=1)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertIn(hash_str, search.result_container.answers['hash']['answer'])
|
||||||
|
|
||||||
|
def test_md5_bytes_no_answer(self):
|
||||||
|
request = Mock(remote_addr='127.0.0.1')
|
||||||
|
search = get_search_mock(query=b'md5 test', pageno=2)
|
||||||
|
self.store.call(self.store.plugins, 'post_search', request, search)
|
||||||
|
self.assertNotIn('hash', search.result_container.answers)
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
# pylint: disable=missing-module-docstring
|
|
||||||
|
|
||||||
from tests import SearxTestCase
|
|
||||||
from searx import compat
|
|
||||||
from searx.favicons.config import DEFAULT_CFG_TOML_PATH
|
|
||||||
|
|
||||||
|
|
||||||
class CompatTest(SearxTestCase): # pylint: disable=missing-class-docstring
|
|
||||||
|
|
||||||
def test_toml(self):
|
|
||||||
with DEFAULT_CFG_TOML_PATH.open("rb") as f:
|
|
||||||
_ = compat.tomllib.load(f)
|
|
|
@ -1,21 +1,16 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# pylint: disable=missing-module-docstring, invalid-name
|
# pylint: disable=missing-module-docstring, invalid-name
|
||||||
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
from lxml import html
|
from lxml import html
|
||||||
from parameterized.parameterized import parameterized
|
from parameterized.parameterized import parameterized
|
||||||
|
|
||||||
from searx.exceptions import SearxXPathSyntaxException, SearxEngineXPathException
|
from searx.exceptions import SearxXPathSyntaxException, SearxEngineXPathException
|
||||||
from searx import utils
|
from searx import utils
|
||||||
|
|
||||||
from tests import SearxTestCase
|
from tests import SearxTestCase
|
||||||
|
|
||||||
|
|
||||||
def random_string(length, choices=string.ascii_letters):
|
|
||||||
return ''.join(random.choice(choices) for _ in range(length))
|
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(SearxTestCase): # pylint: disable=missing-class-docstring
|
class TestUtils(SearxTestCase): # pylint: disable=missing-class-docstring
|
||||||
def test_gen_useragent(self):
|
def test_gen_useragent(self):
|
||||||
self.assertIsInstance(utils.gen_useragent(), str)
|
self.assertIsInstance(utils.gen_useragent(), str)
|
||||||
|
@ -239,4 +234,4 @@ class TestXPathUtils(SearxTestCase): # pylint: disable=missing-class-docstring
|
||||||
self.assertIsNone(l)
|
self.assertIsNone(l)
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
utils.detect_language(None) # type: ignore
|
utils.detect_language(None)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from urllib.parse import ParseResult
|
from urllib.parse import ParseResult
|
||||||
import babel
|
|
||||||
from mock import Mock
|
from mock import Mock
|
||||||
from searx.results import Timing
|
from searx.results import Timing
|
||||||
|
|
||||||
|
@ -83,7 +82,6 @@ class ViewsTestCase(SearxTestCase): # pylint: disable=missing-class-docstring,
|
||||||
redirect_url=None,
|
redirect_url=None,
|
||||||
engine_data={},
|
engine_data={},
|
||||||
)
|
)
|
||||||
search_self.search_query.locale = babel.Locale.parse("en-US", sep='-')
|
|
||||||
|
|
||||||
self.setattr4test(Search, 'search', search_mock)
|
self.setattr4test(Search, 'search', search_mock)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue