dailymotion.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """
  3. Dailymotion (Videos)
  4. ~~~~~~~~~~~~~~~~~~~~
  5. .. _REST GET: https://developers.dailymotion.com/tools/
  6. .. _Global API Parameters: https://developers.dailymotion.com/api/#global-parameters
  7. .. _Video filters API: https://developers.dailymotion.com/api/#video-filters
  8. .. _Fields selection: https://developers.dailymotion.com/api/#fields-selection
  9. """
  10. from typing import TYPE_CHECKING
  11. from datetime import datetime, timedelta
  12. from urllib.parse import urlencode
  13. import time
  14. import babel
  15. from searx.network import get, raise_for_httperror # see https://github.com/searxng/searxng/issues/762
  16. from searx.utils import html_to_text
  17. from searx.exceptions import SearxEngineAPIException
  18. from searx.locales import region_tag, language_tag
  19. from searx.enginelib.traits import EngineTraits
  20. if TYPE_CHECKING:
  21. import logging
  22. logger: logging.Logger
  23. traits: EngineTraits
  24. # about
  25. about = {
  26. "website": 'https://www.dailymotion.com',
  27. "wikidata_id": 'Q769222',
  28. "official_api_documentation": 'https://www.dailymotion.com/developer',
  29. "use_official_api": True,
  30. "require_api_key": False,
  31. "results": 'JSON',
  32. }
  33. # engine dependent config
  34. categories = ['videos']
  35. paging = True
  36. number_of_results = 10
  37. time_range_support = True
  38. time_delta_dict = {
  39. "day": timedelta(days=1),
  40. "week": timedelta(days=7),
  41. "month": timedelta(days=31),
  42. "year": timedelta(days=365),
  43. }
  44. safesearch = True
  45. safesearch_params = {
  46. 2: {'is_created_for_kids': 'true'},
  47. 1: {'is_created_for_kids': 'true'},
  48. 0: {},
  49. }
  50. """True if this video is "Created for Kids" / intends to target an audience
  51. under the age of 16 (``is_created_for_kids`` in `Video filters API`_ )
  52. """
  53. family_filter_map = {
  54. 2: 'true',
  55. 1: 'true',
  56. 0: 'false',
  57. }
  58. """By default, the family filter is turned on. Setting this parameter to
  59. ``false`` will stop filtering-out explicit content from searches and global
  60. contexts (``family_filter`` in `Global API Parameters`_ ).
  61. """
  62. result_fields = [
  63. 'allow_embed',
  64. 'description',
  65. 'title',
  66. 'created_time',
  67. 'duration',
  68. 'url',
  69. 'thumbnail_360_url',
  70. 'id',
  71. ]
  72. """`Fields selection`_, by default, a few fields are returned. To request more
  73. specific fields, the ``fields`` parameter is used with the list of fields
  74. SearXNG needs in the response to build a video result list.
  75. """
  76. search_url = 'https://api.dailymotion.com/videos?'
  77. """URL to retrieve a list of videos.
  78. - `REST GET`_
  79. - `Global API Parameters`_
  80. - `Video filters API`_
  81. """
  82. iframe_src = "https://www.dailymotion.com/embed/video/{video_id}"
  83. """URL template to embed video in SearXNG's result list."""
  84. def request(query, params):
  85. if not query:
  86. return False
  87. eng_region: str = traits.get_region(params['searxng_locale'], 'en_US') # type: ignore
  88. eng_lang = traits.get_language(params['searxng_locale'], 'en')
  89. args = {
  90. 'search': query,
  91. 'family_filter': family_filter_map.get(params['safesearch'], 'false'),
  92. 'thumbnail_ratio': 'original', # original|widescreen|square
  93. # https://developers.dailymotion.com/api/#video-filters
  94. 'languages': eng_lang,
  95. 'page': params['pageno'],
  96. 'password_protected': 'false',
  97. 'private': 'false',
  98. 'sort': 'relevance',
  99. 'limit': number_of_results,
  100. 'fields': ','.join(result_fields),
  101. }
  102. args.update(safesearch_params.get(params['safesearch'], {}))
  103. # Don't add localization and country arguments if the user does select a
  104. # language (:de, :en, ..)
  105. if len(params['searxng_locale'].split('-')) > 1:
  106. # https://developers.dailymotion.com/api/#global-parameters
  107. args['localization'] = eng_region
  108. args['country'] = eng_region.split('_')[1]
  109. # Insufficient rights for the `ams_country' parameter of route `GET /videos'
  110. # 'ams_country': eng_region.split('_')[1],
  111. time_delta = time_delta_dict.get(params["time_range"])
  112. if time_delta:
  113. created_after = datetime.now() - time_delta
  114. args['created_after'] = datetime.timestamp(created_after)
  115. query_str = urlencode(args)
  116. params['url'] = search_url + query_str
  117. return params
  118. # get response from search-request
  119. def response(resp):
  120. results = []
  121. search_res = resp.json()
  122. # check for an API error
  123. if 'error' in search_res:
  124. raise SearxEngineAPIException(search_res['error'].get('message'))
  125. raise_for_httperror(resp)
  126. # parse results
  127. for res in search_res.get('list', []):
  128. title = res['title']
  129. url = res['url']
  130. content = html_to_text(res['description'])
  131. if len(content) > 300:
  132. content = content[:300] + '...'
  133. publishedDate = datetime.fromtimestamp(res['created_time'], None)
  134. length = time.gmtime(res.get('duration'))
  135. if length.tm_hour:
  136. length = time.strftime("%H:%M:%S", length)
  137. else:
  138. length = time.strftime("%M:%S", length)
  139. thumbnail = res['thumbnail_360_url']
  140. thumbnail = thumbnail.replace("http://", "https://")
  141. item = {
  142. 'template': 'videos.html',
  143. 'url': url,
  144. 'title': title,
  145. 'content': content,
  146. 'publishedDate': publishedDate,
  147. 'length': length,
  148. 'thumbnail': thumbnail,
  149. }
  150. # HINT: no mater what the value is, without API token videos can't shown
  151. # embedded
  152. if res['allow_embed']:
  153. item['iframe_src'] = iframe_src.format(video_id=res['id'])
  154. results.append(item)
  155. # return results
  156. return results
  157. def fetch_traits(engine_traits: EngineTraits):
  158. """Fetch locales & languages from dailymotion.
  159. Locales fetched from `api/locales <https://api.dailymotion.com/locales>`_.
  160. There are duplications in the locale codes returned from Dailymotion which
  161. can be ignored::
  162. en_EN --> en_GB, en_US
  163. ar_AA --> ar_EG, ar_AE, ar_SA
  164. The language list `api/languages <https://api.dailymotion.com/languages>`_
  165. contains over 7000 *languages* codes (see PR1071_). We use only those
  166. language codes that are used in the locales.
  167. .. _PR1071: https://github.com/searxng/searxng/pull/1071
  168. """
  169. resp = get('https://api.dailymotion.com/locales')
  170. if not resp.ok: # type: ignore
  171. print("ERROR: response from dailymotion/locales is not OK.")
  172. for item in resp.json()['list']: # type: ignore
  173. eng_tag = item['locale']
  174. if eng_tag in ('en_EN', 'ar_AA'):
  175. continue
  176. try:
  177. sxng_tag = region_tag(babel.Locale.parse(eng_tag))
  178. except babel.UnknownLocaleError:
  179. print("ERROR: item unknown --> %s" % item)
  180. continue
  181. conflict = engine_traits.regions.get(sxng_tag)
  182. if conflict:
  183. if conflict != eng_tag:
  184. print("CONFLICT: babel %s --> %s, %s" % (sxng_tag, conflict, eng_tag))
  185. continue
  186. engine_traits.regions[sxng_tag] = eng_tag
  187. locale_lang_list = [x.split('_')[0] for x in engine_traits.regions.values()]
  188. resp = get('https://api.dailymotion.com/languages')
  189. if not resp.ok: # type: ignore
  190. print("ERROR: response from dailymotion/languages is not OK.")
  191. for item in resp.json()['list']: # type: ignore
  192. eng_tag = item['code']
  193. if eng_tag in locale_lang_list:
  194. sxng_tag = language_tag(babel.Locale.parse(eng_tag))
  195. engine_traits.languages[sxng_tag] = eng_tag