[feat] add SensCritique (FR) engine

Closes: https://github.com/searxng/searxng/issues/4623
This commit is contained in:
RobinFrcd 2025-04-12 00:24:25 +02:00 committed by Markus Heiser
parent b84ae39978
commit 087da66565
2 changed files with 157 additions and 0 deletions

View File

@ -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

View File

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