Browse Source

[feat] add SensCritique (FR) engine

Closes: https://github.com/searxng/searxng/issues/4623
RobinFrcd 2 weeks ago
parent
commit
087da66565
2 changed files with 157 additions and 0 deletions
  1. 151 0
      searx/engines/senscritique.py
  2. 6 0
      searx/settings.yml

+ 151 - 0
searx/engines/senscritique.py

@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: AGPL-3.0-or-later
+"""SensCritique (movies)
+"""
+from __future__ import annotations
+
+from json import dumps, loads
+from typing import Any, Optional
+from searx.result_types import EngineResults, MainResult
+
+about = {
+    "website": 'https://www.senscritique.com/',
+    "wikidata_id": 'Q16676060',
+    "official_api_documentation": None,
+    "use_official_api": False,
+    "require_api_key": False,
+    "results": 'JSON',
+    'language': 'fr',
+}
+
+categories = ['movies']
+paging = True
+page_size = 16
+graphql_url = 'https://apollo.senscritique.com/'
+
+graphql_query = """query SearchProductExplorer($query: String, $offset: Int, $limit: Int,
+                    $sortBy: SearchProductExplorerSort) {
+  searchProductExplorer(
+    query: $query
+    filters: []
+    sortBy: $sortBy
+    offset: $offset
+    limit: $limit
+  ) {
+    items {
+      category
+      dateRelease
+      duration
+      id
+      originalTitle
+      rating
+      title
+      url
+      yearOfProduction
+      medias {
+        picture
+      }
+      countries {
+        name
+      }
+      genresInfos {
+        label
+      }
+      directors {
+        name
+      }
+      stats {
+        ratingCount
+      }
+    }
+  }
+}"""
+
+
+def request(query: str, params: dict[str, Any]) -> dict[str, Any]:
+    offset = (params['pageno'] - 1) * page_size
+
+    data = {
+        "operationName": "SearchProductExplorer",
+        "variables": {"offset": offset, "limit": page_size, "query": query, "sortBy": "RELEVANCE"},
+        "query": graphql_query,
+    }
+
+    params['url'] = graphql_url
+    params['method'] = 'POST'
+    params['headers']['Content-Type'] = 'application/json'
+    params['data'] = dumps(data)
+
+    return params
+
+
+def response(resp) -> EngineResults:
+    res = EngineResults()
+    response_data = loads(resp.text)
+
+    items = response_data.get('data', {}).get('searchProductExplorer', {}).get('items', [])
+    if not items:
+        return res
+
+    for item in items:
+        result = parse_item(item)
+        if not result:
+            continue
+        res.add(result=result)
+
+    return res
+
+
+def parse_item(item: dict[str, Any]) -> MainResult | None:
+    """Parse a single item from the SensCritique API response"""
+    title = item.get('title', '')
+    if not title:
+        return None
+    year = item.get('yearOfProduction')
+    original_title = item.get('originalTitle')
+
+    thumbnail: str = ""
+    if item.get('medias', {}) and item['medias'].get('picture'):
+        thumbnail = item['medias']['picture']
+
+    content_parts = build_content_parts(item, title, original_title)
+    url = f"https://www.senscritique.com{item['url']}"
+
+    return MainResult(
+        url=url,
+        title=title + (f' ({year})' if year else ''),
+        content=' | '.join(content_parts),
+        thumbnail=thumbnail,
+    )
+
+
+def build_content_parts(item: dict[str, Any], title: str, original_title: Optional[str]) -> list[str]:
+    """Build the content parts for an item"""
+    content_parts = []
+
+    if item.get('category'):
+        content_parts.append(item['category'])
+
+    if original_title and original_title != title:
+        content_parts.append(f"Original title: {original_title}")
+
+    if item.get('directors'):
+        directors = [director['name'] for director in item['directors']]
+        content_parts.append(f"Director(s): {', '.join(directors)}")
+
+    if item.get('countries'):
+        countries = [country['name'] for country in item['countries']]
+        content_parts.append(f"Country: {', '.join(countries)}")
+
+    if item.get('genresInfos'):
+        genres = [genre['label'] for genre in item['genresInfos']]
+        content_parts.append(f"Genre(s): {', '.join(genres)}")
+
+    if item.get('duration'):
+        minutes = item['duration'] // 60
+        if minutes > 0:
+            content_parts.append(f"Duration: {minutes} min")
+
+    if item.get('rating') and item.get('stats', {}).get('ratingCount'):
+        content_parts.append(f"Rating: {item['rating']}/10 ({item['stats']['ratingCount']} votes)")
+
+    return content_parts

+ 6 - 0
searx/settings.yml

@@ -2642,6 +2642,12 @@ engines:
     shortcut: pgo
     shortcut: pgo
     disabled: true
     disabled: true
 
 
+  - name: senscritique
+    engine: senscritique
+    shortcut: scr
+    timeout: 4.0
+    disabled: true
+
 # Doku engine lets you access to any Doku wiki instance:
 # Doku engine lets you access to any Doku wiki instance:
 # A public one or a privete/corporate one.
 # A public one or a privete/corporate one.
 #  - name: ubuntuwiki
 #  - name: ubuntuwiki