mirror of
https://github.com/searxng/searxng.git
synced 2026-07-27 02:11:23 +00:00
Compare commits
14 Commits
fd42d4fda1
...
cce0957f54
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cce0957f54 | ||
|
|
9375c0a6b6 | ||
|
|
a702741e4e | ||
|
|
aeced67249 | ||
|
|
199e03de1d | ||
|
|
9cd2439e5e | ||
|
|
9f4d8bca02 | ||
|
|
de76a4a39b | ||
|
|
a85a5e2794 | ||
|
|
92abd98a55 | ||
|
|
93e867c6b1 | ||
|
|
75c1b1dade | ||
|
|
097ab64c70 | ||
|
|
0e9f513efc |
47
client/simple/package-lock.json
generated
47
client/simple/package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
"browserslist": "^4.28.2",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"edge.js": "^6.5.1",
|
||||
"less": "^4.6.4",
|
||||
"less": "^4.6.6",
|
||||
"mathjs": "^15.2.0",
|
||||
"sharp": "~0.35.1",
|
||||
"sort-package-json": "^4.0.0",
|
||||
@@ -2890,9 +2890,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/less": {
|
||||
"version": "4.6.4",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.6.4.tgz",
|
||||
"integrity": "sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==",
|
||||
"version": "4.6.6",
|
||||
"resolved": "https://registry.npmjs.org/less/-/less-4.6.6.tgz",
|
||||
"integrity": "sha512-ooPSwQGQ2sVe8Dh1jVsbKKsRR2gd8lFK72BDkeSzjnD1T5aIHL65hCMfO0GVmtriKgDKrQv6xp9UrihUsWuAzA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -2909,7 +2909,7 @@
|
||||
"errno": "^0.1.1",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"image-size": "~0.5.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"make-dir": "^5.1.0",
|
||||
"mime": "^1.4.1",
|
||||
"needle": "^3.1.0",
|
||||
"source-map": "~0.6.0"
|
||||
@@ -3191,18 +3191,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-5.1.0.tgz",
|
||||
"integrity": "sha512-IfpFq6UM39dUNiphpA6uDezNx/AvWyhwfICWPR3t1VspkgkMZrL+Rk1RbN1bx+aeNYwOrqGJgEgV3yotk+ZUVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mathjs": {
|
||||
@@ -3491,17 +3490,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
@@ -3861,17 +3849,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.35.1",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.1.tgz",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"browserslist": "^4.28.2",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"edge.js": "^6.5.1",
|
||||
"less": "^4.6.4",
|
||||
"less": "^4.6.6",
|
||||
"mathjs": "^15.2.0",
|
||||
"sharp": "~0.35.1",
|
||||
"sort-package-json": "^4.0.0",
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
.. _aol engine:
|
||||
|
||||
===
|
||||
AOL
|
||||
===
|
||||
|
||||
.. automodule:: searx.engines.aol
|
||||
:members:
|
||||
@@ -2,16 +2,16 @@ mock==5.2.0
|
||||
nose2[coverage_plugin]==0.16.0
|
||||
cov-core==1.15.0
|
||||
black==25.9.0
|
||||
pylint==4.0.5
|
||||
pylint==4.0.6
|
||||
splinter==0.21.0
|
||||
selenium==4.44.0
|
||||
selenium==4.45.0
|
||||
Sphinx==8.2.3;python_version <= "3.11"
|
||||
Sphinx==9.1.0; python_version > "3.11"
|
||||
sphinx-issues==6.0.0
|
||||
sphinx-jinja==2.0.2
|
||||
sphinx-tabs==3.5.0
|
||||
furo==2025.12.19
|
||||
sphinxcontrib-programoutput==0.19
|
||||
sphinxcontrib-programoutput==0.20
|
||||
sphinx-autobuild==2025.8.25
|
||||
sphinx-notfound-page==1.1.0
|
||||
myst-parser==5.0.0
|
||||
@@ -24,5 +24,5 @@ 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
|
||||
basedpyright==1.39.7
|
||||
basedpyright==1.39.8
|
||||
types-lxml==2026.2.16
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
certifi==2026.5.20
|
||||
certifi==2026.6.17
|
||||
babel==2.18.0
|
||||
flask-babel==4.0.0
|
||||
flask==3.1.3
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""AOL supports WEB, image, and video search. Internally, it uses the Bing
|
||||
index.
|
||||
|
||||
AOL doesn't seem to support setting the language via request parameters, instead
|
||||
the results are based on the URL. For example, there is
|
||||
|
||||
- `search.aol.com <https://search.aol.com>`_ for English results
|
||||
- `suche.aol.de <https://suche.aol.de>`_ for German results
|
||||
|
||||
However, AOL offers its services only in a few regions:
|
||||
|
||||
- en-US: search.aol.com
|
||||
- de-DE: suche.aol.de
|
||||
- fr-FR: recherche.aol.fr
|
||||
- en-GB: search.aol.co.uk
|
||||
- en-CA: search.aol.ca
|
||||
|
||||
In order to still offer sufficient support for language and region, the `search
|
||||
keywords`_ known from Bing, ``language`` and ``loc`` (region), are added to the
|
||||
search term (AOL is basically just a proxy for Bing).
|
||||
|
||||
.. _search keywords:
|
||||
https://support.microsoft.com/en-us/topic/advanced-search-keywords-ea595928-5d63-4a0b-9c6b-0b769865e78a
|
||||
|
||||
"""
|
||||
|
||||
from urllib.parse import urlencode, unquote_plus
|
||||
import typing as t
|
||||
|
||||
from lxml import html
|
||||
from dateutil import parser
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import eval_xpath_list, eval_xpath, extract_text
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
about = {
|
||||
"website": "https://www.aol.com",
|
||||
"wikidata_id": "Q27585",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "HTML",
|
||||
}
|
||||
|
||||
categories = ["general"]
|
||||
search_type = "search" # supported: search, image, video
|
||||
|
||||
paging = True
|
||||
safesearch = True
|
||||
time_range_support = True
|
||||
results_per_page = 10
|
||||
|
||||
|
||||
base_url = "https://search.aol.com"
|
||||
time_range_map = {"day": "1d", "week": "1w", "month": "1m", "year": "1y"}
|
||||
safesearch_map = {0: "p", 1: "r", 2: "i"}
|
||||
|
||||
enable_http2 = False
|
||||
|
||||
|
||||
def init(_):
|
||||
if search_type not in ("search", "image", "video"):
|
||||
raise ValueError(f"unsupported search type {search_type}")
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
|
||||
language, region = (params["searxng_locale"].split("-") + [None])[:2]
|
||||
if language and language != "all":
|
||||
query = f"{query} language:{language}"
|
||||
if region:
|
||||
query = f"{query} loc:{region}"
|
||||
|
||||
args: dict[str, str | int | None] = {
|
||||
"q": query,
|
||||
"b": params["pageno"] * results_per_page + 1, # page is 1-indexed
|
||||
"pz": results_per_page,
|
||||
}
|
||||
|
||||
if params["time_range"]:
|
||||
args["fr2"] = "time"
|
||||
args["age"] = params["time_range"]
|
||||
else:
|
||||
args["fr2"] = "sb-top-search"
|
||||
|
||||
params["cookies"]["sB"] = f"vm={safesearch_map[params['safesearch']]}"
|
||||
params["url"] = f"{base_url}/aol/{search_type}?{urlencode(args)}"
|
||||
logger.debug(params)
|
||||
|
||||
|
||||
def _deobfuscate_url(obfuscated_url: str) -> str | None:
|
||||
# URL looks like "https://search.aol.com/click/_ylt=AwjFSDjd;_ylu=JfsdjDFd/RV=2/RE=1774058166/RO=10/RU=https%3a%2f%2fen.wikipedia.org%2fwiki%2fTree/RK=0/RS=BP2CqeMLjscg4n8cTmuddlEQA2I-" # pylint: disable=line-too-long
|
||||
if not obfuscated_url:
|
||||
return None
|
||||
|
||||
for part in obfuscated_url.split("/"):
|
||||
if part.startswith("RU="):
|
||||
return unquote_plus(part[3:])
|
||||
# pattern for de-obfuscating URL not found, fall back to Yahoo's tracking link
|
||||
return obfuscated_url
|
||||
|
||||
|
||||
def _general_results(doc: html.HtmlElement) -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
for result in eval_xpath_list(doc, "//div[@id='web']//ol/li[not(contains(@class, 'first'))]"):
|
||||
obfuscated_url = extract_text(eval_xpath(result, ".//h3/a/@href"))
|
||||
if not obfuscated_url:
|
||||
continue
|
||||
|
||||
url = _deobfuscate_url(obfuscated_url)
|
||||
if not url:
|
||||
continue
|
||||
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=url,
|
||||
title=extract_text(eval_xpath(result, ".//h3/a")) or "",
|
||||
content=extract_text(eval_xpath(result, ".//div[contains(@class, 'compText')]")) or "",
|
||||
thumbnail=extract_text(eval_xpath(result, ".//a[contains(@class, 'thm')]/img/@data-src")) or "",
|
||||
)
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
def _video_results(doc: html.HtmlElement) -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
for result in eval_xpath_list(doc, "//div[contains(@class, 'results')]//ol/li"):
|
||||
obfuscated_url = extract_text(eval_xpath(result, ".//a/@href"))
|
||||
if not obfuscated_url:
|
||||
continue
|
||||
|
||||
url = _deobfuscate_url(obfuscated_url)
|
||||
if not url:
|
||||
continue
|
||||
|
||||
published_date_raw = extract_text(eval_xpath(result, ".//div[contains(@class, 'v-age')]"))
|
||||
try:
|
||||
published_date = parser.parse(published_date_raw or "")
|
||||
except parser.ParserError:
|
||||
published_date = None
|
||||
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
{
|
||||
"template": "videos.html",
|
||||
"url": url,
|
||||
"title": extract_text(eval_xpath(result, ".//h3")),
|
||||
"content": extract_text(eval_xpath(result, ".//div[contains(@class, 'compText')]")),
|
||||
"thumbnail": extract_text(eval_xpath(result, ".//img[contains(@class, 'thm')]/@src")),
|
||||
"length": extract_text(eval_xpath(result, ".//span[contains(@class, 'v-time')]")),
|
||||
"publishedDate": published_date,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _image_results(doc: html.HtmlElement) -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
for result in eval_xpath_list(doc, "//section[@id='results']//ul/li"):
|
||||
obfuscated_url = extract_text(eval_xpath(result, "./a/@href"))
|
||||
if not obfuscated_url:
|
||||
continue
|
||||
|
||||
url = _deobfuscate_url(obfuscated_url)
|
||||
if not url:
|
||||
continue
|
||||
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
{
|
||||
"template": "images.html",
|
||||
# results don't have an extra URL, only the image source
|
||||
"url": url,
|
||||
"title": extract_text(eval_xpath(result, ".//a/@aria-label")),
|
||||
"thumbnail_src": extract_text(eval_xpath(result, ".//img/@src")),
|
||||
"img_src": url,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
doc = html.fromstring(resp.text)
|
||||
|
||||
match search_type:
|
||||
case "search":
|
||||
results = _general_results(doc)
|
||||
case "image":
|
||||
results = _image_results(doc)
|
||||
case "video":
|
||||
results = _video_results(doc)
|
||||
case _:
|
||||
raise ValueError("unsupported search type")
|
||||
|
||||
for suggestion in eval_xpath_list(doc, ".//ol[contains(@class, 'searchRightBottom')]//table//a"):
|
||||
results.add(results.types.LegacyResult({"suggestion": extract_text(suggestion)}))
|
||||
|
||||
return results
|
||||
118
searx/engines/findfiles.py
Normal file
118
searx/engines/findfiles.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""FindFiles.net_ is a Germany-based file search engine.
|
||||
|
||||
FindFiles.net_ is a specialized file search engine designed to help you search
|
||||
files online with precision. Unlike traditional search engines that mainly index
|
||||
web pages, FindFiles focuses on finding real files on the internet - including
|
||||
PDFs, documents, archives, videos, datasets, and more.
|
||||
|
||||
.. _FindFiles.net: https://findfiles.net
|
||||
"""
|
||||
|
||||
from os.path import basename
|
||||
from urllib.parse import urlencode
|
||||
import typing as t
|
||||
|
||||
from lxml import html
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
from searx.utils import extract_text, eval_xpath, eval_xpath_list
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from extended_types import SXNG_Response
|
||||
from search.processors import OnlineParams
|
||||
|
||||
about = {
|
||||
"website": "https://findfiles.net",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "HTML",
|
||||
}
|
||||
|
||||
base_url = "https://findfiles.net"
|
||||
categories = ["files"]
|
||||
paging = True
|
||||
safeserach = True
|
||||
|
||||
safesearch_map = {
|
||||
0: "contentguard.off",
|
||||
1: "contentguard.moderate",
|
||||
2: "contentguard.strict",
|
||||
}
|
||||
|
||||
FindFilesCategory = t.Literal[
|
||||
"all",
|
||||
"document",
|
||||
"text",
|
||||
"image",
|
||||
"audio",
|
||||
"video",
|
||||
]
|
||||
FINDFILES_CATEGORIES = t.get_args(FindFilesCategory)
|
||||
|
||||
findfiles_categ: FindFilesCategory = "all"
|
||||
"""Category to search in."""
|
||||
|
||||
|
||||
def setup(_: dict[str, t.Any]) -> bool:
|
||||
if findfiles_categ not in FINDFILES_CATEGORIES:
|
||||
raise ValueError("invalid category: %s" % findfiles_categ)
|
||||
return True
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {
|
||||
"query": query,
|
||||
"contentguard": safesearch_map[params["safesearch"]],
|
||||
"page": params["pageno"],
|
||||
}
|
||||
# the language in the path doesn't change anything about the results, it
|
||||
# only changes the UI
|
||||
params["url"] = f"{base_url}/en/serp/{findfiles_categ}/?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
dom = html.fromstring(resp.text)
|
||||
if findfiles_categ == "image":
|
||||
for result in eval_xpath_list(
|
||||
dom, "//div[contains(@class, 'image-mosaic')]/div[contains(@class, 'image-item')]"
|
||||
):
|
||||
res.add(
|
||||
res.types.Image(
|
||||
url=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/a/@href")) or "",
|
||||
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/a")) or "",
|
||||
thumbnail_src=extract_text(eval_xpath(result, ".//img/@src")) or "",
|
||||
)
|
||||
)
|
||||
elif findfiles_categ == "video":
|
||||
for result in eval_xpath_list(
|
||||
dom, "//div[contains(@class, 'video-mosaic')]/div[contains(@class, 'video-item')]"
|
||||
):
|
||||
video_src = extract_text(eval_xpath(result, ".//video/@src")) or ""
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
template="videos.html",
|
||||
url=video_src,
|
||||
title=extract_text(eval_xpath(result, ".//div[contains(@class, 'caption')]/span")) or "",
|
||||
iframe_src=video_src or "",
|
||||
)
|
||||
)
|
||||
else:
|
||||
for result in eval_xpath_list(dom, "//ol/li[contains(@class, 'result-item')]/article"):
|
||||
filename = basename(extract_text(eval_xpath(result, ".//h3")) or "")
|
||||
res.add(
|
||||
res.types.File(
|
||||
url=extract_text(eval_xpath(result, ".//h3/a/@href")) or "",
|
||||
title=filename,
|
||||
content=" ".join(extract_text(el) or "" for el in eval_xpath_list(result, "./div/span")),
|
||||
filename=filename,
|
||||
size=extract_text(eval_xpath(result, "(.//span[@id])[1]")) or "",
|
||||
embedded=extract_text(eval_xpath(result, ".//audio/@src")) or "",
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
127
searx/engines/giphy.py
Normal file
127
searx/engines/giphy.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Giphy (images)"""
|
||||
|
||||
import random
|
||||
from urllib.parse import urlencode
|
||||
import re
|
||||
|
||||
import typing as t
|
||||
|
||||
from lxml import html
|
||||
|
||||
from searx.enginelib import EngineCache
|
||||
from searx.exceptions import SearxEngineAPIException
|
||||
from searx.network import get
|
||||
from searx.result_types import EngineResults
|
||||
from searx.result_types.image import ImageRef
|
||||
from searx.utils import eval_xpath_list, humanize_bytes
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.extended_types import SXNG_Response
|
||||
from searx.search.processors import OnlineParams
|
||||
|
||||
|
||||
about = {
|
||||
"website": "https://giphy.com",
|
||||
"wikidata_id": "Q17054335",
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
base_url = "https://giphy.com"
|
||||
api_url = "https://api.giphy.com"
|
||||
|
||||
categories = ["images"]
|
||||
paging = True
|
||||
page_size = 15
|
||||
|
||||
GiphyCategs = t.Literal["gifs", "stickers", "clips"]
|
||||
giphy_categ: GiphyCategs = "gifs"
|
||||
"""Giphy category to search in."""
|
||||
|
||||
CACHE: EngineCache
|
||||
"""Cache for storing the extracted api key."""
|
||||
|
||||
|
||||
_GIPHY_API_KEY_RE = re.compile(r"[Aa]piKey\s*:\s*\"(\w+)\"")
|
||||
|
||||
|
||||
def setup(engine_settings: dict[str, str]) -> bool:
|
||||
if giphy_categ not in t.get_args(GiphyCategs):
|
||||
raise ValueError("invalid category: %s" % giphy_categ)
|
||||
|
||||
global CACHE # pylint: disable=global-statement
|
||||
CACHE = EngineCache(engine_settings["name"])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _get_api_key() -> str:
|
||||
"""
|
||||
Extract the Giphy API key from the JavaScript code. There are different API keys
|
||||
(e.g. for mobile, desktop, ...), so we just pick a random one of these.
|
||||
"""
|
||||
cached = CACHE.get("api_key")
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
homepage_resp = get(base_url)
|
||||
homepage_doc = html.fromstring(homepage_resp.text)
|
||||
|
||||
for script_src in eval_xpath_list(homepage_doc, "//script[contains(@src, 'layout')]/@src"):
|
||||
script_resp = get(base_url + script_src)
|
||||
api_keys = _GIPHY_API_KEY_RE.findall(script_resp.text)
|
||||
if api_keys:
|
||||
api_key = random.choice(api_keys)
|
||||
CACHE.set("api_key", api_key, expire=60 * 60 * 6) # 6 hours
|
||||
return api_key
|
||||
|
||||
raise SearxEngineAPIException("failed to extract api keys")
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {
|
||||
"q": query,
|
||||
"api_key": _get_api_key(),
|
||||
"limit": page_size,
|
||||
"offset": (params["pageno"] - 1) * page_size,
|
||||
"type": giphy_categ,
|
||||
}
|
||||
params["url"] = f"{api_url}/v1/{giphy_categ}/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
result: dict[str, t.Any]
|
||||
for result in resp.json()["data"]:
|
||||
img = result['images']['original']
|
||||
formats = [
|
||||
ImageRef(url=img["mp4"], subtype="mp4"), # type: ignore
|
||||
ImageRef(url=img["webp"], subtype="webp"), # type: ignore
|
||||
]
|
||||
thumb = (
|
||||
result["images"].get("downsized")
|
||||
or result["images"].get("downsized_medium")
|
||||
or result["images"].get("downsized_small")
|
||||
or result["images"].get("downsized_large")
|
||||
)
|
||||
res.add(
|
||||
res.types.Image(
|
||||
title=result["title"],
|
||||
content=", ".join(result.get("tags", [])),
|
||||
url=result["url"],
|
||||
thumbnail_src=thumb.get("url") or img["url"],
|
||||
img_src=img["url"],
|
||||
resolution=f"{img['width']}x{img['height']}",
|
||||
img_format="GIF",
|
||||
formats=formats,
|
||||
author=result["username"],
|
||||
filesize=humanize_bytes(int(img["size"])),
|
||||
source=result.get("source_tld") or "",
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
88
searx/engines/iseek.py
Normal file
88
searx/engines/iseek.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""iseek_ is a search engine by the AI company Vantage Labs LLC,
|
||||
that focuses on medical and educational applicances.
|
||||
Although it's an AI company, it doesn't include any AI stuff in its results.
|
||||
|
||||
.. _iseek : https://www.iseek.ai/
|
||||
"""
|
||||
|
||||
import base64
|
||||
from hashlib import sha256
|
||||
import typing as t
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from searx.result_types import EngineResults
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from searx.search.processors import OnlineParams
|
||||
from searx.extended_types import SXNG_Response
|
||||
|
||||
|
||||
about = {
|
||||
"website": 'https://www.iseek.com',
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
categories = ["general"]
|
||||
paging = True
|
||||
|
||||
base_url = "https://api.iseek.com"
|
||||
page_size = 10
|
||||
|
||||
|
||||
def _get_new_token(query: str, pageno: int) -> str:
|
||||
"""Create a new ``qToken``. This reduced the time for fetching subsequent pages
|
||||
from 4 seconds to 200ms when testing."""
|
||||
# The website uses a random value as qToken for the first page. For our use case,
|
||||
# it's easier if the qToken can be deterministically re-calculated based on the search query,
|
||||
# so that we can the same result when calling _get_new_token for the second, third, ... page
|
||||
#
|
||||
# var qToken = Math.ceil(Math.random() * parseInt("ZZZZ", 36)).toString(36);
|
||||
# while (qToken.length < 4) qToken = '0' + qToken;
|
||||
# qToken = qToken + "_" + pageno
|
||||
query_hash = sha256(query.encode()).digest()
|
||||
hash_start = base64.b64encode(query_hash).decode()[0:4]
|
||||
return f"{hash_start}_{pageno}"
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
offset = (params["pageno"] - 1) * page_size
|
||||
|
||||
# always seems to find 20 results max
|
||||
if offset >= 20:
|
||||
params["url"] = None
|
||||
return
|
||||
|
||||
args = {
|
||||
"q": query,
|
||||
"key": "core-web",
|
||||
"num": str(page_size),
|
||||
"off": offset,
|
||||
"rSort": "__metasearch_score_d:desc",
|
||||
# it supports many more fields, but none of them are really relevant
|
||||
"names": "title_t,content_txt,url_s",
|
||||
"qNames": "title_t",
|
||||
"qToken": _get_new_token(query, params["pageno"]),
|
||||
}
|
||||
params["url"] = f"{base_url}/search?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
res = EngineResults()
|
||||
|
||||
for group in resp.json()["data"]:
|
||||
group: dict[str, t.Any]
|
||||
for result in group["doclist"]["docs"]:
|
||||
result: dict[str, str]
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["url_s"],
|
||||
title=result["title_t"],
|
||||
content="".join(result["content_txt"]),
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -44,7 +44,7 @@ about = {
|
||||
|
||||
base_url = "https://api2.marginalia-search.com"
|
||||
safesearch = True
|
||||
categories = ["general"]
|
||||
categories = ["general", "blogs"]
|
||||
paging = True
|
||||
results_per_page = 20
|
||||
api_key = None
|
||||
|
||||
107
searx/engines/startpagina.py
Normal file
107
searx/engines/startpagina.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Startpagina is a Netherlands search engine by `Kompas`_. It takes all its
|
||||
results from Google.
|
||||
|
||||
.. _Kompas: https://www.kompaspublishing.nl/
|
||||
"""
|
||||
|
||||
import typing as t
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from dateutil import parser
|
||||
|
||||
from searx.utils import format_duration
|
||||
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://startpagina.nl",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
language = "ne"
|
||||
paging = True
|
||||
safesearch = True
|
||||
|
||||
categories = ["general"]
|
||||
startpagina_categ = "web"
|
||||
"""Category to search in. Can be either "web", "images", "videos" or "news"."""
|
||||
page_size = 10
|
||||
|
||||
|
||||
api_url = "https://search.kompas.services"
|
||||
|
||||
|
||||
def init(_):
|
||||
if startpagina_categ not in ("web", "images", "videos", "news"):
|
||||
raise ValueError("invalid search type: %s" % startpagina_categ)
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
args = {"q": query, "page_size": page_size, "page": params["pageno"]}
|
||||
params["url"] = f"{api_url}/api/v2/search/{startpagina_categ}/?{urlencode(args)}"
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
json_resp = resp.json()
|
||||
|
||||
for result in json_resp["results"]:
|
||||
if startpagina_categ == "web":
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["original_url"],
|
||||
title=result["title"],
|
||||
content=result["description"],
|
||||
)
|
||||
)
|
||||
elif startpagina_categ == "news":
|
||||
publishedDate = None
|
||||
try:
|
||||
publishedDate = parser.parse(result["date"])
|
||||
except parser.ParserError:
|
||||
pass
|
||||
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["original_url"],
|
||||
title=result["title"],
|
||||
content=result["description"],
|
||||
thumbnail=result["image"]["thumbnail_url"],
|
||||
publishedDate=publishedDate,
|
||||
)
|
||||
)
|
||||
elif startpagina_categ == "videos":
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
template="videos.html",
|
||||
url=result["original_url"],
|
||||
title=result["title"],
|
||||
content=result["description"],
|
||||
thumbnail=result["video"]["thumbnail_url"],
|
||||
length=format_duration(result["video"]["duration"]),
|
||||
)
|
||||
)
|
||||
elif startpagina_categ == "images":
|
||||
res.add(
|
||||
res.types.Image(
|
||||
url=result["original_url"],
|
||||
title=result["title"],
|
||||
content=result["description"],
|
||||
thumbnail_src=result["image"]["thumbnail_url"],
|
||||
resolution=f"{result['image']['width']}x{result['image']['height']}",
|
||||
)
|
||||
)
|
||||
|
||||
for related in json_resp["related_searches"]:
|
||||
res.add(res.types.LegacyResult(suggestion=related["query"]))
|
||||
|
||||
return res
|
||||
162
searx/engines/tusksearch.py
Normal file
162
searx/engines/tusksearch.py
Normal file
@@ -0,0 +1,162 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Tusksearch_ is an American search engine that claims to fight censorship.
|
||||
Its search results are (at least partially) from Brave.
|
||||
|
||||
.. _Tusksearch: https://tusksearch.com/about
|
||||
"""
|
||||
|
||||
from json import loads
|
||||
import random
|
||||
import typing as t
|
||||
from urllib.parse import urlencode
|
||||
from dateutil import parser
|
||||
|
||||
from searx.exceptions import SearxEngineAPIException
|
||||
from searx.network import get
|
||||
from searx.utils import html_to_text
|
||||
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://tusksearch.com",
|
||||
"wikidata_id": None,
|
||||
"official_api_documentation": None,
|
||||
"use_official_api": False,
|
||||
"require_api_key": False,
|
||||
"results": "JSON",
|
||||
}
|
||||
|
||||
paging = True
|
||||
|
||||
categories = ["general"]
|
||||
tusk_categ = "web"
|
||||
"""Category to search in. Can be either "web", "images", "videos" or "news"."""
|
||||
|
||||
|
||||
api_url = "https://api.tusksearch.com"
|
||||
|
||||
|
||||
def init(_):
|
||||
if tusk_categ not in ("web", "images", "videos", "news"):
|
||||
raise ValueError("invalid search type: %s" % tusk_categ)
|
||||
|
||||
|
||||
def _obtain_x_sid() -> tuple[str, str]:
|
||||
"""
|
||||
The session ID ("sid") is encoded as a byte array in ``embed.js``.
|
||||
It is only valid for exactly one request, so we can't cache it.
|
||||
|
||||
The header key is usually called `x-sid-{UUIDv4}`, and the value is
|
||||
usually a plain UUIDv4 (but a different one than in the header key).
|
||||
"""
|
||||
resp = get(f"{api_url}/revcontent/embed.js")
|
||||
if not resp.ok:
|
||||
raise SearxEngineAPIException("failed to obtain request x-sid token")
|
||||
|
||||
# data is prefixed by 'var x='
|
||||
data_array = loads(resp.text[6:])
|
||||
|
||||
def _byte_array_to_ascii(text: list[int]) -> str:
|
||||
"""
|
||||
Converts a byte array (e.g. [81, 101, 97, 114, 88, 78, 71]) to the ASCII
|
||||
string representation (e.g. "SearXNG").
|
||||
"""
|
||||
return "".join([chr(x) for x in text])
|
||||
|
||||
x_sid_header = _byte_array_to_ascii(data_array[3])
|
||||
x_sid_value = _byte_array_to_ascii(data_array[4])
|
||||
return x_sid_header, x_sid_value
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams") -> None:
|
||||
# images don't support pagination, news and videos only support two pages
|
||||
if tusk_categ == "images" and params["pageno"] > 1 or tusk_categ in ("news", "videos") and params["pageno"] > 2:
|
||||
params["url"] = None
|
||||
return
|
||||
|
||||
args = {
|
||||
"q": query,
|
||||
"p": params["pageno"],
|
||||
"l": "center", # political direction: "left", "center" or "right"
|
||||
}
|
||||
if tusk_categ == "images":
|
||||
params["url"] = f"{api_url}/Search/Image?{urlencode(args)}"
|
||||
else:
|
||||
# web response also contains news and videos
|
||||
params["url"] = f"{api_url}/Search/Web?{urlencode(args)}"
|
||||
|
||||
x_sid_header, x_sid_value = _obtain_x_sid()
|
||||
params["headers"] = {
|
||||
x_sid_header: x_sid_value,
|
||||
# required - we send a random longitude and latitude instead of the actual user location
|
||||
'x-lon': str(random.random() * 90),
|
||||
'x-lat': str(random.random() * 90),
|
||||
}
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response"):
|
||||
res = EngineResults()
|
||||
|
||||
json_resp = resp.json()["results"]
|
||||
|
||||
if tusk_categ == "web":
|
||||
for result in (json_resp.get("web") or {}).get("results", []):
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["url"],
|
||||
title=html_to_text(result["title"]),
|
||||
content=html_to_text(result["description"]),
|
||||
thumbnail=(result["thumbnail"] or {}).get("src") or "",
|
||||
)
|
||||
)
|
||||
elif tusk_categ == "news":
|
||||
for result in (json_resp.get("news") or {}).get("results", []):
|
||||
publishedDate = None
|
||||
try:
|
||||
publishedDate = parser.parse(result["age"])
|
||||
except parser.ParserError:
|
||||
pass
|
||||
|
||||
res.add(
|
||||
res.types.MainResult(
|
||||
url=result["url"],
|
||||
title=html_to_text(result["title"]),
|
||||
content=html_to_text(result["description"]),
|
||||
thumbnail=result["thumbnail"]["src"],
|
||||
publishedDate=publishedDate,
|
||||
)
|
||||
)
|
||||
elif tusk_categ == "videos":
|
||||
for result in (json_resp.get("videos") or {}).get("results", []):
|
||||
publishedDate = None
|
||||
try:
|
||||
publishedDate = parser.parse(result["age"])
|
||||
except parser.ParserError:
|
||||
pass
|
||||
|
||||
res.add(
|
||||
res.types.LegacyResult(
|
||||
template="videos.html",
|
||||
url=result["url"],
|
||||
title=html_to_text(result["title"]),
|
||||
content=html_to_text(result["description"]),
|
||||
thumbnail=result["thumbnail"]["src"],
|
||||
publishedDate=publishedDate,
|
||||
length=result["video"].get("duration"),
|
||||
)
|
||||
)
|
||||
elif tusk_categ == "images":
|
||||
for result in json_resp:
|
||||
res.add(
|
||||
res.types.Image(
|
||||
url=result["url"],
|
||||
title=html_to_text(result["title"]),
|
||||
img_src=result["properties"]["url"],
|
||||
thumbnail_src=result["thumbnail"]["src"],
|
||||
)
|
||||
)
|
||||
|
||||
return res
|
||||
@@ -444,27 +444,6 @@ engines:
|
||||
shortcut: conda
|
||||
disabled: true
|
||||
|
||||
- name: aol
|
||||
engine: aol
|
||||
search_type: search
|
||||
categories: [general]
|
||||
shortcut: aol
|
||||
disabled: true
|
||||
|
||||
- name: aol images
|
||||
engine: aol
|
||||
search_type: image
|
||||
categories: [images]
|
||||
shortcut: aoli
|
||||
disabled: true
|
||||
|
||||
- name: aol videos
|
||||
engine: aol
|
||||
search_type: video
|
||||
categories: [videos]
|
||||
shortcut: aolv
|
||||
disabled: true
|
||||
|
||||
- name: arch linux wiki
|
||||
engine: archlinux
|
||||
shortcut: al
|
||||
@@ -688,6 +667,26 @@ engines:
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: cl0q
|
||||
engine: json_engine
|
||||
shortcut: cl
|
||||
categories: general
|
||||
paging: true
|
||||
first_page_num: 0
|
||||
page_size: 20
|
||||
search_url: https://cl0q.com/search?q={query}&limit=20&offset={pageno}
|
||||
results_query: results
|
||||
url_query: domain
|
||||
url_prefix: https://
|
||||
title_query: title
|
||||
content_query: description
|
||||
disabled: true
|
||||
inactive: true
|
||||
about:
|
||||
website: https://cl0q.com
|
||||
description: "Open source network for searching domains"
|
||||
results: JSON
|
||||
|
||||
- name: cloudflareai
|
||||
engine: cloudflareai
|
||||
shortcut: cfai
|
||||
@@ -978,6 +977,34 @@ engines:
|
||||
shortcut: fd
|
||||
disabled: true
|
||||
|
||||
- name: findfiles
|
||||
engine: findfiles
|
||||
findfiles_categ: all
|
||||
categories: files
|
||||
shortcut: fif
|
||||
disabled: true
|
||||
|
||||
- name: findfiles images
|
||||
engine: findfiles
|
||||
findfiles_categ: image
|
||||
categories: images
|
||||
shortcut: fifi
|
||||
disabled: true
|
||||
|
||||
- name: findfiles videos
|
||||
engine: findfiles
|
||||
findfiles_categ: video
|
||||
categories: videos
|
||||
shortcut: fifv
|
||||
disabled: true
|
||||
|
||||
- name: findfiles music
|
||||
engine: findfiles
|
||||
findfiles_categ: audio
|
||||
categories: music
|
||||
shortcut: fifm
|
||||
disabled: true
|
||||
|
||||
- name: findthatmeme
|
||||
engine: findthatmeme
|
||||
shortcut: ftm
|
||||
@@ -1114,6 +1141,11 @@ engines:
|
||||
search_type: text
|
||||
timeout: 10
|
||||
|
||||
- name: giphy
|
||||
engine: giphy
|
||||
shortcut: gif
|
||||
disabled: true
|
||||
|
||||
- name: gitlab
|
||||
engine: gitlab
|
||||
base_url: https://gitlab.com
|
||||
@@ -1295,6 +1327,13 @@ engines:
|
||||
require_api_key: false
|
||||
results: JSON
|
||||
|
||||
- name: iseek
|
||||
engine: iseek
|
||||
shortcut: isk
|
||||
timeout: 4
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: il post
|
||||
engine: il_post
|
||||
shortcut: pst
|
||||
@@ -1383,6 +1422,23 @@ engines:
|
||||
# api_key: "" # required
|
||||
# kagi_categ: videos
|
||||
|
||||
- name: kozmonavt
|
||||
engine: xpath
|
||||
paging: true
|
||||
# pageno query param key is U+1D5D1 (looks like an "x")
|
||||
search_url: https://kozmonavt.su/s?q={query}&%F0%9D%97%91={pageno}
|
||||
shortcut: koz
|
||||
disabled: true
|
||||
# inactive: true
|
||||
results_xpath: //div[contains(@class, 'list')]/section
|
||||
url_xpath: concat('https://', substring-after(.//a/@href, '?q='))
|
||||
title_xpath: .//a
|
||||
content_xpath: .//div[contains(@class, 'snip')]
|
||||
about:
|
||||
website: https://kozmonavt.su
|
||||
description: "Web site directory with its own index"
|
||||
results: HTML
|
||||
|
||||
- name: jisho
|
||||
engine: jisho
|
||||
shortcut: js
|
||||
@@ -1400,6 +1456,22 @@ engines:
|
||||
shortcut: kc
|
||||
timeout: 4.0
|
||||
|
||||
- name: kukei
|
||||
engine: xpath
|
||||
categories: [general, blogs]
|
||||
search_url: https://kukei.eu/?q={query}
|
||||
shortcut: kuk
|
||||
disabled: true
|
||||
inactive: true
|
||||
results_xpath: //ul/li[contains(@class, "result-item-first-level")]
|
||||
url_xpath: .//a/@href
|
||||
title_xpath: .//h3
|
||||
content_xpath: .//p
|
||||
about:
|
||||
website: https://kukei.eu
|
||||
description: "Curated search for the small web"
|
||||
results: HTML
|
||||
|
||||
- name: lemmy communities
|
||||
engine: lemmy
|
||||
lemmy_type: Communities
|
||||
@@ -2033,7 +2105,7 @@ engines:
|
||||
- name: rawweb
|
||||
engine: json_engine
|
||||
shortcut: rw
|
||||
categories: general
|
||||
categories: [general, blogs]
|
||||
paging: true
|
||||
search_url: 'https://api.rawweb.org/api/search?keyword={query}&page={pageno}&lang=*'
|
||||
results_query: data
|
||||
@@ -2088,7 +2160,7 @@ engines:
|
||||
- name: searchmysite
|
||||
engine: xpath
|
||||
shortcut: sms
|
||||
categories: general
|
||||
categories: [general, blogs]
|
||||
paging: true
|
||||
search_url: https://searchmysite.net/search/?q={query}&page={pageno}
|
||||
results_xpath: //div[contains(@class,'search-result')]
|
||||
@@ -2404,6 +2476,35 @@ engines:
|
||||
- 5000
|
||||
inactive: true
|
||||
|
||||
- name: tusksearch
|
||||
engine: tusksearch
|
||||
shortcut: tu
|
||||
tusk_categ: web
|
||||
categories: general
|
||||
disabled: true
|
||||
|
||||
- name: tusksearch images
|
||||
engine: tusksearch
|
||||
shortcut: tui
|
||||
paging: false
|
||||
tusk_categ: images
|
||||
categories: images
|
||||
disabled: true
|
||||
|
||||
- name: tusksearch videos
|
||||
engine: tusksearch
|
||||
shortcut: tuv
|
||||
tusk_categ: videos
|
||||
categories: videos
|
||||
disabled: true
|
||||
|
||||
- name: tusksearch news
|
||||
engine: tusksearch
|
||||
shortcut: tun
|
||||
tusk_categ: news
|
||||
categories: news
|
||||
disabled: true
|
||||
|
||||
# tmp suspended - too slow, too many errors
|
||||
# - name: urbandictionary
|
||||
# engine : xpath
|
||||
@@ -2417,6 +2518,24 @@ engines:
|
||||
engine: unsplash
|
||||
shortcut: us
|
||||
|
||||
- name: unobtanium
|
||||
engine: xpath
|
||||
shortcut: uno
|
||||
categories: [general, blogs]
|
||||
paging: true
|
||||
first_page_num: 0
|
||||
search_url: https://unobtanium.rocks/search?search={query}&page={pageno}
|
||||
results_xpath: //ul[contains(@class, 'search-results')]/li
|
||||
url_xpath: .//a/@href
|
||||
title_xpath: .//a/b
|
||||
content_xpath: .//p[2]
|
||||
disabled: true
|
||||
inactive: true
|
||||
about:
|
||||
website: https://unobtanium.rocks
|
||||
description: "Personal websites focused search engine."
|
||||
results: HTML
|
||||
|
||||
- name: yandex
|
||||
engine: yandex
|
||||
categories: general
|
||||
@@ -2476,7 +2595,7 @@ engines:
|
||||
url_query: URL
|
||||
title_query: Title
|
||||
content_query: Snippet
|
||||
categories: [general, web]
|
||||
categories: [general, blogs]
|
||||
shortcut: wib
|
||||
disabled: true
|
||||
about:
|
||||
@@ -2901,6 +3020,38 @@ engines:
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: startpagina
|
||||
engine: startpagina
|
||||
shortcut: spnl
|
||||
startpagina_categ: web
|
||||
categories: general
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: startpagina images
|
||||
engine: startpagina
|
||||
shortcut: spnli
|
||||
startpagina_categ: images
|
||||
categories: images
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: startpagina videos
|
||||
engine: startpagina
|
||||
shortcut: spnlv
|
||||
startpagina_categ: videos
|
||||
categories: videos
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: startpagina news
|
||||
engine: startpagina
|
||||
shortcut: spnln
|
||||
startpagina_categ: news
|
||||
categories: news
|
||||
disabled: true
|
||||
inactive: true
|
||||
|
||||
- name: swisscows
|
||||
engine: swisscows
|
||||
categories: general
|
||||
@@ -3023,6 +3174,22 @@ engines:
|
||||
shortcut: wttr
|
||||
timeout: 9.0
|
||||
|
||||
- name: xonaly
|
||||
engine: xpath
|
||||
search_url: https://xonaly.com/?query={query}
|
||||
categories: general
|
||||
shortcut: xo
|
||||
disabled: true
|
||||
inactive: true
|
||||
results_xpath: //div[contains(@class, 'results-block')]/ul/li
|
||||
url_xpath: ./div[contains(@class, 'result-title')]/a/@href
|
||||
title_xpath: ./div[contains(@class, 'result-title')]/a
|
||||
content_xpath: ./p[contains(@class, 'excerpt')]
|
||||
about:
|
||||
website: https://xonaly.com
|
||||
description: "Canadian search engine with its own index"
|
||||
results: HTML
|
||||
|
||||
- name: zapmeta
|
||||
engine: xpath
|
||||
shortcut: zpm
|
||||
|
||||
Reference in New Issue
Block a user