Initial commit - ColdFilm parser

This commit is contained in:
2026-02-24 16:00:09 +03:00
commit 7b75373a57
10 changed files with 676 additions and 0 deletions

46
.htaccess Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}