mirror of
https://github.com/searxng/searxng.git
synced 2026-11-06 22:06:29 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac11e14c5e | ||
|
|
e45dc87f93 | ||
|
|
a3013ef11e | ||
|
|
87cbfef309 | ||
|
|
583bf7b810 |
@@ -4,7 +4,7 @@ cov-core==1.15.0
|
||||
black==26.10.1
|
||||
pylint==4.1.3
|
||||
splinter==0.21.0
|
||||
selenium==4.51.0
|
||||
selenium==4.52.0
|
||||
Sphinx==8.2.3;python_version <= "3.11"
|
||||
Sphinx==9.1.0; python_version > "3.11"
|
||||
sphinx-issues==6.0.0
|
||||
@@ -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.8.5
|
||||
granian[reload]==2.8.7
|
||||
basedpyright==1.40.2
|
||||
types-lxml==2026.2.16
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
granian==2.8.5
|
||||
granian[pname]==2.8.5
|
||||
granian==2.8.7
|
||||
granian[pname]==2.8.7
|
||||
|
||||
+20
-11
@@ -43,6 +43,9 @@ Possible values: "Websuche", "News".
|
||||
CACHE: EngineCache
|
||||
"""Cache to store session codes (result of solved CAPTCHA)."""
|
||||
|
||||
_TIGER_SESSION_COOKIE = "Tiger.ch"
|
||||
_ASP_NET_SESSION_COOKIE = "ASP.NET_SessionId"
|
||||
|
||||
|
||||
def setup(engine_settings: dict[str, t.Any]) -> bool:
|
||||
if tiger_category not in ("Websuche", "News"):
|
||||
@@ -53,7 +56,7 @@ def setup(engine_settings: dict[str, t.Any]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _obtain_session_code() -> str:
|
||||
def _obtain_session_code() -> tuple[str, str]:
|
||||
"""The challenge works like this:
|
||||
|
||||
- We first generate 3 random numbers.
|
||||
@@ -68,10 +71,13 @@ def _obtain_session_code() -> str:
|
||||
"""
|
||||
cached_session = CACHE.get("session")
|
||||
if cached_session:
|
||||
return cached_session
|
||||
return cached_session.split("|")
|
||||
|
||||
results_page = get(f"{base_url}/checkCode.aspx")
|
||||
doc = results_page.html()
|
||||
challenge_page = get(f"{base_url}/checkCode.aspx")
|
||||
asp_session = challenge_page.cookies[_ASP_NET_SESSION_COOKIE]
|
||||
if not asp_session:
|
||||
raise SearxEngineAPIException("missing asp session cookie")
|
||||
doc = challenge_page.html()
|
||||
|
||||
extra_data: dict[str, str] = {}
|
||||
for extra_param in ("__VIEWSTATE", "__VIEWSTATEGENERATOR", "__EVENTVALIDATION"):
|
||||
@@ -84,7 +90,7 @@ def _obtain_session_code() -> str:
|
||||
num2 = random.randint(1, 9)
|
||||
num3 = random.randint(1, 9)
|
||||
|
||||
challenge = get(f"{base_url}/Services/Human.svc/Make?M1={num1}&M2={num2}&M3={num3}", cookies=results_page.cookies)
|
||||
challenge = get(f"{base_url}/Services/Human.svc/Make?M1={num1}&M2={num2}&M3={num3}", cookies=challenge_page.cookies)
|
||||
signs = loads(challenge.json()["d"])[0]
|
||||
sign1 = signs["Z1"]
|
||||
sign2 = signs["Z2"]
|
||||
@@ -105,26 +111,29 @@ def _obtain_session_code() -> str:
|
||||
|
||||
challenge_response = post(
|
||||
f"{base_url}/checkCode.aspx",
|
||||
cookies=results_page.cookies,
|
||||
cookies=challenge_page.cookies,
|
||||
data=data,
|
||||
)
|
||||
|
||||
cookie = challenge_response.cookies["Tiger.ch"]
|
||||
cookie = challenge_response.cookies[_TIGER_SESSION_COOKIE]
|
||||
code = extr(cookie, "Code=", "&")
|
||||
if not code:
|
||||
raise SearxEngineAPIException("failed to obtain session code")
|
||||
|
||||
CACHE.set("session", code, expire=60 * 24 * 60 * 60) # cookie is valid for two months
|
||||
return code
|
||||
CACHE.set("session", f"{code}|{asp_session}", expire=60 * 24 * 60 * 60) # cookie is valid for two months
|
||||
return code, asp_session
|
||||
|
||||
|
||||
def request(query: str, params: "OnlineParams"):
|
||||
code = _obtain_session_code()
|
||||
code, asp_session = _obtain_session_code()
|
||||
args = {"w": query, "page": params["pageno"]}
|
||||
params["url"] = f"{base_url}/{tiger_category}?{urlencode(args)}"
|
||||
# Setting Checked=1 shows related search terms / suggestions
|
||||
# Language and country could be set with Lng= and Land= in the future
|
||||
params["cookies"]["Tiger.ch"] = f"Tiger.ch=&Code={code}&Checked=1"
|
||||
params["cookies"] = {
|
||||
_TIGER_SESSION_COOKIE: f"Code={code}&Checked=1",
|
||||
_ASP_NET_SESSION_COOKIE: asp_session,
|
||||
}
|
||||
|
||||
|
||||
def response(resp: "SXNG_Response") -> EngineResults:
|
||||
|
||||
@@ -15,6 +15,15 @@ PluginStorage (it is not available for selection).
|
||||
'(.*\\.)?youtu\\.be$': 'invidious.example.com'
|
||||
...
|
||||
|
||||
If needed, you can also capture patterns from the URL with ``(?P<name>...)``
|
||||
and use the matched pattern in the replacement term by referencing ``<name>``.
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
'(?P<subdomain>.*)\\.stackexchange\\.com$': 'anonymousoverflow.example.com/<subdomain>'
|
||||
|
||||
For instance, this will rewrite ``math.stackexchange.com`` to ``anonymousoverflow.example.com/math``.
|
||||
|
||||
- ``hostnames.remove``: A **list** of regular expressions of the hostnames whose
|
||||
results should be taken from the results list.
|
||||
|
||||
@@ -192,7 +201,12 @@ def filter_url_field(result: "Result|LegacyResult", field_name: str, url_src: st
|
||||
return False
|
||||
|
||||
for pattern, replacement in REPLACE.items():
|
||||
if pattern.search(url_src_parsed.netloc):
|
||||
if match := pattern.search(url_src_parsed.netloc):
|
||||
# replace named groups, e.g. for the pattern `(?P<subdomain>.*)\.example\.com` and the input
|
||||
# "foo.example.com", this will replace the <subdomain> variable in the result with "foo"
|
||||
for name, value in match.groupdict().items():
|
||||
replacement = replacement.replace(f"<{name}>", value)
|
||||
|
||||
new_url = url_src_parsed._replace(netloc=pattern.sub(replacement, url_src_parsed.netloc))
|
||||
new_url = urlunparse(new_url)
|
||||
return new_url
|
||||
|
||||
Binary file not shown.
@@ -28,19 +28,20 @@
|
||||
# mvtbh <mvtbh@noreply.codeberg.org>, 2026.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: searx\n"
|
||||
"Project-Id-Version: searx\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2026-10-09 19:03+0000\n"
|
||||
"PO-Revision-Date: 2026-08-04 12:25+0000\n"
|
||||
"PO-Revision-Date: 2026-11-02 02:27+0000\n"
|
||||
"Last-Translator: mvtbh <mvtbh@noreply.codeberg.org>\n"
|
||||
"Language: fa_IR\n"
|
||||
"Language-Team: Persian "
|
||||
"<https://translate.codeberg.org/projects/searxng/searxng/fa/>\n"
|
||||
"Language-Team: Persian <https://translate.codeberg.org/projects/searxng/"
|
||||
"searxng/fa/>\n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.18.0\n"
|
||||
"X-Generator: Weblate 2026.10.1\n"
|
||||
|
||||
#. CONSTANT_NAMES['NO_SUBGROUPING']
|
||||
#: searx/searxng.msg
|
||||
@@ -60,7 +61,7 @@ msgstr "برنامه ها"
|
||||
#. CATEGORY_NAMES['BLOGS']
|
||||
#: searx/searxng.msg
|
||||
msgid "blogs"
|
||||
msgstr ""
|
||||
msgstr "وبلاگ ها"
|
||||
|
||||
#. CATEGORY_NAMES['DICTIONARIES']
|
||||
#: searx/searxng.msg
|
||||
@@ -105,7 +106,7 @@ msgstr "نقشه"
|
||||
#. CATEGORY_NAMES['MOVIES']
|
||||
#: searx/searxng.msg
|
||||
msgid "movies"
|
||||
msgstr ""
|
||||
msgstr "فیلم ها"
|
||||
|
||||
#. CATEGORY_NAMES['MUSIC']
|
||||
#: searx/searxng.msg
|
||||
@@ -165,12 +166,12 @@ msgstr "ویکیهای نرمافزارها"
|
||||
#. CATEGORY_NAMES['STOCK_IMAGES']
|
||||
#: searx/searxng.msg
|
||||
msgid "stock images"
|
||||
msgstr ""
|
||||
msgstr "تصاویر آماده"
|
||||
|
||||
#. CATEGORY_NAMES['TRANSLATE']
|
||||
#: searx/searxng.msg
|
||||
msgid "translate"
|
||||
msgstr ""
|
||||
msgstr "ترجمه"
|
||||
|
||||
#. CATEGORY_NAMES['TV']
|
||||
#: searx/searxng.msg
|
||||
@@ -185,7 +186,7 @@ msgstr "ویدیوها"
|
||||
#. CATEGORY_NAMES['WEATHER']
|
||||
#: searx/searxng.msg
|
||||
msgid "weather"
|
||||
msgstr ""
|
||||
msgstr "آب و هوا"
|
||||
|
||||
#. CATEGORY_NAMES['WEB']
|
||||
#: searx/searxng.msg
|
||||
@@ -2531,4 +2532,3 @@ msgstr "پنهانسازی ویدئو"
|
||||
|
||||
#~ msgid "Select service used by DOI rewrite"
|
||||
#~ msgstr "سرویس مورد استفاده توسط بازنویسی DOI را انتخاب کنید"
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# pylint: disable=missing-module-docstring,disable=missing-class-docstring,invalid-name
|
||||
|
||||
import re
|
||||
|
||||
from parameterized.parameterized import parameterized
|
||||
|
||||
from searx.plugins import hostnames
|
||||
from searx.result_types import MainResult
|
||||
from tests import SearxTestCase
|
||||
|
||||
|
||||
class PluginHostnameReplaceTest(SearxTestCase):
|
||||
@parameterized.expand(
|
||||
[
|
||||
(
|
||||
{re.compile(r".*.baz\.com")},
|
||||
{},
|
||||
[
|
||||
("https://baz.com", True),
|
||||
("https://foo.baz.com", False),
|
||||
],
|
||||
),
|
||||
(
|
||||
set(),
|
||||
{
|
||||
re.compile(r"example\.com"): "example.org",
|
||||
},
|
||||
[
|
||||
("https://example.com", "https://example.org"),
|
||||
("https://foo.example.com", "https://foo.example.org"),
|
||||
("https://example.net", True),
|
||||
],
|
||||
),
|
||||
(
|
||||
set(),
|
||||
{
|
||||
re.compile(r"(?P<subdomain>.*)\.foo\.bar"): "foo.bar/<subdomain>",
|
||||
},
|
||||
[
|
||||
("https://sub.foo.bar", "https://foo.bar/sub"),
|
||||
("https://foo.bar", True),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_filter_url_field(
|
||||
self, remove: set[re.Pattern], replace: dict[re.Pattern, str], test_cases: list[tuple[str, str]]
|
||||
):
|
||||
hostnames.REMOVE = remove
|
||||
hostnames.REPLACE = replace
|
||||
|
||||
for input_url, expected_result in test_cases:
|
||||
result = hostnames.filter_url_field(MainResult(url=input_url), "url", input_url)
|
||||
self.assertEqual(result, expected_result)
|
||||
Reference in New Issue
Block a user