Loo-Stick a pensé à ceux qui ne veulent pas payer de débrideur pour utiliser Stremio/Nuvio/whatever en créant cet addon de flux qui agrège les sources vidéos de divers sites de streaming.
Faut pas voir ça comme la source de flux par excellence mais c’est entièrement gratuit, ça dépanne et c’est sale à souhait vis-à-vis des sites de streaming 🙂
Voici mon exemple de compose. Il ne prévoit pas de proxy, pour le compose originel, voir GitHub.
services:
loostream:
image: ghcr.io/loo-stick/loostream:latest
container_name: stremio-loostream
restart: always
ports:
- ${PORT:-7002}:${PORT:-7002}
env_file:
- .env
environment:
- NODE_ENV=production
volumes:
- /home/aerya/docker/stremio-loostream:/app/config
healthcheck:
test:
- CMD
- node
- -e
- require('http').get('http://localhost:7002/api/stats',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks: {}
Et le .env d’exemple est à prendre sur le GitHub directement.
Addon sympa mais qui souffre de 2 défauts à mon goût :
– impossible de le configurer sans proxy pour streamer depuis les sites de streaming (le proxy local = passer par le serveur hébergeant l’addon),
– s’il est possible de modifier l’ordre de VF, MULTI, VO et VOSTFR, on ne peut en revanche pas en désélectionner.
Addon amélioré hier et ces fonctionnalités ont été ajoutées :
Fiabilité nettement améliorée (rétro-ingénierie façon Onyx) • Fini la mire « troll » sur fsvid (Movix HD, FrenchStream, Coflix) • Vidmoly & luluvdo (Wiflix, VoirDrama, VoirAnime) réparés • Liste de sources plus rapide (moins de latence)
Nouveau dans le configure • Cases à cocher pour exclure les langues non voulues • Option « Direct — sans proxy »
Du coup, voici un userscript qui ajoute ces fonctionnalités. A utiliser avec ViolentMonkey, TamperMonkey etc. Pensez à modifier l’URL de l’addon dans // @match
// ==UserScript==
// @name LooStream - Mode Direct + filtres langues
// @namespace https://github.com/Loo-stick/loostream
// @version 1.1.0
// @description Ajoute un mode Direct et permet d'activer/désactiver MULTI, VF, VOSTFR et VO
// @match http://192.168.0.196:7002/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
if (!location.pathname.endsWith('/configure')) return;
let directMode = false;
// ------------------------------------------------------------------------
// MODE DIRECT
// ------------------------------------------------------------------------
const proxyCard = [...document.querySelectorAll('.card')]
.find(card => card.querySelector('h2')?.textContent.includes('Mode de proxy'));
if (proxyCard) {
const localOption = proxyCard.querySelector('[data-mode="local"]');
if (localOption && !proxyCard.querySelector('[data-mode="direct"]')) {
const directOption = document.createElement('div');
directOption.className = 'option';
directOption.dataset.mode = 'direct';
directOption.innerHTML = `
<h3>
<span>🚀</span> Direct — sans proxy
<span class="badge">Self-host</span>
</h3>
<p>
LooStream récupère les URLs des flux puis Stremio lit
directement depuis les CDN, sans MediaFlow.
</p>
`;
localOption.insertAdjacentElement('afterend', directOption);
directOption.addEventListener('click', () => {
directMode = true;
document.querySelectorAll('.option').forEach(option => {
option.classList.remove('selected');
});
directOption.classList.add('selected');
document.getElementById('mf-config')?.classList.remove('show');
document.getElementById('local-warning')?.classList.remove('show');
document.getElementById('result')?.classList.remove('show');
});
document
.querySelectorAll('[data-mode="mediaflow"], [data-mode="local"]')
.forEach(option => {
option.addEventListener('click', () => {
directMode = false;
});
});
}
}
// ------------------------------------------------------------------------
// FILTRES LANGUES
// ------------------------------------------------------------------------
const langList = document.getElementById('lang-list');
if (langList) {
const title = langList
.closest('.input-group')
?.querySelector('label');
if (title) {
title.textContent =
'Langues activées et ordre de préférence (glisser pour réordonner)';
}
langList.querySelectorAll('.lang-item').forEach(item => {
const lang = item.dataset.lang;
if (!lang || item.querySelector('.loostream-lang-toggle')) return;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.className = 'loostream-lang-toggle';
checkbox.dataset.lang = lang;
checkbox.style.width = '18px';
checkbox.style.height = '18px';
checkbox.style.cursor = 'pointer';
checkbox.style.flexShrink = '0';
// Empêche le clic sur la checkbox de lancer le drag
checkbox.addEventListener('mousedown', e => {
e.stopPropagation();
});
checkbox.addEventListener('click', e => {
e.stopPropagation();
item.style.opacity = checkbox.checked ? '1' : '0.45';
const desc = item.querySelector('.lang-desc');
if (desc) {
desc.style.textDecoration =
checkbox.checked ? '' : 'line-through';
}
document.getElementById('result')
?.classList.remove('show');
});
item.insertBefore(
checkbox,
item.querySelector('.position')?.nextSibling || item.firstChild
);
});
// Valeurs par défaut :
// MULTI = activé
// VF = activé
// VOSTFR = désactivé
// VO = désactivé
['VOSTFR', 'VO'].forEach(lang => {
const checkbox =
langList.querySelector(
`.loostream-lang-toggle[data-lang="${lang}"]`
);
if (checkbox) {
checkbox.checked = false;
const item = checkbox.closest('.lang-item');
if (item) {
item.style.opacity = '0.45';
const desc = item.querySelector('.lang-desc');
if (desc) {
desc.style.textDecoration = 'line-through';
}
}
}
});
}
// ------------------------------------------------------------------------
// LANGUES SÉLECTIONNÉES
// ------------------------------------------------------------------------
function getSelectedLangOrder() {
const result = [];
document
.querySelectorAll('#lang-list .lang-item')
.forEach(item => {
const checkbox =
item.querySelector('.loostream-lang-toggle');
if (checkbox?.checked) {
result.push(item.dataset.lang);
}
});
return result;
}
// ------------------------------------------------------------------------
// GÉNÉRATION DU MANIFESTE
// ------------------------------------------------------------------------
const originalGenerateUrl = window.generateUrl;
window.generateUrl = function () {
// Pour MediaFlow / Local, on conserve le fonctionnement LooStream
// mais on veut quand même appliquer le filtrage des langues.
if (!directMode) {
const originalGetLangOrder = window.getLangOrder;
window.getLangOrder = getSelectedLangOrder;
try {
return originalGenerateUrl();
} finally {
window.getLangOrder = originalGetLangOrder;
}
}
const tmdbKey =
document.getElementById('tmdb-key')?.value.trim();
if (!tmdbKey) {
alert('Veuillez entrer votre clé API TMDB');
return;
}
const langOrder = getSelectedLangOrder();
if (langOrder.length === 0) {
alert('Sélectionnez au moins une langue.');
return;
}
const config = {
// Le backend LooStream accepte "mediaflow".
// Sans mfUrl, il utilise son fallback URL directe.
proxy: 'mediaflow',
tmdbKey,
prefQuality:
document.getElementById('pref-quality')?.value || '1080p',
langOrder,
minStreams:
parseInt(
document.getElementById('min-streams')?.value || '5',
10
)
};
const accessKey =
document.getElementById('access-key')?.value.trim();
if (accessKey) {
config.accessKey = accessKey;
}
// Volontairement absents :
//
// config.mfUrl
// config.mfPass
const configStr =
btoa(JSON.stringify(config));
const installUrl =
`${location.origin}/${configStr}/manifest.json`;
const input =
document.getElementById('install-url');
if (input) {
input.value = installUrl;
}
document
.getElementById('result')
?.classList.add('show');
console.log(
'[LooStream Direct] Config générée:',
config
);
};
console.log(
'[LooStream Direct] Mode Direct + sélection des langues activés.'
);
})();




