mirror of
https://github.com/movixcorp/MovixOpenSource.git
synced 2026-08-10 22:33:25 +00:00
Gros lot de travail synchronisé depuis le repo privé. Cette mise à jour regroupe la refonte du lecteur et du Cast, de nouvelles sources VOD/Live TV, le durcissement de l’API et des proxies, ainsi qu’une nouvelle version de l’application mobile. Lecteur, sources et Live TV - Refonte du lecteur HLS avec sélection et mémorisation de la qualité, détection des résolutions, scan des liens, choix des pistes audio et fallback contrôlé de 1080p vers 720p. - Ajoute une garde contre les boucles de changement de source, notamment lors des erreurs vidéo et des réponses 429. - Revoit SeekStreaming de bout en bout : validation des embeds, prise en charge de plusieurs candidats, regroupement par serveur, règles de headers persistantes et cache d’extraction raccourci. - Ajoute SwiftFlow pour les films et séries, avec lecteurs VF/VOSTFR, cache stale-while-revalidate, déduplication des requêtes et limite partagée côté API. - Adapte Coflix à son nouveau domaine et à son nouveau thème, ajoute un kill switch, développe les wrappers 1jour1film et actualise le domaine Wiflix. - Ajoute NorthLive et Vavoo à la Live TV, regroupe les variantes d’une même chaîne et permet de choisir le serveur ou la qualité. - Enrichit les Top 10 avec des filtres par période et par algorithme, ainsi que de nouvelles métriques de visionnage. Cast et application mobile - Unifie le contrôle Cast entre le Web Cast SDK et le pont Android : état distant, lecture, pause, seek, arrêt et reprise après reconnexion. - Ajoute sur Android un relais média LAN sécurisé, limité au Chromecast et au réseau Wi-Fi sélectionnés, avec sessions opaques et service foreground. - Prépare les sources avant diffusion et prend en charge les playlists HLS TS/fMP4, les flux progressifs, les sous-titres VTT, SeekStreaming et les segments MPEG-TS encapsulés dans des PNG. - Renforce le proxy média local : support Range, validation des redirections, liaison DNS/socket au réseau Cast et meilleure gestion des headers Fsvid. - Sécurise le bridge WebView avec contrôle de l’origine top-frame, capacités propres à chaque document et invalidation lors des navigations. - Ajoute le Picture-in-Picture Android avec lecture/pause et seek de 10 secondes depuis la fenêtre flottante. - Maintient désormais l’écran allumé uniquement pendant une lecture active. - Publie l’application Android 2.5.7, build 16, avec le nouvel APK et ses métadonnées d’intégrité. API, base de données et paiements - Remplace 13 migrations SQL isolées par un manifeste de schéma MySQL centralisé, additif et capable de détecter les divergences de structure. - Ajoute le workflow db:init avec prévisualisation, dry-run, verrou MySQL, confirmations pour les tables Wrapped et couverture de tests dédiée. - Rend configurables les limites du pool MySQL et fiabilise les advisory locks. - Revoit entièrement PayGate : références de callback séparées, nonces, validation des wallets et transactions, états monotones, déduplication des paiements et protection contre les courses ou réutilisations de txid. - Renforce l’annulation, la réconciliation et la livraison idempotente des factures VIP, avec réponses no-store sur les routes sensibles. - Distingue les JWT invalides des indisponibilités MySQL afin de renvoyer correctement 401 ou 503 sans provoquer de déconnexions inutiles. - Utilise ZZAPI/Claude Haiku pour la modération, avec OpenRouter/DeepSeek en solution de secours. - Améliore la gestion HLS et Range dans proxiesembed. Frontend, profils et infrastructure - Protège la sélection et l’hydratation des profils contre les réponses concurrentes, les intentions obsolètes et l’effacement accidentel du stockage local. - Rend VITE_SITE_URL obligatoire et l’injecte dans le runtime, les données structurées, le service worker, robots.txt et sitemap.xml. - Optimise les probes réseau du service worker avec cache court et single-flight. - Charge dynamiquement les traductions anglaises, plusieurs routes secondaires et les données Search/TMDB uniquement lorsqu’elles deviennent nécessaires. - Ajoute des caches mémoire/session pour les images et contenus TMDB et réduit les animations, calculs et rendus hors écran. - Ajoute la page publique des frais, les coins carrés persistants, une nouvelle intro Movix, les avertissements de publicités adultes et de nombreux textes Cast, qualité, Live TV et sources. - Met à jour les extensions Chrome 1.3.12 et Firefox 1.5.9, ainsi que le userscript, avec la nouvelle extraction SeekStreaming et la préparation Cast. - Ajoute une large couverture de tests Kotlin et JavaScript pour le Cast, le proxy média, le PiP, le bridge, la base de données, PayGate et le lecteur. - Ajoute bootstrap-icons et patch-package, régénère les lockfiles npm et actualise la documentation d’installation et d’initialisation MySQL. Co-authored-by: GPT-5.6 Sol <noreply@openai.com>
217 lines
6.8 KiB
JavaScript
217 lines
6.8 KiB
JavaScript
'use strict';
|
|
|
|
function clone(value) {
|
|
return value === undefined ? undefined : structuredClone(value);
|
|
}
|
|
|
|
function normalizeSql(sql) {
|
|
return String(sql).replace(/\s+/g, ' ').trim();
|
|
}
|
|
|
|
function createFakeVipInvoicePool(initialInvoices, options = {}) {
|
|
const rows = new Map(initialInvoices.map((invoice) => [Number(invoice.id), clone(invoice)]));
|
|
const events = [];
|
|
const calls = [];
|
|
const stats = {
|
|
updates: 0,
|
|
identityUpdates: 0,
|
|
commits: 0,
|
|
rollbacks: 0
|
|
};
|
|
let transactionTail = Promise.resolve();
|
|
|
|
function findBy(field, value) {
|
|
return [...rows.values()].find((row) => row[field] === value) || null;
|
|
}
|
|
|
|
async function read(sql, params) {
|
|
const normalized = normalizeSql(sql);
|
|
if (/WHERE paygate_callback_reference = \?/i.test(normalized)) {
|
|
return [[clone(findBy('paygate_callback_reference', params[0]))].filter(Boolean), []];
|
|
}
|
|
if (/WHERE public_id = \?/i.test(normalized)) {
|
|
const invoice = findBy('public_id', params[0]);
|
|
if (/paygate_callback_reference IS NULL/i.test(normalized) && invoice?.paygate_callback_reference) {
|
|
return [[], []];
|
|
}
|
|
return [[clone(invoice)].filter(Boolean), []];
|
|
}
|
|
if (/WHERE id = \?/i.test(normalized) && /SELECT \*/i.test(normalized)) {
|
|
return [[clone(rows.get(Number(params[0])))].filter(Boolean), []];
|
|
}
|
|
if (/SELECT id FROM vip_invoices WHERE paygate_paid_txid_in = \?/i.test(normalized)) {
|
|
const owner = [...rows.values()].find((row) => (
|
|
row.paygate_paid_txid_in === params[0] && Number(row.id) !== Number(params[1])
|
|
));
|
|
return [[owner ? { id: owner.id } : null].filter(Boolean), []];
|
|
}
|
|
if (/SELECT key_value FROM access_keys WHERE key_value = \?/i.test(normalized)) {
|
|
return [[], []];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function executeSql(sql, params, scope) {
|
|
const normalized = normalizeSql(sql);
|
|
calls.push({ scope, sql: normalized, params: clone(params) });
|
|
const selected = await read(normalized, params);
|
|
if (selected) return selected;
|
|
|
|
if (/UPDATE vip_invoices SET status = \?/i.test(normalized)) {
|
|
const [
|
|
nextStatus,
|
|
txHash,
|
|
markPaidAt,
|
|
paidCoin,
|
|
paidValue,
|
|
valueCoin,
|
|
txidIn,
|
|
txidOut,
|
|
_statusForNextCheck,
|
|
invoiceId
|
|
] = params;
|
|
const invoice = rows.get(Number(invoiceId));
|
|
if (!invoice || !['awaiting_payment', 'partial_payment', 'expired'].includes(invoice.status)) {
|
|
return [{ affectedRows: 0 }, []];
|
|
}
|
|
invoice.status = nextStatus;
|
|
invoice.tx_hash = invoice.tx_hash || txHash;
|
|
invoice.paid_at = markPaidAt ? (invoice.paid_at || new Date().toISOString()) : invoice.paid_at;
|
|
invoice.paygate_paid_coin = paidCoin;
|
|
invoice.paygate_paid_value = paidValue;
|
|
invoice.amount_crypto_received = valueCoin;
|
|
invoice.paygate_paid_txid_in = invoice.paygate_paid_txid_in || txidIn;
|
|
invoice.paygate_paid_txid = invoice.paygate_paid_txid || txidOut;
|
|
stats.updates += 1;
|
|
return [{ affectedRows: 1 }, []];
|
|
}
|
|
|
|
if (/UPDATE vip_invoices SET paygate_paid_txid_in = \?/i.test(normalized)) {
|
|
if (options.failTxidClaim) throw options.failTxidClaim;
|
|
const [txidIn, invoiceId] = params;
|
|
const invoice = rows.get(Number(invoiceId));
|
|
if (
|
|
!invoice
|
|
|| invoice.status === 'cancelled'
|
|
|| invoice.paygate_paid_txid_in
|
|
) {
|
|
return [{ affectedRows: 0 }, []];
|
|
}
|
|
const duplicateOwner = [...rows.values()].find((row) => (
|
|
Number(row.id) !== Number(invoiceId)
|
|
&& row.paygate_paid_txid_in === txidIn
|
|
));
|
|
if (duplicateOwner) {
|
|
const error = new Error('Duplicate entry');
|
|
error.code = 'ER_DUP_ENTRY';
|
|
error.errno = 1062;
|
|
throw error;
|
|
}
|
|
invoice.paygate_paid_txid_in = txidIn;
|
|
stats.identityUpdates += 1;
|
|
return [{ affectedRows: 1 }, []];
|
|
}
|
|
|
|
if (/UPDATE vip_invoices SET status = 'paid'/i.test(normalized)) {
|
|
const paymentMethod = params[0];
|
|
const invoiceId = Number(params.at(-1));
|
|
const invoice = rows.get(invoiceId);
|
|
if (!invoice) {
|
|
return [{ affectedRows: 0 }, []];
|
|
}
|
|
invoice.status = 'paid';
|
|
invoice.paid_at = invoice.paid_at || new Date().toISOString();
|
|
if (paymentMethod === 'paygate_hosted') {
|
|
invoice.paygate_paid_coin = invoice.paygate_paid_coin || 'manual';
|
|
invoice.paygate_paid_value = invoice.paygate_paid_value ?? invoice.amount_usd;
|
|
}
|
|
stats.updates += 1;
|
|
return [{ affectedRows: 1 }, []];
|
|
}
|
|
|
|
if (/UPDATE vip_invoices SET status = 'delivered'/i.test(normalized)) {
|
|
const invoiceId = Number(params.at(-1));
|
|
const invoice = rows.get(invoiceId);
|
|
if (!invoice || invoice.status !== 'paid' || invoice.vip_key_value) {
|
|
return [{ affectedRows: 0 }, []];
|
|
}
|
|
invoice.status = 'delivered';
|
|
invoice.vip_key_value = params[0];
|
|
return [{ affectedRows: 1 }, []];
|
|
}
|
|
|
|
if (/INSERT INTO vip_invoice_events/i.test(normalized)) {
|
|
if (options.failEvent) throw options.failEvent;
|
|
events.push({
|
|
invoiceId: params[0],
|
|
eventType: params[1],
|
|
actorType: params[2],
|
|
actorId: params[3],
|
|
message: params[4],
|
|
payload: params[5] ? JSON.parse(params[5]) : null
|
|
});
|
|
return [{ insertId: events.length, affectedRows: 1 }, []];
|
|
}
|
|
|
|
return [{ affectedRows: 1 }, []];
|
|
}
|
|
|
|
const pool = {
|
|
async execute(sql, params = []) {
|
|
return executeSql(sql, params, 'pool');
|
|
},
|
|
async getConnection() {
|
|
let unlock = null;
|
|
let snapshot = null;
|
|
let eventLength = 0;
|
|
return {
|
|
async beginTransaction() {
|
|
const previous = transactionTail;
|
|
transactionTail = new Promise((resolve) => {
|
|
unlock = resolve;
|
|
});
|
|
await previous;
|
|
snapshot = new Map([...rows].map(([id, row]) => [id, clone(row)]));
|
|
eventLength = events.length;
|
|
},
|
|
async execute(sql, params = []) {
|
|
return executeSql(sql, params, 'connection');
|
|
},
|
|
async commit() {
|
|
stats.commits += 1;
|
|
unlock?.();
|
|
unlock = null;
|
|
},
|
|
async rollback() {
|
|
stats.rollbacks += 1;
|
|
rows.clear();
|
|
for (const [id, row] of snapshot || []) rows.set(id, row);
|
|
events.length = eventLength;
|
|
unlock?.();
|
|
unlock = null;
|
|
},
|
|
release() {
|
|
unlock?.();
|
|
unlock = null;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
return {
|
|
pool,
|
|
events,
|
|
calls,
|
|
stats,
|
|
getInvoice(id) {
|
|
return clone(rows.get(Number(id)));
|
|
},
|
|
mutateInvoice(id, changes) {
|
|
Object.assign(rows.get(Number(id)), clone(changes));
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
createFakeVipInvoicePool
|
|
};
|