docs: add implementation plan and sync spec with it

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
IgorVolochay
2026-10-25 10:48:07 +03:00
co-authored by Claude Sonnet 5.5
parent aab868c937
commit 05d8bff171
2 changed files with 2507 additions and 6 deletions
File diff suppressed because it is too large Load Diff
@@ -22,9 +22,11 @@
| Часть | Файл | Ответственность |
|---|---|---|
| Content script | `src/content.js` | Следит за DOM на `youtube.com`, достаёт ID видео из ссылок, читает значок VidIQ, шлёт пачки в background |
| Service worker | `src/background.js` | Очередь ID, вызовы API, расчёт score, решение «сохранять или нет», запись в БД, бейдж на иконке |
| Service worker | `src/background.js` | Очередь ID, цикл сброса пачками, статус и бейдж на иконке, реакция на смену ключа |
| Обработка пачки | `src/lib/process.js` | `processBatch`: проверка `seen`, запросы к API, расчёт score, решение «сохранять или нет», запись в БД. Зависимости (БД и API) передаются параметрами, поэтому логика покрыта тестами |
| Настройки и статус | `src/lib/storage.js` | Значения по умолчанию, `normalizeSettings`, чтение и запись настроек и статуса в `chrome.storage.local` |
| Формула и решение | `src/lib/score.js` | Чистые функции: `computeScore`, `shouldSave` |
| Разбор ссылок | `src/lib/video-id.js` | Извлечение ID из `/watch?v=`, `/shorts/` и отсев мусора |
| Разбор ссылок | `src/lib/video-id.js` | Извлечение ID из `/watch?v=`, `/shorts/`, отсев мусора, определение типа страницы по пути |
| Значок VidIQ | `src/lib/vidiq.js` | Селектор значка (одна константа) и `parseVidiqScore(text)` |
| API | `src/lib/api.js` | `videos.list`, `channels.list`, разбиение на пачки по 50, разбор ошибок |
| База | `src/lib/db.js` | Обёртка над IndexedDB |
@@ -36,14 +38,14 @@
## Поток данных
1. Content script на `youtube.com` реагирует на событие `yt-navigate-finish` (YouTube — SPA, страницы не перезагружаются) и на `MutationObserver` по документу. Срабатывание дебаунсится на 500 мс.
1. Content script на `youtube.com` реагирует на событие `yt-navigate-finish` (YouTube — SPA, страницы не перезагружаются) и на `MutationObserver` по документу. Сканирование запускается не чаще раза в 500 мс (троттлинг, а не дебаунс: пока играет видео, DOM меняется постоянно, и дебаунс не сработал бы). После навигации список уже отправленных ID сбрасывается.
2. Из ссылок `a[href*="/watch?v="]` и `a[href*="/shorts/"]` через `video-id.js` берутся ID. Для каждой карточки пробуется прочитать значок VidIQ. Уже отправленные с этой страницы ID запоминаются в `Set`, чтобы не спамить сообщениями.
3. Content script шлёт background сообщение `{videoId, vidiqScore | null, viaShortsUrl, page}`, где `page` — `home | watch | search | channel`.
4. Background копит ID и сбрасывает очередь раз в секунду либо при накоплении 50 штук. ID, проверенные менее 24 часов назад (`seen.checkedAt`), пропускаются.
4. Background копит ID и сбрасывает очередь раз в секунду либо при накоплении 50 штук. ID, проверенные менее 24 часов назад (`seen.checkedAt`), пропускаются, кроме ID со значком VidIQ: значок может отрисоваться позже первого сканирования, поэтому content script отправляет ID повторно, когда у него появился score, а проверка `seen` для таких ID не применяется.
5. `videos.list` (`part=snippet,statistics,contentDetails`) возвращает название, превью, просмотры, дату публикации, длительность и `channelId`.
6. Для каналов, которых нет в кэше `channels` или чья запись старше 24 часов, вызывается `channels.list` (`part=statistics,snippet`).
7. `score = views / max(subscribers, 1)`. Если подписчики скрыты, `score = null`.
8. Решение о сохранении (`shouldSave`): видео сохраняется, если `views ≥ minViews` и `score ≥ threshold`, **или** если `vidiqScore` не `null`. Условие по VidIQ действует независимо от своего порога и от `minViews`.
8. Решение о сохранении (`shouldSave`): видео сохраняется, если `views ≥ minViews` и `score ≥ threshold`, **или** если `vidiqScore` не `null`. Условие по VidIQ действует независимо от своего порога и от `minViews`. Уже сохранённое видео обновляется при каждой проверке, даже если его score опустился ниже порога.
9. Результат пишется upsert-ом в `outliers`, ID с временем проверки — в `seen` (в том числе несохранённые, чтобы не запрашивать их повторно).
## Модель данных
@@ -94,7 +96,7 @@
| Скрытые подписчики | `score = null`, видео сохраняется только при наличии `vidiqScore` |
| Очень мало подписчиков | Делитель `max(subs, 1)`, мусор отсекается через `minViews` |
| Приватное или удалённое видео | API его не возвращает; в `seen` пишется `status: unavailable`, повторная проверка после TTL |
| Service worker выгрузили | Очередь живёт в памяти и может потеряться. Content script пришлёт ID снова при следующем сканировании, так как `seen` пишется только после результата |
| Service worker выгрузили | Очередь живёт в памяти и может потеряться. `seen` пишется только после результата, а content script присылает ID снова после следующей навигации по YouTube или перезагрузки страницы |
| Повторная встреча видео | Upsert: обновляются `views`, `subscribers`, `score`, `vidiqScore` (если он есть сейчас), `updatedAt`; `isShort` может только стать `true` |
## Расход квоты