Не блокирует рекламу — проматывает её. Реклама загружается и «показывается» (показ засчитывается площадке), но на скорости до 16x, без звука, закрытая аккуратной заглушкой, а кнопка «Пропустить» нажимается автоматически. После рекламы ваши скорость и звук возвращаются в точности как были.
- YouTube из коробки — детект по классам плеера, полный исторический набор селекторов кнопки скипа.
- Любые сайты — добавляете домен в popup (право запрашивается точечно, только на этот домен), срабатывает универсальный детектор: Google IMA SDK, Video.js, JW Player + поиск кнопки по тексту «Skip/Пропустить».
- Обучение — если универсальный детектор не справился, жмёте «Указать кнопку на этой странице», кликаете по кнопке скипа на рекламе — AdFast запоминает её селектор для этого домена навсегда.
- Заглушка вместо стробоскопа — реклама на 16x выглядит как мельтешение кадров; AdFast закрывает её экраном «Проматываю рекламу…» со счётчиком.
- Счётчик времени — popup показывает, сколько часов жизни вам вернулось и сколько реклам промотано.
- Белый список каналов — «не трогать рекламу на этом канале», чтобы поддержать любимых авторов: один чекбокс на вкладке YouTube.
- Самопочинка — селекторы обновляются раз в сутки из JSON на GitHub: когда YouTube в очередной раз сменит классы, починка — это коммит, а не релиз через модерацию.
- Приватность — никакой аналитики и внешних запросов (кроме опционального GET за селекторами); права только на YouTube + добавленные вами сайты.
Исходники лежат в src/ как ES-модули, браузер загружает собранную папку dist/.
Бандл обязателен: MV3 не поддерживает type="module" для content scripts.
npm install
npm run build # разовая сборка в dist/
npm run watch # пересборка на лету во время разработки
npm run dist # минифицированная сборка для раздачи
npm test # smoke-тест ядра на заглушке DOM
npm run zip # dist/ -> adfast.zipnpm install && npm run build- Откройте
chrome://extensions - Включите Режим разработчика (справа вверху)
- Загрузить распакованное расширение → выберите папку
dist - Иконка »» в панели — все настройки там.
После правок в src/ держите включённым npm run watch и жмите «Обновить» на карточке расширения.
| Хочу | Делаю |
|---|---|
| Изменить скорость промотки | Слайдер в popup (2–16x), применяется сразу, даже посреди рекламы |
| Добавить сайт | Popup → «Сайты» → ввести vk.com → разрешить в системном диалоге |
| Сайт добавлен, но скип не работает | Открыть страницу с рекламой → popup → «Указать кнопку на этой странице» → кликнуть по кнопке скипа |
| Поддержать канал рекламой | На вкладке видео → popup → чекбокс «Не трогать рекламу на …» |
| Видеть рекламу вместо заглушки | Снять галку «Заглушка вместо мелькающих кадров» |
| Выключить всё | Тумблер в шапке popup |
- Создайте публичный репозиторий с файлом
selectors.json(структура — объектBUILTIN_DBизsrc/shared/selector-db.js, полеversionувеличивайте при каждом изменении). - В
src/shared/config.jsвпишите raw-ссылку вREMOTE_SELECTORS_URL:https://raw.githubusercontent.com/ВАШ_ЛОГИН/adfast-selectors/main/selectors.json - Расширение проверяет файл при старте браузера и раз в сутки; кривой JSON игнорируется, при недоступности сети работает встроенный набор.
Код уже совместим (шим browser || chrome, коллбэк-стиль storage), нужна только нативная обёртка:
npm run build
xcrun safari-web-extension-converter /путь/к/adfast-extension/dist --macos-only- В открывшемся Xcode-проекте — Run (⌘R). Для подписи хватит бесплатного Apple ID.
- Safari → Настройки → Дополнения → включить меню «Разработка».
- Меню Разработка → Разрешить неподписанные расширения (галка слетает при перезапуске Safari; с платным Developer-сертификатом не нужна).
- Safari → Настройки → Расширения → включить AdFast.
Нюансы Safari:
- Требуется Safari 16.4+ (
scripting.registerContentScriptsдля пользовательских сайтов). - Права на добавленные сайты Safari может дополнительно спросить своим баннером при первом заходе, либо выдайте вручную: Настройки → Расширения → AdFast → «Разрешить» для нужных сайтов.
- Всё остальное — детект, ускорение, оверлей, picker, статистика — идентично Chrome: content script один и тот же.
Три контекста расширения общаются только через storage и короткий протокол сообщений:
┌─ popup ────────────────────────┐ ┌─ service worker ───────────────┐
│ settings · stats │ │ sites регистрация │
│ sites · channel │ sync-sites │ content.js │
│ trainer │ ──────────▶ │ selectors fetch 1×/сутки │
└───────────────┬────────────────┘ └───────────────┬────────────────┘
│ │
│ пишет настройки │ selectorDB
▼ ▼
┌─ storage ──────────────────────────────────────────────────────────────────────┐
│ sync настройки · сайты · каналы · обученные селекторы │
│ local статистика · кеш селекторов │
└───────────────────────────────────────┬────────────────────────────────────────┘
│ onChanged — без перезагрузки вкладки
▼
┌─ content script (вкладка) ─────────────────────────────────────────────────────┐
│ page guard от двойной инъекции, hostname, isYouTube │
│ store настройки · белый список · обученный селектор · база селекторов │
│ adapters youtube · generic — «идёт ли реклама» и «где кнопка скипа» │
│ core engine (тик 250 мс) · stats │
│ ui overlay · picker · toast │
└────────────────────────────────────────────────────────────────────────────────┘
прямые сообщения popup ↔ content: page-info · start-picker · forget-trained
Общего состояния между контекстами нет: popup пишет в storage, content script
подхватывает через storage.onChanged — поэтому настройки применяются посреди рекламы.
Внутри вкладки:
тик каждые 250 мс
├─ рекламы нет, была → вернуть скорость и звук пользователя, записать статистику
├─ реклама началась → запомнить скорость/звук → 16x + mute + оверлей
└─ реклама идёт → удерживать скорость (плеер может сбрасывать)
→ кнопка скипа появилась? клик через 300–900 мс
полной цепочкой pointer/mouse-событий
Канал из белого списка → полный no-op. Обученная кнопка видна → считается признаком рекламы на кастомных сайтах.
- Server-side ad insertion (Twitch-стримы, ТВ-стриминги) и DRM-платформы (Netflix и т.п.) — не поддерживаются принципиально: реклама там вшита в поток на сервере, на клиенте нечего ускорять.
- Прямые эфиры — ускорять нечего, вы на краю буфера.
- YouTube периодически меняет классы плеера; при настроенном самообновлении чинится без вашего участия, иначе — правкой
BUILTIN_DBвsrc/shared/selector-db.js. - Проект для личного использования: модерация Chrome Web Store и App Store к таким расширениям враждебна, реалистичная дистрибуция — исходники/self-hosting.
adfast-extension/
├── src/
│ ├── shared/ общее для всех трёх контекстов
│ │ ├── api.js шим browser || chrome
│ │ ├── config.js REMOTE_SELECTORS_URL и периодичность обновления
│ │ ├── defaults.js модель данных: что лежит в sync, что в local
│ │ ├── messages.js константы протокола popup ↔ content ↔ background
│ │ ├── selector-db.js встроенная база селекторов + её валидация
│ │ ├── site-pattern.js домен ↔ origin-паттерн `*://*.host/*`
│ │ └── storage.js промис-обёртки над storage.sync/local
│ │
│ ├── content/ живёт во вкладке
│ │ ├── index.js точка входа: guard → store → engine
│ │ ├── page.js hostname, isYouTube, защита от двойной инъекции
│ │ ├── dom.js видимость, поиск видео, «человеческий» клик
│ │ ├── store.js настройки/белый список/обучение/база, live
│ │ ├── messages.js ответы на page-info, start-picker, forget-trained
│ │ ├── adapters/ «идёт ли реклама» и «где кнопка скипа»
│ │ │ ├── youtube.js точный детект по классам плеера + канал
│ │ │ ├── generic.js эвристики плееров, IMA, обученный селектор
│ │ │ └── index.js выбор адаптера под площадку
│ │ ├── core/
│ │ │ ├── engine.js тик 250 мс: enterAd → удержание → exitAd
│ │ │ └── stats.js экономия сессии → storage.local
│ │ └── ui/
│ │ ├── overlay.js заглушка поверх плеера
│ │ ├── picker.js режим «укажи кнопку Пропустить»
│ │ ├── selector-builder.js устойчивый CSS-селектор для элемента
│ │ └── toast.js уведомления picker'а
│ │
│ ├── background/ service worker
│ │ ├── index.js подписки на сообщения, install/startup, alarm
│ │ ├── sites.js регистрация content.js на сайтах пользователя
│ │ └── selectors.js суточный fetch базы селекторов
│ │
│ └── popup/ интерфейс настроек
│ ├── index.js инициализация секций
│ ├── dom.js $, bindCheckbox, formatDuration
│ ├── tab-context.js один запрос page-info на весь popup
│ └── sections/ settings · stats · channel · sites · trainer
│
├── public/ копируется в dist/ как есть
│ ├── manifest.json MV3-манифест
│ ├── popup.html · popup.css
│ └── icons/ 16/48/128
│
├── tests/content.smoke.mjs прогон ядра на заглушке DOM
├── build.mjs esbuild: src/ → dist/{content,background,popup}.js
├── LICENSE MIT
├── README.md этот файл
└── TZ.md полное техническое задание
Правило зависимостей: shared/ не знает ни о ком, content/core работает через
adapters, а не через DOM конкретной площадки, ui/ ничего не решает про рекламу.
Добавить новую площадку = написать один файл в adapters/ с двумя методами.
MIT — берите код куда угодно, сохраняя копирайт.
Лицензия относится только к коду. Ответственность за то, как расширение используется на конкретных площадках (правила и условия YouTube и других сайтов), лежит на пользователе; авторы гарантий не дают — см. текст лицензии.