dailymotion.py 7.4 KB

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