mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-12 01:36:20 +02:00
L'hébergeur des picons du fournisseur (51.158.145.100) répond toujours 503 sur toutes ses URL. Couper le flot de requêtes ne suffisait pas : la grille restait sans logo. Pire, le pixel transparent servi en 200 était pris pour une image valide par Flutter, qui n'affichait plus son icône de repli — d'où des tuiles entièrement vides. - Nouvelle route `/api/logo?src=…&name=…` : URL du panneau d'abord (via le proxy, mêmes protections anti-SSRF et même mémoire des hôtes morts), puis recherche par nom dans le dépôt public tv-logo/tv-logos - Appariement par nom : préfixe pays et suffixes de qualité ignorés, `+` → `plus`, variante plus longue (`bein-sports-1-french`), puis marque sans numéro pour les canaux événementiels. Aucune approximation sur un mot seul - Liste de logos en cache 24 h (API GitHub : 60 requêtes/h non authentifiées), 15 min de recul après échec, images en mémoire - Image indisponible : 410 mis en cache au lieu du pixel. 410 et non 404 : la Cascade du serveur rattrape les 404 et renvoyait celui du handler statique, sans cache-control - Client (grille bureau, mobile, enregistrements) : logo toujours demandé au backend, nom compris, même sans stream_icon. L'onglet Enregistrements chargeait l'URL http brute, bloquée en HTTPS Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.3 KiB
Dart
73 lines
2.3 KiB
Dart
import 'package:shelf/shelf.dart';
|
|
|
|
import '../services/logo_catalog.dart';
|
|
import 'asset_failure_cache.dart';
|
|
|
|
/// GET /api/logo?src=<stream_icon>&name=<nom de chaîne>
|
|
///
|
|
/// Logo d'une chaîne, avec repli. L'URL fournie par le panneau est tentée
|
|
/// d'abord, via le proxy Xtream (mêmes protections anti-SSRF, même mémoire
|
|
/// des hôtes morts). En cas d'échec, le logo est cherché par nom dans le
|
|
/// dépôt public `tv-logos`.
|
|
///
|
|
/// POURQUOI : quand l'hébergeur de picons du revendeur tombe, toute la grille
|
|
/// perd ses logos d'un coup, alors que les autres applications IPTV les
|
|
/// affichent encore grâce à leurs propres sources de repli.
|
|
class LogoApi {
|
|
LogoApi(this._proxy, this._catalog);
|
|
|
|
/// Proxy Xtream (`ProxyHandler.handler`), qui attend `/api/xtream/<url>`.
|
|
final Handler _proxy;
|
|
|
|
final LogoCatalog _catalog;
|
|
|
|
/// Un logo trouvé change rarement : un jour de cache navigateur.
|
|
static const _found = 'public, max-age=86400';
|
|
|
|
Future<Response> handle(Request request) async {
|
|
final src = request.url.queryParameters['src'] ?? '';
|
|
final name = request.url.queryParameters['name'] ?? '';
|
|
|
|
// Une URL panneau malformée ne doit pas priver la chaîne du repli.
|
|
final proxied = src.startsWith('http://') || src.startsWith('https://')
|
|
? Uri.tryParse('${request.requestedUri.origin}/api/xtream/$src')
|
|
: null;
|
|
|
|
if (proxied != null) {
|
|
final upstream = await _proxy(
|
|
Request(
|
|
'GET',
|
|
proxied,
|
|
headers: {
|
|
for (final header in const ['cookie', 'authorization'])
|
|
if (request.headers[header] != null)
|
|
header: request.headers[header]!,
|
|
},
|
|
),
|
|
);
|
|
|
|
final type = upstream.headers['content-type'] ?? '';
|
|
if (upstream.statusCode == 200 && type.startsWith('image/')) {
|
|
return Response.ok(
|
|
upstream.read(),
|
|
headers: {'content-type': type, 'cache-control': _found},
|
|
);
|
|
}
|
|
// Corps d'erreur consommé : sinon la connexion amont reste ouverte.
|
|
await upstream.read().drain<void>();
|
|
}
|
|
|
|
if (name.isNotEmpty) {
|
|
final bytes = await _catalog.logoFor(name);
|
|
if (bytes != null) {
|
|
return Response.ok(
|
|
bytes,
|
|
headers: {'content-type': 'image/png', 'cache-control': _found},
|
|
);
|
|
}
|
|
}
|
|
|
|
return missingImageResponse();
|
|
}
|
|
}
|