summaryrefslogtreecommitdiff
path: root/searx/engines/tiger.py
blob: a7b429a89f9a67942451a6ea72b5169ee2a2996a (plain)
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