Initial commit - ColdFilm parser
This commit is contained in:
46
.htaccess
Normal file
46
.htaccess
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# .htaccess для films.tolchin.pro
|
||||||
|
|
||||||
|
# Включение RewriteEngine
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# Перенаправление на HTTPS (раскомментируйте если есть SSL)
|
||||||
|
# RewriteCond %{HTTPS} off
|
||||||
|
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||||
|
|
||||||
|
# Перенаправление www на без-www
|
||||||
|
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||||
|
RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
|
||||||
|
|
||||||
|
# Кэширование статических файлов
|
||||||
|
<IfModule mod_expires.c>
|
||||||
|
ExpiresActive On
|
||||||
|
ExpiresByType image/jpeg "access plus 1 year"
|
||||||
|
ExpiresByType image/png "access plus 1 year"
|
||||||
|
ExpiresByType image/webp "access plus 1 year"
|
||||||
|
ExpiresByType image/x-icon "access plus 1 year"
|
||||||
|
ExpiresByType text/css "access plus 1 month"
|
||||||
|
ExpiresByType application/javascript "access plus 1 month"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Сжатие
|
||||||
|
<IfModule mod_deflate.c>
|
||||||
|
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Защита от прямого доступа к конфигурационным файлам
|
||||||
|
<FilesMatch "^\.">
|
||||||
|
Order allow,deny
|
||||||
|
Deny from all
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Безопасные заголовки
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header set X-Content-Type-Options "nosniff"
|
||||||
|
Header set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header set X-XSS-Protection "1; mode=block"
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# Правила для SEO
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^$ index.html [L]
|
||||||
71
README.md
Normal file
71
README.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# 🎬 ColdFilm - Парсер фильмов и сериалов
|
||||||
|
|
||||||
|
Веб-приложение для мониторинга новых релизов с сайта ColdFilm и быстрого доступа к торрентам.
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- 📡 Автоматическое получение списка новых фильмов и сериалов
|
||||||
|
- 🖼️ Отображение постеров
|
||||||
|
- 📥 Быстрый выбор качества торрента (720p, 1080p, 4K, Magnet)
|
||||||
|
- 🔄 Автообновление списка каждые 15 минут
|
||||||
|
- 🎨 Тёмная тема с адаптивным дизайном
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Локальный запуск
|
||||||
|
|
||||||
|
Просто откройте `index.html` в браузере.
|
||||||
|
|
||||||
|
### На Synology (Web Station)
|
||||||
|
|
||||||
|
1. Скопируйте файлы в папку веб-сервера:
|
||||||
|
```
|
||||||
|
/volume1/web/films/
|
||||||
|
```
|
||||||
|
2. Настройте Web Station
|
||||||
|
3. Откройте `http://ваш-ip/films/`
|
||||||
|
|
||||||
|
### Структура файлов
|
||||||
|
|
||||||
|
```
|
||||||
|
├── index.html # Основная страница
|
||||||
|
├── style.css # Стили
|
||||||
|
├── script.js # Скрипты
|
||||||
|
├── favicon.svg # Иконка сайта
|
||||||
|
├── robots.txt # Правила для поисковиков
|
||||||
|
├── .htaccess # Настройки Apache
|
||||||
|
├── sitemap.xml # Карта сайта
|
||||||
|
├── sitemap-index.xml # Индекс карт сайта
|
||||||
|
├── sitemap-images.xml # Карта изображений
|
||||||
|
└── README.md # Этот файл
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEO и карты сайта
|
||||||
|
|
||||||
|
- **sitemap.xml** — основная карта сайта с главной страницей
|
||||||
|
- **sitemap-index.xml** — индекс всех sitemap-файлов
|
||||||
|
- **sitemap-images.xml** — карта изображений (постеров) — заполняется динамически
|
||||||
|
- **robots.txt** — правила для поисковых роботов
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Современный браузер с поддержкой JavaScript
|
||||||
|
- Доступ к интернету (для проксирования запросов)
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
### CORS-прокси
|
||||||
|
|
||||||
|
По умолчанию используются публичные прокси. Для повышения надёжности можно использовать свой прокси-сервер.
|
||||||
|
|
||||||
|
### Автообновление
|
||||||
|
|
||||||
|
Интервал обновления: 15 минут (настраивается в `script.js`)
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
## Автор
|
||||||
|
|
||||||
|
Для вопросов и предложений: свяжитесь с автором
|
||||||
13
favicon.svg
Normal file
13
favicon.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#00d4ff;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#007bff;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="32" height="32" rx="6" fill="#1a1a2e"/>
|
||||||
|
<circle cx="16" cy="16" r="10" fill="none" stroke="url(#grad)" stroke-width="2"/>
|
||||||
|
<circle cx="16" cy="16" r="4" fill="url(#grad)"/>
|
||||||
|
<rect x="6" y="12" width="4" height="8" rx="1" fill="#00d4ff"/>
|
||||||
|
<rect x="22" y="12" width="4" height="8" rx="1" fill="#00d4ff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 640 B |
30
index.html
Normal file
30
index.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>🎬 ColdFilm</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎥 Новые релизы ColdFilm</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status" class="status loading">🔄 Инициализация...</div>
|
||||||
|
<button class="refresh-btn" onclick="loadFilms()">🔄 Обновить</button>
|
||||||
|
<ul id="films" class="film-list"></ul>
|
||||||
|
|
||||||
|
<!-- Модальное окно выбора качества -->
|
||||||
|
<div id="qualityModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h2>📥 Выберите качество</h2>
|
||||||
|
<div id="qualityOptions" class="quality-options"></div>
|
||||||
|
<button class="btn quality-close" onclick="closeModal()">Отмена</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
robots.txt
Normal file
34
robots.txt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# robots.txt для films.tolchin.pro
|
||||||
|
|
||||||
|
Host: films.tolchin.pro
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Разрешаем основные страницы
|
||||||
|
Allow: /$
|
||||||
|
Allow: /index.html
|
||||||
|
|
||||||
|
# Запрещаем индексацию технических файлов
|
||||||
|
Disallow: /style.css
|
||||||
|
Disallow: /script.js
|
||||||
|
Disallow: /favicon.svg
|
||||||
|
Disallow: /.htaccess
|
||||||
|
Disallow: /README.md
|
||||||
|
|
||||||
|
# Запрещаем служебные пути
|
||||||
|
Disallow: /*.md$
|
||||||
|
Disallow: /*.txt$
|
||||||
|
Disallow: /*.xml$
|
||||||
|
|
||||||
|
# Индексация
|
||||||
|
Sitemap: https://films.tolchin.pro/sitemap.xml
|
||||||
|
Sitemap: https://films.tolchin.pro/sitemap-index.xml
|
||||||
|
|
||||||
|
# Clean параметры
|
||||||
|
Clean-param: ref /
|
||||||
|
|
||||||
|
# Ограничение индексации дубликатов
|
||||||
|
Noindex: /style.css
|
||||||
|
Noindex: /script.js
|
||||||
|
Noindex: /favicon.svg
|
||||||
259
script.js
Normal file
259
script.js
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
const targetUrl = 'https://coldfilm.ink';
|
||||||
|
|
||||||
|
let currentFilmName = '';
|
||||||
|
let availableTorrents = [];
|
||||||
|
|
||||||
|
// Публичные CORS-прокси
|
||||||
|
const PROXIES = [
|
||||||
|
'https://corsproxy.io/?',
|
||||||
|
'https://api.allorigins.win/raw?url=',
|
||||||
|
'https://proxy.cors.sh/',
|
||||||
|
'https://corsproxy.org/?',
|
||||||
|
'https://allorigins.win/raw?url='
|
||||||
|
];
|
||||||
|
|
||||||
|
// Кеш страниц фильмов и постеров
|
||||||
|
const filmData = new Map();
|
||||||
|
|
||||||
|
async function fetchWithProxy(url) {
|
||||||
|
for (const proxy of PROXIES) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(proxy + encodeURIComponent(url), { timeout: 15000 });
|
||||||
|
if (response.ok) return await response.text();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
throw new Error('Все прокси недоступны');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseColdfilm(html) {
|
||||||
|
const films = [];
|
||||||
|
|
||||||
|
// Сначала соберём все ссылки на фильмы с названиями
|
||||||
|
// Сразу исключаем Telegram
|
||||||
|
const linksMap = new Map();
|
||||||
|
let match;
|
||||||
|
const linkRegex = new RegExp('href="(/news/[^"#]+)"[^>]*class="kino-h"[^>]*title="([^"]+)\\[Смотреть Онлайн\\]"', 'gi');
|
||||||
|
while ((match = linkRegex.exec(html)) !== null) {
|
||||||
|
const title = match[2].trim();
|
||||||
|
if (title.toLowerCase().includes('telegram')) continue;
|
||||||
|
linksMap.set(match[1], title);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('linksMap:', linksMap.size);
|
||||||
|
|
||||||
|
// Пробуем разные паттерны для поиска постеров
|
||||||
|
const patterns = [
|
||||||
|
'<a[^>]+href="(/news/[^"#]+)"[^>]*>\\s*<img[^>]+src="([^"]+)"',
|
||||||
|
'<img[^>]+src="([^"]+)"[^>]*>\\s*<a[^>]+href="(/news/[^"#]+)"',
|
||||||
|
'class="kino-h"[^>]*title="[^"]*"[^>]*>[\\s\\S]*?<img[^>]+src="([^"]+)"'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const pat of patterns) {
|
||||||
|
const regex = new RegExp(pat, 'gi');
|
||||||
|
let count = 0;
|
||||||
|
while ((match = regex.exec(html)) !== null) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
console.log('Pattern:', pat.substring(0, 50), 'matches:', count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем постеры по alt/title - там есть название фильма
|
||||||
|
const posterMap = new Map();
|
||||||
|
const posterRegex = new RegExp('<img[^>]+src="([^"]+)"[^>]+alt="([^"]*\\[Смотреть Онлайн\\])"[^>]*>', 'gi');
|
||||||
|
while ((match = posterRegex.exec(html)) !== null) {
|
||||||
|
const poster = match[1];
|
||||||
|
const alt = match[2].replace('[Смотреть Онлайн]', '').trim();
|
||||||
|
|
||||||
|
let fullPoster = poster;
|
||||||
|
if (!fullPoster.startsWith('http')) {
|
||||||
|
fullPoster = targetUrl + poster;
|
||||||
|
}
|
||||||
|
posterMap.set(alt, fullPoster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Альтернативно - title вместо alt
|
||||||
|
const posterRegex2 = new RegExp('<img[^>]+src="([^"]+)"[^>]+title="([^"]*\\[Смотреть Онлайн\\])"[^>]*>', 'gi');
|
||||||
|
while ((match = posterRegex2.exec(html)) !== null) {
|
||||||
|
const poster = match[1];
|
||||||
|
const title = match[2].replace('[Смотреть Онлайн]', '').trim();
|
||||||
|
|
||||||
|
if (!posterMap.has(title)) {
|
||||||
|
let fullPoster = poster;
|
||||||
|
if (!fullPoster.startsWith('http')) {
|
||||||
|
fullPoster = targetUrl + poster;
|
||||||
|
}
|
||||||
|
posterMap.set(title, fullPoster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Найдено постеров:', posterMap.size);
|
||||||
|
|
||||||
|
// Собираем фильмы и ищем постер по названию
|
||||||
|
const urls = Array.from(linksMap.keys());
|
||||||
|
const titles = Array.from(linksMap.values());
|
||||||
|
|
||||||
|
console.log('URLs:', urls.length, 'Posters:', posterMap.size);
|
||||||
|
|
||||||
|
for (let i = 0; i < titles.length; i++) {
|
||||||
|
const url = urls[i];
|
||||||
|
const title = titles[i];
|
||||||
|
const poster = posterMap.get(title) || '';
|
||||||
|
|
||||||
|
films.push({ title, url, poster });
|
||||||
|
filmData.set(title, { url, poster });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Фильмов с постерами:', films.length);
|
||||||
|
return films.slice(0, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFilms() {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
const list = document.getElementById('films');
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.textContent = '🌐 Загружаем...';
|
||||||
|
const html = await fetchWithProxy(targetUrl);
|
||||||
|
status.textContent = '🔍 Парсим...';
|
||||||
|
|
||||||
|
const films = parseColdfilm(html);
|
||||||
|
|
||||||
|
if (films.length > 0) {
|
||||||
|
status.className = 'status success';
|
||||||
|
status.textContent = `✅ Найдено ${films.length} релизов!`;
|
||||||
|
|
||||||
|
list.innerHTML = '';
|
||||||
|
films.forEach((film, i) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'film-item';
|
||||||
|
|
||||||
|
// Постер
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = 'film-poster';
|
||||||
|
img.src = film.poster;
|
||||||
|
img.alt = film.title;
|
||||||
|
img.onerror = () => { img.style.display = 'none'; };
|
||||||
|
|
||||||
|
// Инфо
|
||||||
|
const info = document.createElement('div');
|
||||||
|
info.className = 'film-info';
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'film-title';
|
||||||
|
span.textContent = `${i + 1}. ${film.title}`;
|
||||||
|
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'btn';
|
||||||
|
btn.textContent = '📥 Скачать';
|
||||||
|
btn.onclick = () => showQualityModal(film.title);
|
||||||
|
|
||||||
|
info.appendChild(span);
|
||||||
|
info.appendChild(btn);
|
||||||
|
|
||||||
|
li.appendChild(img);
|
||||||
|
li.appendChild(info);
|
||||||
|
|
||||||
|
list.appendChild(li);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
status.textContent = '❌ Фильмы не найдены';
|
||||||
|
status.className = 'status error';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
status.textContent = `❌ Ошибка: ${e.message}`;
|
||||||
|
status.className = 'status error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showQualityModal(filmName) {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
const modal = document.getElementById('qualityModal');
|
||||||
|
const options = document.getElementById('qualityOptions');
|
||||||
|
|
||||||
|
currentFilmName = filmName;
|
||||||
|
availableTorrents = [];
|
||||||
|
options.innerHTML = '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
status.className = 'status downloading';
|
||||||
|
status.textContent = `🔍 Ищем торренты для ${filmName}...`;
|
||||||
|
|
||||||
|
const filmInfo = filmData.get(filmName);
|
||||||
|
if (!filmInfo) throw new Error('Ссылка не найдена');
|
||||||
|
const filmUrl = filmInfo.url;
|
||||||
|
|
||||||
|
const filmPageHtml = await fetchWithProxy(targetUrl + filmUrl);
|
||||||
|
|
||||||
|
// Ищем все торренты на странице
|
||||||
|
let match;
|
||||||
|
const torrentRegex = /href="(\/t\d+\/[^"]+\.torrent)"/gi;
|
||||||
|
while ((match = torrentRegex.exec(filmPageHtml)) !== null) {
|
||||||
|
let url = match[1];
|
||||||
|
if (!url.startsWith('http')) {
|
||||||
|
url = targetUrl + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем качество из названия файла
|
||||||
|
const filename = url.toLowerCase();
|
||||||
|
let quality = 'Стандарт';
|
||||||
|
if (filename.includes('720p') || filename.includes('hd720')) quality = '720p';
|
||||||
|
else if (filename.includes('1080p') || filename.includes('hd1080')) quality = '1080p';
|
||||||
|
else if (filename.includes('4k') || filename.includes('2160p')) quality = '4K';
|
||||||
|
|
||||||
|
// Проверяем, не добавляли ли уже
|
||||||
|
if (!availableTorrents.find(t => t.url === url)) {
|
||||||
|
availableTorrents.push({ url, quality });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ищем magnet
|
||||||
|
const magnetRegex = /href="(magnet:[^"]+)"/gi;
|
||||||
|
while ((match = magnetRegex.exec(filmPageHtml)) !== null) {
|
||||||
|
if (!availableTorrents.find(t => t.url === match[1])) {
|
||||||
|
availableTorrents.push({ url: match[1], quality: 'Magnet' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (availableTorrents.length === 0) {
|
||||||
|
throw new Error('Торренты не найдены');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сортируем: 4K, 1080p, 720p, STD, Magnet
|
||||||
|
const sortOrder = { '4K': 1, '1080p': 2, '720p': 3, 'Стандарт': 4, 'Magnet': 5 };
|
||||||
|
availableTorrents.sort((a, b) => (sortOrder[a.quality] || 99) - (sortOrder[b.quality] || 99));
|
||||||
|
|
||||||
|
// Создаём кнопки
|
||||||
|
availableTorrents.forEach(t => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'quality-btn';
|
||||||
|
btn.textContent = `📥 ${t.quality}`;
|
||||||
|
btn.onclick = () => downloadTorrent(t.url);
|
||||||
|
options.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.classList.add('active');
|
||||||
|
status.textContent = `✅ Найдено ${availableTorrents.length} торрентов`;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
status.className = 'status error';
|
||||||
|
status.textContent = `❌ Ошибка: ${e.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
document.getElementById('qualityModal').classList.remove('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadTorrent(url) {
|
||||||
|
const status = document.getElementById('status');
|
||||||
|
status.textContent = '⬇️ Открываем торрент...';
|
||||||
|
window.open(url, '_blank');
|
||||||
|
status.className = 'status success';
|
||||||
|
status.textContent = `✅ Торрент открыт!`;
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск при загрузке
|
||||||
|
loadFilms();
|
||||||
|
setInterval(loadFilms, 15 * 60 * 1000);
|
||||||
20
sitemap-images.xml
Normal file
20
sitemap-images.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Sitemap для изображений - заполняется автоматически при обновлении контента
|
||||||
|
https://developers.google.com/search/docs/advanced/sitemaps/image-sitemaps
|
||||||
|
-->
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
|
||||||
|
|
||||||
|
<!-- Пример структуры (заполняется динамически) -->
|
||||||
|
<!--
|
||||||
|
<url>
|
||||||
|
<loc>https://films.tolchin.pro/</loc>
|
||||||
|
<image:image>
|
||||||
|
<image:loc>https://films.tolchin.pro/images/poster1.jpg</image:loc>
|
||||||
|
<image:title>Название фильма</image:title>
|
||||||
|
</image:image>
|
||||||
|
</url>
|
||||||
|
-->
|
||||||
|
|
||||||
|
</urlset>
|
||||||
16
sitemap-index.xml
Normal file
16
sitemap-index.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
|
||||||
|
<!-- Основной sitemap -->
|
||||||
|
<sitemap>
|
||||||
|
<loc>https://films.tolchin.pro/sitemap.xml</loc>
|
||||||
|
<lastmod>2026-02-24</lastmod>
|
||||||
|
</sitemap>
|
||||||
|
|
||||||
|
<!-- Sitemap для изображений (когда постеры будут на своём домене) -->
|
||||||
|
<sitemap>
|
||||||
|
<loc>https://films.tolchin.pro/sitemap-images.xml</loc>
|
||||||
|
<lastmod>2026-02-24</lastmod>
|
||||||
|
</sitemap>
|
||||||
|
|
||||||
|
</sitemapindex>
|
||||||
25
sitemap.xml
Normal file
25
sitemap.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0"
|
||||||
|
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
|
||||||
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
||||||
|
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||||
|
|
||||||
|
<!-- Главная страница -->
|
||||||
|
<url>
|
||||||
|
<loc>https://films.tolchin.pro/</loc>
|
||||||
|
<changefreq>hourly</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
<lastmod>2026-02-24</lastmod>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
<!-- Альтернативные языковые версии (если будут) -->
|
||||||
|
<url>
|
||||||
|
<loc>https://films.tolchin.pro/</loc>
|
||||||
|
<xhtml:link rel="alternate" hreflang="ru" href="https://films.tolchin.pro/"/>
|
||||||
|
<xhtml:link rel="alternate" hreflang="en" href="https://films.tolchin.pro/"/>
|
||||||
|
<xhtml:link rel="alternate" hreflang="x-default" href="https://films.tolchin.pro/"/>
|
||||||
|
</url>
|
||||||
|
|
||||||
|
</urlset>
|
||||||
162
style.css
Normal file
162
style.css
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #1a1a2e;
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header { text-align: center; margin-bottom: 30px; }
|
||||||
|
h1 { font-size: 2.5em; color: #00d4ff; }
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 15px;
|
||||||
|
margin: 15px 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading { background: #333; color: #ffd700; animation: pulse 1.5s infinite; }
|
||||||
|
.success { background: #28a745; color: white; }
|
||||||
|
.error { background: #dc3545; color: white; }
|
||||||
|
.downloading { background: #007bff; color: white; }
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-list {
|
||||||
|
list-style: none;
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-item {
|
||||||
|
background: #16213e;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border-left: 4px solid #00d4ff;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-item:hover {
|
||||||
|
background: #0f3460;
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-poster {
|
||||||
|
width: 80px;
|
||||||
|
height: 120px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.film-title {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: #00d4ff;
|
||||||
|
color: #1a1a2e;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover { background: #00b8e6; }
|
||||||
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
background: #00d4ff;
|
||||||
|
color: #1a1a2e;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover { background: #00b8e6; }
|
||||||
|
|
||||||
|
/* Модальное окно выбора качества */
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.active {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: #16213e;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #00d4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-btn {
|
||||||
|
background: #0f3460;
|
||||||
|
color: white;
|
||||||
|
border: 2px solid #00d4ff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-btn:hover {
|
||||||
|
background: #00d4ff;
|
||||||
|
color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-close {
|
||||||
|
margin-top: 15px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid #666;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user