2"""`Anna's Archive`_ is a free non-profit online shadow library metasearch
3engine providing access to a variety of book resources (also via IPFS), created
4by a team of anonymous archivists (AnnaArchivist_).
6.. _Anna's Archive: https://annas-archive.org/
7.. _AnnaArchivist: https://annas-software.org/AnnaArchivist/annas-archive
12The engine has the following additional settings:
18With this options a SearXNG maintainer is able to configure **additional**
19engines for specific searches in Anna's Archive. For example a engine to search
20for *newest* articles and journals (PDF) / by shortcut ``!aaa <search-term>``.
24 - name: annas articles
27 aa_content: 'magazine'
37from urllib.parse
import urlencode
39from lxml.etree
import ElementBase
41from searx.utils import extract_text, eval_xpath, eval_xpath_getindex, eval_xpath_list
52about: dict[str, t.Any] = {
53 "website":
"https://annas-archive.org/",
54 "wikidata_id":
"Q115288326",
55 "official_api_documentation":
None,
56 "use_official_api":
False,
57 "require_api_key":
False,
62categories: list[str] = [
"files"]
66base_url: str =
"https://annas-archive.org"
68"""Anan's search form field **Content** / possible values::
70 book_fiction, book_unknown, book_nonfiction,
71 book_comic, magazine, standards_document
73To not filter use an empty string (default).
76"""Sort Anna's results, possible values::
78 newest, oldest, largest, smallest
80To sort by *most relevant* use an empty string (default)."""
83"""Filter Anna's results by a file ending. Common filters for example are
88 Anna's Archive is a beta release: Filter results by file extension does not
89 really work on Anna's Archive.
94def init(engine_settings: dict[str, t.Any]) ->
None:
95 """Check of engine's settings."""
98 if aa_content
and aa_content
not in traits.custom[
'content']:
99 raise ValueError(f
'invalid setting content: {aa_content}')
101 if aa_sort
and aa_sort
not in traits.custom[
'sort']:
102 raise ValueError(f
'invalid setting sort: {aa_sort}')
104 if aa_ext
and aa_ext
not in traits.custom[
'ext']:
105 raise ValueError(f
'invalid setting ext: {aa_ext}')
108def request(query: str, params: dict[str, t.Any]) ->
None:
109 lang = traits.get_language(params[
"language"], traits.all_locale)
112 'content': aa_content,
116 'page': params[
'pageno'],
119 filtered_args = dict((k, v)
for k, v
in args.items()
if v)
120 params[
"url"] = f
"{base_url}/search?{urlencode(filtered_args)}"
123def response(resp:
"SXNG_Response") -> EngineResults:
125 dom = html.fromstring(resp.text)
132 for item
in eval_xpath_list(dom,
'//main//div[contains(@class, "js-aarecord-list-outer")]/div'):
135 except SearxEngineXPathException:
137 res.add(res.types.LegacyResult(**kwargs))
143 'template':
'paper.html',
144 'url': base_url + eval_xpath_getindex(item,
'./a/@href', 0),
145 'title': extract_text(eval_xpath(item,
'./div//a[starts-with(@href, "/md5")]')),
146 'authors': [extract_text(eval_xpath_getindex(item,
'.//a[starts-with(@href, "/search")]', 0))],
147 'publisher': extract_text(
148 eval_xpath_getindex(item,
'.//a[starts-with(@href, "/search")]', 1, default=
None), allow_none=
True
150 'content': extract_text(eval_xpath(item,
'.//div[contains(@class, "relative")]')),
151 'thumbnail': extract_text(eval_xpath_getindex(item,
'.//img/@src', 0, default=
None), allow_none=
True),
156 """Fetch languages and other search arguments from Anna's search form."""
163 engine_traits.all_locale =
''
164 engine_traits.custom[
'content'] = []
165 engine_traits.custom[
'ext'] = []
166 engine_traits.custom[
'sort'] = []
168 resp = get(base_url +
'/search')
170 raise RuntimeError(
"Response from Anna's search page is not OK.")
171 dom = html.fromstring(resp.text)
176 for x
in eval_xpath_list(dom,
"//form//input[@name='lang']"):
177 eng_lang = x.get(
"value")
178 if eng_lang
in (
'',
'_empty',
'nl-BE',
'und')
or eng_lang.startswith(
'anti__'):
181 locale = babel.Locale.parse(lang_map.get(eng_lang, eng_lang), sep=
'-')
182 except babel.UnknownLocaleError:
186 sxng_lang = language_tag(locale)
187 conflict = engine_traits.languages.get(sxng_lang)
189 if conflict != eng_lang:
190 print(
"CONFLICT: babel %s --> %s, %s" % (sxng_lang, conflict, eng_lang))
192 engine_traits.languages[sxng_lang] = eng_lang
194 for x
in eval_xpath_list(dom,
"//form//input[@name='content']"):
195 if not x.get(
"value").startswith(
"anti__"):
196 engine_traits.custom[
'content'].append(x.get(
"value"))
198 for x
in eval_xpath_list(dom,
"//form//input[@name='ext']"):
199 if not x.get(
"value").startswith(
"anti__"):
200 engine_traits.custom[
'ext'].append(x.get(
"value"))
202 for x
in eval_xpath_list(dom,
"//form//select[@name='sort']//option"):
203 engine_traits.custom[
'sort'].append(x.get(
"value"))
206 engine_traits.custom[
'content'].sort()
207 engine_traits.custom[
'ext'].sort()
208 engine_traits.custom[
'sort'].sort()
None init(dict[str, t.Any] engine_settings)
EngineResults response("SXNG_Response" resp)
fetch_traits(EngineTraits engine_traits)
None request(str query, dict[str, t.Any] params)
dict[str, t.Any] _get_result(ElementBase item)