Site: add _deprecate

This commit is contained in:
None 2025-05-13 11:04:42 +02:00
parent fab21e572c
commit bfed63bd41
28 changed files with 1278 additions and 1277 deletions

639
.github/.site/css/style.css vendored Normal file
View File

@ -0,0 +1,639 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary-color: #8c52ff;
--secondary-color: #6930c3;
--accent-color: #00e5ff;
--background-color: #121212;
--card-background: #1e1e1e;
--text-color: #f8f9fa;
--shadow-color: rgba(0, 0, 0, 0.25);
--card-hover: #2a2a2a;
--border-color: #333333;
}
[data-theme="light"] {
--background-color: #ffffff;
--card-background: #f8f9fa;
--text-color: #212529;
--shadow-color: rgba(0, 0, 0, 0.1);
--card-hover: #e9ecef;
--border-color: #dee2e6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: all 0.2s ease;
}
body {
font-family: 'Inter', 'Segoe UI', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
background-color: var(--header-bg);
backdrop-filter: blur(10px);
position: fixed;
width: 100%;
padding: 15px 0;
z-index: 1000;
box-shadow: 0 2px 12px var(--shadow-color);
}
.header-container {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: var(--card-background);
border-radius: 12px;
border: 1px solid var(--border-color);
margin-bottom: 20px;
}
.sites-stats {
display: flex;
gap: 20px;
align-items: center;
}
.total-sites, .last-update-global {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-color);
font-size: 0.95rem;
background: var(--background-color);
padding: 8px 16px;
border-radius: 8px;
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.total-sites:hover, .last-update-global:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
}
.total-sites i, .last-update-global i {
color: var(--primary-color);
font-size: 1.1rem;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
flex: 1;
}
.site-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
padding: 2rem 0;
}
.site-item {
min-height: 220px;
background-color: var(--card-background);
border-radius: 16px;
padding: 30px;
box-shadow: 0 6px 20px var(--shadow-color);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
cursor: pointer;
}
.site-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
transition: height 0.3s ease;
}
.site-item:hover {
transform: translateY(-5px);
box-shadow: 0 12px 30px var(--shadow-color);
border-color: var(--primary-color);
}
.site-item:hover::before {
height: 6px;
}
.site-item img {
width: 80px;
height: 80px;
margin-bottom: 1.5rem;
border-radius: 16px;
object-fit: cover;
border: 2px solid var(--border-color);
transition: transform 0.3s ease;
}
.site-item:hover img {
transform: scale(1.05);
}
.site-item h3 {
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
text-align: center;
transition: color 0.3s ease;
}
.site-item:hover h3 {
color: var(--accent-color);
}
.site-content {
text-align: center;
width: 100%;
}
.domain {
color: var(--text-color);
opacity: 0.8;
font-size: 0.9rem;
margin-bottom: 1.5rem;
word-break: break-all;
}
.site-item a {
margin-top: 1rem;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
text-decoration: none;
font-weight: 500;
padding: 12px 28px;
border-radius: 8px;
width: fit-content;
margin: 0 auto;
display: flex;
align-items: center;
gap: 8px;
}
.site-item a:hover {
opacity: 0.9;
transform: translateY(-2px);
}
.site-title {
opacity: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
padding: 10px 20px;
border-radius: 8px;
transition: opacity 0.3s ease;
color: white;
font-size: 1.2rem;
text-align: center;
width: 80%;
pointer-events: none;
z-index: 2;
}
.site-item:hover .site-title {
opacity: 1;
}
.site-item::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.site-item:hover::after {
opacity: 1;
}
.site-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
margin-top: 10px;
text-align: center;
font-size: 0.85rem;
color: var(--text-color);
opacity: 0.8;
}
.last-update, .old-domain {
display: flex;
align-items: center;
gap: 6px;
}
.last-update i, .old-domain i {
color: var(--primary-color);
}
.site-item:hover .site-info {
opacity: 1;
}
footer {
background: var(--card-background);
border-top: 1px solid var(--border-color);
margin-top: auto;
padding: 40px 20px;
position: relative;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
position: relative;
padding: 20px;
}
.footer-section {
padding: 20px;
border-radius: 12px;
transition: transform 0.3s ease, background-color 0.3s ease;
background-color: var(--card-background);
border: 1px solid var(--border-color);
}
.footer-section:hover {
transform: translateY(-5px);
background-color: var(--card-hover);
}
.footer-title {
color: var(--accent-color);
font-size: 1.3rem;
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
position: relative;
letter-spacing: 0.5px;
}
.footer-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 3px;
border-radius: 2px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
}
.footer-links {
list-style: none;
}
.footer-links li {
margin-bottom: 0.8rem;
}
.footer-links a {
color: var(--text-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
opacity: 0.8;
transition: all 0.3s ease;
padding: 8px 12px;
border-radius: 8px;
background-color: transparent;
}
.footer-links a:hover {
opacity: 1;
color: var(--accent-color);
transform: translateX(8px);
background-color: rgba(140, 82, 255, 0.1);
}
.footer-links i {
width: 20px;
text-align: center;
font-size: 1.2rem;
color: var(--primary-color);
transition: transform 0.3s ease;
}
.footer-links a:hover i {
transform: scale(1.2);
}
.github-stats {
display: flex;
gap: 10px;
margin-top: 10px;
font-size: 0.8rem;
}
.github-badge {
background-color: var(--background-color);
padding: 4px 8px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.github-badge i {
color: var(--accent-color);
}
.footer-description {
margin-top: 15px;
font-size: 0.9rem;
color: var(--text-color);
opacity: 0.8;
line-height: 1.5;
}
.update-info {
text-align: center;
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid var(--border-color);
}
.update-note {
color: var(--accent-color);
font-size: 0.9rem;
opacity: 0.9;
}
.theme-toggle {
position: relative;
top: unset;
right: unset;
z-index: 1;
}
.theme-toggle input {
display: none;
}
.theme-toggle label {
cursor: pointer;
padding: 8px;
background: var(--background-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 10px var(--shadow-color);
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.theme-toggle label:hover {
border-color: var(--primary-color);
transform: translateY(-2px);
}
.theme-toggle .fa-sun {
display: none;
color: #ffd700;
}
.theme-toggle .fa-moon {
color: #8c52ff;
}
.theme-toggle input:checked ~ label .fa-sun {
display: block;
}
.theme-toggle input:checked ~ label .fa-moon {
display: none;
}
.loader {
width: 48px;
height: 48px;
border: 3px solid var(--primary-color);
border-bottom-color: transparent;
border-radius: 50%;
display: inline-block;
position: relative;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.loader::after {
content: '';
position: absolute;
box-sizing: border-box;
left: 0;
top: 0;
width: 48px;
height: 48px;
border-radius: 50%;
border: 3px solid transparent;
border-bottom-color: var(--accent-color);
animation: rotationBack 0.5s linear infinite;
transform: rotate(45deg);
}
@keyframes rotation {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}
@keyframes rotationBack {
0% { transform: rotate(0deg) }
100% { transform: rotate(-360deg) }
}
/* Improved Responsiveness */
@media (max-width: 768px) {
.site-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
padding: 1rem;
}
.site-item {
min-height: 250px;
padding: 20px;
}
.footer-content {
grid-template-columns: 1fr;
gap: 20px;
padding: 15px;
}
.theme-toggle {
top: 10px;
right: 10px;
}
.header-container {
flex-direction: column;
gap: 15px;
}
.sites-stats {
flex-direction: column;
width: 100%;
}
.total-sites, .last-update-global {
width: 100%;
justify-content: center;
}
}
@media (max-width: 480px) {
.site-grid {
grid-template-columns: 1fr;
}
.site-item {
min-height: 220px;
}
.container {
padding: 10px;
}
}
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
text-align: center;
}
.footer-title::after {
left: 50%;
transform: translateX(-50%);
}
.footer-links a {
justify-content: center;
}
.footer-links a:hover {
transform: translateY(-5px);
}
.footer-section {
margin-bottom: 20px;
}
}
.time-change {
color: var(--text-color);
opacity: 0.7;
font-size: 0.85rem;
margin-bottom: 0.5rem;
word-break: break-all;
}
.label {
color: var(--accent-color);
font-weight: 500;
}
.controls-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px 20px;
background: var(--card-background);
border-radius: 12px;
border: 1px solid var(--border-color);
}
.grid-controls {
display: flex;
align-items: center;
gap: 10px;
}
.grid-controls label {
color: var(--text-color);
font-weight: 500;
}
.grid-controls select {
padding: 8px 12px;
border-radius: 8px;
border: 1px solid var(--border-color);
background: var(--background-color);
color: var(--text-color);
cursor: pointer;
transition: all 0.3s ease;
}
.grid-controls select:hover {
border-color: var(--primary-color);
}
.sites-stats {
display: flex;
gap: 20px;
align-items: center;
}
.total-sites, .last-update-global {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-color);
font-size: 0.9rem;
}
.total-sites i, .last-update-global i {
color: var(--primary-color);
}
.site-status {
position: absolute;
top: 10px;
right: 10px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #4CAF50;
}
.site-status.offline {
background: #f44336;
}

View File

@ -9,9 +9,19 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<main>
<section class="container">
<body> <main>
<section class="container"> <div class="header-container">
<div class="sites-stats">
<span class="total-sites">
<i class="fas fa-globe"></i>
Total Sites: <span id="sites-count">0</span>
</span>
<span class="last-update-global">
<i class="fas fa-clock"></i>
Last Update: <span id="last-update-time">-</span>
</span>
</div>
</div>
<div class="sites-container">
<div id="site-list" class="site-grid">
<div class="loader"></div>
@ -21,8 +31,7 @@
</main>
<footer>
<div class="footer-content">
<div class="footer-section">
<div class="footer-content"> <div class="footer-section">
<h3 class="footer-title">Repository</h3>
<ul class="footer-links">
<li>
@ -33,22 +42,22 @@
</li>
</ul>
<p class="footer-description">
An updated collection of streaming sites. Contribute to the project on GitHub!
An open-source script for downloading movies, TV shows, and anime from various websites.
</p>
</div>
<div class="footer-section">
<h3 class="footer-title">Author</h3>
<h3 class="footer-title">Support</h3>
<ul class="footer-links">
<li>
<a href="https://github.com/Arrowar" target="_blank" rel="noopener noreferrer">
<i class="fas fa-user-tie"></i>
Arrowar Profile
<a href="https://www.paypal.com/donate/?hosted_button_id=UXTWMT8P6HE2C" target="_blank" rel="noopener noreferrer">
<i class="fab fa-paypal"></i>
Donate with PayPal
</a>
</li>
</ul>
<p class="footer-description">
Developer of the project.
Support the development of this project through donations.
</p>
</div>
@ -71,4 +80,4 @@
<script src="js/script.js"></script>
</body>
</html>
</html>

179
.github/.site/js/script.js vendored Normal file
View File

@ -0,0 +1,179 @@
document.documentElement.setAttribute('data-theme', 'dark');
function initGridControls() {
const gridSize = document.getElementById('grid-size');
const siteGrid = document.querySelector('.site-grid');
gridSize.addEventListener('change', function() {
switch(this.value) {
case 'small':
siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(200px, 1fr))';
break;
case 'medium':
siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(300px, 1fr))';
break;
case 'large':
siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(400px, 1fr))';
break;
}
localStorage.setItem('preferredGridSize', this.value);
});
const savedSize = localStorage.getItem('preferredGridSize');
if (savedSize) {
gridSize.value = savedSize;
gridSize.dispatchEvent(new Event('change'));
}
}
async function checkSiteStatus(url) {
try {
console.log(`Checking status for: ${url}`);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch(url, {
method: 'HEAD',
mode: 'no-cors',
signal: controller.signal,
headers: {
'Accept': 'text/html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/133.0.0.0'
}
});
clearTimeout(timeoutId);
const isOnline = response.type === 'opaque';
console.log(`Site ${url} is ${isOnline ? 'online' : 'offline'} (Type: ${response.type})`);
return isOnline;
} catch (error) {
console.log(`Error checking ${url}:`, error.message);
return false;
}
}
const supabaseUrl = 'https://zvfngpoxwrgswnzytadh.supabase.co';
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE';
async function loadSiteData() {
try {
console.log('Starting to load site data...');
const siteList = document.getElementById('site-list');
siteList.innerHTML = '<div class="loader"></div>';
const headers = {
'accept': '*/*',
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
'apikey': supabaseKey,
'authorization': `Bearer ${supabaseKey}`,
'content-type': 'application/json',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'range': '0-9'
};
console.log('Fetching from Supabase with headers:', headers);
const response = await fetch(`${supabaseUrl}/rest/v1/public?select=*`, {
method: 'GET',
headers: headers
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
siteList.innerHTML = ''; if (data && data.length > 0) {
console.log('Raw data from Supabase:', data);
const configSite = data[0].data;
console.log('Parsed config site:', configSite);
let totalSites = Object.keys(configSite).length;
let latestUpdate = new Date(0);
document.getElementById('sites-count').textContent = totalSites;
for (const siteName in configSite) {
const site = configSite[siteName];
const siteItem = document.createElement('div');
siteItem.className = 'site-item';
siteItem.style.cursor = 'pointer';
// Add status indicator
const statusDot = document.createElement('div');
statusDot.className = 'site-status';
const isOnline = await checkSiteStatus(site.full_url);
if (!isOnline) statusDot.classList.add('offline');
siteItem.appendChild(statusDot);
// Update latest update time
const updateTime = new Date(site.time_change);
if (updateTime > latestUpdate) {
latestUpdate = updateTime;
}
const siteInfo = document.createElement('div');
siteInfo.className = 'site-info';
if (site.time_change) {
const updateDate = new Date(site.time_change);
const formattedDate = updateDate.toLocaleDateString('it-IT', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
const lastUpdate = document.createElement('span');
lastUpdate.className = 'last-update';
lastUpdate.innerHTML = `<i class="fas fa-clock"></i> ${formattedDate}`;
siteInfo.appendChild(lastUpdate);
}
if (site.old_domain) {
const oldDomain = document.createElement('span');
oldDomain.className = 'old-domain';
oldDomain.innerHTML = `<i class="fas fa-history"></i> ${site.old_domain}`;
siteInfo.appendChild(oldDomain);
} siteItem.addEventListener('click', function() {
window.open(site.full_url, '_blank', 'noopener,noreferrer');
});
const siteIcon = document.createElement('img');
siteIcon.src = `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${site.full_url}&size=128`;
siteIcon.alt = `${siteName} icon`;
siteIcon.onerror = function() {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="%238c52ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>';
};
const siteTitle = document.createElement('h3');
siteTitle.textContent = siteName;
siteItem.appendChild(siteIcon);
siteItem.appendChild(siteTitle);
siteItem.appendChild(siteInfo);
siteList.appendChild(siteItem);
}
const formattedDate = latestUpdate.toLocaleDateString('it-IT', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
document.getElementById('last-update-time').textContent = formattedDate;
} else {
siteList.innerHTML = '<div class="no-sites">No sites available</div>';
}
} catch (error) {
console.error('Errore:', error);
siteList.innerHTML = `
<div class="error-message">
<p>Errore nel caricamento</p>
<button onclick="loadSiteData()" class="retry-button">Riprova</button>
</div>
`;
}
}
document.addEventListener('DOMContentLoaded', () => {
loadSiteData();
});

View File

@ -64,6 +64,14 @@ jobs:
# Get certifi certificate path
CERT_PATH=$(python -c "import certifi; print(certifi.where())")
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
CERT_DATA="${CERT_PATH};certifi"
SC_DATA="StreamingCommunity;StreamingCommunity"
else
CERT_DATA="${CERT_PATH}:certifi"
SC_DATA="StreamingCommunity:StreamingCommunity"
fi
pyinstaller --onefile --hidden-import=pycryptodomex --hidden-import=ua_generator \
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
@ -82,8 +90,8 @@ jobs:
--hidden-import=Cryptodome.Random \
--hidden-import=telebot \
--additional-hooks-dir=pyinstaller/hooks \
--add-data "${CERT_PATH};certifi" \
--add-data "StreamingCommunity${{ matrix.separator }}StreamingCommunity" \
--add-data="$CERT_DATA" \
--add-data="$SC_DATA" \
--name=${{ matrix.artifact_name }} test_run.py
- name: Upload executable to latest release

View File

@ -108,6 +108,14 @@ jobs:
# Get certifi certificate path
CERT_PATH=$(python -c "import certifi; print(certifi.where())")
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
CERT_DATA="${CERT_PATH};certifi"
SC_DATA="StreamingCommunity;StreamingCommunity"
else
CERT_DATA="${CERT_PATH}:certifi"
SC_DATA="StreamingCommunity:StreamingCommunity"
fi
pyinstaller --onefile --hidden-import=pycryptodomex --hidden-import=ua_generator \
--hidden-import=qbittorrentapi --hidden-import=qbittorrent \
--hidden-import=bs4 --hidden-import=httpx --hidden-import=rich --hidden-import=tqdm \
@ -126,8 +134,8 @@ jobs:
--hidden-import=Cryptodome.Random \
--hidden-import=telebot \
--additional-hooks-dir=pyinstaller/hooks \
--add-data "${CERT_PATH};certifi" \
--add-data "StreamingCommunity${{ matrix.separator }}StreamingCommunity" \
--add-data="$CERT_DATA" \
--add-data="$SC_DATA" \
--name=${{ matrix.artifact_name }} test_run.py
- name: Upload executable

View File

@ -25,7 +25,7 @@ jobs:
- name: Copy site files
run: |
mkdir -p _site
cp -r Test/.site/* _site/
cp -r .github/.site/* _site/
ls -la _site/
- name: Upload artifact

View File

@ -2,4 +2,4 @@ build-container:
docker build -t streaming-community-api .
run-container:
docker run --rm -it -p 8000:8000 -v ${LOCAL_DIR}:/app/Video -v ./config.json:/app/config.json streaming-community-api
docker run --rm -it --dns 9.9.9.9 -p 8000:8000 -v ${LOCAL_DIR}:/app/Video -v ./config.json:/app/config.json streaming-community-api

743
README.md
View File

@ -31,6 +31,9 @@
# 📋 Table of Contents
<details>
<summary>📦 Installation</summary>
- 🔄 [Update Domains](#update-domains)
- 🌐 [Available Sites](https://arrowar.github.io/StreamingDirectory/)
- 🛠️ [Installation](#installation)
@ -40,6 +43,11 @@
- 📝 [Manual Installation](#3-manual-installation)
- 💻 [Win 7](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Installation#win-7)
- 📱 [Termux](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Termux)
</details>
<details>
<summary>⚙️ Configuration & Usage</summary>
- ⚙️ [Configuration](#configuration)
- 🔧 [Default](#default-settings)
- 📩 [Request](#requests-settings)
@ -48,15 +56,23 @@
- 📝 [Command](#command)
- 🔍 [Global search](#global-search)
- 💻 [Examples of terminal](#examples-of-terminal-usage)
</details>
<details>
<summary>🔧 Advanced Features</summary>
- 🔧 [Manual domain configuration](#update-domains)
- 🐳 [Docker](#docker)
- 📝 [Telegram Usage](#telegram-usage)
</details>
<details>
<summary> Help & Support</summary>
- 🎓 [Tutorial](#tutorials)
- 📝 [To do](#to-do)
- 💬 [Support](#support)
- 🤝 [Contribute](#contributing)
- ⚠️ [Disclaimer](#disclaimer)
- ⚡ [Contributors](#contributors)
</details>
# Installation
@ -111,7 +127,8 @@ python run_streaming.py
## Modules
### HLS Downloader
<details>
<summary>📥 HLS Downloader</summary>
Download HTTP Live Streaming (HLS) content from m3u8 URLs.
@ -129,8 +146,10 @@ downloader.download()
```
See [HLS example](./Test/Download/HLS.py) for complete usage.
</details>
### MP4 Downloader
<details>
<summary>📽️ MP4 Downloader</summary>
Direct MP4 file downloader with support for custom headers and referrer.
@ -159,8 +178,10 @@ downloader.download()
```
See [MP4 example](./Test/Download/MP4.py) for complete usage.
</details>
### Torrent Client
<details>
<summary>🧲 Torrent Client</summary>
Download content via torrent magnet links.
@ -178,67 +199,21 @@ client.start_download()
```
See [Torrent example](./Test/Download/TOR.py) for complete usage.
## 2. Automatic Installation
### Supported Operating Systems 💿
| OS | Automatic Installation Support |
|:----------------|:------------------------------:|
| Windows 10/11 | ✔️ |
| Windows 7 | ❌ |
| Debian Linux | ✔️ |
| Arch Linux | ✔️ |
| CentOS Stream 9 | ✔️ |
| FreeBSD | ⏳ |
| MacOS | ✔️ |
| Termux | ❌ |
### Installation Steps
#### On Windows:
```powershell
.\Installer\win_install.bat
```
#### On Linux/MacOS/BSD:
```bash
sudo chmod +x Installer/unix_install.sh && ./Installer/unix_install.sh
```
### Usage
#### On Windows:
```powershell
python .\test_run.py
```
or
```powershell
source .venv/bin/activate && python test_run.py && deactivate
```
#### On Linux/MacOS/BSD:
```bash
./test_run.py
```
</details>
## Binary Location
### Default Locations
<details>
<summary>📂 Default Locations</summary>
- **Windows**: `C:\binary`
- **MacOS**: `~/Applications/binary`
- **Linux**: `~/.local/bin/binary`
</details>
You can customize these locations by following these steps for your operating system:
<details>
<summary>🪟 Windows Configuration</summary>
#### Windows
1. Move the binary folder from `C:\binary` to your desired location
2. Add the new path to Windows environment variables:
- Open Start menu and search for "Environment Variables"
@ -250,8 +225,11 @@ You can customize these locations by following these steps for your operating sy
- Click "OK" to save changes
For detailed Windows PATH instructions, see the [Windows PATH guide](https://www.eukhost.com/kb/how-to-add-to-the-path-on-windows-10-and-windows-11/).
</details>
<details>
<summary>🍎 MacOS Configuration</summary>
#### MacOS
1. Move the binary folder from `~/Applications/binary` to your desired location
2. Add the new path to your shell's configuration file:
```bash
@ -269,8 +247,11 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www
# For zsh
source ~/.zshrc
```
</details>
<details>
<summary>🐧 Linux Configuration</summary>
#### Linux
1. Move the binary folder from `~/.local/bin/binary` to your desired location
2. Add the new path to your shell's configuration file:
```bash
@ -286,6 +267,7 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www
# or
source ~/.zshrc # for zsh
```
</details>
> [!IMPORTANT]
> After moving the binary folder, ensure that all executables (ffmpeg, ffprobe, ffplay) are present in the new location and have the correct permissions:
@ -294,19 +276,24 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www
## 3. Manual Installation
### Requirements 📋
<details>
<summary>📋 Requirements</summary>
Prerequisites:
* [Python](https://www.python.org/downloads/) > 3.8
* [FFmpeg](https://www.gyan.dev/ffmpeg/builds/)
</details>
### Install Python Dependencies
<details>
<summary>⚙️ Python Dependencies</summary>
```bash
pip install -r requirements.txt
```
</details>
### Usage
<details>
<summary>🚀 Usage</summary>
#### On Windows:
@ -319,6 +306,7 @@ python test_run.py
```bash
python3 test_run.py
```
</details>
## Update
@ -338,278 +326,11 @@ python3 update.py
<br>
# Configuration
You can change some behaviors by tweaking the configuration file.
The configuration file is divided into several main sections:
## DEFAULT Settings
```json
{
"DEFAULT": {
"debug": false,
"show_message": true,
"clean_console": true,
"show_trending": true,
"use_api": true,
"not_close": false,
"telegram_bot": false,
"download_site_data": false,
"validate_github_config": false
}
}
```
- `debug`: Enables debug logging
- `show_message`: Displays informational messages
- `clean_console`: Clears the console between operations
- `show_trending`: Shows trending content
- `use_api`: Uses API for domain updates instead of local configuration
- `not_close`: If set to true, keeps the program running after download is complete
* Can be changed from terminal with `--not_close true/false`
- `telegram_bot`: Enables Telegram bot integration
- `download_site_data`: If set to false, disables automatic site data download
- `validate_github_config`: If set to false, disables validation and updating of configuration from GitHub
## OUT_FOLDER Settings
```json
{
"OUT_FOLDER": {
"root_path": "Video",
"movie_folder_name": "Movie",
"serie_folder_name": "Serie",
"anime_folder_name": "Anime",
"map_episode_name": "E%(episode)_%(episode_name)",
"add_siteName": false
}
}
```
- `root_path`: Directory where all videos will be saved
### Path examples:
* Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder)
* Linux/MacOS: `Desktop/MyLibrary/Folder`
<br/><br/>
- `movie_folder_name`: The name of the subdirectory where movies will be stored
* Can be changed from terminal with `--movie_folder_name`
<br/><br/>
- `serie_folder_name`: The name of the subdirectory where TV series will be stored
* Can be changed from terminal with `--serie_folder_name`
<br/><br/>
- `anime_folder_name`: The name of the subdirectory where anime will be stored
* Can be changed from terminal with `--anime_folder_name`
<br/><br/>
- `map_episode_name`: Template for episode filenames
### Episode name usage:
You can choose different vars:
* `%(tv_name)` : Is the name of TV Show
* `%(season)` : Is the number of the season
* `%(episode)` : Is the number of the episode
* `%(episode_name)` : Is the name of the episode
* Can be changed from terminal with `--map_episode_name`
<br><br>
- `add_siteName`: If set to true, appends the site_name to the root path before the movie and serie folders
* Can be changed from terminal with `--add_siteName true/false`
<br/><br/>
## QBIT_CONFIG Settings
```json
{
"QBIT_CONFIG": {
"host": "192.168.1.51",
"port": "6666",
"user": "admin",
"pass": "adminadmin"
}
}
```
To enable qBittorrent integration, follow the setup guide [here](https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI).
## REQUESTS Settings
```json
{
"REQUESTS": {
"verify": false,
"timeout": 20,
"max_retry": 8
}
}
```
- `verify`: Verifies SSL certificates
- `timeout`: Maximum timeout (in seconds) for each request
- `max_retry`: Number of retry attempts per segment during M3U8 index download
## M3U8_DOWNLOAD Settings
```json
{
"M3U8_DOWNLOAD": {
"tqdm_delay": 0.01,
"default_video_workser": 12,
"default_audio_workser": 12,
"segment_timeout": 8,
"download_audio": true,
"merge_audio": true,
"specific_list_audio": [
"ita"
],
"download_subtitle": true,
"merge_subs": true,
"specific_list_subtitles": [
"ita",
"eng"
],
"cleanup_tmp_folder": true
}
}
```
- `tqdm_delay`: Delay between progress bar updates
- `default_video_workser`: Number of threads for video download
* Can be changed from terminal with `--default_video_worker <number>`
<br/><br/>
- `default_audio_workser`: Number of threads for audio download
* Can be changed from terminal with `--default_audio_worker <number>`
<br/><br/>
- `segment_timeout`: Timeout for downloading individual segments
- `download_audio`: Whether to download audio tracks
- `merge_audio`: Whether to merge audio with video
- `specific_list_audio`: List of audio languages to download
* Can be changed from terminal with `--specific_list_audio ita,eng`
<br/><br/>
- `download_subtitle`: Whether to download subtitles
- `merge_subs`: Whether to merge subtitles with video
- `specific_list_subtitles`: List of subtitle languages to download
* Can be changed from terminal with `--specific_list_subtitles ita,eng`
<br/><br/>
- `cleanup_tmp_folder`: Remove temporary .ts files after download
## Available Language Codes
| European | Asian | Middle Eastern | Others |
|-----------------|-----------------|-----------------|-----------------|
| ita - Italian | chi - Chinese | ara - Arabic | eng - English |
| spa - Spanish | jpn - Japanese | heb - Hebrew | por - Portuguese|
| fre - French | kor - Korean | tur - Turkish | fil - Filipino |
| ger - German | hin - Hindi | | ind - Indonesian|
| rus - Russian | mal - Malayalam | | may - Malay |
| swe - Swedish | tam - Tamil | | vie - Vietnamese|
| pol - Polish | tel - Telugu | | |
| ukr - Ukrainian | tha - Thai | | |
## M3U8_CONVERSION Settings
```json
{
"M3U8_CONVERSION": {
"use_codec": false,
"use_vcodec": true,
"use_acodec": true,
"use_bitrate": true,
"use_gpu": false,
"default_preset": "ultrafast"
}
}
```
- `use_codec`: Use specific codec settings
- `use_vcodec`: Use specific video codec
- `use_acodec`: Use specific audio codec
- `use_bitrate`: Apply bitrate settings
- `use_gpu`: Enable GPU acceleration (if available)
- `default_preset`: FFmpeg encoding preset (ultrafast, fast, medium, slow, etc.)
### Advanced M3U8 Conversion Options
The software supports various advanced encoding options via FFmpeg:
#### Encoding Presets
The `default_preset` configuration can be set to one of the following values:
- `ultrafast`: Extremely fast conversion but larger file size
- `superfast`: Very fast with good quality/size ratio
- `veryfast`: Fast with good compression
- `faster`: Optimal balance for most users
- `fast`: Good compression, moderate time
- `medium`: FFmpeg default setting
- `slow`: High quality, slower process
- `slower`: Very high quality, slow process
- `veryslow`: Maximum quality, very slow process
#### GPU Acceleration
When `use_gpu` is enabled, the system will use available hardware acceleration:
- NVIDIA: NVENC
- AMD: AMF
- Intel: QSV
You need to have updated drivers and FFmpeg compiled with hardware acceleration support.
## M3U8_PARSER Settings
```json
{
"M3U8_PARSER": {
"force_resolution": "Best",
"get_only_link": false
}
}
```
- `force_resolution`: Choose the video resolution for downloading:
* `"Best"`: Highest available resolution
* `"Worst"`: Lowest available resolution
* `"720p"`: Force 720p resolution
* Or specify one of these resolutions:
- 1080p (1920x1080)
- 720p (1280x720)
- 480p (640x480)
- 360p (640x360)
- 320p (480x320)
- 240p (426x240)
- 240p (320x240)
- 144p (256x144)
- `get_only_link`: Return M3U8 playlist/index URL instead of downloading
## SITE_EXTRA Settings
```json
{
"SITE_EXTRA": {
"ddlstreamitaly": {
"ips4_device_key": "",
"ips4_member_id": "",
"ips4_login_key": ""
}
}
}
```
- Site-specific configuration for `ddlstreamitaly`:
- `ips4_device_key`: Device key for authentication
- `ips4_member_id`: Member ID for authentication
- `ips4_login_key`: Login key for authentication
## Update Domains
<details>
<summary>🌐 Domain Configuration Methods</summary>
There are two ways to update the domains for the supported websites:
### 1. Using Local Configuration
@ -645,23 +366,281 @@ Note: If `use_api` is set to `false` and no `domains.json` file is found, the sc
#### 💡 Adding a New Site to the Legacy API
If you want to add a new site to the legacy API, just message me on the Discord server, and I'll add it!
</details>
# Configuration
<details>
<summary>⚙️ Overview</summary>
You can change some behaviors by tweaking the configuration file. The configuration file is divided into several main sections.
</details>
<details>
<summary>🔧 DEFAULT Settings</summary>
```json
{
"DEFAULT": {
"debug": false,
"show_message": true,
"clean_console": true,
"show_trending": true,
"use_api": true,
"not_close": false,
"telegram_bot": false,
"download_site_data": false,
"validate_github_config": false
}
}
```
- `debug`: Enables debug logging
- `show_message`: Displays informational messages
- `clean_console`: Clears the console between operations
- `show_trending`: Shows trending content
- `use_api`: Uses API for domain updates instead of local configuration
- `not_close`: If set to true, keeps the program running after download is complete
* Can be changed from terminal with `--not_close true/false`
- `telegram_bot`: Enables Telegram bot integration
- `download_site_data`: If set to false, disables automatic site data download
- `validate_github_config`: If set to false, disables validation and updating of configuration from GitHub
</details>
<details>
<summary>📁 OUT_FOLDER Settings</summary>
```json
{
"OUT_FOLDER": {
"root_path": "Video",
"movie_folder_name": "Movie",
"serie_folder_name": "Serie",
"anime_folder_name": "Anime",
"map_episode_name": "E%(episode)_%(episode_name)",
"add_siteName": false
}
}
```
#### Directory Configuration
- `root_path`: Directory where all videos will be saved
* Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (network folder)
* Linux/MacOS: `Desktop/MyLibrary/Folder`
#### Folder Names
- `movie_folder_name`: Subdirectory for movies (can be changed with `--movie_folder_name`)
- `serie_folder_name`: Subdirectory for TV series (can be changed with `--serie_folder_name`)
- `anime_folder_name`: Subdirectory for anime (can be changed with `--anime_folder_name`)
#### Episode Naming
- `map_episode_name`: Template for episode filenames
* `%(tv_name)`: Name of TV Show
* `%(season)`: Season number
* `%(episode)`: Episode number
* `%(episode_name)`: Episode name
* Can be changed with `--map_episode_name`
#### Additional Options
- `add_siteName`: Appends site_name to root path (can be changed with `--add_siteName true/false`)
</details>
<details>
<summary>🔄 QBIT_CONFIG Settings</summary>
```json
{
"QBIT_CONFIG": {
"host": "192.168.1.51",
"port": "6666",
"user": "admin",
"pass": "adminadmin"
}
}
```
To enable qBittorrent integration, follow the setup guide [here](https://github.com/lgallard/qBittorrent-Controller/wiki/How-to-enable-the-qBittorrent-Web-UI).
</details>
<details>
<summary>📡 REQUESTS Settings</summary>
```json
{
"REQUESTS": {
"verify": false,
"timeout": 20,
"max_retry": 8
}
}
```
- `verify`: Verifies SSL certificates
- `timeout`: Maximum timeout (in seconds) for each request
- `max_retry`: Number of retry attempts per segment during M3U8 index download
</details>
<details>
<summary>📥 M3U8_DOWNLOAD Settings</summary>
```json
{
"M3U8_DOWNLOAD": {
"tqdm_delay": 0.01,
"default_video_workser": 12,
"default_audio_workser": 12,
"segment_timeout": 8,
"download_audio": true,
"merge_audio": true,
"specific_list_audio": [
"ita"
],
"download_subtitle": true,
"merge_subs": true,
"specific_list_subtitles": [
"ita",
"eng"
],
"cleanup_tmp_folder": true
}
}
```
#### Performance Settings
- `tqdm_delay`: Delay between progress bar updates
- `default_video_workser`: Number of threads for video download
* Can be changed with `--default_video_worker <number>`
- `default_audio_workser`: Number of threads for audio download
* Can be changed with `--default_audio_worker <number>`
- `segment_timeout`: Timeout for downloading individual segments
#### Audio Settings
- `download_audio`: Whether to download audio tracks
- `merge_audio`: Whether to merge audio with video
- `specific_list_audio`: List of audio languages to download
* Can be changed with `--specific_list_audio ita,eng`
#### Subtitle Settings
- `download_subtitle`: Whether to download subtitles
- `merge_subs`: Whether to merge subtitles with video
- `specific_list_subtitles`: List of subtitle languages to download
* Can be changed with `--specific_list_subtitles ita,eng`
#### Cleanup
- `cleanup_tmp_folder`: Remove temporary .ts files after download
</details>
<details>
<summary>🌍 Available Language Codes</summary>
| European | Asian | Middle Eastern | Others |
|-----------------|-----------------|-----------------|-----------------|
| ita - Italian | chi - Chinese | ara - Arabic | eng - English |
| spa - Spanish | jpn - Japanese | heb - Hebrew | por - Portuguese|
| fre - French | kor - Korean | tur - Turkish | fil - Filipino |
| ger - German | hin - Hindi | | ind - Indonesian|
| rus - Russian | mal - Malayalam | | may - Malay |
| swe - Swedish | tam - Tamil | | vie - Vietnamese|
| pol - Polish | tel - Telugu | | |
| ukr - Ukrainian | tha - Thai | | |
</details>
<details>
<summary>🎥 M3U8_CONVERSION Settings</summary>
```json
{
"M3U8_CONVERSION": {
"use_codec": false,
"use_vcodec": true,
"use_acodec": true,
"use_bitrate": true,
"use_gpu": false,
"default_preset": "ultrafast"
}
}
```
#### Basic Settings
- `use_codec`: Use specific codec settings
- `use_vcodec`: Use specific video codec
- `use_acodec`: Use specific audio codec
- `use_bitrate`: Apply bitrate settings
- `use_gpu`: Enable GPU acceleration (if available)
- `default_preset`: FFmpeg encoding preset
#### Encoding Presets
The `default_preset` configuration can be set to:
- `ultrafast`: Extremely fast conversion but larger file size
- `superfast`: Very fast with good quality/size ratio
- `veryfast`: Fast with good compression
- `faster`: Optimal balance for most users
- `fast`: Good compression, moderate time
- `medium`: FFmpeg default setting
- `slow`: High quality, slower process
- `slower`: Very high quality, slow process
- `veryslow`: Maximum quality, very slow process
#### GPU Acceleration
When `use_gpu` is enabled, supports:
- NVIDIA: NVENC
- AMD: AMF
- Intel: QSV
Note: Requires updated drivers and FFmpeg with hardware acceleration support.
</details>
<details>
<summary>🔍 M3U8_PARSER Settings</summary>
```json
{
"M3U8_PARSER": {
"force_resolution": "Best",
"get_only_link": false
}
}
```
#### Resolution Options
- `force_resolution`: Choose video resolution:
* `"Best"`: Highest available resolution
* `"Worst"`: Lowest available resolution
* `"720p"`: Force 720p resolution
* Specific resolutions:
- 1080p (1920x1080)
- 720p (1280x720)
- 480p (640x480)
- 360p (640x360)
- 320p (480x320)
- 240p (426x240)
- 240p (320x240)
- 144p (256x144)
#### Link Options
- `get_only_link`: Return M3U8 playlist/index URL instead of downloading
</details>
# Global Search
<details>
<summary>🔍 Feature Overview</summary>
You can now search across multiple streaming sites at once using the Global Search feature. This allows you to find content more efficiently without having to search each site individually.
</details>
## Using Global Search
The Global Search feature provides a unified interface to search across all supported sites:
## Search Options
<details>
<summary>🎯 Search Options</summary>
When using Global Search, you have three ways to select which sites to search:
1. **Search all sites** - Searches across all available streaming sites
2. **Search by category** - Group sites by their categories (movies, series, anime, etc.)
3. **Select specific sites** - Choose individual sites to include in your search
</details>
## Navigation and Selection
<details>
<summary>📝 Navigation and Selection</summary>
After performing a search:
@ -673,13 +652,16 @@ After performing a search:
2. Select an item by number to view details or download
3. The system will automatically use the appropriate site's API to handle the download
</details>
## Command Line Arguments
<details>
<summary>⌨️ Command Line Arguments</summary>
The Global Search can be configured from the command line:
- `--global` - Perform a global search across multiple sites.
- `-s`, `--search` - Specify the search terms.
</details>
# Examples of terminal usage
@ -699,25 +681,32 @@ python test_run.py --global -s "cars"
# Docker
You can run the script in a docker container, to build the image just run
<details>
<summary>🐳 Basic Setup</summary>
Build the image:
```
docker build -t streaming-community-api .
```
and to run it use
Run the container with Cloudflare DNS for better connectivity:
```
docker run -it --dns 1.1.1.1 -p 8000:8000 streaming-community-api
```
</details>
<details>
<summary>💾 Custom Storage Location</summary>
By default the videos will be saved in `/app/Video` inside the container. To save them on your machine:
```
docker run -it -p 8000:8000 streaming-community-api
docker run -it --dns 9.9.9.9 -p 8000:8000 -v /path/to/download:/app/Video streaming-community-api
```
</details>
By default the videos will be saved in `/app/Video` inside the container, if you want to to save them in your machine instead of the container just run
```
docker run -it -p 8000:8000 -v /path/to/download:/app/Video streaming-community-api
```
### Docker quick setup with Make
<details>
<summary>🛠️ Quick Setup with Make</summary>
Inside the Makefile (install `make`) are already configured two commands to build and run the container:
@ -729,52 +718,57 @@ make LOCAL_DIR=/path/to/download run-container
```
The `run-container` command mounts also the `config.json` file, so any change to the configuration file is reflected immediately without having to rebuild the image.
</details>
# Telegram Usage
## Configuration
<details>
<summary>⚙️ Basic Configuration</summary>
The bot was created to replace terminal commands and allow interaction via Telegram. Each download runs within a screen session, enabling multiple downloads to run simultaneously.
To run the bot in the background, simply start it inside a screen session and then press Ctrl + A, followed by D, to detach from the session without stopping the bot.
</details>
<details>
<summary>🤖 Bot Commands</summary>
Command Functions:
🔹 /start Starts a new search for a download. This command performs the same operations as manually running the script in the terminal with test_run.py.
🔹 /list Displays the status of active downloads, with options to:
Stop an incorrect download using /stop <ID>.
View the real-time output of a download using /screen <ID>.
- Stop an incorrect download using /stop <ID>
- View the real-time output of a download using /screen <ID>
⚠ Warning: If a download is interrupted, incomplete files may remain in the folder specified in config.json. These files must be deleted manually to avoid storage or management issues.
</details>
🛠 Configuration: Currently, the bot's settings are stored in the config.json file, which is located in the same directory as the telegram_bot.py script.
<details>
<summary>🔧 Environment Setup</summary>
## .env Example:
You need to create an .env file and enter your Telegram token and user ID to authorize only one user to use it
Create an `.env` file with:
```
TOKEN_TELEGRAM=IlTuo2131TOKEN$12D3Telegram
AUTHORIZED_USER_ID=12345678
DEBUG=False
```
</details>
## Install Python Dependencies
<details>
<summary>📥 Dependencies & Launch</summary>
Install dependencies:
```bash
pip install -r requirements.txt
```
## On Linux/MacOS:
Start the bot from the folder /StreamingCommunity/TelegramHelp
Start the bot (from /StreamingCommunity/TelegramHelp):
```bash
python3 telegram_bot.py
```
</details>
# Tutorials
@ -788,19 +782,6 @@ python3 telegram_bot.py
- To Finish [website API](https://github.com/Arrowar/StreamingCommunity/tree/test_gui_1)
- To finish [website API 2](https://github.com/hydrosh/StreamingCommunity/tree/test_gui_1)
# Contributing
Contributions are welcome! Steps:
1. Fork the repository
2. Create feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to branch (`git push origin feature/AmazingFeature`)
5. Open Pull Request
# Disclaimer
This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
## Useful Project
### 🎯 [Unit3Dup](https://github.com/31December99/Unit3Dup)
@ -816,8 +797,6 @@ API non ufficiale per accedere ai contenuti del sito italiano StreamingCommunity
### 🎥 [stream-buddy](https://github.com/Bbalduzz/stream-buddy)
Tool per guardare o scaricare film dalla piattaforma StreamingCommunity.
## Contributors
# Disclaimer
<a href="https://github.com/Arrowar/StreamingCommunity/graphs/contributors" alt="View Contributors">
<img src="https://contrib.rocks/image?repo=Arrowar/StreamingCommunity&max=1000&columns=10" alt="Contributors" />
</a>
This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.

View File

@ -24,6 +24,7 @@ indice = 3
_useFor = "film_serie"
_priority = 0
_engineDownload = "tor"
_deprecate = False
console = Console()
msg = Prompt()

View File

@ -27,6 +27,7 @@ indice = 2
_useFor = "film_serie"
_priority = 0
_engineDownload = "hls"
_deprecate = True
msg = Prompt()
console = Console()

View File

@ -27,6 +27,7 @@ indice = 1
_useFor = "anime"
_priority = 0
_engineDownload = "mp4"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -22,6 +22,7 @@ indice = 8
_useFor = "anime"
_priority = 0
_engineDownload = "mp4"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -1,74 +0,0 @@
# 09.06.24
import logging
from urllib.parse import quote_plus
# External library
from rich.console import Console
from rich.prompt import Prompt
# Internal utilities
from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Logic class
from .site import title_search, media_search_manager, table_show_manager
from .series import download_thread
# Variable
indice = 6
_useFor = "serie"
_priority = 0
_engineDownload = "mp4"
msg = Prompt()
console = Console()
def process_search_result(select_title):
"""
Handles the search result and initiates the download for either a film or series.
"""
if "Serie TV" in str(select_title.type):
download_thread(select_title)
else:
logging.error(f"Not supported: {select_title.type}")
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
# Search on database
len_database = title_search(quote_plus(string_to_search))
# If only the database is needed, return the manager
if get_onlyDatabase:
return media_search_manager
if len_database > 0:
select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
else:
# If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
search()

View File

@ -1,118 +0,0 @@
# 13.06.24
import os
from urllib.parse import urlparse
from typing import Tuple
# External library
from rich.console import Console
# Internal utilities
from StreamingCommunity.Util.message import start_message
from StreamingCommunity.Util.os import os_manager
from StreamingCommunity.Lib.Downloader import MP4_downloader
# Logic class
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.Api.Template.Util import (
manage_selection,
map_episode_title,
validate_episode_selection,
display_episodes_list
)
from StreamingCommunity.Api.Template.config_loader import site_constant
# Player
from .util.ScrapeSerie import GetSerieInfo
from StreamingCommunity.Api.Player.ddl import VideoSource
# Variable
console = Console()
def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo) -> Tuple[str,bool]:
"""
Downloads a specific episode.
Parameters:
- index_episode_selected (int): Episode index
- scape_info_serie (GetSerieInfo): Scraper object with series information
Returns:
- str: Path to downloaded file
- bool: Whether download was stopped
"""
start_message()
# Get episode information
obj_episode = scape_info_serie.selectEpisode(1, index_episode_selected-1)
console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [bold magenta]{obj_episode.get('name')}[/bold magenta] ([cyan]E{index_episode_selected}[/cyan]) \n")
# Define filename and path for the downloaded video
title_name = os_manager.get_sanitize_file(
f"{map_episode_title(scape_info_serie.tv_name, None, index_episode_selected, obj_episode.get('name'))}.mp4"
)
mp4_path = os.path.join(site_constant.SERIES_FOLDER, scape_info_serie.tv_name)
# Create output folder
os_manager.create_path(mp4_path)
# Setup video source
video_source = VideoSource(site_constant.COOKIE, obj_episode.get('url'))
# Get m3u8 master playlist
master_playlist = video_source.get_playlist()
# Parse start page url
parsed_url = urlparse(obj_episode.get('url'))
# Start download
r_proc = MP4_downloader(
url=master_playlist,
path=os.path.join(mp4_path, title_name),
referer=f"{parsed_url.scheme}://{parsed_url.netloc}/",
)
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
return os.path.join(mp4_path, title_name), False
def download_thread(dict_serie: MediaItem, episode_selection: str = None):
"""
Download all episode of a thread
Parameters:
dict_serie (MediaItem): The selected media item
episode_selection (str, optional): Episode selection input that bypasses manual input
"""
scrape_serie = GetSerieInfo(dict_serie, site_constant.COOKIE)
# Get episode list
episodes = scrape_serie.getEpisodeSeasons()
episodes_count = len(episodes)
# Display episodes list and manage user selection
if episode_selection is None:
last_command = display_episodes_list(scrape_serie.list_episodes)
else:
last_command = episode_selection
console.print(f"\n[cyan]Using provided episode selection: [yellow]{episode_selection}")
# Validate episode selection
list_episode_select = manage_selection(last_command, episodes_count)
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
# Download selected episodes
kill_handler = bool(False)
for i_episode in list_episode_select:
if kill_handler:
break
kill_handler = download_video(i_episode, scrape_serie)[1]

View File

@ -1,87 +0,0 @@
# 09.06.24
import logging
# External libraries
import httpx
from bs4 import BeautifulSoup
from rich.console import Console
# Internal utilities
from StreamingCommunity.Util.config_json import config_manager
from StreamingCommunity.Util.headers import get_userAgent
from StreamingCommunity.Util.table import TVShowManager
# Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable
console = Console()
media_search_manager = MediaManager()
table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
def title_search(query: str) -> int:
"""
Search for titles based on a search query.
Parameters:
- query (str): The query to search for.
Returns:
- int: The number of titles found.
"""
media_search_manager.clear()
table_show_manager.clear()
search_url = f"{site_constant.FULL_URL}/search/?&q={query}&quick=1&type=videobox_video&nodes=11"
console.print(f"[cyan]Search url: [yellow]{search_url}")
try:
response = httpx.get(
search_url,
headers={'user-agent': get_userAgent()},
timeout=max_timeout,
follow_redirects=True
)
response.raise_for_status()
except Exception as e:
console.print(f"[red]Site: {site_constant.SITE_NAME}, request search error: {e}")
return 0
# Create soup and find table
soup = BeautifulSoup(response.text, "html.parser")
table_content = soup.find('ol', class_="ipsStream")
if table_content:
for title_div in table_content.find_all('li', class_='ipsStreamItem'):
try:
title_type = title_div.find("p", class_="ipsType_reset").find_all("a")[-1].get_text(strip=True)
name = title_div.find("span", class_="ipsContained").find("a").get_text(strip=True)
link = title_div.find("span", class_="ipsContained").find("a").get("href")
title_info = {
'name': name,
'url': link,
'type': title_type,
'image': title_div.find("div", class_="ipsColumn").find("img").get("src")
}
media_search_manager.add_media(title_info)
except Exception as e:
print(f"Error parsing a film entry: {e}")
return media_search_manager.get_length()
else:
logging.error("No table content found.")
return -999

View File

@ -1,112 +0,0 @@
# 13.06.24
import sys
import logging
from typing import List, Dict
# External libraries
import httpx
from bs4 import BeautifulSoup
# Internal utilities
from StreamingCommunity.Util.config_json import config_manager
from StreamingCommunity.Util.headers import get_userAgent
# Logic class
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Variable
max_timeout = config_manager.get_int("REQUESTS", "timeout")
class GetSerieInfo:
def __init__(self, dict_serie: MediaItem, cookies) -> None:
"""
Initializes the GetSerieInfo object with default values.
Parameters:
- dict_serie (MediaItem): Dictionary containing series information (optional).
"""
self.headers = {'user-agent': get_userAgent()}
self.cookies = cookies
self.url = dict_serie.url
self.tv_name = None
self.list_episodes = None
def get_episode_number(self) -> List[Dict[str, str]]:
"""
Retrieves the number of episodes for a specific season.
Parameters:
n_season (int): The season number.
Returns:
List[Dict[str, str]]: List of dictionaries containing episode information.
"""
try:
response = httpx.get(f"{self.url}?area=online", cookies=self.cookies, headers=self.headers, timeout=max_timeout)
response.raise_for_status()
except Exception as e:
logging.error(f"Insert value for [ips4_device_key, ips4_member_id, ips4_login_key] in config.json file SITE \\ ddlstreamitaly \\ cookie. Use browser debug and cookie request with a valid account, filter by DOC. Error: {e}")
sys.exit(0)
# Parse HTML content of the page
soup = BeautifulSoup(response.text, "html.parser")
# Get tv name
self.tv_name = soup.find("span", class_= "ipsType_break").get_text(strip=True)
# Find the container of episodes for the specified season
table_content = soup.find('div', class_='ipsMargin_bottom:half')
list_dict_episode = []
for episode_div in table_content.find_all('a', href=True):
# Get text of episode
part_name = episode_div.get_text(strip=True)
if part_name:
obj_episode = {
'name': part_name,
'url': episode_div['href']
}
list_dict_episode.append(obj_episode)
self.list_episodes = list_dict_episode
return list_dict_episode
# ------------- FOR GUI -------------
def getNumberSeason(self) -> int:
"""
Get the total number of seasons available for the series.
Note: DDLStreamItaly typically provides content organized as threads, not seasons.
"""
return 1
def getEpisodeSeasons(self, season_number: int = 1) -> list:
"""
Get all episodes for a specific season.
Note: For DDLStreamItaly, this returns all episodes as they're typically in one list.
"""
if not self.list_episodes:
self.list_episodes = self.get_episode_number()
return self.list_episodes
def selectEpisode(self, season_number: int = 1, episode_index: int = 0) -> dict:
"""
Get information for a specific episode.
"""
episodes = self.getEpisodeSeasons()
if not episodes or episode_index < 0 or episode_index >= len(episodes):
logging.error(f"Episode index {episode_index} is out of range")
return None
return episodes[episode_index]

View File

@ -24,6 +24,7 @@ indice = 5
_useFor = "serie"
_priority = 0
_engineDownload = "hls"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -23,6 +23,7 @@ indice = 8
_useFor = "film_serie"
_priority = 1 # NOTE: Site search need the use of tmbd obj
_engineDownload = "hls"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -28,6 +28,7 @@ indice = 0
_useFor = "film_serie"
_priority = 0
_engineDownload = "hls"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -23,6 +23,7 @@ indice = 8
_useFor = "film_serie"
_priority = 10 # !!! MOLTO LENTO
_engineDownload = "hls"
_deprecate = False
msg = Prompt()
console = Console()

View File

@ -166,7 +166,6 @@ class ConfigManager:
raise Exception(f"Error downloading reference configuration. Code: {response.status_code}")
reference_config = response.json()
console.print(f"[bold cyan]Reference configuration downloaded:[/bold cyan] [green]{len(reference_config)} keys available[/green]")
# Compare and update missing keys
merged_config = self._deep_merge_configs(self.config, reference_config)
@ -285,7 +284,6 @@ class ConfigManager:
self.configSite = data[0]['data']
site_count = len(self.configSite) if isinstance(self.configSite, dict) else 0
console.print(f"[bold green]Site data retrieved:[/bold green] {site_count} streaming services available")
else:
console.print("[bold yellow]API returned an empty data set[/bold yellow]")

View File

@ -61,7 +61,8 @@ def load_search_functions():
priority = getattr(mod, '_priority', 0)
if priority == 0:
modules.append((module_name, indice, use_for))
if not getattr(mod, '_deprecate'):
modules.append((module_name, indice, use_for))
except Exception as e:
console.print(f"[red]Failed to import module {module_name}: {str(e)}")

View File

@ -30,7 +30,7 @@ from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, Teleg
# Config
SHOW_TRENDING = config_manager.get_bool('DEFAULT', 'show_trending')
CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
NOT_CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
@ -61,7 +61,7 @@ def load_search_functions():
loaded_functions = {}
# Lista dei siti da escludere se TELEGRAM_BOT è attivo
excluded_sites = {"cb01new", "ddlstreamitaly", "guardaserie", "ilcorsaronero", "mostraguarda"} if TELEGRAM_BOT else set()
excluded_sites = {"cb01new", "guardaserie", "ilcorsaronero", "mostraguarda"} if TELEGRAM_BOT else set()
# Find api home directory
if getattr(sys, 'frozen', False): # Modalità PyInstaller
@ -89,9 +89,11 @@ def load_search_functions():
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
# Get 'indice' from the module
indice = getattr(mod, 'indice', 0)
use_for = getattr(mod, '_useFor', 'other')
modules.append((module_name, indice, use_for))
indice = getattr(mod, 'indice')
use_for = getattr(mod, '_useFor')
if not getattr(mod, '_deprecate'):
modules.append((module_name, indice, use_for))
except Exception as e:
console.print(f"[red]Failed to import module {module_name}: {str(e)}")
@ -202,13 +204,16 @@ def main(script_id = 0):
initialize()
if not internet_manager.check_dns_provider():
print()
console.print("[red]❌ ERROR: DNS configuration is required!")
console.print("[red]The program cannot function correctly without proper DNS settings.")
console.print("[yellow]Please configure one of these DNS servers:")
console.print("[blue]• Cloudflare (1.1.1.1)")
console.print("[blue]• Quad9 (9.9.9.9)")
console.print("[blue]• Cloudflare (1.1.1.1) 'https://developers.cloudflare.com/1.1.1.1/setup/windows/'")
console.print("[blue]• Quad9 (9.9.9.9) 'https://docs.quad9.net/Setup_Guides/Windows/Windows_10/'")
console.print("\n[yellow]⚠️ The program will not work until you configure your DNS settings.")
input("[yellow]Press Enter to exit...")
time.sleep(1)
msg.ask("[yellow]Press Enter to exit...")
# Load search functions
search_functions = load_search_functions()
@ -259,21 +264,9 @@ def main(script_id = 0):
"other": "white"
}
# Add dynamic arguments based on loaded search modules
used_short_options = set()
for alias, (_, use_for) in search_functions.items():
short_option = alias[:3].upper()
original_short_option = short_option
count = 1
while short_option in used_short_options:
short_option = f"{original_short_option}{count}"
count += 1
used_short_options.add(short_option)
long_option = alias
parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.')
# Add numeric arguments for each search module
for idx, (alias, (_, use_for)) in enumerate(search_functions.items()):
parser.add_argument(f'--{idx}', action='store_true', help=f'Search using {alias.split("_")[0]} ({use_for})')
parser.add_argument('-s', '--search', default=None, help='Search terms')
@ -309,12 +302,11 @@ def main(script_id = 0):
global_search(search_terms)
return
# Map command-line arguments to functions
arg_to_function = {alias: func for alias, (func, _) in search_functions.items()}
# Check which argument is provided and run the corresponding function
for arg, func in arg_to_function.items():
if getattr(args, arg):
# Check for numeric arguments
search_functions_list = list(search_functions.items())
for i in range(len(search_functions_list)):
if getattr(args, str(i)):
alias, (func, _) = search_functions_list[i]
run_function(func, search_terms=search_terms)
return
@ -324,28 +316,23 @@ def main(script_id = 0):
# Create dynamic prompt message and choices
choice_labels = {str(i): (alias.split("_")[0].capitalize(), use_for) for i, (alias, (_, use_for)) in enumerate(search_functions.items())}
# Add global search option to the menu
#global_search_key = str(len(choice_labels))
#choice_labels[global_search_key] = ("Global Search", "all")
#input_to_function[global_search_key] = global_search
# Display the category legend in a single line
legend_text = " | ".join([f"[{color}]{category.capitalize()}[/{color}]" for category, color in color_map.items()])
console.print(f"\n[bold green]Category Legend:[/bold green] {legend_text}")
# Construct the prompt message with color-coded site names
# Construct the prompt message with color-coded site names and aliases
prompt_message = "[green]Insert category [white](" + ", ".join(
[f"{key}: [{color_map.get(label[1], 'white')}]{label[0]}[/{color_map.get(label[1], 'white')}]" for key, label in choice_labels.items()]
[f"{key}: [{color_map.get(label[1], 'white')}]{label[0]}"
for key, label in choice_labels.items()]
) + "[white])"
if TELEGRAM_BOT:
# Display the category legend in a single line
category_legend_str = "Categorie: \n" + " | ".join([
f"{category.capitalize()}" for category in color_map.keys()
])
# Costruisci il messaggio senza emoji
# Build message with aliases
prompt_message = "Inserisci il sito:\n" + "\n".join(
[f"{key}: {label[0]}" for key, label in choice_labels.items()]
)
@ -356,7 +343,7 @@ def main(script_id = 0):
category = bot.ask(
"select_provider",
f"{category_legend_str}\n\n{prompt_message}",
None # Passiamo la lista delle chiavi come scelte
None
)
else:
@ -379,10 +366,11 @@ def main(script_id = 0):
console.print("[red]Invalid category.")
if CLOSE_CONSOLE:
restart_script() # Riavvia lo script invece di uscire
if NOT_CLOSE_CONSOLE:
restart_script()
else:
force_exit() # Usa la funzione per chiudere sempre
force_exit()
if TELEGRAM_BOT:
bot.send_message(f"Chiusura in corso", None)
@ -390,4 +378,4 @@ def main(script_id = 0):
# Delete script_id
script_id = TelegramSession.get_session()
if script_id != "unknown":
TelegramSession.deleteScriptId(script_id)
TelegramSession.deleteScriptId(script_id)

View File

@ -1,329 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--primary-color: #8c52ff;
--secondary-color: #6930c3;
--accent-color: #00e5ff;
--background-color: #121212;
--card-background: #1e1e1e;
--text-color: #f8f9fa;
--shadow-color: rgba(0, 0, 0, 0.25);
--card-hover: #2a2a2a;
--border-color: #333333;
--header-bg: rgba(18, 18, 18, 0.95);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
transition: all 0.2s ease;
}
body {
font-family: 'Inter', 'Segoe UI', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
}
header {
background-color: var(--header-bg);
backdrop-filter: blur(10px);
position: fixed;
width: 100%;
padding: 15px 0;
z-index: 1000;
box-shadow: 0 2px 12px var(--shadow-color);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
flex: 1;
}
.site-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
padding: 2rem 0;
}
.site-item {
min-height: 280px;
background-color: var(--card-background);
border-radius: 16px;
padding: 30px;
box-shadow: 0 6px 20px var(--shadow-color);
transition: transform 0.3s ease, box-shadow 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
}
.site-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
}
.site-item:hover {
transform: translateY(-5px);
box-shadow: 0 12px 30px var(--shadow-color);
}
.site-item img {
width: 80px;
height: 80px;
margin-bottom: 1.5rem;
border-radius: 16px;
object-fit: cover;
border: 2px solid var(--border-color);
}
.site-content {
text-align: center;
width: 100%;
}
.site-item h3 {
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--primary-color);
}
.domain {
color: var(--text-color);
opacity: 0.8;
font-size: 0.9rem;
margin-bottom: 1.5rem;
word-break: break-all;
}
.site-item a {
margin-top: 1rem;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
text-decoration: none;
font-weight: 500;
padding: 12px 28px;
border-radius: 8px;
width: fit-content;
margin: 0 auto;
display: flex;
align-items: center;
gap: 8px;
}
.site-item a:hover {
opacity: 0.9;
transform: translateY(-2px);
}
footer {
background: var(--card-background);
border-top: 1px solid var(--border-color);
margin-top: auto;
padding: 40px 20px;
position: relative;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 30px;
position: relative;
padding: 10px 0;
}
.footer-section {
padding: 20px;
border-radius: 12px;
transition: transform 0.3s ease, background-color 0.3s ease;
background-color: var(--card-background);
border: 1px solid var(--border-color);
}
.footer-section:hover {
transform: translateY(-5px);
background-color: var(--card-hover);
}
.footer-title {
color: var(--accent-color);
font-size: 1.3rem;
margin-bottom: 1.5rem;
padding-bottom: 0.5rem;
position: relative;
letter-spacing: 0.5px;
}
.footer-title::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 60px;
height: 3px;
border-radius: 2px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
}
.footer-links {
list-style: none;
}
.footer-links li {
margin-bottom: 0.8rem;
}
.footer-links a {
color: var(--text-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
opacity: 0.8;
transition: all 0.3s ease;
padding: 8px 12px;
border-radius: 8px;
background-color: transparent;
}
.footer-links a:hover {
opacity: 1;
color: var(--accent-color);
transform: translateX(8px);
background-color: rgba(140, 82, 255, 0.1);
}
.footer-links i {
width: 20px;
text-align: center;
font-size: 1.2rem;
color: var(--primary-color);
transition: transform 0.3s ease;
}
.footer-links a:hover i {
transform: scale(1.2);
}
.github-stats {
display: flex;
gap: 10px;
margin-top: 10px;
font-size: 0.8rem;
}
.github-badge {
background-color: var(--background-color);
padding: 4px 8px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.github-badge i {
color: var(--accent-color);
}
.footer-description {
margin-top: 15px;
font-size: 0.9rem;
color: var(--text-color);
opacity: 0.8;
line-height: 1.5;
}
.update-info {
text-align: center;
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid var(--border-color);
}
.update-note {
color: var(--accent-color);
font-size: 0.9rem;
opacity: 0.9;
}
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
text-align: center;
}
.footer-title::after {
left: 50%;
transform: translateX(-50%);
}
.footer-links a {
justify-content: center;
}
.footer-links a:hover {
transform: translateY(-5px);
}
.footer-section {
margin-bottom: 20px;
}
}
.loader {
border: 3px solid var(--border-color);
border-top: 3px solid var(--primary-color);
border-right: 3px solid var(--accent-color);
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.site-item {
padding: 25px;
}
.site-item img {
width: 70px;
height: 70px;
}
}
.old-domain, .time-change {
color: var(--text-color);
opacity: 0.7;
font-size: 0.85rem;
margin-bottom: 0.5rem;
word-break: break-all;
}
.label {
color: var(--accent-color);
font-weight: 500;
}

View File

@ -1,88 +0,0 @@
const supabaseUrl = 'https://zvfngpoxwrgswnzytadh.supabase.co';
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inp2Zm5ncG94d3Jnc3duenl0YWRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDAxNTIxNjMsImV4cCI6MjA1NTcyODE2M30.FNTCCMwi0QaKjOu8gtZsT5yQttUW8QiDDGXmzkn89QE';
async function loadSiteData() {
try {
const siteList = document.getElementById('site-list');
const headers = {
'apikey': supabaseKey,
'Authorization': `Bearer ${supabaseKey}`,
'Content-Type': 'application/json'
};
const response = await fetch(`${supabaseUrl}/rest/v1/public`, {
method: 'GET',
headers: headers
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
siteList.innerHTML = '';
if (data && data.length > 0) {
const configSite = data[0].data;
for (const siteName in configSite) {
const site = configSite[siteName];
const siteItem = document.createElement('div');
siteItem.className = 'site-item';
const siteIcon = document.createElement('img');
siteIcon.src = `https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${site.full_url}&size=128`;
siteIcon.alt = `${siteName} icon`;
siteIcon.onerror = function() {
this.src = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 24 24" fill="none" stroke="%238c52ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>';
};
const siteContent = document.createElement('div');
siteContent.className = 'site-content';
const siteTitle = document.createElement('h3');
siteTitle.textContent = siteName;
if (site.old_domain) {
const oldDomain = document.createElement('p');
oldDomain.className = 'old-domain';
oldDomain.innerHTML = `<span class="label">Previous domain:</span> ${site.old_domain.replace(/^https?:\/\//, '')}`;
siteContent.appendChild(oldDomain);
}
if (site.time_change) {
const timeChange = document.createElement('p');
timeChange.className = 'time-change';
const changeDate = new Date(site.time_change);
const dateString = isNaN(changeDate) ? site.time_change : changeDate.toLocaleDateString();
timeChange.innerHTML = `<span class="label">Updated:</span> ${dateString}`;
siteContent.appendChild(timeChange);
}
const siteLink = document.createElement('a');
siteLink.href = site.full_url;
siteLink.target = '_blank';
siteLink.innerHTML = 'Visit <i class="fas fa-external-link-alt"></i>';
siteLink.rel = 'noopener noreferrer';
siteContent.appendChild(siteTitle);
siteContent.appendChild(siteLink);
siteItem.appendChild(siteIcon);
siteItem.appendChild(siteContent);
siteList.appendChild(siteItem);
}
} else {
siteList.innerHTML = '<div class="no-sites">No sites available</div>';
}
} catch (error) {
console.error('Errore:', error);
siteList.innerHTML = `
<div class="error-message">
<p>Errore nel caricamento</p>
<button onclick="loadSiteData()" class="retry-button">Riprova</button>
</div>
`;
}
}
document.addEventListener('DOMContentLoaded', loadSiteData);

View File

@ -24,11 +24,6 @@
"user": "admin",
"pass": "adminadmin"
},
"REQUESTS": {
"verify": false,
"timeout": 20,
"max_retry": 8
},
"M3U8_DOWNLOAD": {
"tqdm_delay": 0.01,
"default_video_workser": 12,
@ -59,11 +54,9 @@
"force_resolution": "Best",
"get_only_link": false
},
"SITE_EXTRA": {
"ddlstreamitaly": {
"ips4_device_key": "",
"ips4_member_id": "",
"ips4_login_key": ""
}
"REQUESTS": {
"verify": false,
"timeout": 20,
"max_retry": 8
}
}

View File

@ -1,20 +1,19 @@
FROM python:3.11-slim
COPY . /app
WORKDIR /app
ENV TEMP /tmp
RUN mkdir -p $TEMP
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg \
build-essential \
libssl-dev \
libffi-dev \
python3-dev \
libxml2-dev \
libxslt1-dev
libxslt1-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "test_run.py"]

View File

@ -29,4 +29,4 @@ setup(
"Bug Reports": "https://github.com/Lovi-0/StreamingCommunity/issues",
"Source": "https://github.com/Lovi-0/StreamingCommunity",
}
)
)