diff --git a/README.md b/README.md index 696a0ba..368a686 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,179 @@ -# thumbnail-tinder +# Thumbnail Tinder -Приложение для итогового отбора превью из расширения Outlier Saver. +Приложение для итогового отбора превью. Оно работает с файлами браузерного расширения Outlier Saver, которое сохраняет видео с аномально высокими просмотрами (outliers). -## Запуск +Вы загружаете CSV из расширения и листаете видео как в Tinder: превью, название, просмотры, подписчики и outlier score. Свайп вправо — нравится, влево — нет. В конце приложение отдаёт новый CSV в том же формате, но только с понравившимися видео. Отдельная страница показывает отобранное сеткой, как главная YouTube. -Откройте `index.html` в браузере (двойной клик). Сервер и установка не нужны. -Файлы `index.html`, `liked.html` и `style.css` должны лежать в одной папке. Шрифты и превью подгружаются из интернета. +Приложение статическое: три файла, без сервера, базы данных и сборки. Файл CSV читается прямо в браузере и никуда не отправляется. -1. Перетащите CSV из расширения на страницу или нажмите «Выбрать файл». -2. Оцените видео: - - свайп вправо / `→` / ♥ — нравится; - - свайп влево / `←` / ✕ — не нравится; - - кнопка «Пропустить» вверху / свайп вверх / `↑` / `S` — отложить; - - `Z` — отменить последнее решение, `Enter` — открыть видео на YouTube. -3. Нажмите «Скачать ♥». Получится файл `<имя>-liked.csv` в том же формате, что и исходный - (BOM, CRLF, те же колонки и кавычки), но только с понравившимися строками в исходном порядке. +## Как пользоваться -Пропущенные видео в файл не попадают. Когда основной проход закончится, их можно пересмотреть. -Прогресс сохраняется в браузере: если снова открыть тот же файл, работа продолжится с того места, где вы остановились. +### 1. Отбор: `index.html` -## Галерея понравившихся +1. Перетащите CSV на страницу или нажмите «Выбрать файл». +2. Оценивайте видео: -Откройте `liked.html` (или кнопку «Галерея» в приложении) и загрузите скачанный `…-liked.csv`. -Видео покажутся сеткой, как на главной YouTube: 2 колонки на узком экране, 3 на среднем и 4 на широком. -У каждой карточки есть превью, название, канал, просмотры, подписчики и outlier score на превью. -Клик ведёт на видео, сортировать можно по score, просмотрам, подписчикам или дате добавления. + | Действие | Мышь / тач | Клавиатура | + |---|---|---| + | Нравится | свайп вправо или кнопка «Нравится» | `→` или `D` | + | Не нравится | свайп влево или кнопка «Не нравится» | `←` или `A` | + | Пропустить (отложить) | кнопка «Пропустить» вверху или свайп вверх | `↑` или `S` | + | Отменить последнее решение | «Отменить» | `Z` или `Backspace` | + | Открыть видео на YouTube | «Открыть на YouTube» или клик по названию | `Enter` | + +3. Нажмите «Скачать CSV». Получится файл `<имя исходного файла>-liked.csv`. Скачать можно в любой момент, не обязательно оценивать всё. + +**Формат результата.** Понравившиеся строки копируются из исходного файла без изменений: те же колонки, кавычки, BOM и переводы строк (CRLF), исходный порядок. Если выгрузить все строки, получится файл, идентичный исходному. + +**Пропущенные видео** в файл не попадают. Когда основной проход закончится, их можно пересмотреть отдельно. + +**Прогресс сохраняется в браузере.** Если закрыть вкладку и снова загрузить тот же файл, работа продолжится с того места, где вы остановились. Прогресс привязан к содержимому файла, браузеру и адресу страницы. + +На карточке под числом outlier score есть шкала от 1× до 1000× (логарифмическая), на ней отмечено, где находится видео. + +### 2. Галерея: `liked.html` + +Откройте её кнопкой «Галерея» и загрузите скачанный `…-liked.csv`. Подойдёт и любой CSV из расширения. + +- Сетка: 2 колонки на узком экране, 3 на среднем, 4 на широком. +- На карточке: превью, название, канал, просмотры, подписчики, outlier score. +- Сортировка: как в файле, по outlier score, по просмотрам, по подписчикам, по дате добавления. +- Клик по превью или названию открывает видео, клик по каналу открывает канал. + +### Формат входного CSV + +Обязательные колонки: `videoId`, `title`, `thumbnailUrl`. Остальные показываются, если есть: + +``` +videoId,title,url,thumbnailUrl,channelTitle,channelUrl,subscribers,views,outlierScore,isShort,foundOn,firstSeenAt,updatedAt +``` + +Пример лежит в репозитории: `outliers-2026-11-14.csv`. + +## Локальный запуск + +Откройте `index.html` в браузере двойным кликом. Ничего устанавливать не нужно. + +Файлы `index.html`, `liked.html` и `style.css` должны лежать в одной папке. Шрифты (Google Fonts) и превью (`i.ytimg.com`) подгружаются из интернета. Без сети интерфейс работает, но превью не покажутся. + +Если нужен локальный сервер: + +```bash +python3 -m http.server 8080 +# http://localhost:8080 +``` + +## Развёртывание на сервере (nginx) + +Нужен сервер с Linux (ниже команды для Debian/Ubuntu) и домен, у которого A/AAAA-запись указывает на сервер. Порты 80 и 443 должны быть открыты. + +Конфиг: [`deploy/nginx.conf`](deploy/nginx.conf). + +### 1. Установите nginx + +```bash +sudo apt update +sudo apt install nginx +``` + +### 2. Выложите файлы приложения + +Нужны только три файла. CSV и остальное на сервер класть не надо. + +```bash +git clone <адрес-репозитория> ~/thumbnail-tinder +cd ~/thumbnail-tinder + +sudo mkdir -p /var/www/thumbnail-tinder +sudo install -m 644 index.html liked.html style.css /var/www/thumbnail-tinder/ +``` + +### 3. Подключите конфиг + +```bash +sudo cp deploy/nginx.conf /etc/nginx/sites-available/thumbnail-tinder +sudo sed -i 's/thumbnails.example.com/ВАШ.ДОМЕН/' /etc/nginx/sites-available/thumbnail-tinder +sudo ln -s /etc/nginx/sites-available/thumbnail-tinder /etc/nginx/sites-enabled/ + +sudo nginx -t && sudo systemctl reload nginx +``` + +Если `nginx -t` ругается на конфликт `server_name` или на порт, проверьте, не включён ли стандартный сайт `default`: `sudo rm /etc/nginx/sites-enabled/default`. + +Проверьте: `http://ВАШ.ДОМЕН` должен открыть приложение. + +### 4. Включите HTTPS + +```bash +sudo apt install certbot python3-certbot-nginx +sudo certbot --nginx -d ВАШ.ДОМЕН +``` + +Certbot сам добавит блок `listen 443 ssl`, редирект с HTTP и настроит автопродление сертификата. Проверить продление: `sudo certbot renew --dry-run`. + +После этого по желанию включите HSTS: добавьте в блок `server` с `listen 443` строку +`add_header Strict-Transport-Security "max-age=31536000" always;` +Делайте это только когда уверены, что HTTPS работает стабильно. + +### 5. Обновление + +Статика раздаётся без сборки, перезапускать nginx не нужно: + +```bash +cd ~/thumbnail-tinder +git pull +sudo install -m 644 index.html liked.html style.css /var/www/thumbnail-tinder/ +``` + +HTML отдаётся с `Cache-Control: no-cache`, а `style.css` кэшируется на 1 час, поэтому изменения стилей могут появиться у пользователей с задержкой до часа. + +### Что делает конфиг + +- **Белый список путей.** Отдаются только `/`, `/index.html`, `/liked.html` и `/style.css`. Всё остальное (CSV, `.git`, README) отвечает 404, даже если файл случайно окажется в папке. +- **Только GET и HEAD.** Остальные методы получают 405. +- **Content-Security-Policy.** Разрешены только ресурсы самого сайта, шрифты с `fonts.googleapis.com` и `fonts.gstatic.com` и превью с `i.ytimg.com`. Запросы из скриптов запрещены (`connect-src 'none'`): приложение ничего не отправляет по сети, CSV остаётся в браузере. Встроенные скрипты и стили разрешены через `'unsafe-inline'`, потому что они лежат прямо в HTML. +- **Прочие заголовки:** `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`, `Permissions-Policy`. +- **Сжатие gzip**, кодировка UTF-8, отдельные логи `thumbnail-tinder.*.log`. + +Если вы добавите новую внешнюю зависимость (другой шрифт, CDN-скрипт), обновите CSP в конфиге, иначе браузер её заблокирует. + +### Проверка конфига без сервера + +Конфиг можно проверить в Docker: + +```bash +docker run --rm -p 8080:80 \ + -v "$PWD/deploy/nginx.conf":/etc/nginx/conf.d/default.conf:ro \ + -v "$PWD":/var/www/thumbnail-tinder:ro \ + nginx:alpine +# http://localhost:8080 +``` + +### Доступ только для себя + +Приложение не хранит данных на сервере, но если не хотите, чтобы им пользовались посторонние, добавьте в блок `server` базовую авторизацию: + +```nginx +auth_basic "Thumbnail Tinder"; +auth_basic_user_file /etc/nginx/.htpasswd; +``` + +```bash +sudo apt install apache2-utils +sudo htpasswd -c /etc/nginx/.htpasswd ИМЯ +``` + +## Структура + +``` +index.html отбор: свайпы и экспорт CSV +liked.html галерея понравившихся +style.css общие стили и цвета +deploy/nginx.conf конфиг nginx +outliers-2026-11-14.csv пример входного файла +``` + +## Ограничения + +- Прогресс хранится в `localStorage` браузера: он не переносится между браузерами и устройствами, и очистка данных сайта его удалит. Результат лучше скачать до этого. +- Для превью берётся `maxresdefault.jpg`, а если его нет, ссылка из CSV. Видео, удалённые с YouTube, покажутся без картинки. diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..0de6faf --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,81 @@ +# Thumbnail Tinder: static site. +# +# Куда положить: /etc/nginx/sites-available/thumbnail-tinder +# (затем ссылка в sites-enabled; на RHEL-подобных системах просто /etc/nginx/conf.d/thumbnail-tinder.conf). +# Перед запуском замените thumbnails.example.com на свой домен. +# HTTPS добавляется одной командой после запуска: sudo certbot --nginx -d <домен> +# (подробности в README.md, раздел «Развёртывание»). + +server { + listen 80; + listen [::]:80; + server_name thumbnails.example.com; + + root /var/www/thumbnail-tinder; + index index.html; + charset utf-8; + + access_log /var/log/nginx/thumbnail-tinder.access.log; + error_log /var/log/nginx/thumbnail-tinder.error.log; + + # Приложение только отдаёт статику. + if ($request_method !~ ^(GET|HEAD)$) { + return 405; + } + + # ---------- Сжатие ---------- + gzip on; + gzip_comp_level 5; + gzip_min_length 512; + gzip_vary on; + gzip_types text/css text/plain application/javascript application/json image/svg+xml; + # text/html сжимается по умолчанию + + # ---------- Заголовки безопасности ---------- + # Страницам нужны: шрифты с Google Fonts и превью с i.ytimg.com. + # Скрипты и стили встроены в HTML, поэтому разрешён 'unsafe-inline'. + # connect-src 'none': приложение не делает сетевых запросов, файл CSV читается локально в браузере. + # Важно: add_header в location сбрасывает унаследованные заголовки, + # поэтому ниже в location используется только expires. + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' data: https://i.ytimg.com; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "no-referrer" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; + + # ---------- Маршруты: белый список ---------- + # Отдаются только три файла приложения. Всё остальное (в том числе случайно + # скопированные CSV, .git, README) отвечает 404. + + location = / { + try_files /index.html =404; + expires epoch; # Cache-Control: no-cache, страница всегда свежая + } + + location = /index.html { + expires epoch; + } + + location = /liked.html { + expires epoch; + } + + location = /style.css { + expires 1h; # файл без хеша в имени, поэтому кэш короткий + } + + location = /favicon.ico { + access_log off; + return 204; + } + + # Для certbot (если используется метод webroot, а не --nginx) + location ^~ /.well-known/acme-challenge/ { + root /var/www/certbot; + default_type text/plain; + } + + location / { + return 404; + } +}