# IPTV Checker (iptvc)

[![Статус последнего релиза](https://img.shields.io/gitea/v/release/IPTV/iptvc?gitea_url=https%3A%2F%2Fgit.axenov.dev&display_name=release&color=green&cacheSeconds=600)](https://git.axenov.dev/IPTV/iptvc/releases/latest)

Консольная программа для проверки IPTV-плейлистов в формате m3u или m3u8.

> **Веб-сайт:** [m3u.su](https://m3u.su)  
> Документация: [m3u.su/docs](https://m3u.su/docs)  
> Telegram-канал: [@iptv_aggregator](https://t.me/iptv_aggregator)  
> Исходный код: [git.axenov.dev/IPTV](https://git.axenov.dev/IPTV)  

## Установка

Достаточно скачать и распаковать архив с подходящим исполняемым файлом [со страницы последнего релиза](https://git.axenov.dev/IPTV/iptvc/releases/latest):

| ОС      | Скачать для `amd64`                | Скачать для `arm64`                |
| ------- | ---------------------------------- | ---------------------------------- |
| Linux   | [linux_amd64.zip][linux_amd64]     | [linux_arm64.zip][linux_arm64]     |
| MacOS   | [darwin_amd64.zip][darwin_amd64]   | [darwin_arm64.zip][darwin_arm64]   |
| Windows | [windows_amd64.zip][windows_amd64] | [windows_arm64.zip][windows_arm64] |

[linux_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_amd64.zip
[darwin_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_amd64.zip
[windows_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_amd64.zip
[linux_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_arm64.zip
[darwin_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_arm64.zip
[windows_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_arm64.zip

## Компиляция

Для сборки потребуется Go версии 1.23.6 или выше.

```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
make help
```

### Цели Makefile

| Цель        | Описание                                                        |
| ----------- | --------------------------------------------------------------- |
| `help`      | Показать список доступных целей (по умолчанию)                  |
| `linux`     | Собрать Linux-бинарник и ZIP-архив в `bin/linux_$(GOARCH)/`     |
| `win`       | Собрать Windows-бинарник и ZIP-архив в `bin/windows_$(GOARCH)/` |
| `darwin`    | Собрать macOS-бинарник и ZIP-архив в `bin/darwin_$(GOARCH)/`    |
| `release`   | Собрать все платформы (`amd64` и `arm64`) и упаковать в ZIP     |
| `test`      | Запустить все тесты                                             |
| `clear`     | Удалить все собранные бинарники                                 |
| `image`     | Собрать Docker-образ и загрузить в локальный Docker daemon      |
| `image-all` | Собрать и отправить multi-arch манифест в registry              |

### Переменные Makefile

| Переменная        | По умолчанию                 | Описание                                         |
| ----------------- | ---------------------------- | ------------------------------------------------ |
| `BINARY_NAME`     | `iptvc`                      | Имя бинарного файла                              |
| `GOARCH`          | `amd64`                      | Целевая архитектура (`amd64`, `arm64`)           |
| `IMAGE_NAME`      | `git.axenov.dev/iptv/iptvc`  | Имя Docker-образа                                |
| `IMAGE_TAG`       | `latest`                     | Тег Docker-образа                                |
| `IMAGE_PLATFORMS` | `linux/amd64,linux/arm64`    | Платформы для multi-arch манифеста (`image-all`) |
| `VERSION`         | `git describe --tags ...`    | Версия, вшиваемая в бинарник                     |
| `COMMIT`          | `git rev-parse --short HEAD` | Коммит, вшиваемый в бинарник                     |

### Примеры сборки

```bash
# Быстрая сборка для текущей платформы
make build

# Сборка для Linux amd64
make linux GOARCH=amd64

# Сборка для Windows arm64
make win GOARCH=arm64

# Сборка всех релизных архивов
make release

# Сборка Docker-образа с тегом
make image IMAGE_TAG=1.0.0

# Сборка и отправка multi-arch манифеста
make image-all
```

### Запуск из исходников

Для быстрого запуска без сборки используйте `go run .`.
Для запуска тестов и статического анализа:

```bash
go test ./...
go vet ./...
```

## Быстрый старт

Открыть терминал в директории, куда распакован исполняемый файл `iptvc`.

Выполнить `./iptvc help` для получения краткой справки.

Если был клонирован репозиторий, то вместо `./iptvc` можно запустить `go run .` или `make help`.

Ниже рассмотрены простые примеры использования программы для проверки плейлистов.

### Проверка файла плейлиста

1. Скачать любой файл плейлиста, сохранив его в файл с именем, например, `mypls.m3u`
2. Выполнить команду `./iptvc check -f mypls.m3u`

Можно указывать множество разных файлов (каждый с `-f`) и комбинировать с другими аргументами.

### Проверка плейлиста по ссылке

1. Найти прямую ссылку на плейлист в интернете, например, `http://m3u.su/XYZ`
2. Выполнить команду `./iptvc check -u http://m3u.su/XYZ`

Можно указывать множество разных ссылок (каждый с `-u`) и комбинировать с другими аргументами.

### Проверка плейлиста из ini-списка

Подробности об ini-файле и его формате можно прочесть здесь: https://git.axenov.dev/IPTV/playlists

1. Скачать файл [`playlists.ini`](https://git.axenov.dev/IPTV/playlists/raw/branch/master/playlists.ini) или создать локальный файл в аналогичном формате (например, `./test.ini`)
2. Выполнить команду `./iptvc check` или `./iptvc check -i playlist.ini` для проверки всех плейлистов из файла `./playlists.ini`
3. Выполнить команду `./iptvc check -i test.ini -c ABC`, чтобы проверить только плейлист с кодом `ABC` из файла `./test.ini`

Если `-i` не указан явно, то будет попытка прочитать файл `playlists.ini`, находящийся в одной директории с iptvc.

Аргумент `-i` можно указывать только однажды, но его можно комбинировать с `-f` и `-u`.

### Флаги команды `check`

Источники плейлистов:

* `--file|-f PATH` — локальный файл плейлиста M3U/M3U8; флаг можно указывать несколько раз;
* `--url|-u URL` — удалённый плейлист по HTTP/HTTPS; флаг можно указывать несколько раз;
* `--code|-c CODE` — код плейлиста из ini-файла; флаг можно указывать несколько раз;
* `--ini|-i PATH` — путь к ini-файлу;
* `--tags|-t PATH` — путь к файлу тегов каналов.

Управление выводом и повторными проверками:

* `--json|-j` — вывести результаты проверки в формате JSON;
* `--quiet|-q` — подавить журналы, не отключая вывод JSON;
* `--verbose|-v` — включить подробный журнал;
* `--random|-r N` — выбрать N случайных плейлистов из ini-файла;
* `--repeat N` — повторить проверку N раз, значение `0` означает бесконечный цикл;
* `--playlists-all-cooldown N` — ждать N секунд между полными циклами проверки; значение также можно задать через `check.playlists.allCooldown`.

Параметры проверки и кеша также можно переопределить CLI-флагами.
Полный список доступен через `./iptvc check --help`.

Например, можно получить только json с результатами, передать его в `jq` и, отфильтровав результат, вывести названия оффлайн каналов:

```
./iptvc check ... -j -q | jq '.[].channels[] | select(.isOnline == false).title'
```

> [!NOTE]
> Набери `./iptvc help` для получения помощи.

## Веб-интерфейс

Программа включает встроенный веб-сервер для просмотра плейлистов и результатов их проверки.

### Запуск

```
./iptvc serve -i playlists.ini -p 8800
```

### Параметры команды `serve`

* `-p, --port` — порт для веб-сервера (переопределяет `config.yml` и `SERVER_PORT`);
* `--host` — хост для привязки (переопределяет `config.yml` и `SERVER_HOST`);
* `--check` — включить фоновую проверку плейлистов (по умолчанию выключена).

При указании `--check` доступны флаги проверки:

* `-i, --ini` — путь к ini-файлу (по умолчанию `./playlists.ini`);
* `-t, --tags` — путь к файлу тегов (по умолчанию `./channels.json`);
* `--playlists-all-cooldown N` — пауза между полными циклами проверки в секундах; если флаг не указан, используется `check.playlists.allCooldown` из конфигурации;
* `--repeat` — количество циклов проверки (по умолчанию `0` — бесконечно).

### Конфигурация

Приоритет настроек (от низшего к высшему):

1. **Значения по умолчанию** — встроены в код;
2. **`config.yml`** — YAML-файл в корне проекта (путь можно задать через флаг `--config`);
3. **Переменные окружения** — переопределяют значения из `config.yml` (если заданы);
4. **CLI-флаги** — переопределяют значения из окружения и `config.yml` (если заданы явно).

Флаги `--port` и `--host` переопределяют конфигурацию только если переданы явно.
Если флаг не указан, используется значение из переменной окружения, затем из `config.yml`,
затем значение по умолчанию.

Файл `.env` загружается автоматически, переменные из него применяются как переменные окружения.

### Иконки

Иконки следует подбирать здесь:
- https://pictogrammers.com/library/mdi/ (с поиском)
- https://pictogrammers.github.io/@mdi/font/7.4.47/ (полный справочник)

В конфиге указывается название иконки без префикса `mdi-`.

#### `config.yml`

```yaml
app:
  timezone: GMT
  debug: false
  logLevel: info
  playlists: ./playlists.ini
  tags: ./channels.json

server:
  host: ""
  port: 8800

site:
  baseUrl: http://localhost:8800
  repoUrl: https://git.axenov.dev/IPTV
  pageSize: 0
  favicon: # опциональный путь к иконке
  title: IPTV Checker
  meta:
    description: Самообновляемые бесплатные IPTV-плейлисты для домашнего просмотра по коротким ссылкам, списки каналов, проверка доступности
    keywords: самообновляемые,бесплатные,iptv-плейлисты,iptv,плейлисты
  tabs:
    raw:
      visible: true
    legal:
      visible: false
      text: |
        Данные, представленные на данной странице, получены автоматически из открыто доступных в интернете IPTV-плейлистов, опубликованных третьими лицами.

        Сервис не размещает и не транслирует медиаконтент, не создаёт, не призывает использовать и распространять плейлисты третьих лиц, а также не оказывает услуг по ретрансляции телепрограмм.

        Информация о телеканалах формируется исключительно путём обработки содержимого самого плейлиста.
  header:
    menu:
      - title: Помощь
        icon: help-circle-outline
        children:
          - title: Документация
            url: https://m3u.su/docs
            icon: book-open-variant-outline
          - title: Исходники
            url: https://git.axenov.dev/IPTV
            icon: code-braces
          - title: "@iptv_aggregator"
            url: https://t.me/iptv_aggregator
            icon: bullhorn-variant-outline
  footer:
    links:
      - title: Исходники
        url: https://git.axenov.dev/IPTV
        icon: code-braces
      - title: axenov.dev
        url: https://axenov.dev
        icon: account-outline
      - title: "@iptv_aggregator"
        url: https://t.me/iptv_aggregator
        icon: bullhorn-variant-outline

check:
  startOnServe: false
  playlists:
    userAgent:
      - Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
    timeout: 10
    allCooldown: 1800
    oneCooldown: 2
    maxRoutines: 1
    perRoutine: 1
  channels:
    userAgent: Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
    timeout: 10
    byteRange: 512
    cooldown: 0
    maxRoutines: 50
    perRoutine: 10

cache:
  enabled: true
  host: cache
  port: 6379
  username:
  password:
  db: 0
  ttl: 3600
```

### Часовой пояс

Параметр `app.timezone` задаёт часовой пояс, в котором отображается время последнего изменения плейлистов на сайте.
Поддерживаются значения из базы IANA (`Europe/Moscow`, `UTC`, `GMT` и т.д.) и фиксированные смещения (`UTC+3`, `UTC+03:00`). 
Если указано некорректное значение, используется `UTC`.

### SEO-метаданные

Секция `site.meta` позволяет задать описание и ключевые слова для главной страницы и других страниц, где мета-данные не переопределяются шаблоном:

```yaml
site:
  meta:
    description: Самообновляемые бесплатные IPTV-плейлисты...
    keywords: самообновляемые,бесплатные,iptv-плейлисты,iptv,плейлисты
```

Те же значения можно переопределить через переменные окружения `SITE_META_DESCRIPTION` и `SITE_META_KEYWORDS`.

`check.playlists.allCooldown` задаёт паузу между полными циклами проверки.
Она используется командами `check` и `serve` и больше не применяется дополнительно внутри одного цикла. `check.playlists.oneCooldown` задаёт паузу между отдельными плейлистами, а `check.channels.cooldown` — между каналами одного плейлиста.

Параметры задержек (`timeout`, `allCooldown`, `oneCooldown`, `cooldown`) и переменные окружения `CHECK_*_TIMEOUT`, `CHECK_*_COOLDOWN` задаются в секундах.
Они могут быть заданы как число или диапазон `[min, max]` в секундах.
При диапазоне для каждой операции выбирается случайное значение.

#### Переменные окружения

| Переменная                          | Соответствует в `config.yml`   | CLI-флаг                   | Описание                            |
| ----------------------------------- | ------------------------------ | -------------------------- | ----------------------------------- |
| `APP_DEBUG`                         | `app.debug`                    | `--debug`                  | Режим отладки                       |
| `APP_LOG_LEVEL`                     | `app.logLevel`                | `--log-level`              | Уровень журналирования              |
| `APP_TIMEZONE`                      | `app.timezone`                 | —                          | Часовой пояс                        |
| `APP_PLAYLISTS`                     | `app.playlists`                | `-i`, `--ini`              | Путь к `playlists.ini`              |
| `APP_TAGS`                          | `app.tags`                     | `-t`, `--tags`             | Путь к `channels.json`              |
| `SERVER_HOST`                       | `server.host`                  | `serve --host`             | Хост для привязки                   |
| `SERVER_PORT`                       | `server.port`                  | `-p`, `serve --port`       | Порт веб-сервера                    |
| `SITE_BASE_URL`                     | `site.baseUrl`                | —                          | Базовый URL для ссылок              |
| `SITE_PAGE_SIZE`                    | `site.pageSize`               | —                          | Размер страницы (0 — без пагинации) |
| `SITE_REPO_URL`                     | `site.repoUrl`                | —                          | Ссылка на репозиторий               |
| `SITE_FAVICON`                      | `site.favicon`                 | —                          | Путь к иконке сайта                 |
| `SITE_TITLE`                        | `site.title`                   | —                          | Заголовок сайта                     |
| `SITE_META_DESCRIPTION`             | `site.meta.description`        | —                          | Описание сайта (meta)               |
| `SITE_META_KEYWORDS`                | `site.meta.keywords`           | —                          | Ключевые слова сайта (meta)         |
| `CHECK_START_ON_SERVE`              | `check.startOnServe`         | `serve --check`            | Запускать проверку вместе с `serve` |
| `CHECK_PLAYLISTS_TIMEOUT`           | `check.playlists.timeout`      | `--playlists-timeout`      | Таймаут запроса плейлиста (с)       |
| `CHECK_PLAYLISTS_ALL_COOLDOWN`      | `check.playlists.allCooldown`  | `--playlists-all-cooldown` | Пауза между циклами проверки (с)    |
| `CHECK_PLAYLISTS_ONE_COOLDOWN`      | `check.playlists.oneCooldown`  | `--playlists-one-cooldown` | Пауза между плейлистами (с)         |
| `CHECK_PLAYLISTS_MAX_ROUTINES`      | `check.playlists.maxRoutines`  | `--playlists-max-routines` | Параллельных проверок плейлистов    |
| `CHECK_PLAYLISTS_PER_ROUTINE`       | `check.playlists.perRoutine`   | `--playlists-per-routine`  | Плейлистов на одну процедуру        |
| `CHECK_PLAYLISTS_USER_AGENT_[1..*]` | `check.playlists.userAgent`    | `--playlists-user-agent`   | User-Agent для проверки плейлистов  |
| `CHECK_CHANNELS_TIMEOUT`            | `check.channels.timeout`       | `--channels-timeout`       | Таймаут запроса канала (с)          |
| `CHECK_CHANNELS_BYTE_RANGE`         | `check.channels.byteRange`     | `--channels-byte-range`    | Объём данных для получения (байт)   |
| `CHECK_CHANNELS_COOLDOWN`           | `check.channels.cooldown`      | `--channels-cooldown`      | Пауза между каналами (с)            |
| `CHECK_CHANNELS_MAX_ROUTINES`       | `check.channels.maxRoutines`   | `--channels-max-routines`  | Параллельных проверок каналов       |
| `CHECK_CHANNELS_PER_ROUTINE`        | `check.channels.perRoutine`    | `--channels-per-routine`   | Каналов на одну процедуру           |
| `CHECK_CHANNELS_USER_AGENT_[1..*]`  | `check.channels.userAgent`    | `--channels-user-agent`    | User-Agent для проверки каналов     |
| `CACHE_ENABLED`                     | `cache.enabled`                | `--cache-enabled`          | Включить кеш                        |
| `CACHE_HOST`                        | `cache.host`                   | `--cache-host`             | Хост кеша                           |
| `CACHE_PORT`                        | `cache.port`                   | `--cache-port`             | Порт кеша                           |
| `CACHE_USERNAME`                    | `cache.username`               | `--cache-username`         | Имя пользователя кеша               |
| `CACHE_PASSWORD`                    | `cache.password`               | `--cache-password`         | Пароль кеша                         |
| `CACHE_DB`                          | `cache.db`                     | `--cache-db`               | Номер БД кеша                       |
| `CACHE_TTL`                         | `cache.ttl`                    | `--cache-ttl`              | TTL записей в кеше (сек)            |

### Маршруты

| Метод | Путь                             | Описание                                 |
| ----- | -------------------------------- | ---------------------------------------- |
| GET   | `/`                              | Главная страница со списком плейлистов   |
| GET   | `/page/{N}`                      | Страница N списка плейлистов             |
| GET   | `/{code}`                        | Редирект на прямую ссылку плейлиста      |
| GET   | `/{code}.m3u`                    | Редирект на прямую ссылку плейлиста      |
| GET   | `/{code}.m3u8`                   | Редирект на прямую ссылку плейлиста      |
| GET   | `/{code}/details`                | Страница с описанием плейлиста           |
| GET   | `/api`                           | Редирект на `/api/` (Swagger UI)         |
| GET   | `/api/`                          | Swagger UI с описанием REST API          |
| GET   | `/api/openapi.json`              | OpenAPI-схема в формате JSON             |
| GET   | `/api/playlists`                 | JSON: список плейлистов                  |
| GET   | `/api/playlists/{code}`          | JSON: информация о плейлисте             |
| GET   | `/api/playlists/{code}/channels` | JSON: каналы плейлиста                   |
| GET   | `/api/version`                   | JSON: версии компонентов                 |
| GET   | `/api/health`                    | JSON: состояние сервиса                  |
| GET   | `/api/stats`                     | JSON: статистика по плейлистам и каналам |

### Связь с проверкой

Веб-сервер отображает данные из кеша, который заполняется командой `check`.
Если кеш недоступен, все плейлисты отображаются со статусом `unknown`.

Для обновления данных в фоне запустите `serve` с флагом `--check`:

```
./iptvc serve -i playlists.ini -p 8800 --check
```

Результаты проверок появляются на веб-страницах немедленно после проверки каждого
плейлиста, не дожидаясь завершения полного цикла.
После завершения цикла приложение
ждёт значение `check.playlists.allCooldown` и затем начинает следующий цикл.

Можно запускать `serve` и `check` отдельными процессами:

```
./iptvc check --repeat 0 --playlists-all-cooldown 60 &
./iptvc serve
```

Или используйте Docker Compose. Перед запуском подготовьте `.env`, `config.yml`, `playlists.ini` и `channels.json` в рабочем каталоге:

```bash
cp .env.example .env
cp config.yml.example config.yml
docker compose up -d --build
docker compose logs -f iptvc
docker compose down
```

Compose запускает сервисы `iptvc`, `cache` и `docs`.
В текущем `compose.yml` контексты сборки указаны как `./iptvc` и `./docs`, поэтому запуск из одного только каталога этого репозитория может потребовать внешней структуры проекта или корректировки контекстов.

## Результаты проверки

Программа логирует процесс и результаты своей работы.
Рассмотрим лог на примере.

В рабочей директории лежит файл `playlists.ini`, в котором минимально описано два плейлиста:

```ini
[first]
pls='https://example.com/list1.m3u'
[second]
pls='https://example.com/list2.m3u'
```

Программа запущена следующим образом: `./iptvc check -c first` (чтобы проверить только плейлист с кодом `first` из ini-файла).

Вывод будет примерно таким:

```
2025/01/02 12:34:00 Loading playlists from ini-file: /home/user/playlists.ini
2025/01/02 12:34:00 Loaded 2 playlists
2025/01/02 12:34:00 1 playlists will be checked (maxRoutines=1)
2025/01/02 12:34:00 Fetching playlist [first] (https://example.com/list1.m3u)
2025/01/02 12:34:00 Parsing content...
2025/01/02 12:34:00 Parsed, checking channels (114)...
2025/01/02 12:34:00 Check channels parameters: timeout=10.00s byteRange=512 maxRoutines=50
2025/01/02 12:34:12 Checked successfully! online=101 onlinePercent=88.60% offline=13 offlinePercent=11.40% elapsedTime=12.39s
2025/01/02 12:34:12 Done! count=1 online=1 offline=0 elapsedTime=12.39s
```

Разберём построчно:
1. Загрузка ini-файла
2. Файл загружен, в нём 2 плейлиста
3. Начало проверки 1 плейлиста (выбран кодом `first`)
4. Скачивание плейлиста `first` по ссылке `https://example.com/list1.m3u`
5. Плейлист скачан, начало разбора
6. Плейлист разобран, начало проверки каналов (114 штук)
7. Параметры проверки каналов (таймаут, объём данных, число горутин)
8. (спустя время) Проверка каналов завершена
9. Проверка плейлиста завершена

### Коды возврата

* 0 — успех
* 1 — общая ошибка

## Лицензия

ПО распространяется на условиях [лицензии MIT](LICENSE).

Используется Material Design Icons под лицензией [Apache License 2.0](https://pictogrammers.com/docs/general/license/).
