senscritique.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. # SPDX-License-Identifier: AGPL-3.0-or-later
  2. """SensCritique (movies)
  3. """
  4. from __future__ import annotations
  5. from json import dumps, loads
  6. from typing import Any, Optional
  7. from searx.result_types import EngineResults, MainResult
  8. about = {
  9. "website": 'https://www.senscritique.com/',
  10. "wikidata_id": 'Q16676060',
  11. "official_api_documentation": None,
  12. "use_official_api": False,
  13. "require_api_key": False,
  14. "results": 'JSON',
  15. 'language': 'fr',
  16. }
  17. categories = ['movies']
  18. paging = True
  19. page_size = 16
  20. graphql_url = 'https://apollo.senscritique.com/'
  21. graphql_query = """query SearchProductExplorer($query: String, $offset: Int, $limit: Int,
  22. $sortBy: SearchProductExplorerSort) {
  23. searchProductExplorer(
  24. query: $query
  25. filters: []
  26. sortBy: $sortBy
  27. offset: $offset
  28. limit: $limit
  29. ) {
  30. items {
  31. category
  32. dateRelease
  33. duration
  34. id
  35. originalTitle
  36. rating
  37. title
  38. url
  39. yearOfProduction
  40. medias {
  41. picture
  42. }
  43. countries {
  44. name
  45. }
  46. genresInfos {
  47. label
  48. }
  49. directors {
  50. name
  51. }
  52. stats {
  53. ratingCount
  54. }
  55. }
  56. }
  57. }"""
  58. def request(query: str, params: dict[str, Any]) -> dict[str, Any]:
  59. offset = (params['pageno'] - 1) * page_size
  60. data = {
  61. "operationName": "SearchProductExplorer",
  62. "variables": {"offset": offset, "limit": page_size, "query": query, "sortBy": "RELEVANCE"},
  63. "query": graphql_query,
  64. }
  65. params['url'] = graphql_url
  66. params['method'] = 'POST'
  67. params['headers']['Content-Type'] = 'application/json'
  68. params['data'] = dumps(data)
  69. return params
  70. def response(resp) -> EngineResults:
  71. res = EngineResults()
  72. response_data = loads(resp.text)
  73. items = response_data.get('data', {}).get('searchProductExplorer', {}).get('items', [])
  74. if not items:
  75. return res
  76. for item in items:
  77. result = parse_item(item)
  78. if not result:
  79. continue
  80. res.add(result=result)
  81. return res
  82. def parse_item(item: dict[str, Any]) -> MainResult | None:
  83. """Parse a single item from the SensCritique API response"""
  84. title = item.get('title', '')
  85. if not title:
  86. return None
  87. year = item.get('yearOfProduction')
  88. original_title = item.get('originalTitle')
  89. thumbnail: str = ""
  90. if item.get('medias', {}) and item['medias'].get('picture'):
  91. thumbnail = item['medias']['picture']
  92. content_parts = build_content_parts(item, title, original_title)
  93. url = f"https://www.senscritique.com{item['url']}"
  94. return MainResult(
  95. url=url,
  96. title=title + (f' ({year})' if year else ''),
  97. content=' | '.join(content_parts),
  98. thumbnail=thumbnail,
  99. )
  100. def build_content_parts(item: dict[str, Any], title: str, original_title: Optional[str]) -> list[str]:
  101. """Build the content parts for an item"""
  102. content_parts = []
  103. if item.get('category'):
  104. content_parts.append(item['category'])
  105. if original_title and original_title != title:
  106. content_parts.append(f"Original title: {original_title}")
  107. if item.get('directors'):
  108. directors = [director['name'] for director in item['directors']]
  109. content_parts.append(f"Director(s): {', '.join(directors)}")
  110. if item.get('countries'):
  111. countries = [country['name'] for country in item['countries']]
  112. content_parts.append(f"Country: {', '.join(countries)}")
  113. if item.get('genresInfos'):
  114. genres = [genre['label'] for genre in item['genresInfos']]
  115. content_parts.append(f"Genre(s): {', '.join(genres)}")
  116. if item.get('duration'):
  117. minutes = item['duration'] // 60
  118. if minutes > 0:
  119. content_parts.append(f"Duration: {minutes} min")
  120. if item.get('rating') and item.get('stats', {}).get('ratingCount'):
  121. content_parts.append(f"Rating: {item['rating']}/10 ({item['stats']['ratingCount']} votes)")
  122. return content_parts