mirror of
https://github.com/searxng/searxng.git
synced 2026-07-17 21:41:24 +00:00
Compare commits
16 Commits
update_dat
...
bfeaad6c37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfeaad6c37 | ||
|
|
28d3885764 | ||
|
|
b084194c09 | ||
|
|
8c4df4cf3f | ||
|
|
a0e594e16e | ||
|
|
cb4bfbe129 | ||
|
|
a5660bcc4f | ||
|
|
1396265774 | ||
|
|
0fd40d5f29 | ||
|
|
13a5ace8b5 | ||
|
|
a6831797ba | ||
|
|
0e990f78a3 | ||
|
|
990c63b709 | ||
|
|
e535e4c61a | ||
|
|
79506441c5 | ||
|
|
6cb1c077e9 |
@@ -23,6 +23,6 @@ coloredlogs==15.0.1
|
||||
docutils>=0.21.2;python_version <= "3.11"
|
||||
docutils>=0.22.4; python_version > "3.11"
|
||||
parameterized==0.9.0
|
||||
granian[reload]==2.7.6
|
||||
granian[reload]==2.7.7
|
||||
basedpyright==1.39.8
|
||||
types-lxml==2026.2.16
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
granian==2.7.6
|
||||
granian[pname]==2.7.6
|
||||
granian==2.7.7
|
||||
granian[pname]==2.7.7
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -773,7 +773,7 @@
|
||||
"nl": "Boliviaanse boliviano",
|
||||
"oc": "Boliviano",
|
||||
"pa": "ਬੋਲੀਵੀਆਨੋ",
|
||||
"pl": "boliviano",
|
||||
"pl": "boliwiano",
|
||||
"pt": "Boliviano",
|
||||
"ro": "boliviano",
|
||||
"ru": "боливиано",
|
||||
@@ -1107,7 +1107,7 @@
|
||||
"fi": "Kongon frangi",
|
||||
"fr": "franc congolais",
|
||||
"ga": "franc an Chongó",
|
||||
"gl": "Franco congolés",
|
||||
"gl": "franco congolés",
|
||||
"he": "פרנק קונגולזי",
|
||||
"hr": "Kongoanski franak",
|
||||
"hu": "kongói frank",
|
||||
@@ -1636,7 +1636,7 @@
|
||||
"fi": "Algerian dinaari",
|
||||
"fr": "dinar algérien",
|
||||
"ga": "dinar na hAilgéire",
|
||||
"gl": "Dinar alxeriano",
|
||||
"gl": "dinar alxeriano",
|
||||
"he": "דינר אלג'ירי",
|
||||
"hr": "Alžirski dinar",
|
||||
"hu": "algériai dinár",
|
||||
@@ -1651,7 +1651,7 @@
|
||||
"pap": "dinar argelino",
|
||||
"pl": "dinar algierski",
|
||||
"pt": "dinar argelino",
|
||||
"ro": "Dinar algerian",
|
||||
"ro": "dinar algerian",
|
||||
"ru": "алжирский динар",
|
||||
"sk": "Alžírský dinár",
|
||||
"sl": "alžirski dinar",
|
||||
@@ -1786,7 +1786,7 @@
|
||||
"cy": "Ewro",
|
||||
"da": "Euro",
|
||||
"de": "Euro",
|
||||
"en": "Euro",
|
||||
"en": "euro",
|
||||
"eo": "eŭro",
|
||||
"es": "Euro",
|
||||
"et": "Euro",
|
||||
@@ -1803,7 +1803,7 @@
|
||||
"it": "Euro",
|
||||
"ja": "ユーロ",
|
||||
"ko": "유로",
|
||||
"lt": "Euras",
|
||||
"lt": "euras",
|
||||
"lv": "eiro",
|
||||
"ml": "യൂറോ",
|
||||
"ms": "Euro",
|
||||
@@ -2759,7 +2759,7 @@
|
||||
"pa": "ਜਪਾਨੀ ਯੈੱਨ",
|
||||
"pl": "jen",
|
||||
"pt": "iene",
|
||||
"ro": "yeni",
|
||||
"ro": "yen",
|
||||
"ru": "японская иена",
|
||||
"sk": "jen",
|
||||
"sl": "japonski jen",
|
||||
@@ -3337,6 +3337,7 @@
|
||||
"fi": "Libyan dinaari",
|
||||
"fr": "dinar libyen",
|
||||
"ga": "dinar na Libia",
|
||||
"gl": "dinar libio",
|
||||
"he": "דינר לובי ",
|
||||
"hr": "Libijski dinar",
|
||||
"hu": "líbiai dinár",
|
||||
@@ -3539,6 +3540,7 @@
|
||||
"ja": "チャット",
|
||||
"ko": "미얀마 짯",
|
||||
"lt": "Kijatas",
|
||||
"lv": "Kjats",
|
||||
"ml": "ബർമ്മീസ് ക്യാറ്റ്",
|
||||
"nl": "Myanmarese kyat",
|
||||
"oc": "Kyat",
|
||||
@@ -4310,7 +4312,7 @@
|
||||
"ar": "بيسو فلبيني",
|
||||
"bg": "Филипинско песо",
|
||||
"ca": "peso filipí",
|
||||
"cs": "Filipínské peso",
|
||||
"cs": "filipínské peso",
|
||||
"de": "philippinischer Peso",
|
||||
"en": "Philippine peso",
|
||||
"eo": "filipina peso",
|
||||
@@ -4614,7 +4616,7 @@
|
||||
"fi": "Serbian dinaari",
|
||||
"fr": "dinar serbe",
|
||||
"ga": "Dinar na Seirbia",
|
||||
"gl": "Dinar serbio",
|
||||
"gl": "dinar serbio",
|
||||
"he": "דינר סרבי",
|
||||
"hr": "srpski dinar",
|
||||
"hu": "szerb dinár",
|
||||
@@ -5331,7 +5333,7 @@
|
||||
"pa": "ਤਾਜਿਕਿਸਤਾਨੀ ਸੋਮੋਨੀ",
|
||||
"pl": "Somoni",
|
||||
"pt": "Somoni",
|
||||
"ro": "Somoni tadjic",
|
||||
"ro": "somoni tadjic",
|
||||
"ru": "таджикский сомони",
|
||||
"sk": "tadžický som",
|
||||
"sl": "tadžikistanski somoni",
|
||||
@@ -5394,6 +5396,7 @@
|
||||
"fi": "Tunisian dinaari",
|
||||
"fr": "dinar tunisien",
|
||||
"ga": "dinar na Túinéise",
|
||||
"gl": "dinar tunisiano",
|
||||
"he": "דינר תוניסאי",
|
||||
"hr": "tuniski dinar",
|
||||
"hu": "tunéziai dinár",
|
||||
@@ -5766,7 +5769,7 @@
|
||||
"fi": "Uruguayn peso",
|
||||
"fr": "peso uruguayen",
|
||||
"ga": "peso Uragua",
|
||||
"gl": "Peso uruguaio",
|
||||
"gl": "peso uruguaio",
|
||||
"he": "פסו של אורוגוואי",
|
||||
"hr": "Urugvajski pezo",
|
||||
"hu": "uruguayi peso",
|
||||
@@ -6147,7 +6150,7 @@
|
||||
"oc": "Drechs de tiratge Especials",
|
||||
"pl": "specjalne prawa ciągnienia",
|
||||
"pt": "direitos especiais de saque",
|
||||
"ro": "Drepturi speciale de tragere",
|
||||
"ro": "drepturi speciale de tragere",
|
||||
"ru": "специальные права заимствования",
|
||||
"sk": "Zvláštne práva čerpania",
|
||||
"sl": "posebne pravice črpanja",
|
||||
@@ -6225,6 +6228,7 @@
|
||||
"ja": "CFPフラン",
|
||||
"ko": "CFP 프랑",
|
||||
"lt": "CFP frankas",
|
||||
"lv": "Klusā okeāna franks",
|
||||
"ms": "Franc CFP",
|
||||
"nl": "CFP-frank",
|
||||
"oc": "Franc CFP",
|
||||
@@ -7055,6 +7059,7 @@
|
||||
"bolivjano": "BOB",
|
||||
"bolivya bolivianosu": "BOB",
|
||||
"bolivya bolivyanosu": "BOB",
|
||||
"boliwiano": "BOB",
|
||||
"bolívar digital": "VED",
|
||||
"bolívar soberano": "VES",
|
||||
"bolívar sobirà": "VES",
|
||||
@@ -8425,6 +8430,7 @@
|
||||
"drame arménio": "AMD",
|
||||
"dramm": "AMD",
|
||||
"drechs de tiratge especials": "XDR",
|
||||
"drept special de tragere": "XDR",
|
||||
"drepturi speciale de tragere": "XDR",
|
||||
"drets especials de gir": "XDR",
|
||||
"droits de tirage speciaux": "XDR",
|
||||
@@ -9518,6 +9524,8 @@
|
||||
"kíp lào": "LAK",
|
||||
"kīp": "LAK",
|
||||
"kjat": "MMK",
|
||||
"kjats": "MMK",
|
||||
"klusā okeāna franks": "XPF",
|
||||
"km": "BAM",
|
||||
"kmf": "KMF",
|
||||
"koeweitse dinar": "KWD",
|
||||
@@ -10023,6 +10031,7 @@
|
||||
"lire sterline": "GBP",
|
||||
"lire turque": "TRY",
|
||||
"lisente": "LSL",
|
||||
"list of syrian coins": "SYP",
|
||||
"liura de gibartar": "GIP",
|
||||
"liura egipciana": "EGP",
|
||||
"liura esterlina": "GBP",
|
||||
@@ -11313,7 +11322,6 @@
|
||||
"riel camboxano": "KHR",
|
||||
"riel camboyano": "KHR",
|
||||
"riel campuchia": "KHR",
|
||||
"riel kambodżański": "KHR",
|
||||
"riel kamboja": "KHR",
|
||||
"riel na cambóide": "KHR",
|
||||
"rietumāfrikas franks": "XOF",
|
||||
@@ -12257,6 +12265,7 @@
|
||||
"thaise baht": "THB",
|
||||
"thajský baht": "THB",
|
||||
"thb": "THB",
|
||||
"the australian dollar": "AUD",
|
||||
"thebe": "BWP",
|
||||
"third belarusian ruble": "BYN",
|
||||
"tical": "THB",
|
||||
@@ -12740,6 +12749,7 @@
|
||||
"yen": "JPY",
|
||||
"yen giapponese": "JPY",
|
||||
"yen japones": "JPY",
|
||||
"yen japonez": "JPY",
|
||||
"yen japonés": "JPY",
|
||||
"yeni": "JPY",
|
||||
"yeni i̇srail şekeli": "ILS",
|
||||
@@ -12806,7 +12816,6 @@
|
||||
"zimbabwe zig": "ZWG",
|
||||
"zimbabwean dollar": "ZWL",
|
||||
"zimbabwean gold": "ZWG",
|
||||
"zimbabwean zig": "ZWG",
|
||||
"zimbabwen kulta": "ZWG",
|
||||
"zimbabwiansky zlatý": "ZWG",
|
||||
"zimbabwský dolar": "ZWL",
|
||||
@@ -12965,6 +12974,7 @@
|
||||
"FKP",
|
||||
"EGP"
|
||||
],
|
||||
"£S": "SYP",
|
||||
"£e": "EGP",
|
||||
"£s": "SYP",
|
||||
"¥": [
|
||||
@@ -14959,6 +14969,8 @@
|
||||
"فورنت مجري": "HUF",
|
||||
"فورينت مجري": "HUF",
|
||||
"فِرَنْكٌ رُوَنْدِيٌّ": "RWF",
|
||||
"قائمة النقود المعدنية السورية": "SYP",
|
||||
"قائمة عملات سوريا المعدنية": "SYP",
|
||||
"ك": "KWD",
|
||||
"كتزال غواتيمالي": "GTQ",
|
||||
"كرونة آيسلندية": "ISK",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6544,6 +6544,103 @@
|
||||
"to-TO": "to"
|
||||
}
|
||||
},
|
||||
"neosearch": {
|
||||
"all_locale": null,
|
||||
"custom": {
|
||||
"countrycodes": [
|
||||
"AE",
|
||||
"AM",
|
||||
"AR",
|
||||
"AT",
|
||||
"AU",
|
||||
"AZ",
|
||||
"BE",
|
||||
"BG",
|
||||
"BH",
|
||||
"BO",
|
||||
"BR",
|
||||
"BY",
|
||||
"CA",
|
||||
"CH",
|
||||
"CL",
|
||||
"CN",
|
||||
"CO",
|
||||
"CY",
|
||||
"CZ",
|
||||
"DE",
|
||||
"DK",
|
||||
"DZ",
|
||||
"EC",
|
||||
"EE",
|
||||
"EG",
|
||||
"ES",
|
||||
"FI",
|
||||
"FR",
|
||||
"GB",
|
||||
"GE",
|
||||
"GH",
|
||||
"GR",
|
||||
"HK",
|
||||
"HR",
|
||||
"HU",
|
||||
"ID",
|
||||
"IE",
|
||||
"IL",
|
||||
"IN",
|
||||
"IT",
|
||||
"JO",
|
||||
"JP",
|
||||
"KE",
|
||||
"KG",
|
||||
"KR",
|
||||
"KW",
|
||||
"KZ",
|
||||
"LB",
|
||||
"LT",
|
||||
"LU",
|
||||
"LV",
|
||||
"MA",
|
||||
"MD",
|
||||
"MT",
|
||||
"MX",
|
||||
"MY",
|
||||
"NG",
|
||||
"NL",
|
||||
"NO",
|
||||
"NZ",
|
||||
"OM",
|
||||
"PE",
|
||||
"PH",
|
||||
"PL",
|
||||
"PT",
|
||||
"PY",
|
||||
"QA",
|
||||
"RO",
|
||||
"RU",
|
||||
"SA",
|
||||
"SE",
|
||||
"SG",
|
||||
"SI",
|
||||
"SK",
|
||||
"TH",
|
||||
"TJ",
|
||||
"TM",
|
||||
"TN",
|
||||
"TR",
|
||||
"TW",
|
||||
"UA",
|
||||
"US",
|
||||
"UY",
|
||||
"UZ",
|
||||
"VE",
|
||||
"VN",
|
||||
"ZA"
|
||||
]
|
||||
},
|
||||
"data_type": "traits_v1",
|
||||
"languages": {},
|
||||
"regions": {}
|
||||
},
|
||||
"odysee": {
|
||||
"all_locale": null,
|
||||
"custom": {},
|
||||
@@ -9585,4 +9682,4 @@
|
||||
},
|
||||
"regions": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
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}",
|
||||
"versions": [
|
||||
"151.0",
|
||||
"150.0"
|
||||
"152.0",
|
||||
"151.0"
|
||||
]
|
||||
}
|
||||
@@ -3272,7 +3272,7 @@
|
||||
"Q128822": {
|
||||
"si_name": "Q182429",
|
||||
"symbol": "kn",
|
||||
"to_si_factor": 0.5144444444444445
|
||||
"to_si_factor": 0.514
|
||||
},
|
||||
"Q12912288": {
|
||||
"si_name": null,
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
# There exits a https://github.com/ohblue/baidu-serp-api/
|
||||
# but we don't use it here (may we can learn from).
|
||||
|
||||
import typing as t
|
||||
|
||||
from urllib.parse import urlencode
|
||||
from datetime import datetime
|
||||
from html import unescape
|
||||
import time
|
||||
import json
|
||||
|
||||
from searx.exceptions import SearxEngineAPIException, SearxEngineCaptchaException
|
||||
from searx.exceptions import SearxEngineAPIException, SearxEngineCaptchaException, SearxEngineAccessDeniedException
|
||||
from searx.enginelib import EngineCache
|
||||
from searx.network import get as http_get
|
||||
from searx.utils import html_to_text
|
||||
|
||||
about = {
|
||||
@@ -35,6 +39,31 @@ baidu_category = 'general'
|
||||
time_range_support = True
|
||||
time_range_dict = {"day": 86400, "week": 604800, "month": 2592000, "year": 31536000}
|
||||
|
||||
image_base_url = "https://image.baidu.com/"
|
||||
|
||||
COOKIE_CACHE_KEY = "cookie"
|
||||
COOKIE_CACHE_EXPIRATION_SECONDS = 3600
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Stores cookies from Baidu image search warmup."""
|
||||
|
||||
|
||||
def setup(engine_settings: dict[str, t.Any]) -> bool:
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"])
|
||||
return True
|
||||
|
||||
|
||||
def get_image_cookies(headers: dict[str, str]) -> dict[str, str]:
|
||||
cookies: dict[str, str] | None = CACHE.get(COOKIE_CACHE_KEY)
|
||||
if cookies:
|
||||
return cookies
|
||||
|
||||
warmup = http_get(image_base_url, headers=headers, timeout=10)
|
||||
cookies = dict(warmup.cookies.items())
|
||||
CACHE.set(key=COOKIE_CACHE_KEY, value=cookies, expire=COOKIE_CACHE_EXPIRATION_SECONDS)
|
||||
return cookies
|
||||
|
||||
|
||||
def init(_):
|
||||
if baidu_category not in ('general', 'images', 'it'):
|
||||
@@ -88,6 +117,9 @@ def request(query, params):
|
||||
if baidu_category == 'it':
|
||||
query_params["paramList"] += f",timestamp_range={past}-{now}"
|
||||
|
||||
if baidu_category == 'images':
|
||||
params["cookies"] = get_image_cookies(params["headers"])
|
||||
|
||||
params["url"] = f"{query_url}?{urlencode(query_params)}"
|
||||
params["allow_redirects"] = False
|
||||
return params
|
||||
@@ -103,6 +135,8 @@ def response(resp):
|
||||
# baidu's JSON encoder wrongly quotes / and ' characters by \\ and \'
|
||||
text = text.replace(r"\/", "/").replace(r"\'", "'")
|
||||
data = json.loads(text, strict=False)
|
||||
if data.get("antiFlag") == 1:
|
||||
raise SearxEngineAccessDeniedException(data.get("message", "Forbid spider access"))
|
||||
parsers = {'general': parse_general, 'images': parse_images, 'it': parse_it}
|
||||
|
||||
return parsers[baidu_category](data)
|
||||
|
||||
@@ -8,6 +8,7 @@ import random
|
||||
import string
|
||||
from urllib.parse import urlencode
|
||||
from datetime import datetime, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from searx import utils
|
||||
|
||||
@@ -39,6 +40,32 @@ cookie = {
|
||||
"home_feed_column": "4",
|
||||
}
|
||||
|
||||
_CN_TZ = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
# Calendar-day time filter (Asia/Shanghai); dict values are days to subtract from today.
|
||||
time_range_support = True
|
||||
time_range_dict = {"day": 0, "week": 6, "month": 29, "year": 364}
|
||||
|
||||
|
||||
def _pubtime_range(time_range: str) -> tuple[int, int]:
|
||||
"""Return ``(pubtime_begin_s, pubtime_end_s)`` for Bilibili's search API.
|
||||
|
||||
Time ranges follow Bilibili's website semantics: they are counted in
|
||||
**calendar days** in China Standard Time (``Asia/Shanghai``), not as
|
||||
sliding 24-hour windows. For example, ``day`` means from 00:00:00 to
|
||||
23:59:59 of the current local day; ``week`` spans from 00:00:00 on the
|
||||
calendar day six days ago through the end of today, and so on.
|
||||
|
||||
The returned Unix timestamps (seconds) map to Bilibili's
|
||||
``pubtime_begin_s`` and ``pubtime_end_s`` query parameters.
|
||||
"""
|
||||
now = datetime.now(_CN_TZ)
|
||||
pubtime_end_s = int(now.replace(hour=23, minute=59, second=59, microsecond=0).timestamp())
|
||||
begin_day = now - timedelta(days=time_range_dict[time_range])
|
||||
pubtime_begin_s = int(begin_day.replace(hour=0, minute=0, second=0, microsecond=0).timestamp())
|
||||
|
||||
return pubtime_begin_s, pubtime_end_s
|
||||
|
||||
|
||||
def request(query, params):
|
||||
query_params = {
|
||||
@@ -50,6 +77,11 @@ def request(query, params):
|
||||
"search_type": "video",
|
||||
}
|
||||
|
||||
if params.get("time_range") in time_range_dict:
|
||||
pubtime_begin_s, pubtime_end_s = _pubtime_range(params["time_range"])
|
||||
query_params["pubtime_begin_s"] = pubtime_begin_s
|
||||
query_params["pubtime_end_s"] = pubtime_end_s
|
||||
|
||||
params["url"] = f"{base_url}?{urlencode(query_params)}"
|
||||
params["headers"]["Referer"] = "https://www.bilibili.com/"
|
||||
params["headers"]["Accept"] = "application/json, text/javascript, */*; q=0.01"
|
||||
|
||||
62
searx/engines/magnific.py
Normal file
62
searx/engines/magnific.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Magnific_ is a database for images.
|
||||
|
||||
.. _Magnific: https://www.magnific.com
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import typing as t
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://www.magnific.com",
|
||||
"wikidata_id": "Q104211654",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
base_url = "https://www.magnific.com"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
|
||||
free_images_only = True
|
||||
"""
|
||||
Whether to only load images that may be used for free, without a Magnific account.
|
||||
"""
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {"term": query, "filters[ai-generated][excluded]": 1, "page": params["pageno"], "locale": "en"}
|
||||
if free_images_only:
|
||||
args["filters[license]"] = "free"
|
||||
|
||||
params["headers"]["Referer"] = f"{base_url}/search"
|
||||
params["url"] = f"{base_url}/api/regular/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
result: dict[str, t.Any] # TBH: dict[str, t.Any]
|
||||
for result in resp.json()["items"]:
|
||||
res.add(
|
||||
res.types.Image(
|
||||
title=result["name"],
|
||||
url=result["url"],
|
||||
thumbnail_src=result["preview"]["url"],
|
||||
img_src=result["preview"]["url"],
|
||||
resolution=f"{result['preview']['width']}x{result['preview']['height']}",
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -99,23 +99,35 @@ def parse_general(data):
|
||||
|
||||
dom = html.fromstring(data)
|
||||
|
||||
for item in eval_xpath_list(dom, "//ul[contains(@class, 'lst_total')]/li[contains(@class, 'bx')]"):
|
||||
thumbnail = None
|
||||
for item in eval_xpath_list(dom, "//div[contains(@class, 'fds-web-normal-doc-root')]"):
|
||||
thumbnail = extract_text(
|
||||
eval_xpath(
|
||||
item,
|
||||
".//div[contains(@class, 'sds-comps-image') and not(contains(@class, 'sds-comps-image-circle'))]/img/@src",
|
||||
)
|
||||
)
|
||||
|
||||
title = extract_text(eval_xpath(item, ".//span[contains(@class, 'sds-comps-text-type-headline1')]"))
|
||||
|
||||
url = None
|
||||
try:
|
||||
thumbnail = eval_xpath_getindex(item, ".//div[contains(@class, 'thumb_single')]//img/@data-lazysrc", 0)
|
||||
url = eval_xpath_getindex(
|
||||
item, ".//a[starts-with(@href, 'http') and not(contains(@href, 'keep.naver.com'))]/@href", 0
|
||||
)
|
||||
except (ValueError, TypeError, SearxEngineXPathException):
|
||||
pass
|
||||
|
||||
results.add(
|
||||
MainResult(
|
||||
title=extract_text(eval_xpath(item, ".//a[contains(@class, 'link_tit')]")),
|
||||
url=eval_xpath_getindex(item, ".//a[contains(@class, 'link_tit')]/@href", 0),
|
||||
content=extract_text(
|
||||
eval_xpath(item, ".//div[contains(@class, 'total_dsc_wrap')]//a[contains(@class, 'api_txt_lines')]")
|
||||
),
|
||||
thumbnail=thumbnail,
|
||||
content = extract_text(eval_xpath(item, ".//span[contains(@class, 'sds-comps-text-type-body1')]"))
|
||||
|
||||
if title and url:
|
||||
results.add(
|
||||
MainResult(
|
||||
title=title,
|
||||
url=url,
|
||||
content=content or "",
|
||||
thumbnail=thumbnail or "",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@@ -173,7 +185,7 @@ def parse_news(data):
|
||||
title=title,
|
||||
url=url,
|
||||
content=content,
|
||||
thumbnail=thumbnail,
|
||||
thumbnail=thumbnail or "",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -196,7 +208,7 @@ def parse_videos(data):
|
||||
|
||||
length = None
|
||||
try:
|
||||
length = parse_duration_string(extract_text(eval_xpath(item, ".//span[contains(@class, 'time')]")))
|
||||
length = parse_duration_string(extract_text(eval_xpath(item, ".//span[contains(@class, 'time')]")) or "")
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
92
searx/engines/neosearch.py
Normal file
92
searx/engines/neosearch.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Neosearch_ aims to be a privacy-first alternative to Google.
|
||||
|
||||
.. _Neosearch: https://neosearch.org/About
|
||||
"""
|
||||
|
||||
from json import loads
|
||||
import typing as t
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.enginelib.traits import EngineTraits
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
traits: EngineTraits
|
||||
|
||||
about = {
|
||||
"website": "https://neosearch.org",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
base_url = "https://neosearch.org"
|
||||
categories = ["general"]
|
||||
|
||||
paging = False
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
args = {"q": query, "generate": "auto"}
|
||||
countrycode = params["searxng_locale"].split("-")[-1].upper()
|
||||
if countrycode in traits.custom["countrycodes"]:
|
||||
args["loc"] = countrycode
|
||||
params["url"] = f"{base_url}/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
# first line contains something like `{"location": "de"}`
|
||||
# second line contains the actual results
|
||||
json_resp = loads(resp.text.splitlines()[-1])
|
||||
for lens in json_resp["lenses"]:
|
||||
for category in lens["categories"]:
|
||||
for result in category["links"]:
|
||||
if not result["url"]:
|
||||
continue
|
||||
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["url"],
|
||||
title=result["title"],
|
||||
content=result["snippet"] or result["description"],
|
||||
)
|
||||
)
|
||||
|
||||
for suggestion in json_resp.get("suggestions", []):
|
||||
res.add(res.types.LegacyResult(suggestion=suggestion))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def fetch_traits(engine_traits: "EngineTraits") -> None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from searx.network import get
|
||||
from searx.utils import extr, js_obj_str_to_python
|
||||
from babel.core import get_global
|
||||
|
||||
resp = get(base_url)
|
||||
|
||||
locations_js_raw = extr(resp.text, "const LOCATIONS = ", ";")
|
||||
if not locations_js_raw:
|
||||
raise RuntimeError("failed to find locations in neosearch HTML response")
|
||||
locations: list[dict[str, str]] = js_obj_str_to_python(locations_js_raw)
|
||||
|
||||
babel_reg_list = get_global("territory_languages").keys()
|
||||
|
||||
countrycodes: list[str] = []
|
||||
for loc in locations:
|
||||
_reg = loc["code"].upper()
|
||||
if _reg not in babel_reg_list:
|
||||
print(f"ERROR: region tag {_reg} is unknown by babel")
|
||||
continue
|
||||
countrycodes.append(_reg)
|
||||
|
||||
countrycodes.sort()
|
||||
engine_traits.custom["countrycodes"] = countrycodes
|
||||
63
searx/engines/picjumbo.py
Normal file
63
searx/engines/picjumbo.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Picjumbo_ provides free stock photos.
|
||||
|
||||
.. _Picjumbo: https://picjumbo.com
|
||||
"""
|
||||
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
import typing as t
|
||||
|
||||
from lxml import html
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import eval_xpath, eval_xpath_list, extract_text
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://picjumbo.com",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "HTML",
|
||||
}
|
||||
|
||||
base_url = "https://picjumbo.com"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
params["url"] = f"{base_url}/search/{query}/page/{params['pageno']}"
|
||||
|
||||
|
||||
def _get_max_res_url(url: str) -> str:
|
||||
"""Get the maximum resolution of the image based on the thumbnail URL."""
|
||||
parsed_url = urlparse(url)
|
||||
max_res_url = parsed_url._replace(query="w=10000&quality=100")
|
||||
return urlunparse(max_res_url)
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
doc = html.fromstring(resp.text)
|
||||
|
||||
for result in eval_xpath_list(doc, "//div[contains(@class, 'photo_query')]/div[contains(@class, 'photo_item')]"):
|
||||
thumbnail = extract_text(eval_xpath(result, ".//img[contains(@class, 'image')]/@src")) or ""
|
||||
res.add(
|
||||
res.types.Image(
|
||||
url=extract_text(eval_xpath(result, ".//a[contains(@class, 'image')]/@href")) or "",
|
||||
title=extract_text(eval_xpath(result, ".//h3")) or "",
|
||||
content=extract_text(eval_xpath(result, ".//meta[@itemprop='keywords']/@content")) or "",
|
||||
thumbnail_src=thumbnail,
|
||||
img_src=_get_max_res_url(thumbnail),
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
62
searx/engines/shopify_stock.py
Normal file
62
searx/engines/shopify_stock.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Shopify stock photos provides royalty-free images, intended for use with
|
||||
Shopify.
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from lxml import html
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import eval_xpath, eval_xpath_list, extract_text
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://www.shopify.com/stock-photos",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "HTML",
|
||||
}
|
||||
|
||||
base_url = "https://www.shopify.com"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {"q": query, "page": params["pageno"]}
|
||||
params["url"] = f"{base_url}/stock-photos/photos/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def _get_download_url(url: str) -> str:
|
||||
"""Get the link to the full quality image."""
|
||||
query_start = url.find("?")
|
||||
return url[:query_start] + "/download?quality=premium"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
doc = html.fromstring(resp.text)
|
||||
|
||||
for result in eval_xpath_list(doc, "//div[contains(@class, 'js-masonry-grid')]/div"):
|
||||
url = base_url + (extract_text(eval_xpath(result, ".//a[contains(@class, 'photo-tile')]/@href")) or "")
|
||||
res.add(
|
||||
res.types.Image(
|
||||
url=url,
|
||||
title=extract_text(eval_xpath(result, ".//p[contains(@class, 'photo-tile__title')]")) or "",
|
||||
thumbnail_src=extract_text(eval_xpath(result, ".//img[contains(@class, 'photo-card__image')]/@src"))
|
||||
or "",
|
||||
img_src=_get_download_url(url),
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
55
searx/engines/stocksnap.py
Normal file
55
searx/engines/stocksnap.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Stocksnap_ is a search engine for CC0-licensed images.
|
||||
|
||||
.. _Stocksnap: https://stocksnap.io
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://stocksnap.io",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
# otherwise all requests get blocked, probably HTTP2 fingerprinting
|
||||
enable_http2 = False
|
||||
|
||||
base_url = "https://stocksnap.io"
|
||||
cdn_url = "https://cdn.stocksnap.io"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
params["url"] = f"{base_url}/api/search-photos/{query}/relevance/desc/{params['pageno']}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
result: dict[str, str] # TBH: dict[str, t.Any]
|
||||
for result in resp.json()["results"]:
|
||||
slug = "-".join(result['keywords'][:1]) + "-" + result["img_id"]
|
||||
res.add(
|
||||
res.types.Image(
|
||||
title=result["tags"],
|
||||
url=f"{base_url}/photo/{slug}",
|
||||
thumbnail_src=f"{cdn_url}/img-thumbs/280h/{result['img_id']}.jpg",
|
||||
img_src=f"{cdn_url}/img-thumbs/960w/{result['img_id']}.jpg",
|
||||
img_format="JPEG",
|
||||
resolution=f"{result['img_width']}x{result['img_height']}",
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -1585,6 +1585,11 @@ engines:
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: magnific
|
||||
engine: magnific
|
||||
shortcut: mag
|
||||
disabled: true
|
||||
|
||||
- name: marginalia
|
||||
engine: marginalia
|
||||
shortcut: mar
|
||||
@@ -1857,6 +1862,11 @@ engines:
|
||||
engine: photon
|
||||
shortcut: ph
|
||||
|
||||
- name: picjumbo
|
||||
engine: picjumbo
|
||||
shortcut: pj
|
||||
disabled: true
|
||||
|
||||
- name: pinterest
|
||||
engine: pinterest
|
||||
shortcut: pin
|
||||
@@ -2165,6 +2175,11 @@ engines:
|
||||
engine: sepiasearch
|
||||
shortcut: sep
|
||||
|
||||
- name: shopify stock
|
||||
engine: shopify_stock
|
||||
shortcut: shs
|
||||
disabled: true
|
||||
|
||||
- name: sogou
|
||||
engine: sogou
|
||||
shortcut: sogou
|
||||
@@ -2195,6 +2210,11 @@ engines:
|
||||
api_site: 'stackoverflow'
|
||||
categories: [it, q&a]
|
||||
|
||||
- name: stocksnap
|
||||
engine: stocksnap
|
||||
shortcut: sto
|
||||
disabled: true
|
||||
|
||||
- name: askubuntu
|
||||
engine: stackexchange
|
||||
shortcut: ubuntu
|
||||
@@ -2834,6 +2854,12 @@ engines:
|
||||
shortcut: nvrv
|
||||
disabled: true
|
||||
|
||||
- name: neosearch
|
||||
engine: neosearch
|
||||
shortcut: neo
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: rubygems
|
||||
shortcut: rbg
|
||||
engine: xpath
|
||||
|
||||
@@ -74,6 +74,8 @@ sxng_locales = (
|
||||
('ja-JP', '日本語', '日本', 'Japanese', '\U0001f1ef\U0001f1f5'),
|
||||
('ko', '한국어', '', 'Korean', '\U0001f310'),
|
||||
('ko-KR', '한국어', '대한민국', 'Korean', '\U0001f1f0\U0001f1f7'),
|
||||
('mi', 'Māori', '', 'Māori', '\U0001f310'),
|
||||
('mi-NZ', 'Māori', 'Aotearoa', 'Māori', '\U0001f1f3\U0001f1ff'),
|
||||
('nb', 'Norsk Bokmål', '', 'Norwegian Bokmål', '\U0001f310'),
|
||||
('nb-NO', 'Norsk Bokmål', 'Norge', 'Norwegian Bokmål', '\U0001f1f3\U0001f1f4'),
|
||||
('nl', 'Nederlands', '', 'Dutch', '\U0001f310'),
|
||||
|
||||
Reference in New Issue
Block a user