Compare commits

...

14 Commits

Author SHA1 Message Date
GenericMale
8e8eb0e650
Merge e4e6f21494 into 738906358b 2025-01-29 19:41:37 +08:00
return42
738906358b [data] update searx.data - update_currencies.py 2025-01-29 06:23:04 +01:00
return42
fc8938c968 [data] update searx.data - update_ahmia_blacklist.py 2025-01-29 06:07:28 +01:00
return42
3b9e06fbd2 [data] update searx.data - update_wikidata_units.py 2025-01-29 06:06:50 +01:00
return42
4934922156 [data] update searx.data - update_engine_traits.py 2025-01-29 06:05:58 +01:00
return42
bee39e4ec0 [data] update searx.data - update_engine_descriptions.py 2025-01-29 06:04:54 +01:00
Markus Heiser
3f4e0b0859 [fix] gettext can't work with f-strings (i10n)
``str.format`` is the pythonic way of handling strings returned by
gettext.gettext that retain interpolation tokens.

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2025-01-29 06:04:09 +01:00
Markus Heiser
a235c54f8c [mod] rudimentary implementation of a MainResult type
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2025-01-29 05:04:41 +01:00
Markus Heiser
df3344e5d5 [fix] JSON & CSV format return error 500
For CSV and JSON output, the LegacyResult and the Result objects needs to be
converted to a python dictionary.

Closes: https://github.com/searxng/searxng/issues/4244
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2025-01-29 05:04:41 +01:00
GenericMale
e4e6f21494 [mod] tavily engine: revert double punctuation & remove f-stringed gettexts 2025-01-23 11:02:44 +01:00
GenericMale
1a3ffdb4ea [mod] small tavily engine changes
- add include_image_descriptions & include_answer to engine settings
- move [ai] prefix in results from title to content content
- minor doc fixes
2025-01-23 03:10:17 +01:00
Markus Heiser
8ab16bb419 [mod] update intersphinx links and add missing CATEGORY_GROUPS
To get translations for, the missed CATEGORY_GROUPS has been added:

- ai
- movies
- translate
- wikimedia

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2025-01-22 14:14:28 +01:00
Markus Heiser
1273ed7f7d [mod] add doc to tavily and slightly improve the engine
- Config options like ``search_type`` renamed to follow the upstream
  API (``topic``).
- Default ``max_results`` is set to 5
- use image description if one exists
- add an init function to check engine's settings
- settings example: additional category 'ai'

To review the added documentation of this path::

    make docs.live

and jump to: http://0.0.0.0:8000/dev/engines/online/tavily.html

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2025-01-22 14:14:28 +01:00
GenericMale
d8a4d589eb [feat] new engine: tavily.com 2025-01-22 14:13:05 +01:00
16 changed files with 1275 additions and 263 deletions

View File

@ -143,10 +143,10 @@ suppress_warnings = ['myst.domains']
intersphinx_mapping = {
"python": ("https://docs.python.org/3/", None),
"babel" : ("https://babel.readthedocs.io/en/latest/", None),
"flask": ("https://flask.palletsprojects.com/", None),
"flask": ("https://flask.palletsprojects.com/en/stable/", None),
"flask_babel": ("https://python-babel.github.io/flask-babel/", None),
# "werkzeug": ("https://werkzeug.palletsprojects.com/", None),
"jinja": ("https://jinja.palletsprojects.com/", None),
"jinja": ("https://jinja.palletsprojects.com/en/stable/", None),
"linuxdoc" : ("https://return42.github.io/linuxdoc/", None),
"sphinx" : ("https://www.sphinx-doc.org/en/master/", None),
"redis": ('https://redis.readthedocs.io/en/stable/', None),

View File

@ -0,0 +1,8 @@
.. _tavily engine:
======
Tavily
======
.. automodule:: searx.engines.tavily
:members:

View File

@ -33,7 +33,7 @@ class SXNGAnswerer(Answerer):
return AnswererInfo(
name=gettext(self.__doc__),
description=gettext(f"Compute {'/'.join(self.keywords)} of the arguments"),
description=gettext("Compute {func} of the arguments".format(func='/'.join(self.keywords))),
keywords=self.keywords,
examples=["avg 123 548 2.04 24.2"],
)

File diff suppressed because it is too large Load Diff

View File

@ -5359,6 +5359,7 @@
"pt": "pa'anga",
"ru": "тонганская паанга",
"sk": "Tonžská paʻanga",
"sl": "tongovska paanga",
"sr": "тонганска панга",
"sv": "Tongansk pa'anga",
"th": "ปาอางา",
@ -5990,6 +5991,23 @@
"uk": "Східно-карибський долар",
"vi": "Đô la Đông Caribe"
},
"XCG": {
"ar": "الجلدر الكاريبي",
"ca": "florí caribeny",
"de": "Karibischer Gulden",
"en": "Caribbean guilder",
"eo": "Karibia guldeno",
"es": "florín caribeño",
"fr": "Florin caribéen",
"hr": "Karipski gulden",
"hu": "karibi forint",
"it": "fiorino caraibico",
"nl": "Caribische gulden",
"pap": "Florin karibense",
"pt": "Florim do Caribe",
"ru": "Карибский гульден",
"sl": "karibski goldinar"
},
"XDR": {
"ar": "حقوق السحب الخاصة",
"bg": "Специални права на тираж",
@ -6109,6 +6127,7 @@
"vi": "Franc CFP"
},
"XPT": {
"ar": "استثمار البلاتين",
"de": "Platinpreis",
"en": "platinum as an investment"
},
@ -6375,6 +6394,7 @@
"CLP$": "CLP",
"COL$": "COP",
"COU$": "COU",
"Cg": "XCG",
"D": "GMD",
"DA": "DZD",
"DEN": "MKD",
@ -6439,6 +6459,7 @@
"NT$": "TWD",
"NZ$": "NZD",
"Nfk": "ERN",
"Noha heinen krasss": "EUR",
"Nu": "BTN",
"N₨": "NPR",
"P": "BWP",
@ -6469,6 +6490,7 @@
"Ush": "UGX",
"VT": "VUV",
"WS$": "WST",
"XCG": "XCG",
"XDR": "XDR",
"Z$": "ZWL",
"ZK": "ZMW",
@ -7041,6 +7063,8 @@
"canadiske dollar": "CAD",
"cape verde escudo": "CVE",
"cape verdean escudo": "CVE",
"caribbean guilder": "XCG",
"caribische gulden": "XCG",
"cayman adaları doları": "KYD",
"cayman islands dollar": "KYD",
"caymaneilandse dollar": "KYD",
@ -8382,6 +8406,7 @@
"filler": "HUF",
"fillér": "HUF",
"fiorino arubano": "AWG",
"fiorino caraibico": "XCG",
"fiorino delle antille olandesi": "ANG",
"fiorino di aruba": "AWG",
"fiorino ungherese": "HUF",
@ -8393,6 +8418,7 @@
"florim das antilhas holandesas": "ANG",
"florim das antilhas neerlandesas": "ANG",
"florim de aruba": "AWG",
"florim do caribe": "XCG",
"florim húngaro": "HUF",
"florin": "AWG",
"florin antiano": "ANG",
@ -8406,14 +8432,17 @@
"florin arubeño": "AWG",
"florin arubez": "AWG",
"florin arubiano": "AWG",
"florin caribéen": "XCG",
"florin d'aruba": "AWG",
"florin de las antilhas neerlandesas": "ANG",
"florin des antilles néerlandaises": "ANG",
"florin daruba": "AWG",
"florin hungaro": "HUF",
"florin húngaro": "HUF",
"florin karibense": "XCG",
"florint": "HUF",
"florint húngaro": "HUF",
"florí caribeny": "XCG",
"florí d'aruba": "AWG",
"florí de les antilles neerlandeses": "ANG",
"florí daruba": "AWG",
@ -8421,6 +8450,7 @@
"florín antillano neerlandés": "ANG",
"florín arubeno": "AWG",
"florín arubeño": "AWG",
"florín caribeño": "XCG",
"florín das antillas neerlandesas": "ANG",
"florín de aruba": "AWG",
"florín hungaro": "HUF",
@ -9162,6 +9192,11 @@
"kapverdské escudo": "CVE",
"kapverdski eskudo": "CVE",
"karbovanet": "UAH",
"karibi forint": "XCG",
"karibia guldeno": "XCG",
"karibischer gulden": "XCG",
"karibski goldinar": "XCG",
"karipski gulden": "XCG",
"karod": "NPR",
"kartvela lario": "GEL",
"katar riyal": "QAR",
@ -11966,6 +12001,7 @@
"tongaška paanga": "TOP",
"tongos pa'anga": "TOP",
"tongos paanga": "TOP",
"tongovska paanga": "TOP",
"tonška paanga": "TOP",
"tonžská pa'anga": "TOP",
"tonžská paanga": "TOP",
@ -13202,6 +13238,7 @@
"канадски долар": "CAD",
"канадский доллар": "CAD",
"канадський долар": "CAD",
"карибский гульден": "XCG",
"катар риалы": "QAR",
"катарски риал": "QAR",
"катарски ријал": "QAR",
@ -14234,6 +14271,7 @@
"שקל ישראלי חדש": "ILS",
"؋": "AFN",
"ارياري": "MGA",
"استثمار البلاتين": "XPT",
"استثمار الذهب": "XAU",
"استثمار الفضة": "XAG",
"الاستثمار في الذهب": "XAU",
@ -14243,6 +14281,7 @@
"البوليفيانو": "BOB",
"البيزو الكوبي": "CUP",
"البيزو المكسيكي": "MXN",
"الجلدر الكاريبي": "XCG",
"الجنية البريطاني": "GBP",
"الجنية المصري": "EGP",
"الجنيه الاسترليني": "GBP",

File diff suppressed because one or more lines are too long

View File

@ -19,11 +19,11 @@
"cbz",
"djvu",
"doc",
"docx",
"epub",
"fb2",
"htm",
"html",
"jpg",
"lit",
"lrf",
"mht",
@ -42,6 +42,7 @@
"newest_added",
"oldest",
"oldest_added",
"random",
"smallest"
]
},
@ -195,7 +196,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "fa-fa",
"fa": "prs-prs",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -203,12 +204,14 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -218,6 +221,8 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -225,6 +230,7 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -232,22 +238,33 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -255,9 +272,13 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant"
"zh_Hant": "zh-hant",
"zu": "zu-zu"
},
"regions": {
"am-ET": "am-et",
@ -473,12 +494,14 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -512,6 +535,8 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -520,14 +545,23 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -560,7 +594,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "fa-fa",
"fa": "prs-prs",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -568,12 +602,14 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -583,6 +619,8 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -590,6 +628,7 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -597,22 +636,33 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -620,9 +670,13 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant"
"zh_Hant": "zh-hant",
"zu": "zu-zu"
},
"regions": {
"am-ET": "am-et",
@ -838,12 +892,14 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -877,6 +933,8 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -885,14 +943,23 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -925,7 +992,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "fa-fa",
"fa": "prs-prs",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -933,12 +1000,14 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -948,6 +1017,8 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -955,6 +1026,7 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -962,22 +1034,33 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -985,9 +1068,13 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant"
"zh_Hant": "zh-hant",
"zu": "zu-zu"
},
"regions": {
"am-ET": "am-et",
@ -1203,12 +1290,14 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -1242,6 +1331,8 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -1250,14 +1341,23 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "en-hk",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",
@ -1290,7 +1390,7 @@
"es": "es-es",
"et": "et-et",
"eu": "eu-eu",
"fa": "fa-fa",
"fa": "prs-prs",
"fi": "fi-fi",
"fil": "fil-fil",
"fr": "fr-fr",
@ -1298,12 +1398,14 @@
"gd": "gd-gd",
"gl": "gl-gl",
"gu": "gu-gu",
"ha": "ha-latn",
"he": "he-he",
"hi": "hi-hi",
"hr": "hr-hr",
"hu": "hu-hu",
"hy": "hy-hy",
"id": "id-id",
"ig": "ig-ig",
"is": "is-is",
"it": "it-it",
"ja": "ja-ja",
@ -1313,6 +1415,8 @@
"kn": "kn-kn",
"ko": "ko-ko",
"kok": "kok-kok",
"ku": "ku-arab",
"ky": "ky-ky",
"lb": "lb-lb",
"lo": "lo-lo",
"lt": "lt-lt",
@ -1320,6 +1424,7 @@
"mi": "mi-mi",
"mk": "mk-mk",
"ml": "ml-ml",
"mn": "mn-cyrl-mn",
"mr": "mr-mr",
"ms": "ms-ms",
"mt": "mt-mt",
@ -1327,22 +1432,33 @@
"ne": "ne-ne",
"nl": "nl-nl",
"nn": "nn-nn",
"nso": "nso-nso",
"or": "or-or",
"pa_Arab": "pa-arab",
"pa_Guru": "pa-guru",
"pl": "pl-pl",
"pt": "pt-br",
"qu": "quz-quz",
"quc": "quc-quc",
"ro": "ro-ro",
"ru": "ru-ru",
"rw": "rw-rw",
"sd_Arab": "sd-arab",
"si": "si-si",
"sk": "sk-sk",
"sl": "sl-sl",
"sq": "sq-sq",
"sr_Cyrl": "sr-cyrl",
"sr_Latn": "sr-latn",
"sv": "sv-sv",
"sw": "sw-sw",
"ta": "ta-ta",
"te": "te-te",
"tg": "tg-cyrl",
"th": "th-th",
"ti": "ti-ti",
"tk": "tk-tk",
"tn": "tn-tn",
"tr": "tr-tr",
"tt": "tt-tt",
"ug": "ug-ug",
@ -1350,9 +1466,13 @@
"ur": "ur-ur",
"uz_Latn": "uz-latn",
"vi": "vi-vi",
"wo": "wo-wo",
"xh": "xh-xh",
"yo": "yo-yo",
"zh": "zh-hans",
"zh_Hans": "zh-hans",
"zh_Hant": "zh-hant"
"zh_Hant": "zh-hant",
"zu": "zu-zu"
},
"regions": {
"am-ET": "am-et",
@ -1568,12 +1688,14 @@
"kk-KZ": "kk-kz",
"km-KH": "km-kh",
"ko-KR": "ko-kr",
"ky-KG": "ky-kg",
"lb-LU": "lb-lu",
"lo-LA": "lo-la",
"lt-LT": "lt-lt",
"lv-LV": "lv-lv",
"mi-NZ": "mi-nz",
"mk-MK": "mk-mk",
"mn-MN": "mn-mn",
"ms-BN": "ms-bn",
"ms-MY": "ms-my",
"ms-SG": "ms-sg",
@ -1607,6 +1729,8 @@
"ru-KZ": "ru-kz",
"ru-RU": "ru-ru",
"ru-UA": "ru-ua",
"rw-RW": "rw-rw",
"si-LK": "si-lk",
"sk-SK": "sk-sk",
"sl-SI": "sl-si",
"sq-AL": "sq-al",
@ -1615,14 +1739,23 @@
"sr-RS": "sr-rs",
"sv-FI": "sv-fi",
"sv-SE": "sv-se",
"sw-KE": "sw-ke",
"sw-TZ": "sw-tz",
"sw-UG": "sw-ug",
"ta-LK": "ta-lk",
"ta-SG": "ta-sg",
"tg-TJ": "tg-tj",
"th-TH": "th-th",
"ti-ER": "ti-er",
"tk-TM": "tk-tm",
"tn-BW": "tn-bw",
"tr-CY": "tr-cy",
"tr-TR": "tr-tr",
"uk-UA": "uk-ua",
"ur-PK": "ur-pk",
"vi-VN": "vi-vn",
"wo-SN": "wo-sn",
"yo-NG": "yo-ng",
"zh-CN": "zh-cn",
"zh-HK": "en-hk",
"zh-MO": "zh-mo",

View File

@ -2574,11 +2574,6 @@
"symbol": "ʰ",
"to_si_factor": 0.2617993878
},
"Q11644875": {
"si_name": "Q12438",
"symbol": "Tf",
"to_si_factor": 9806.65
},
"Q1165639": {
"si_name": "Q89992008",
"symbol": "daraf",
@ -3179,6 +3174,21 @@
"symbol": "₿",
"to_si_factor": null
},
"Q131824443": {
"si_name": "Q12438",
"symbol": "tf",
"to_si_factor": 9806.65
},
"Q131824444": {
"si_name": "Q12438",
"symbol": "LTf",
"to_si_factor": 9964.01641818352
},
"Q131824445": {
"si_name": "Q12438",
"symbol": "STf",
"to_si_factor": 8896.443230521
},
"Q1322380": {
"si_name": "Q11574",
"symbol": "Ts",
@ -3420,7 +3430,7 @@
"to_si_factor": null
},
"Q1628990": {
"si_name": "Q12874593",
"si_name": "Q12831618",
"symbol": "hph",
"to_si_factor": 745.7
},

246
searx/engines/tavily.py Normal file
View File

@ -0,0 +1,246 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
.. sidebar:: info
Before reporting an issue with this engine,
please consult `API error codes`_.
Tavily_ search API (AI engine). This engine implements the REST API
(`POST /search`_) and does not make use of the `Tavily Python Wrapper`_.
From the API response, this engine generates *result items* (shown in the main
result list) and an *answer result* (shown on top of the main result list).
If the *answer* from Tavily contains an image, the *answer result* is turned
into an *infobox result*.
.. attention::
AI queries take considerably longer to process than queries to conventional
search engines. The ``timeout`` should therefore also be set considerably
higher, but it is not recommended to activate AI queries by default
(set ``disabled: true``), as otherwise all user searches will have to wait
for the AI.
.. _Tavily: https://tavily.com/
.. _Tavily Python Wrapper: https://pypi.org/project/tavily-python/
.. _POST /search: https://docs.tavily.com/docs/rest-api/api-reference#endpoint-post-search
.. _Tavily API Credit Deduction:
https://docs.tavily.com/docs/rest-api/api-reference#tavily-api-credit-deduction-overview
.. _Getting started: https://docs.tavily.com/docs/welcome#getting-started
.. _API error codes: https://docs.tavily.com/docs/rest-api/api-reference#error-codes
Configuration
=============
The engine has the following mandatory setting:
- :py:obj:`api_key`
- :py:obj:`topic`
Optional settings are:
- :py:obj:`days`
- :py:obj:`search_depth`
- :py:obj:`max_results`
- :py:obj:`include_answer`
- :py:obj:`include_images`
- :py:obj:`include_image_descriptions`
- :py:obj:`include_domains`
- :py:obj:`exclude_domains`
Example configuration for general search queries:
.. code:: yaml
- name: tavily
engine: tavily
shortcut: tav
categories: [general, ai]
api_key: xxxxxxxx
topic: general
include_images: true
timeout: 15
disabled: true
Example configuration for news search:
.. code:: yaml
- name: tavily news
engine: tavily
shortcut: tavnews
categories: [news, ai]
api_key: xxxxxxxx
topic: news
timeout: 15
disabled: true
Implementation
==============
"""
from json import dumps
from datetime import datetime
from flask_babel import gettext
# about
about = {
"website": "https://tavily.com/",
"wikidata_id": None,
"official_api_documentation": "https://docs.tavily.com/docs/rest-api/api-reference",
"use_official_api": True,
"require_api_key": True,
"results": "JSON",
}
search_url = "https://api.tavily.com/search"
paging = False
time_range_support = True
api_key: str = "unset"
"""Tavily API Key (`Getting started`_)."""
search_depth: str = "basic"
"""The depth of the search. It can be ``basic`` or ``advanced``. Default is
``basic`` unless specified otherwise in a given method.
- have an eye on your `Tavily API Credit Deduction`_!
"""
topic: str = ""
"""The category of the search. This will determine which of Tavily's agents
will be used for the search. Currently, only ``general`` and ``news`` are
supported."""
days: int = 3
"""The number of days back from the current date to include in the search results.
This specifies the time frame of data to be retrieved. Please note that this
feature is only available when using the ``news`` search topic. Default is 3."""
max_results: int = 5
"""The maximum number of search results to return. Default is 5."""
include_answer: bool = True
"""Include a short answer to the original query, generated by an LLM based on Tavily's
search results."""
include_images: bool = False
"""Include a list of query-related images in the response. Creates an infobox
with the first image (as far as there are any images in the response) and the answer,
if ``include_answer`` is also enabled.
"""
include_image_descriptions: bool = False
"""When ``include_images`` is set to True, this option adds descriptive text for
each image."""
include_domains: list[str] = []
"""A list of domains to specifically include in the search results. Default
is ``[]``, which includes all domains."""
exclude_domains: list[str] = []
"""A list of domains to specifically exclude from the search results. Default
is ``[]``, which doesn't exclude any domains.
"""
def request(query, params):
data = {
"query": query,
"api_key": api_key,
"search_depth": search_depth,
"topic": topic,
"time_range": params["time_range"],
"max_results": max_results,
"include_images": include_images,
"include_domains": include_domains,
"exclude_domains": exclude_domains,
}
if include_images:
data["include_image_descriptions"] = include_image_descriptions
if topic == "general":
data["include_answer"] = include_answer
elif topic == "news":
data["days"] = days
params["url"] = search_url
params["method"] = "POST"
params["headers"]["Content-type"] = "application/json"
params["data"] = dumps(data)
return params
def response(resp):
results = []
data = resp.json()
for result in data.get("results", []):
results.append(
{
"title": result["title"],
"url": result["url"],
"content": "[" + gettext("ai") + "] " + result["content"],
"publishedDate": _parse_date(result.get("published_date")),
}
)
img_list = data.get("images")
if img_list:
result = {
"infobox": "Tavily [" + gettext("ai") + "]",
"img_src": img_list[0],
}
content = data.get("answer")
if isinstance(img_list[0], dict):
result["img_src"] = img_list[0]["url"]
img_caption = gettext("Image caption") + ": " + img_list[0]["description"]
if not content:
result["content"] = img_caption
else:
result["content"] = content + "//" + img_caption
elif content:
result["content"] = content
results.append(result)
elif data["answer"]:
results.append({"answer": data["answer"]})
return results
def _parse_date(pubDate):
if pubDate is not None:
try:
return datetime.strptime(pubDate, "%a, %d %b %Y %H:%M:%S %Z")
except (ValueError, TypeError) as e:
logger.debug("ignore exception (publishedDate): %s", e)
return None
def init(engine_settings: dict):
msg = []
val = engine_settings.get("api_key") or api_key
if not val or val == "unset":
msg.append("missing api_key")
val = engine_settings.get("topic") or topic
if val not in ["general", "news"]:
msg.append(f"invalid topic: '{val}'")
val = engine_settings.get("search_depth") or search_depth
if val not in ["basic", "advanced"]:
msg.append(f"invalid search_depth: '{val}'")
if msg:
raise ValueError(f"[{engine_settings['name']}] engine's settings: {' / '.join(msg)}")

View File

@ -110,6 +110,28 @@ class Result(msgspec.Struct, kw_only=True):
return iter(self.__struct_fields__)
def as_dict(self):
return {f: getattr(self, f) for f in self.__struct_fields__}
class MainResult(Result): # pylint: disable=missing-class-docstring
# open_group and close_group should not manged in the Result class (we should rop it from here!)
open_group: bool = False
close_group: bool = False
title: str = ""
"""Link title of the result item."""
content: str = ""
"""Extract or description of the result item"""
img_src: str = ""
"""URL of a image that is displayed in the result item."""
thumbnail: str = ""
"""URL of a thumbnail that is displayed in the result item."""
class LegacyResult(dict):
"""A wrapper around a legacy result item. The SearXNG core uses this class
@ -130,10 +152,12 @@ class LegacyResult(dict):
UNSET = object()
WHITESPACE_REGEX = re.compile('( |\t|\n)+', re.M | re.U)
def as_dict(self):
return self
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
# Init fields with defaults / compare with defaults of the fields in class Result
self.engine = self.get("engine", "")

View File

@ -7,72 +7,77 @@ from searx import webutils
from searx import engines
__all__ = [
'CONSTANT_NAMES',
'CATEGORY_NAMES',
'CATEGORY_GROUPS',
'STYLE_NAMES',
'BRAND_CUSTOM_LINKS',
'WEATHER_TERMS',
'CATEGORY_GROUPS',
'CATEGORY_NAMES',
'CONSTANT_NAMES',
'SOCIAL_MEDIA_TERMS',
'STYLE_NAMES',
'WEATHER_TERMS',
]
CONSTANT_NAMES = {
# Constants defined in other modules
'NO_SUBGROUPING': webutils.NO_SUBGROUPING,
'DEFAULT_CATEGORY': engines.DEFAULT_CATEGORY,
'NO_SUBGROUPING': webutils.NO_SUBGROUPING,
}
CATEGORY_NAMES = {
'FILES': 'files',
'GENERAL': 'general',
'MUSIC': 'music',
'SOCIAL_MEDIA': 'social media',
'IMAGES': 'images',
'VIDEOS': 'videos',
'RADIO': 'radio',
'TV': 'tv',
'IT': 'it',
'NEWS': 'news',
'MAP': 'map',
'MUSIC': 'music',
'NEWS': 'news',
'ONIONS': 'onions',
'RADIO': 'radio',
'SCIENCE': 'science',
'SOCIAL_MEDIA': 'social media',
'TV': 'tv',
'VIDEOS': 'videos',
}
CATEGORY_GROUPS = {
# non-tab categories
'AI': 'ai',
'APPS': 'apps',
'DICTIONARIES': 'dictionaries',
'LYRICS': 'lyrics',
'MOVIES': 'movies',
'PACKAGES': 'packages',
'Q_A': 'q&a',
'REPOS': 'repos',
'SCIENTIFIC_PUBLICATIONS': 'scientific publications',
'SOFTWARE_WIKIS': 'software wikis',
'TRANSLATE': 'translate',
'WEATHER': 'weather',
'WEB': 'web',
'SCIENTIFIC PUBLICATIONS': 'scientific publications',
'WIKIMEDIA': 'wikimedia',
}
STYLE_NAMES = {
'AUTO': 'auto',
'LIGHT': 'light',
'DARK': 'dark',
'BLACK': 'black',
'DARK': 'dark',
'LIGHT': 'light',
}
BRAND_CUSTOM_LINKS = {
'UPTIME': 'Uptime',
'ABOUT': 'About',
'UPTIME': 'Uptime',
}
WEATHER_TERMS = {
'AVERAGE TEMP.': 'Average temp.',
'CLOUD COVER': 'Cloud cover',
'AVERAGE_TEMP.': 'Average temp.',
'CLOUD_COVER': 'Cloud cover',
'CONDITION': 'Condition',
'CURRENT CONDITION': 'Current condition',
'CURRENT_CONDITION': 'Current condition',
'EVENING': 'Evening',
'FEELS LIKE': 'Feels like',
'FEELS_LIKE': 'Feels like',
'HUMIDITY': 'Humidity',
'MAX TEMP.': 'Max temp.',
'MIN TEMP.': 'Min temp.',
'MAX_TEMP.': 'Max temp.',
'MIN_TEMP.': 'Min temp.',
'MORNING': 'Morning',
'NIGHT': 'Night',
'NOON': 'Noon',
@ -80,22 +85,22 @@ WEATHER_TERMS = {
'SUNRISE': 'Sunrise',
'SUNSET': 'Sunset',
'TEMPERATURE': 'Temperature',
'UV INDEX': 'UV index',
'UV_INDEX': 'UV index',
'VISIBILITY': 'Visibility',
'WIND': 'Wind',
}
SOCIAL_MEDIA_TERMS = {
'SUBSCRIBERS': 'subscribers',
'POSTS': 'posts',
'ACTIVE USERS': 'active users',
'ACTIVE_USERS': 'active users',
'AUTHOR': 'author',
'COMMENTS': 'comments',
'USER': 'user',
'COMMUNITY': 'community',
'POINTS': 'points',
'POSTS': 'posts',
'SUBSCRIBERS': 'subscribers',
'THREAD_ANSWERED': 'answered',
'THREAD_CLOSED': 'closed',
'THREAD_OPEN': 'open',
'TITLE': 'title',
'AUTHOR': 'author',
'THREAD OPEN': 'open',
'THREAD CLOSED': 'closed',
'THREAD ANSWERED': 'answered',
'USER': 'user',
}

View File

@ -1846,6 +1846,29 @@ engines:
shortcut: tm
disabled: true
# Tavily requires an API key as well as other configurations. Before you
# activate these engines you should read the documentation.
# --> https://docs.searxng.org/dev/engines/online/tavily.html
#
# - name: tavily
# engine: tavily
# shortcut: tav
# categories: [general, ai]
# api_key: unset
# topic: general
# include_images: true
# timeout: 15
# disabled: true
#
# - name: tavily news
# engine: tavily
# shortcut: tavnews
# categories: [news, ai]
# api_key: unset
# topic: news
# timeout: 15
# disabled: true
# Requires Tor
- name: torch
engine: xpath

View File

@ -1,6 +1,6 @@
<table>
{% for key, value in result.items() %}
{% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions', 'pretty_url', 'parsed_url'] %}
{% if key in ['engine', 'engines', 'template', 'score', 'category', 'positions', 'parsed_url'] %}
{% continue %}
{% endif %}
<tr>

View File

@ -694,9 +694,7 @@ def search():
if 'title' in result and result['title']:
result['title'] = highlight_content(escape(result['title'] or ''), search_query.query)
if 'url' in result:
result['pretty_url'] = webutils.prettify_url(result['url'])
if result.get('publishedDate'): # do not try to get a date from an empty string or a None type
if getattr(result, 'publishedDate', None): # do not try to get a date from an empty string or a None type
try: # test if publishedDate >= 1900 (datetime module bug)
result['pubdate'] = result['publishedDate'].strftime('%Y-%m-%d %H:%M:%S%z')
except ValueError:
@ -706,15 +704,15 @@ def search():
# set result['open_group'] = True when the template changes from the previous result
# set result['close_group'] = True when the template changes on the next result
if current_template != result.get('template'):
result['open_group'] = True
if current_template != result.template:
result.open_group = True
if previous_result:
previous_result['close_group'] = True # pylint: disable=unsupported-assignment-operation
current_template = result.get('template')
previous_result.close_group = True # pylint: disable=unsupported-assignment-operation
current_template = result.template
previous_result = result
if previous_result:
previous_result['close_group'] = True
previous_result.close_group = True
# 4.a RSS

View File

@ -123,17 +123,18 @@ def write_csv_response(csv: CSVWriter, rc: ResultContainer) -> None: # pylint:
"""
results = rc.get_ordered_results()
keys = ('title', 'url', 'content', 'host', 'engine', 'score', 'type')
csv.writerow(keys)
for row in results:
for res in rc.get_ordered_results():
row = res.as_dict()
row['host'] = row['parsed_url'].netloc
row['type'] = 'result'
csv.writerow([row.get(key, '') for key in keys])
for a in rc.answers:
row = {'title': a, 'type': 'answer'}
row = a.as_dict()
row['host'] = row['parsed_url'].netloc
csv.writerow([row.get(key, '') for key in keys])
for a in rc.suggestions:
@ -158,18 +159,17 @@ class JSONEncoder(json.JSONEncoder): # pylint: disable=missing-class-docstring
def get_json_response(sq: SearchQuery, rc: ResultContainer) -> str:
"""Returns the JSON string of the results to a query (``application/json``)"""
results = rc.number_of_results
x = {
data = {
'query': sq.query,
'number_of_results': results,
'results': rc.get_ordered_results(),
'answers': list(rc.answers),
'number_of_results': rc.number_of_results,
'results': [_.as_dict() for _ in rc.get_ordered_results()],
'answers': [_.as_dict() for _ in rc.answers],
'corrections': list(rc.corrections),
'infoboxes': rc.infoboxes,
'suggestions': list(rc.suggestions),
'unresponsive_engines': get_translated_errors(rc.unresponsive_engines),
}
response = json.dumps(x, cls=JSONEncoder)
response = json.dumps(data, cls=JSONEncoder)
return response

View File

@ -2,13 +2,13 @@
# pylint: disable=missing-module-docstring,disable=missing-class-docstring,invalid-name
import json
from urllib.parse import ParseResult
import babel
from mock import Mock
import searx.webapp
import searx.search
import searx.search.processors
from searx.result_types._base import MainResult
from searx.results import Timing
from searx.preferences import Preferences
@ -31,30 +31,21 @@ class ViewsTestCase(SearxTestCase): # pylint: disable=too-many-public-methods
# set some defaults
test_results = [
{
'content': 'first test content',
'title': 'First Test',
'url': 'http://first.test.xyz',
'engines': ['youtube', 'startpage'],
'engine': 'startpage',
'parsed_url': ParseResult(
scheme='http', netloc='first.test.xyz', path='/', params='', query='', fragment=''
),
'template': 'default.html',
},
{
'content': 'second test content',
'title': 'Second Test',
'url': 'http://second.test.xyz',
'engines': ['youtube', 'startpage'],
'engine': 'youtube',
'parsed_url': ParseResult(
scheme='http', netloc='second.test.xyz', path='/', params='', query='', fragment=''
),
'template': 'default.html',
},
MainResult(
title="First Test",
url="http://first.test.xyz",
content="first test content",
engine="startpage",
),
MainResult(
title="Second Test",
url="http://second.test.xyz",
content="second test content",
engine="youtube",
),
]
for r in test_results:
r.normalize_result_fields()
timings = [
Timing(engine='startpage', total=0.8, load=0.7),
Timing(engine='youtube', total=0.9, load=0.6),