1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Tiger_ is a Swiss meta search engine.
.. _Tiger: https://tiger.ch
"""
from json import loads
import random
from urllib.parse import urlencode
import typing as t
from dateutil import parser
from lxml import html
from searx.exceptions import SearxEngineAPIException
from searx.extended_types import SXNG_Response
from searx.network import get, post
from searx.result_types import EngineResults
from searx.utils import extr, eval_xpath_list, eval_xpath, extract_text
from searx.enginelib import EngineCache
if t.TYPE_CHECKING:
from searx.search.processors import OnlineParams
about = {
"website": "https://tiger.ch",
"official_api_documentation": None,
"use_official_api": False,
"require_api_key": False,
"results": "HTML",
}
paging = True
base_url = "https://tiger.ch"
categories = []
tiger_category = "Websuche"
"""
Possible values: "Websuche", "News".
"""
CACHE: EngineCache
"""Cache to store session codes (result of solved CAPTCHA)."""
def setup(engine_settings: dict[str, t.Any]) -> bool:
if tiger_category not in ("Websuche", "News"):
raise ValueError("invalid search category: %s" % tiger_category)
global CACHE # pylint: disable=global-statement
CACHE = EngineCache(engine_settings["name"])
return True
def _obtain_session_code() -> str:
"""The challenge works like this:
- We first generate 3 random numbers.
- Then we send them to /Human.svc/Make to get the operands (+, -) for the
math challenge (i.e. a simple calculation)
- Based on the operands, we calculate a result (usually done by the user by
hand)
- We send the result of the math calculation to the server to obtain a
session "code" that has to be sent as cookie parameter for all searches
E.g., challenges look like ``19-3+5``.
"""
cached_session = CACHE.get("session")
if cached_session:
return cached_session
results_page = get(f"{base_url}/checkCode.aspx")
doc = html.fromstring(results_page.text)
extra_data: dict[str, str] = {}
for extra_param in ("__VIEWSTATE", "__VIEWSTATEGENERATOR", "__EVENTVALIDATION"):
extra_data[extra_param] = doc.xpath(f"//input[@name='{extra_param}']/@value")[0]
# var z1 = Math.floor((Math.random() * 8) + 11);
# var z2 = Math.floor((Math.random() * 8) + 1);
# var z3 = Math.floor((Math.random() * 8) + 1);
num1 = random.randint(11, 19)
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)
signs = loads(challenge.json()["d"])[0]
sign1 = signs["Z1"]
sign2 = signs["Z2"]
result = num1
for num, sign in [(num2, sign1), (num3, sign2)]:
if sign == "+":
result += num
else:
result -= num
logger.debug(f"got challenge: {num1} {sign1} {num2} {sign2} {num3} = {result}")
data = {
**extra_data,
"txtM": str(result),
"btnHuman": "OK",
}
challenge_response = post(
f"{base_url}/checkCode.aspx",
cookies=results_page.cookies,
data=data,
)
cookie = challenge_response.cookies["Tiger.ch"]
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
def request(query: str, params: "OnlineParams"):
code = _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"
def response(resp: "SXNG_Response") -> EngineResults:
res = EngineResults()
doc = html.fromstring(resp.text)
if tiger_category == "Websuche":
for result in eval_xpath_list(doc, "//div[@id='mainContainer']//table/tr"):
url = extract_text(eval_xpath(result, ".//a[contains(@class, 'weblink')]/@href"))
if not url:
continue
res.add(
res.types.MainResult(
url=url,
title=extract_text(eval_xpath(result, ".//a[contains(@class, 'weblink')]")) or "",
content=extract_text(eval_xpath(result, ".//*[contains(@class, 'webbodynopic')]")) or "",
)
)
for suggestion in eval_xpath_list(doc, "//a[contains(@class, 'linkAnders')]"):
res.add(res.types.LegacyResult(suggestion=extract_text(suggestion)))
elif tiger_category == "News":
for result in eval_xpath_list(doc, "//div[@id='panNews']/div"):
publishedDate = None
try:
date_str = extract_text(eval_xpath(result, ".//span[contains(@class, 'help')]/span")) or ""
date_str = date_str.strip().removeprefix("-").strip()
publishedDate = parser.parse(date_str)
except parser.ParserError:
pass
thumbnail = extract_text(eval_xpath(result, "./img/@src"))
if thumbnail:
thumbnail = base_url + thumbnail
res.add(
res.types.MainResult(
url=extract_text(eval_xpath(result, ".//a[contains(@class, 'webLink')]/@href")),
title=extract_text(eval_xpath(result, ".//a[contains(@class, 'webLink')]")) or "",
thumbnail=thumbnail or "",
publishedDate=publishedDate,
)
)
return res
|