searx.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. ########################################################################
  2. # searxpy - Provides Python modules for interacting with searx and
  3. # searx-stats2 instances.
  4. # Copyright (C) 2020 CYBERDEViL
  5. #
  6. # This file is part of searxpy.
  7. #
  8. # searxpy is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # searxpy is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #
  21. ########################################################################
  22. import requests
  23. from requests.exceptions import HTTPError, ConnectionError, Timeout
  24. from copy import deepcopy
  25. import json
  26. class SearchErrorType:
  27. HttpError = 0
  28. ConnectionError = 1
  29. Timeout = 3
  30. WrongStatus = 4
  31. DecodeError = 5
  32. NoResults = 6
  33. class SearchResult:
  34. def __init__(self, response=None, err="", errType=None):
  35. self._response = response
  36. self._err = err
  37. self._errType = errType
  38. if errType is None and response.status_code != 200:
  39. self._errType = SearchErrorType.WrongStatus
  40. self._err = "WrongStatus: {0}".format(self._response.status_code)
  41. else:
  42. try:
  43. self.json()
  44. except json.JSONDecodeError as err:
  45. self._errType = SearchErrorType.DecodeError
  46. self._err = "DecodeError: `{0}`".format(err)
  47. if not len(self.json().get('results', {})):
  48. self._errType = SearchErrorType.NoResults
  49. self._err = "NoResults: got: `{0}`".format(self.json())
  50. def __bool__(self):
  51. return not self.failed()
  52. def errorType(self):
  53. """
  54. @return: Error type (see SearchErrorType), None if no error.
  55. @rtype: int or None
  56. """
  57. return self._errType
  58. def error(self):
  59. """
  60. @return: Error message if a error occured, else empty string.
  61. @rtype: str
  62. """
  63. return self._err
  64. def failed(self):
  65. """ Use this to check if there occured a error first!
  66. @return: True if a error occured, False if all is good.
  67. @rtype: bool
  68. """
  69. if self._errType is not None:
  70. return True
  71. return False
  72. def json(self):
  73. """ Response data as json object.
  74. @rtype: json
  75. """
  76. return json.loads(self._response.content)
  77. def data(self):
  78. """
  79. @return: Response data as a dict object.
  80. @rtype: dict
  81. """
  82. if self.errorType() is not None:
  83. return {}
  84. return dict(self.json())
  85. class Categories:
  86. types = {
  87. 'general': ('General', 'category_general'),
  88. 'files': ('Files', 'category_files'),
  89. 'images': ('Images', 'category_images'),
  90. 'videos': ('Videos', 'category_videos'),
  91. 'it': ('IT', 'category_it'),
  92. 'map': ('Location', 'category_map'),
  93. 'music': ('Music', 'category_music'),
  94. 'news': ('News', 'category_news'),
  95. 'science': ('Science', 'category_science'),
  96. 'social': ('Social', 'category_social media')
  97. }
  98. def __init__(self):
  99. """ Enabled / disable certain search catagories.
  100. """
  101. self._options = {}
  102. self.__makeOptions()
  103. def __makeOptions(self):
  104. self._options.clear()
  105. for key, t in self.types.items():
  106. self._options.update({key: False})
  107. def get(self, key):
  108. """
  109. @param key: One of the keys in Categories.types
  110. @type key: str
  111. @return: Returns a tuple with two values.
  112. [0] str Fancy name
  113. [1] str Searx category var name
  114. @rtype: tuple
  115. """
  116. return self._options[key]
  117. def set(self, key, state):
  118. """
  119. @param key: One of the keys in Categories.types
  120. @type key: str
  121. @param state: Enabled / disabled state
  122. @type state: bool
  123. """
  124. self._options[key] = state
  125. def dict(self):
  126. """Returns data on what categories to use, this can be posted or
  127. added as get vars for a request to a Searx instance.
  128. @rtype: dict
  129. """
  130. newDict = {}
  131. for key, state in self._options.items():
  132. if state:
  133. newDict.update({self.types[key][1]: 'on'})
  134. return newDict
  135. class Searx:
  136. def __init__(self, url=""):
  137. """ Preform searches on Searx instances.
  138. @param url: Url of the Searx instance
  139. @type url: str
  140. """
  141. self._url = url
  142. self._categories = Categories()
  143. self._kwargs = {
  144. 'data': {
  145. 'q': '',
  146. 'format': 'json',
  147. 'lang': 'all',
  148. 'pageno': '1'
  149. },
  150. }
  151. @property
  152. def categories(self):
  153. """
  154. @return: Enable / disable search categories with the returned
  155. object.
  156. @rtype: Categories
  157. """
  158. return self._categories
  159. @property
  160. def url(self):
  161. """
  162. @return: Instance url
  163. @rtype: str
  164. """
  165. return self._url
  166. @url.setter
  167. def url(self, url):
  168. """
  169. @param url: Instance url
  170. @type url: str
  171. """
  172. self._url = url
  173. @property
  174. def query(self):
  175. """
  176. @return: Search query
  177. @rtype: str
  178. """
  179. return self._kwargs['data']['q']
  180. @query.setter
  181. def query(self, q):
  182. """
  183. @param q: Search query
  184. @type q: str
  185. """
  186. self._kwargs['data']['q'] = q
  187. @property
  188. def lang(self):
  189. """
  190. @return: Language code
  191. @rtype: str
  192. """
  193. return self._kwargs['data']['lang']
  194. @lang.setter
  195. def lang(self, lang):
  196. """
  197. @param lang: Language code
  198. @type lang: str
  199. """
  200. self._kwargs['data']['lang'] = lang
  201. @property
  202. def pageno(self):
  203. """
  204. @return: Page number
  205. @rtype: int
  206. """
  207. return int(self._kwargs['data']['pageno'])
  208. @pageno.setter
  209. def pageno(self, i):
  210. """
  211. @param i: Page number
  212. @type i: int
  213. """
  214. self._kwargs['data']['pageno'] = str(i)
  215. def search(self, requestKwargs={}):
  216. """ Preform search
  217. @param requestKwargs: Keyworded arguments passed to requests.post
  218. for example setting a proxy or setting ssl behaviour.
  219. @type requestKwargs: dict
  220. @return: Search results
  221. @rtype: SearchResult
  222. """
  223. requestKwargs.update(deepcopy(self._kwargs))
  224. requestKwargs['data'].update(self.categories.dict())
  225. response = None
  226. err = ""
  227. errType = None
  228. try:
  229. response = requests.post(self._url, **requestKwargs)
  230. except HTTPError as e:
  231. print("Search failed! HTTPError: {0}".format(e))
  232. errType = SearchErrorType.HttpError
  233. err = e
  234. except ConnectionError as e:
  235. print("Search failed! ConnectionError: {0}".format(e))
  236. errType = SearchErrorType.ConnectionError
  237. err = e
  238. except Timeout as e:
  239. print("Search failed! Timeout: {0}".format(e))
  240. errType = SearchErrorType.Timeout
  241. err = e
  242. return SearchResult(response=response, err=err, errType=errType)