standalone_searx.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python
  2. # SPDX-License-Identifier: AGPL-3.0-or-later
  3. """Script to run SearXNG from terminal.
  4. DON'T USE THIS SCRIPT!!
  5. .. danger::
  6. Be warned, using the ``standalone_searx.py`` won't give you privacy!
  7. On the contrary, this script behaves like a SearXNG server: your IP is
  8. exposed and tracked by all active engines (google, bing, qwant, ... ), with
  9. every query!
  10. .. note::
  11. This is an old and grumpy hack / SearXNG is a Flask application with
  12. client/server structure, which can't be turned into a command line tool the
  13. way it was done here.
  14. Getting categories without initiate the engine will only return `['general']`
  15. >>> import searx.engines
  16. ... list(searx.engines.categories.keys())
  17. ['general']
  18. >>> import searx.search
  19. ... searx.search.initialize()
  20. ... list(searx.engines.categories.keys())
  21. ['general', 'it', 'science', 'images', 'news', 'videos', 'music', 'files', 'social media', 'map']
  22. Example to use this script:
  23. .. code:: bash
  24. $ python3 searxng_extra/standalone_searx.py rain
  25. """ # pylint: disable=line-too-long
  26. import argparse
  27. import sys
  28. from datetime import datetime
  29. from json import dumps
  30. from typing import Any, Dict, List, Optional
  31. import searx
  32. import searx.preferences
  33. import searx.query
  34. import searx.search
  35. import searx.webadapter
  36. EngineCategoriesVar = Optional[List[str]]
  37. def get_search_query(
  38. args: argparse.Namespace, engine_categories: EngineCategoriesVar = None
  39. ) -> searx.search.SearchQuery:
  40. """Get search results for the query"""
  41. if engine_categories is None:
  42. engine_categories = list(searx.engines.categories.keys())
  43. try:
  44. category = args.category.decode('utf-8')
  45. except AttributeError:
  46. category = args.category
  47. form = {
  48. "q": args.query,
  49. "categories": category,
  50. "pageno": str(args.pageno),
  51. "language": args.lang,
  52. "time_range": args.timerange,
  53. }
  54. preferences = searx.preferences.Preferences(['simple'], engine_categories, searx.engines.engines, [])
  55. preferences.key_value_settings['safesearch'].parse(args.safesearch)
  56. search_query = searx.webadapter.get_search_query_from_webapp(preferences, form)[0]
  57. return search_query
  58. def no_parsed_url(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
  59. """Remove parsed url from dict."""
  60. for result in results:
  61. del result['parsed_url']
  62. return results
  63. def json_serial(obj: Any) -> Any:
  64. """JSON serializer for objects not serializable by default json code.
  65. :raise TypeError: raised when **obj** is not serializable
  66. """
  67. if isinstance(obj, datetime):
  68. serial = obj.isoformat()
  69. return serial
  70. if isinstance(obj, bytes):
  71. return obj.decode('utf8')
  72. if isinstance(obj, set):
  73. return list(obj)
  74. raise TypeError("Type ({}) not serializable".format(type(obj)))
  75. def to_dict(search_query: searx.search.SearchQuery) -> Dict[str, Any]:
  76. """Get result from parsed arguments."""
  77. result_container = searx.search.Search(search_query).search()
  78. result_container_json = {
  79. "search": {
  80. "q": search_query.query,
  81. "pageno": search_query.pageno,
  82. "lang": search_query.lang,
  83. "safesearch": search_query.safesearch,
  84. "timerange": search_query.time_range,
  85. },
  86. "results": no_parsed_url(result_container.get_ordered_results()),
  87. "infoboxes": result_container.infoboxes,
  88. "suggestions": list(result_container.suggestions),
  89. "answers": list(result_container.answers),
  90. "paging": result_container.paging,
  91. "number_of_results": result_container.number_of_results,
  92. }
  93. return result_container_json
  94. def parse_argument(
  95. args: Optional[List[str]] = None, category_choices: EngineCategoriesVar = None
  96. ) -> argparse.Namespace:
  97. """Parse command line.
  98. :raise SystemExit: Query argument required on `args`
  99. Examples:
  100. >>> import importlib
  101. ... # load module
  102. ... spec = importlib.util.spec_from_file_location(
  103. ... 'utils.standalone_searx', 'utils/standalone_searx.py')
  104. ... sas = importlib.util.module_from_spec(spec)
  105. ... spec.loader.exec_module(sas)
  106. ... sas.parse_argument()
  107. usage: ptipython [-h] [--category [{general}]] [--lang [LANG]] [--pageno [PAGENO]] [--safesearch [{0,1,2}]] [--timerange [{day,week,month,year}]]
  108. query
  109. SystemExit: 2
  110. >>> sas.parse_argument(['rain'])
  111. Namespace(category='general', lang='all', pageno=1, query='rain', safesearch='0', timerange=None)
  112. """ # noqa: E501
  113. if not category_choices:
  114. category_choices = list(searx.engines.categories.keys())
  115. parser = argparse.ArgumentParser(description='Standalone searx.')
  116. parser.add_argument('query', type=str, help='Text query')
  117. parser.add_argument(
  118. '--category', type=str, nargs='?', choices=category_choices, default='general', help='Search category'
  119. )
  120. parser.add_argument('--lang', type=str, nargs='?', default='all', help='Search language')
  121. parser.add_argument('--pageno', type=int, nargs='?', default=1, help='Page number starting from 1')
  122. parser.add_argument(
  123. '--safesearch',
  124. type=str,
  125. nargs='?',
  126. choices=['0', '1', '2'],
  127. default='0',
  128. help='Safe content filter from none to strict',
  129. )
  130. parser.add_argument(
  131. '--timerange', type=str, nargs='?', choices=['day', 'week', 'month', 'year'], help='Filter by time range'
  132. )
  133. return parser.parse_args(args)
  134. if __name__ == '__main__':
  135. settings_engines = searx.settings['engines']
  136. searx.search.load_engines(settings_engines)
  137. engine_cs = list(searx.engines.categories.keys())
  138. prog_args = parse_argument(category_choices=engine_cs)
  139. searx.search.initialize_network(settings_engines, searx.settings['outgoing'])
  140. searx.search.check_network_configuration()
  141. searx.search.initialize_metrics([engine['name'] for engine in settings_engines])
  142. searx.search.initialize_processors(settings_engines)
  143. search_q = get_search_query(prog_args, engine_categories=engine_cs)
  144. res_dict = to_dict(search_q)
  145. sys.stdout.write(dumps(res_dict, sort_keys=True, indent=4, ensure_ascii=False, default=json_serial))