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'
36from typing
import List, Dict, Any, Optional
37from urllib.parse
import quote
40from searx.utils import extract_text, eval_xpath, eval_xpath_list
41from searx.enginelib.traits
import EngineTraits
45about: Dict[str, Any] = {
46 "website":
"https://annas-archive.org/",
47 "wikidata_id":
"Q115288326",
48 "official_api_documentation":
None,
49 "use_official_api":
False,
50 "require_api_key":
False,
55categories: List[str] = [
"files"]
59base_url: str =
"https://annas-archive.org"
61"""Anan's search form field **Content** / possible values::
63 book_fiction, book_unknown, book_nonfiction,
64 book_comic, magazine, standards_document
66To not filter use an empty string (default).
69"""Sort Anna's results, possible values::
71 newest, oldest, largest, smallest
73To sort by *most relevant* use an empty string (default)."""
76"""Filter Anna's results by a file ending. Common filters for example are
81 Anna's Archive is a beta release: Filter results by file extension does not
82 really work on Anna's Archive.
87def init(engine_settings=None):
88 """Check of engine's settings."""
91 if aa_content
and aa_content
not in traits.custom[
'content']:
92 raise ValueError(f
'invalid setting content: {aa_content}')
94 if aa_sort
and aa_sort
not in traits.custom[
'sort']:
95 raise ValueError(f
'invalid setting sort: {aa_sort}')
97 if aa_ext
and aa_ext
not in traits.custom[
'ext']:
98 raise ValueError(f
'invalid setting ext: {aa_ext}')
101def request(query, params: Dict[str, Any]) -> Dict[str, Any]:
103 lang = traits.get_language(params[
"language"], traits.all_locale)
104 params[
"url"] = base_url + f
"/search?lang={lang or ''}&content={aa_content}&ext={aa_ext}&sort={aa_sort}&q={q}"
108def response(resp) -> List[Dict[str, Optional[str]]]:
109 results: List[Dict[str, Optional[str]]] = []
110 dom = html.fromstring(resp.text)
112 for item
in eval_xpath_list(dom,
'//main//div[contains(@class, "h-[125]")]/a'):
121 for item
in eval_xpath_list(dom,
'//main//div[contains(@class, "js-scroll-hidden")]'):
122 item = html.fromstring(item.xpath(
'./comment()')[0].text)
130 'template':
'paper.html',
131 'url': base_url + item.xpath(
'./@href')[0],
132 'title': extract_text(eval_xpath(item,
'.//h3/text()[1]')),
133 'publisher': extract_text(eval_xpath(item,
'.//div[contains(@class, "text-sm")]')),
134 'authors': [extract_text(eval_xpath(item,
'.//div[contains(@class, "italic")]'))],
135 'content': extract_text(eval_xpath(item,
'.//div[contains(@class, "text-xs")]')),
136 'img_src': item.xpath(
'.//img/@src')[0],
141 """Fetch languages and other search arguments from Anna's search form."""
148 engine_traits.all_locale =
''
149 engine_traits.custom[
'content'] = []
150 engine_traits.custom[
'ext'] = []
151 engine_traits.custom[
'sort'] = []
153 resp = get(base_url +
'/search')
155 raise RuntimeError(
"Response from Anna's search page is not OK.")
156 dom = html.fromstring(resp.text)
161 for x
in eval_xpath_list(dom,
"//form//input[@name='lang']"):
162 eng_lang = x.get(
"value")
163 if eng_lang
in (
'',
'_empty',
'nl-BE',
'und'):
166 locale = babel.Locale.parse(lang_map.get(eng_lang, eng_lang), sep=
'-')
167 except babel.UnknownLocaleError:
171 sxng_lang = language_tag(locale)
172 conflict = engine_traits.languages.get(sxng_lang)
174 if conflict != eng_lang:
175 print(
"CONFLICT: babel %s --> %s, %s" % (sxng_lang, conflict, eng_lang))
177 engine_traits.languages[sxng_lang] = eng_lang
179 for x
in eval_xpath_list(dom,
"//form//input[@name='content']"):
180 engine_traits.custom[
'content'].append(x.get(
"value"))
182 for x
in eval_xpath_list(dom,
"//form//input[@name='ext']"):
183 engine_traits.custom[
'ext'].append(x.get(
"value"))
185 for x
in eval_xpath_list(dom,
"//form//select[@name='sort']//option"):
186 engine_traits.custom[
'sort'].append(x.get(
"value"))
fetch_traits(EngineTraits engine_traits)
List[Dict[str, Optional[str]]] response(resp)
Dict[str, Any] request(query, Dict[str, Any] params)
init(engine_settings=None)