This commit is contained in:
2026-07-13 12:30:05 +08:00
parent 7d61aadc5d
commit f1849722c8
655 changed files with 4903 additions and 636 deletions
+384
View File
@@ -0,0 +1,384 @@
---
title: check
tags: [./iptvc]
---
# Команда `check`
Команда поддерживает множество аргументов для разных целей.
Они могут дополнять друг друга.
Порядок аргументов не имеет значения.
## `-i`, `--ini` { id=ini }
Указывает путь к локальному [ini-файлу](../../formats/playlists.md) с описанием плейлистов.
Можно указать только однажды.
Значение по умолчанию: `./playlists.ini`
Если файл не найден, проверка плейлистов будет доступна только по ссылкам ([`--url`](#url)) или из локальных файлов ([`--file`](#file)).
```shell title="Пример"
./iptvc check -i ~/my.ini
```
## `-t`, `--tags` { id=tags }
Указывает путь к локальному [json-файлу](../../formats/channels.md) с описанием тегов каналов.
Можно указать только однажды.
Значение по умолчанию: `./channels.json`
Если файл не найден, то будет выведено предупреждение о том, что каналы не будут помечены тегами.
```shell title="Пример"
./iptvc check -t ~/tags.json
```
## `-f`, `--file` { id=file }
Указывает путь к локальному файлу плейлиста `*.m3u`/`*.m3u8`.
Можно указать несколько разных.
```shell title="Пример"
./iptvc check -f playlist.m3u
./iptvc check -f playlist1.m3u --file playlist2.m3u8
./iptvc check --file /path/to/playlist.m3u
```
## `-u`, `--url` { id=url }
Указывает URL удалённого плейлиста (поддерживаются протоколы http/https).
Можно указать несколько разных.
```shell title="Пример"
./iptvc check -u http://example.com/playlist.m3u
./iptvc check -u https://site.com/playlist.m3u8 --url http://other.com/list.m3u
```
## `-c`, `--code` { id=code }
Указывает код плейлиста из файла [playlists.ini](../../formats/playlists.md).
Можно указать несколько разных.
!!! warning "Работает только вместе с [`--ini`](#ini)."
Если не указан ни разу, то будут проверены все плейлисты, которые указаны в ini-файле.
Если используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
./iptvc check -i ~/my.ini -c RU_BASIC --code MOVIE_PREMIUM
```
## `--repeat` { id=repeat }
Указывает количество повторений (итераций) команды.
Значение по умолчанию: `1`
Если указано `0`, тогда:
* повторение будет бесконечным;
* если переданы [`--url`](#url), [`--file`](#file) или [`--code`](#code), то на каждой итерации будут проверяться только указанные плейлисты;
* если не переданы [`--url`](#url), [`--file`](#file) или [`--code`](#code), то на каждой итерации список плейлистов будет подготавливаться заново.
Если при этом используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
# проверить 5 раз плейлисты с кодами xx и yy из my.ini
./iptvc check -i ~/my.ini -c xx --code yy --repeat 5
# бесконечно проверять все плейлисты из my.ini, без учёта проверенных
./iptvc check -i ~/my.ini --repeat 0
# бесконечно проверять плейлист из файла
./iptvc check -f test.m3u --repeat 0
```
## `--every` { id=every }
Указывает количество секунд между повторениями (итерациями) команды.
Значение по умолчанию: `5`
Если указано `0`, то задержки не будет.
```shell title="Пример"
# проверить 5 раз плейлисты с кодами xx и yy из my.ini каждые 5 секунд
./iptvc check -i ~/my.ini -c xx --code yy --repeat 5 --every 5
# бесконечно проверять все плейлисты из my.ini, без учёта проверенных, каждый час
./iptvc check -i ~/my.ini --repeat 0 --every 3600
# бесконечно проверять плейлист из файла каждые 10 секунд
./iptvc check -f test.m3u --repeat 0 --every 10
```
## `-r`, `--random` { id=random }
Указывает максимальное количество случайных плейлистов из ini-файла для проверки.
!!! warning "Работает только вместе с [`--ini`](#ini)."
Если не указан ни разу, то будут проверены все плейлисты, которые указаны в ini-файле.
Если используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
./iptvc check -i ~/my.ini -r 10
```
## `-j`, `--json` { id=json }
Если указано, то подробные результаты проверки будут выводиться в формате JSON.
```shell title="Пример"
./iptvc check -f playlist.m3u --json
```
## `-q`, `--quiet` { id=quiet }
Подавляет вывод всех логов.
!!! info "Не влияет на [`--json`](#json) (JSON-данные будут выведены в stdout), но перекрывает [`--verbose`](#verbose) (логов не будет вовсе, независимо от повышенной подробности)."
```shell title="Пример"
./iptvc check -i ~/my.ini --random 10 --quiet --json
```
## `-v`, `--verbose` { id=verbose }
Включает подробное логирование.
```shell title="Пример"
./iptvc check --random 10 --verbose
```
## Глобальные флаги { id=global }
Эти флаги доступны для всех команд и переопределяют значения из `config.yml`.
### `--debug` { id=debug }
Включает режим отладки. Переопределяет `app.debug` из `config.yml` и переменную `APP_DEBUG`.
```shell title="Пример"
./iptvc check -i ~/my.ini --debug
```
### `--log-level` { id=log-level }
Устанавливает уровень логирования. Переопределяет `app.log_level` из `config.yml`.
Доступные значения: `debug`, `info`, `warn`, `error`.
```shell title="Пример"
./iptvc check -i ~/my.ini --log-level debug
```
## Флаги проверки плейлистов { id=check-playlists }
Эти флаги переопределяют параметры секции `check.playlists` из `config.yml`. Доступны для команд `check` и `serve`.
### `--playlists-timeout` { id=playlists-timeout }
Таймаут HTTP-запроса плейлиста в миллисекундах.
Переопределяет `check.playlists.timeout` (по умолчанию `10000`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-timeout 5000
```
### `--playlists-all-cooldown` { id=playlists-all-cooldown }
Задержка в миллисекундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-all-cooldown 10000
```
### `--playlists-one-cooldown` { id=playlists-one-cooldown }
Задержка в миллисекундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-one-cooldown 2000
```
### `--playlists-max-routines` { id=playlists-max-routines }
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `5`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-max-routines 10
```
### `--playlists-per-routine` { id=playlists-per-routine }
Количество плейлистов на одну процедуру проверки.
Переопределяет `check.playlists.per-routine` (по умолчанию `1`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-per-routine 3
```
### `--playlists-user-agent` { id=playlists-user-agent }
User-Agent для HTTP-запросов плейлистов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.playlists.user-agent`.
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-user-agent "Mozilla/5.0" "curl/8.0"
```
## Флаги проверки каналов { id=check-channels }
Эти флаги переопределяют параметры секции `check.channels` из `config.yml`. Доступны для команд `check` и `serve`.
### `--channels-timeout` { id=channels-timeout }
Таймаут HTTP-запроса канала в миллисекундах.
Переопределяет `check.channels.timeout` (по умолчанию `10000`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-timeout 8000
```
### `--channels-byte-range` { id=channels-byte-range }
Объём данных в байтах для загрузки от сервера при проверке канала.
Переопределяет `check.channels.byte-range` (по умолчанию `512`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-byte-range 1024
```
### `--channels-cooldown` { id=channels-cooldown }
Задержка в миллисекундах после проверки каждого канала.
Переопределяет `check.channels.cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-cooldown 100
```
### `--channels-max-routines` { id=channels-max-routines }
Максимум одновременно проверяемых каналов.
Переопределяет `check.channels.max-routines` (по умолчанию `50`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-max-routines 100
```
### `--channels-per-routine` { id=channels-per-routine }
Количество каналов на одну процедуру проверки.
Переопределяет `check.channels.per-routine` (по умолчанию `10`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-per-routine 20
```
### `--channels-user-agent` { id=channels-user-agent }
User-Agent для HTTP-запросов каналов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.channels.user-agent`.
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-user-agent "Mozilla/5.0" "VLC/3.0"
```
## Флаги кеша { id=cache-flags }
Эти флаги переопределяют параметры секции `cache` из `config.yml`. Доступны для команд `check` и `serve`.
### `--cache-enabled` { id=cache-enabled }
Включает кеширование результатов в KeyDB/Redis.
Переопределяет `cache.enabled` (по умолчанию `false`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled
```
### `--cache-host` { id=cache-host }
Хост KeyDB/Redis.
Переопределяет `cache.host` (по умолчанию `localhost`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-host 192.168.1.10
```
### `--cache-port` { id=cache-port }
Порт KeyDB/Redis.
Переопределяет `cache.port` (по умолчанию `6379`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-port 6380
```
### `--cache-username` { id=cache-username }
Логин для подключения к KeyDB/Redis.
Переопределяет `cache.username`.
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-username myuser
```
### `--cache-password` { id=cache-password }
Пароль для подключения к KeyDB/Redis.
Переопределяет `cache.password`.
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-password secret
```
### `--cache-db` { id=cache-db }
Номер базы данных KeyDB/Redis.
Переопределяет `cache.db` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-db 2
```
### `--cache-ttl` { id=cache-ttl }
TTL записей кеша в секундах.
Переопределяет `cache.ttl` (по умолчанию `1800`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-ttl 3600
```
+49
View File
@@ -0,0 +1,49 @@
---
title: help
tags: [iptvc]
---
# Команда `help`
Для получения помощи о программе, нужно вызвать команду `help` или передать аргумент `-h` или `--help`.
Равнозначные команды:
```
./iptvc help
./iptvc --help
./iptvc -h
```
Вывод (может незначительно отличаться в разных версиях):
```
Simple utility to check iptv playlists. Part of m3u.su project.
Copyright (c) 2025, Антон Аксенов, MIT license.
Usage:
iptvc [command]
Available Commands:
check Check playlists
completion Generate the autocompletion script for the specified shell
help Help about any command
version Show version
Flags:
-h, --help help for iptvc
-v, --verbose enable additional output
Use "iptvc [command] --help" for more information about a command.
```
Чтобы получить справку о конкретной команде, можно вызвать программу одним из способов:
```
./iptvc help <КОМАНДА>
./iptvc <КОМАНДА> --help
./iptvc <КОМАНДА> -h
```
Список команд указан в этом разделе.
+23
View File
@@ -0,0 +1,23 @@
---
icon: octicons/terminal-24
hide: [toc]
---
# :octicons-terminal-24: Справочник команд
* [`check`](check.md) — проверка плейлистов
* [`serve`](serve.md) — запуск веб-интерфейса
* [`version`](version.md) — получение версии и выход
* [`help`](help.md) — получение справки о программе и выход
Каждая команда отвечает за конкретную операцию и имеет свои настройки (аргументы), которыми можно влиять на логику выполнения операции.
Также есть глобальные аргументы, которые доступны для всех команд:
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| ----------------- | ------ | ---------------------------- | ----------------------------------------------------- |
| `--config` | string | — | Путь к файлу конфигурации (по умолчанию `config.yml`) |
| `--debug` | bool | `app.debug` | Включить режим отладки |
| `--log-level` | string | `app.log_level` | Уровень логирования: `debug`, `info`, `warn`, `error` |
| `-v`, `--verbose` | bool | — | Подробное логирование |
+408
View File
@@ -0,0 +1,408 @@
---
title: serve
tags: [iptvc]
---
# Команда `serve`
Запускает встроенный веб-сервер для просмотра плейлистов и результатов их проверки в браузере.
```bash
iptvc serve [flags]
```
## Веб-сервер
<a id="port"></a>
### `-p`, `--port`
Порт для веб-сервера.
Переопределяет `server.port` из `config.yml` и переменную `WEB_PORT`.
Если не указан, используется значение из `config.yml` (по умолчанию `8080`).
```bash
iptvc serve -p 3000
```
<a id="host"></a>
### `--host`
Хост для привязки веб-сервера.
Переопределяет `server.host` из `config.yml` и переменную `WEB_HOST`.
Если не указан, используется значение из `config.yml` (по умолчанию — все интерфейсы).
```bash
iptvc serve --host 127.0.0.1
```
## Фоновая проверка
<a id="check"></a>
### `--check`
Включает фоновую проверку плейлистов. По умолчанию выключена.
Без этого флага веб-сервер работает standalone — отображает данные из кеша (если включён) или статус `unknown` для всех плейлистов.
```bash
iptvc serve --check
```
При `--check` доступны следующие флаги:
<a id="ini"></a>
### `-i`, `--ini`
Путь к локальному [ini-файлу](../../formats/playlists.md) с описанием плейлистов.
Значение по умолчанию: `./playlists.ini`
```bash
iptvc serve --check -i ~/my.ini
```
<a id="tags"></a>
### `-t`, `--tags`
Путь к [json-файлу](../../formats/channels.md) с описанием тегов каналов.
Значение по умолчанию: `./channels.json`
```bash
iptvc serve --check -t ~/tags.json
```
<a id="every"></a>
### `--every`
Интервал между циклами фоновой проверки в секундах.
Значение по умолчанию: `60`
```bash
iptvc serve --check --every 120
```
<a id="repeat"></a>
### `--repeat`
Количество циклов фоновой проверки.
Значение по умолчанию: `0` (бесконечно)
```bash
# проверить один раз и остановить фоновую проверку
iptvc serve --check --repeat 1
```
<a id="random"></a>
### `-r`, `--random`
Максимальное количество случайных плейлистов из ini-файла для проверки.
```bash
iptvc serve --check -r 10
```
## Глобальные флаги
<a id="config"></a>
### `--config`
Путь к файлу конфигурации `config.yml`.
Значение по умолчанию: `config.yml`
```bash
iptvc serve --config /etc/iptvc/config.yml
```
<a id="debug"></a>
### `--debug`
Включает режим отладки. Переопределяет `app.debug` из `config.yml` и переменную `APP_DEBUG`.
```bash
iptvc serve --debug
```
<a id="log-level"></a>
### `--log-level`
Устанавливает уровень логирования. Переопределяет `app.log_level` из `config.yml`.
Доступные значения: `debug`, `info`, `warn`, `error`.
```bash
iptvc serve --log-level debug
```
<a id="verbose"></a>
### `-v`, `--verbose`
Включает подробное логирование.
## Флаги проверки плейлистов
Эти флаги переопределяют параметры секции `check.playlists` из `config.yml`. Доступны для команд `check` и `serve`. Имеют смысл только при включённой фоновой проверке (`--check` или `check.start-on-serve: true`).
<a id="playlists-timeout"></a>
### `--playlists-timeout`
Таймаут HTTP-запроса плейлиста в миллисекундах.
Переопределяет `check.playlists.timeout` (по умолчанию `10000`).
```bash
iptvc serve --check --playlists-timeout 5000
```
<a id="playlists-all-cooldown"></a>
### `--playlists-all-cooldown`
Задержка в миллисекундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --playlists-all-cooldown 10000
```
<a id="playlists-one-cooldown"></a>
### `--playlists-one-cooldown`
Задержка в миллисекундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --playlists-one-cooldown 2000
```
<a id="playlists-max-routines"></a>
### `--playlists-max-routines`
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `5`).
```bash
iptvc serve --check --playlists-max-routines 10
```
<a id="playlists-per-routine"></a>
### `--playlists-per-routine`
Количество плейлистов на одну процедуру проверки.
Переопределяет `check.playlists.per-routine` (по умолчанию `1`).
```bash
iptvc serve --check --playlists-per-routine 3
```
<a id="playlists-user-agent"></a>
### `--playlists-user-agent`
User-Agent для HTTP-запросов плейлистов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.playlists.user-agent`.
```bash
iptvc serve --check --playlists-user-agent "Mozilla/5.0" "curl/8.0"
```
## Флаги проверки каналов
Эти флаги переопределяют параметры секции `check.channels` из `config.yml`. Доступны для команд `check` и `serve`. Имеют смысл только при включённой фоновой проверке.
<a id="channels-timeout"></a>
### `--channels-timeout`
Таймаут HTTP-запроса канала в миллисекундах.
Переопределяет `check.channels.timeout` (по умолчанию `10000`).
```bash
iptvc serve --check --channels-timeout 8000
```
<a id="channels-byte-range"></a>
### `--channels-byte-range`
Объём данных в байтах для загрузки от сервера при проверке канала.
Переопределяет `check.channels.byte-range` (по умолчанию `512`).
```bash
iptvc serve --check --channels-byte-range 1024
```
<a id="channels-cooldown"></a>
### `--channels-cooldown`
Задержка в миллисекундах после проверки каждого канала.
Переопределяет `check.channels.cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --channels-cooldown 100
```
<a id="channels-max-routines"></a>
### `--channels-max-routines`
Максимум одновременно проверяемых каналов.
Переопределяет `check.channels.max-routines` (по умолчанию `50`).
```bash
iptvc serve --check --channels-max-routines 100
```
<a id="channels-per-routine"></a>
### `--channels-per-routine`
Количество каналов на одну процедуру проверки.
Переопределяет `check.channels.per-routine` (по умолчанию `10`).
```bash
iptvc serve --check --channels-per-routine 20
```
<a id="channels-user-agent"></a>
### `--channels-user-agent`
User-Agent для HTTP-запросов каналов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.channels.user-agent`.
```bash
iptvc serve --check --channels-user-agent "Mozilla/5.0" "VLC/3.0"
```
## Флаги кеша
Эти флаги переопределяют параметры секции `cache` из `config.yml`. Доступны для команд `check` и `serve`.
<a id="cache-enabled"></a>
### `--cache-enabled`
Включает кеширование результатов в KeyDB/Redis.
Переопределяет `cache.enabled` (по умолчанию `false`).
```bash
iptvc serve --cache-enabled
```
<a id="cache-host"></a>
### `--cache-host`
Хост KeyDB/Redis.
Переопределяет `cache.host` (по умолчанию `localhost`).
```bash
iptvc serve --cache-enabled --cache-host 192.168.1.10
```
<a id="cache-port"></a>
### `--cache-port`
Порт KeyDB/Redis.
Переопределяет `cache.port` (по умолчанию `6379`).
```bash
iptvc serve --cache-enabled --cache-port 6380
```
<a id="cache-username"></a>
### `--cache-username`
Логин для подключения к KeyDB/Redis.
Переопределяет `cache.username`.
```bash
iptvc serve --cache-enabled --cache-username myuser
```
<a id="cache-password"></a>
### `--cache-password`
Пароль для подключения к KeyDB/Redis.
Переопределяет `cache.password`.
```bash
iptvc serve --cache-enabled --cache-password secret
```
<a id="cache-db"></a>
### `--cache-db`
Номер базы данных KeyDB/Redis.
Переопределяет `cache.db` (по умолчанию `0`).
```bash
iptvc serve --cache-enabled --cache-db 2
```
<a id="cache-ttl"></a>
### `--cache-ttl`
TTL записей кеша в секундах.
Переопределяет `cache.ttl` (по умолчанию `1800`).
```bash
iptvc serve --cache-enabled --cache-ttl 3600
```
## Примеры
```bash
# просто веб-сервер без проверки
iptvc serve
# веб-сервер с фоновой проверкой каждые 2 минуты
iptvc serve --check --every 120
# веб-сервер на порту 3000 с проверкой 10 случайных плейлистов
iptvc serve -p 3000 --check -r 10
# один цикл проверки, затем только веб-сервер
iptvc serve --check --repeat 1 --every 0
# веб-сервер с кешем и фоновой проверкой, увеличенные лимиты параллелизма
iptvc serve --check --cache-enabled \
--playlists-max-routines 10 \
--channels-max-routines 100
# веб-сервер с отладкой и кастомным user-agent
iptvc serve --check --debug \
--playlists-user-agent "Mozilla/5.0" \
--channels-user-agent "VLC/3.0"
```
## Веб-маршруты
| Метод | Путь | Описание |
| --- | --- | --- |
| GET | `/` | Главная страница со списком плейлистов |
| GET | `/page/{N}` | Страница N списка плейлистов |
| GET | `/{code}` | Редирект на прямую ссылку плейлиста |
| GET | `/{code}.m3u[8]` | Редирект на прямую ссылку плейлиста |
| GET | `/{code}/details` | Страница с описанием плейлиста |
| GET | `/api/playlists/{code}` | JSON: информация о плейлисте |
| GET | `/api/version` | JSON: версии компонентов |
| GET | `/api/health` | JSON: состояние сервиса |
| GET | `/api/stats` | JSON: статистика по плейлистам и каналам |
+18
View File
@@ -0,0 +1,18 @@
---
title: version
tags: [iptvc]
---
# Команда `version`
Выводит версию программы:
```bash
./iptvc version
```
Пример результата:
```
iptvc v1.0.6
```
+436
View File
@@ -0,0 +1,436 @@
---
title: config.yml
icon: material/file-cog
tags: ["iptvc", "конфигурация"]
---
# :material-file-cog: Конфигурация config.yml
Программа читает настройки из YAML-файла `config.yml` в корне проекта.
Путь к файлу можно задать через глобальный флаг `--config`.
## Приоритет настроек
От низшего к высшему:
1. **Значения по умолчанию** — встроены в код;
2. **`config.yml`** — YAML-файл;
3. **Переменные окружения** — переопределяют `config.yml` (если заданы);
4. **CLI-флаги** — переопределяют переменные окружения и `config.yml` (если заданы явно).
Файл `.env` загружается автоматически, переменные из него применяются как переменные окружения.
## Структура файла
```yaml
app:
timezone: GMT
debug: false
log_level: info
playlists: ./playlists.ini
tags: ./channels.json
server:
host: localhost
port: 8080
site:
base-url: http://localhost:8080
repo-url: https://git.axenov.dev/IPTV
page-size: 0
favicon:
header:
title: IPTV Checker
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/iptv_aggregator
icon: megaphone-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
check:
start-on-serve: false
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
timeout: 10000
all-cooldown: 0
one-cooldown: 0
max-routines: 5
per-routine: 1
channels:
user-agent: Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
timeout: 10000
byte-range: 512
cooldown: 0
max-routines: 50
per-routine: 10
cache:
enabled: false
host: localhost
port: 6379
username:
password:
db: 0
ttl: 1800
```
## Секция `app`
| Параметр | Тип | По умолчанию | Описание |
| ----------- | ------ | ----------------- | ---------------------- |
| `timezone` | string | `GMT` | Часовой пояс |
| `debug` | bool | `false` | Режим отладки |
| `log_level` | string | `info` | Уровень логирования |
| `playlists` | string | `./playlists.ini` | Путь к `playlists.ini` |
| `tags` | string | `./channels.json` | Путь к `channels.json` |
## Секция `server`
| Параметр | Тип | По умолчанию | Описание |
| -------- | ------ | ------------ | ----------------- |
| `host` | string | (пусто) | Хост для привязки |
| `port` | uint | `8080` | Порт веб-сервера |
## Секция `site`
Настройки сайта: ссылки, заголовок, навигация, пагинация.
| Параметр | Тип | По умолчанию | Описание |
| ----------- | ------ | ----------------------------- | ----------------------------------- |
| `base-url` | string | `http://localhost:8080` | Базовый URL для формирования ссылок |
| `repo-url` | string | `https://git.axenov.dev/IPTV` | Ссылка на репозиторий |
| `page-size` | uint | `0` | Размер страницы (0 — без пагинации) |
| `favicon` | string | (пусто) | Путь к иконке сайта |
### `site.header`
Настройки шапки сайта.
| Параметр | Тип | По умолчанию | Описание |
| ------------ | ------ | -------------- | -------------------------------------- |
| `title` | string | `IPTV Checker` | Заголовок сайта (в navbar и `<title>`) |
| `navigation` | [Link] | (см. ниже) | Ссылки в шапке сайта |
### `site.footer-links`
Ссылки в подвале сайта. Массив элементов `Link`.
### Тип `Link`
Элемент навигации или подвала. Если задано `children`, рендерится как выпадающее меню.
| Параметр | Тип | Описание |
| ---------- | ------ | ----------------------------------------------------------- |
| `title` | string | Текст ссылки |
| `url` | string | URL ссылки (можно опустить, если есть `children`) |
| `icon` | string | Имя иконки |
| `children` | [Link] | Дочерние ссылки (выпадающее меню, один уровень вложенности) |
--8<-- "ionicons-name.md"
Пример:
```yaml
site:
header:
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/iptv_aggregator
icon: megaphone-outline
- title: Чат
url: https://t.me/iptv_aggregator_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: axenov.dev
url: https://axenov.dev
icon: person-outline
```
## Секция `check`
Параметры проверки плейлистов и каналов. Поддерживаются скаляры и массивы.
### `check.start-on-serve`
| Параметр | Тип | По умолчанию | Описание |
| ---------------- | ---- | ------------ | ---------------------------------------------------------- |
| `start-on-serve` | bool | `false` | Запустить фоновую проверку при `serve` без флага `--check` |
### Типы значений
!!! info "timeout, max-routines, per-routine, byte-range"
Эти параметры — всегда целые числа, не массивы.
!!! info "cooldown (all-cooldown, one-cooldown, channels.cooldown)"
Эти параметры могут быть заданы:
- **скаляром** — фиксированное значение, например `all-cooldown: 10`;
- **массивом `[min, max]`** — случайное значение в диапазоне при каждой проверке, например `all-cooldown: [5, 15]`.
!!! info "user-agent"
Параметр `user-agent` может быть задан:
- **строкой** — используется всегда одно значение;
- **массивом строк** — случайный выбор при каждом запросе.
!!! info "byte-range"
Параметр `byte-range` — всегда целое число (не массив).
### `check.playlists`
Параметры проверки плейлистов (загрузка m3u-файлов по URL или из ФС).
| Параметр | Тип | По умолчанию | Единица | Описание |
| -------------- | ------------------ | ----------------- | ------- | ------------------------------------------------ |
| `user-agent` | string \| string[] | `Mozilla/5.0 ...` | — | User-Agent для HTTP-запросов |
| `timeout` | int | `10000` | мс | Таймаут запроса плейлиста |
| `all-cooldown` | int \| int[] | `0` | мс | Задержка после проверки всех плейлистов |
| `one-cooldown` | int \| int[] | `0` | мс | Задержка после проверки каждого плейлиста |
| `max-routines` | int | `5` | шт | Максимум одновременно проверяемых плейлистов |
| `per-routine` | int | `1` | шт | Количество плейлистов на одну процедуру проверки |
### `check.channels`
Параметры проверки каналов внутри плейлиста.
| Параметр | Тип | По умолчанию | Единица | Описание |
| -------------- | ------------------ | ----------------- | ------- | --------------------------------------------- |
| `user-agent` | string \| string[] | `Mozilla/5.0 ...` | — | User-Agent для HTTP-запросов |
| `timeout` | int | `10000` | мс | Таймаут запроса канала |
| `byte-range` | int | `512` | байт | Объём данных для загрузки от сервера |
| `cooldown` | int \| int[] | `0` | мс | Задержка после проверки каждого канала |
| `max-routines` | int | `50` | шт | Максимум одновременно проверяемых каналов |
| `per-routine` | int | `10` | шт | Количество каналов на одну процедуру проверки |
## Секция `cache`
| Параметр | Тип | По умолчанию | Описание |
| ---------- | ------ | ------------ | ---------------------------------- |
| `enabled` | bool | `false` | Включить кеширование (KeyDB/Redis) |
| `host` | string | `localhost` | Хост KeyDB/Redis |
| `port` | uint | `6379` | Порт KeyDB/Redis |
| `username` | string | (пусто) | Логин |
| `password` | string | (пусто) | Пароль |
| `db` | uint | `0` | Номер БД |
| `ttl` | uint | `1800` | TTL записей (сек) |
## Валидация
При запуске конфигурация валидируется.
Некорректные значения исправляются автоматически, каждое исправление логируется:
| Проверка | Действие |
| ---------------------------------------------------- | --------------------------------------- |
| `server.port` = 0 или > 65535 | сброс в `8080` |
| `site.base-url` пусто | автогенерация `http://localhost:{port}` |
| `cache.host` пусто (если cache включён) | `localhost` |
| `cache.port` = 0 (если cache включён) | `6379` |
| `cache.ttl` = 0 (если cache включён) | `1800` |
| `check.playlists.timeout` <= 0 | `10000` |
| `check.channels.timeout` <= 0 | `10000` |
| `check.*.cooldown``min > max` | swap |
| `check.*.cooldown` — выход за границы `[0, 3600000]` | clamp |
| `check.playlists.max-routines` < 1 | `5` |
| `check.channels.max-routines` < 1 | `50` |
| `check.playlists.per-routine` < 1 | `1` |
| `check.channels.per-routine` < 1 | `10` |
| `check.channels.byte-range` <= 0 | `512` |
| `check.*.user-agent` пусто | дефолтный User-Agent |
## Переменные окружения
Переменные окружения переопределяют значения из `config.yml`.
### Приложение
| Переменная | Соответствует в `config.yml` |
| --------------- | ---------------------------- |
| `APP_DEBUG` | `app.debug` |
| `APP_LOG_LEVEL` | `app.log_level` |
| `APP_TIMEZONE` | `app.timezone` |
| `APP_PLAYLISTS` | `app.playlists` |
| `APP_TAGS` | `app.tags` |
### Веб-сервер и сайт
| Переменная | Соответствует в `config.yml` |
| -------------- | ---------------------------- |
| `WEB_PORT` | `server.port` |
| `WEB_HOST` | `server.host` |
| `APP_URL` | `site.base-url` |
| `PAGE_SIZE` | `site.page-size` |
| `REPO_URL` | `site.repo-url` |
| `SITE_FAVICON` | `site.favicon` |
| `APP_TITLE` | `site.header.title` |
### Проверка
| Переменная | Соответствует в `config.yml` |
| ---------------------- | ---------------------------- |
| `CHECK_START_ON_SERVE` | `check.start-on-serve` |
#### `check.playlists`
| Переменная | Соответствует в `config.yml` |
| ---------------------------------- | --------------------------------------------------- |
| `CHECK_PLAYLISTS_TIMEOUT` | `check.playlists.timeout` |
| `CHECK_PLAYLISTS_ALL_COOLDOWN` | `check.playlists.all-cooldown` (скаляр) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MIN` | `check.playlists.all-cooldown` (минимум диапазона) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MAX` | `check.playlists.all-cooldown` (максимум диапазона) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN` | `check.playlists.one-cooldown` (скаляр) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MIN` | `check.playlists.one-cooldown` (минимум диапазона) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MAX` | `check.playlists.one-cooldown` (максимум диапазона) |
| `CHECK_PLAYLISTS_MAX_ROUTINES` | `check.playlists.max-routines` |
| `CHECK_PLAYLISTS_PER_ROUTINE` | `check.playlists.per-routine` |
| `CHECK_PLAYLISTS_USER_AGENT_1` | `check.playlists.user-agent` (первый элемент) |
| `CHECK_PLAYLISTS_USER_AGENT_2` | `check.playlists.user-agent` (второй элемент) |
| `CHECK_PLAYLISTS_USER_AGENT_N` | `check.playlists.user-agent` (N-й элемент) |
#### `check.channels`
| Переменная | Соответствует в `config.yml` |
| ----------------------------- | ---------------------------------------------- |
| `CHECK_CHANNELS_TIMEOUT` | `check.channels.timeout` |
| `CHECK_CHANNELS_BYTE_RANGE` | `check.channels.byte-range` |
| `CHECK_CHANNELS_COOLDOWN` | `check.channels.cooldown` (скаляр) |
| `CHECK_CHANNELS_COOLDOWN_MIN` | `check.channels.cooldown` (минимум диапазона) |
| `CHECK_CHANNELS_COOLDOWN_MAX` | `check.channels.cooldown` (максимум диапазона) |
| `CHECK_CHANNELS_MAX_ROUTINES` | `check.channels.max-routines` |
| `CHECK_CHANNELS_PER_ROUTINE` | `check.channels.per-routine` |
| `CHECK_CHANNELS_USER_AGENT_1` | `check.channels.user-agent` (первый элемент) |
| `CHECK_CHANNELS_USER_AGENT_2` | `check.channels.user-agent` (второй элемент) |
| `CHECK_CHANNELS_USER_AGENT_N` | `check.channels.user-agent` (N-й элемент) |
!!! info "Диапазоны cooldown через env"
Если заданы обе переменные `_MIN` и `_MAX` — используется диапазон.
Если задана только скалярная переменная (без `_MIN`/`_MAX`) — используется фиксированное значение.
Если задана только одна из `_MIN`/`_MAX` — переменная игнорируется.
!!! info "Массивы user-agent через env"
Переменные читаются последовательно: `_1`, `_2`, `_3`, …
Первая отсутствующая переменная останавливает чтение.
Пустые значения пропускаются.
### Кеш
| Переменная | Соответствует в `config.yml` |
| ---------------- | ---------------------------- |
| `CACHE_ENABLED` | `cache.enabled` |
| `CACHE_HOST` | `cache.host` |
| `CACHE_PORT` | `cache.port` |
| `CACHE_USERNAME` | `cache.username` |
| `CACHE_PASSWORD` | `cache.password` |
| `CACHE_DB` | `cache.db` |
| `CACHE_TTL` | `cache.ttl` |
## CLI-флаги
CLI-флаги имеют наивысший приоритет и переопределяют значения из `config.yml` и переменных окружения. Все флаги используют zero-value по умолчанию: переопределение срабатывает, только если флаг задан явно (через `cmd.Flags().Changed()`).
Порядок применения в обработчиках команд:
```
app.Init() → defaults → config.yml → env → logger
applyAppOverrides(cmd) → app.* через Changed()
applyCacheOverrides(cmd) → cache.* через Changed()
applyCheckOverrides(cmd) → check.* через Changed()
app.InitCache() → подключение к KeyDB/Redis
```
### Глобальные флаги
### Флаги путей
Доступны для `check` и `serve`.
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| -------------- | ------ | ---------------------------- | ---------------------- |
| `-i`, `--ini` | string | `app.playlists` | Путь к `playlists.ini` |
| `-t`, `--tags` | string | `app.tags` | Путь к `channels.json` |
### Флаги итерации
Доступны для `check` и `serve`.
| Флаг | Тип | По умолчанию | Описание |
| ---------------- | ---- | ------------------------------ | --------------------------------------------- |
| `-r`, `--random` | uint | `0` | Проверить N случайных плейлистов из ini-файла |
| `--repeat` | uint | `1` (`check`) / `0` (`serve`) | Количество циклов (0 = бесконечно) |
| `--every` | uint | `5` (`check`) / `60` (`serve`) | Секунд между циклами |
### Флаги проверки плейлистов
Доступны для `check` и `serve`. Переопределяют секцию `check.playlists`.
| Флаг | Тип | Соответствует в `config.yml` | Единица | Описание |
| -------------------------- | -------- | ------------------------------ | ------- | -------------------------------- |
| `--playlists-timeout` | int | `check.playlists.timeout` | мс | Таймаут запроса плейлиста |
| `--playlists-all-cooldown` | int | `check.playlists.all-cooldown` | мс | Задержка после всех плейлистов |
| `--playlists-one-cooldown` | int | `check.playlists.one-cooldown` | мс | Задержка после каждого плейлиста |
| `--playlists-max-routines` | int | `check.playlists.max-routines` | шт | Максимум параллельных проверок |
| `--playlists-per-routine` | int | `check.playlists.per-routine` | шт | Плейлистов на процедуру |
| `--playlists-user-agent` | string[] | `check.playlists.user-agent` | — | User-Agent (можно несколько) |
### Флаги проверки каналов
Доступны для `check` и `serve`. Переопределяют секцию `check.channels`.
| Флаг | Тип | Соответствует в `config.yml` | Единица | Описание |
| ------------------------- | -------- | ----------------------------- | ------- | ------------------------------ |
| `--channels-timeout` | int | `check.channels.timeout` | мс | Таймаут запроса канала |
| `--channels-byte-range` | int | `check.channels.byte-range` | байт | Объём данных от сервера |
| `--channels-cooldown` | int | `check.channels.cooldown` | мс | Задержка после каждого канала |
| `--channels-max-routines` | int | `check.channels.max-routines` | шт | Максимум параллельных проверок |
| `--channels-per-routine` | int | `check.channels.per-routine` | шт | Каналов на процедуру |
| `--channels-user-agent` | string[] | `check.channels.user-agent` | — | User-Agent (можно несколько) |
### Флаги кеша
Доступны для `check` и `serve`. Переопределяют секцию `cache`.
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| ------------------ | ------ | ---------------------------- | ----------------- |
| `--cache-enabled` | bool | `cache.enabled` | Включить кеш |
| `--cache-host` | string | `cache.host` | Хост KeyDB/Redis |
| `--cache-port` | uint | `cache.port` | Порт KeyDB/Redis |
| `--cache-username` | string | `cache.username` | Логин |
| `--cache-password` | string | `cache.password` | Пароль |
| `--cache-db` | uint | `cache.db` | Номер БД |
| `--cache-ttl` | uint | `cache.ttl` | TTL записей (сек) |
### Флаги только для `serve`
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| -------------- | ------ | ---------------------------- | ------------------------- |
| `-p`, `--port` | uint | `server.port` | Порт веб-сервера |
| `--host` | string | `server.host` | Хост привязки |
| `--check` | bool | — | Включить фоновую проверку |
### Флаги только для `check`
| Флаг | Тип | Описание |
| --------------- | -------- | -------------------------- |
| `-j`, `--json` | bool | Вывод результатов в JSON |
| `-q`, `--quiet` | bool | Подавить логи |
| `-f`, `--file` | string[] | Локальный m3u-файл |
| `-u`, `--url` | string[] | URL удалённого плейлиста |
| `-c`, `--code` | string[] | Код плейлиста из ini-файла |
+116
View File
@@ -0,0 +1,116 @@
---
icon: simple/dotenv
tags: ["iptvc", "переменные окружения"]
---
# :simple-dotenv: Переменные окружения
Переменные окружения переопределяют значения из [`config.yml`](config.md).
Файл `.env` загружается автоматически при запуске.
Приоритет: **defaults → config.yml → env → CLI-флаги**.
## Приложение
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------------------- | ----------------------------------------- |
| `APP_DEBUG` | bool | `false` | Режим отладки |
| `APP_LOG_LEVEL` | string | `info` | Уровень логирования (`debug`, `info`, `warn`, `error`) |
| `APP_TIMEZONE` | string | `GMT` | Часовой пояс |
| `APP_PLAYLISTS` | string | `./playlists.ini` | Путь к `playlists.ini` |
| `APP_TAGS` | string | `./channels.json` | Путь к `channels.json` |
## Веб-сервер и сайт
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------------------------- | ----------------------------------- |
| `WEB_PORT` | uint | `8080` | Порт веб-сервера |
| `WEB_HOST` | string | (пусто) | Хост для привязки |
| `APP_URL` | string | `http://localhost:8080` | Базовый URL для ссылок |
| `PAGE_SIZE` | uint | `0` | Размер страницы (0 — без пагинации) |
| `REPO_URL` | string | `https://git.axenov.dev/IPTV` | Ссылка на репозиторий |
| `SITE_FAVICON` | string | (пусто) | Путь к иконке сайта |
| `APP_TITLE` | string | `IPTV Checker` | Заголовок сайта |
## Проверка
| Имя | Тип | Умолчание | Назначение |
| ----------------------- | --- | --------- | ------------------------------------------------------- |
| `CHECK_START_ON_SERVE` | bool | `false` | Запустить фоновую проверку при `serve` без флага `--check` |
### `check.playlists`
| Имя | Тип | Умолчание | Назначение |
| --- | --- | --- | --- |
| `CHECK_PLAYLISTS_TIMEOUT` | int | `10000` | Таймаут запроса плейлиста (мс) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN` | int | `0` | Задержка после всех плейлистов (мс, скаляр) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MIN` | int | `0` | Минимум задержки после всех плейлистов (мс) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MAX` | int | `0` | Максимум задержки после всех плейлистов (мс) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN` | int | `0` | Задержка после каждого плейлиста (мс, скаляр) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MIN` | int | `0` | Минимум задержки после каждого плейлиста (мс) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MAX` | int | `0` | Максимум задержки после каждого плейлиста (мс) |
| `CHECK_PLAYLISTS_MAX_ROUTINES` | int | `5` | Максимум параллельных проверок плейлистов |
| `CHECK_PLAYLISTS_PER_ROUTINE` | int | `1` | Плейлистов на процедуру |
| `CHECK_PLAYLISTS_USER_AGENT_1` | string | `Mozilla/5.0 …` | Первый User-Agent для запросов плейлистов |
| `CHECK_PLAYLISTS_USER_AGENT_2` | string | — | Второй User-Agent (и т.д.) |
### `check.channels`
| Имя | Тип | Умолчание | Назначение |
| --- | --- | --- | --- |
| `CHECK_CHANNELS_TIMEOUT` | int | `10000` | Таймаут запроса канала (мс) |
| `CHECK_CHANNELS_BYTE_RANGE` | int | `512` | Объём данных от сервера (байт) |
| `CHECK_CHANNELS_COOLDOWN` | int | `0` | Задержка после каждого канала (мс, скаляр) |
| `CHECK_CHANNELS_COOLDOWN_MIN` | int | `0` | Минимум задержки после каждого канала (мс) |
| `CHECK_CHANNELS_COOLDOWN_MAX` | int | `0` | Максимум задержки после каждого канала (мс) |
| `CHECK_CHANNELS_MAX_ROUTINES` | int | `50` | Максимум параллельных проверок каналов |
| `CHECK_CHANNELS_PER_ROUTINE` | int | `10` | Каналов на процедуру |
| `CHECK_CHANNELS_USER_AGENT_1` | string | `Mozilla/5.0 …` | Первый User-Agent для запросов каналов |
| `CHECK_CHANNELS_USER_AGENT_2` | string | — | Второй User-Agent (и т.д.) |
## Кеширование
Кеш хранится в СУБД redis или keydb.
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------- | ---------------------------------- |
| `CACHE_ENABLED` | bool | `false` | Включает кеширование |
| `CACHE_HOST` | string | `localhost` | Имя хоста СУБД |
| `CACHE_PORT` | uint | `6379` | Порт СУБД |
| `CACHE_USERNAME` | string | (пусто) | Логин пользователя в СУБД |
| `CACHE_PASSWORD` | string | (пусто) | Пароль пользователя в СУБД |
| `CACHE_DB` | uint | `0` | Номер БД в СУБД для кеша |
| `CACHE_TTL` | uint | `1800` | Время жизни ключей кеша в секундах |
## Правила
### Диапазоны cooldown
Поля `all-cooldown`, `one-cooldown` и `channels.cooldown` поддерживают скаляр и диапазон `[min, max]`.
Через env-переменные:
- Заданы **обе** `_MIN` и `_MAX` → диапазон (случайное значение при каждой проверке).
- Задана только **скалярная** переменная (без суффикса) → фиксированное значение.
- Задана только **одна** из `_MIN` / `_MAX` → переменная игнорируется.
```shell title="Скаляр"
CHECK_PLAYLISTS_ALL_COOLDOWN=500
```
```shell title="Диапазон"
CHECK_PLAYLISTS_ALL_COOLDOWN_MIN=100
CHECK_PLAYLISTS_ALL_COOLDOWN_MAX=2000
```
### Массивы user-agent
Поля `user-agent` поддерживают массив строк. Через env задаются индексированными переменными `_1`, `_2`, `_3`, …
- Чтение останавливается на первой отсутствующей переменной.
- Пустые значения пропускаются.
```shell title="Два User-Agent"
CHECK_PLAYLISTS_USER_AGENT_1=Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
CHECK_PLAYLISTS_USER_AGENT_2=curl/8.0
```
+9
View File
@@ -0,0 +1,9 @@
---
icon: material/upload-network
---
# :material-upload-network: Развёртывание и доставка обновлений
!!! info "TODO"
Скоро здесь появится полезная информация.
Следи за обновлениями в канале [@iptv_aggregator](https://t.me/iptv_aggregator) или в [репозитории](https://git.axenov.dev/IPTV/docs).
+24
View File
@@ -0,0 +1,24 @@
---
title: Компиляция
icon: material/cog
---
# Компиляция из исходного кода
Для компиляции потребуется golang v1.23.6 и выше.
На версиях ниже не проверялось.
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
make linux
# или make help для получения помощи по компиляции
```
Поддерживается передача переменной `GOARCH`:
```shell
make darwin GOARCH=arm64
```
Скомпилированные файлы находятся в директории `bin/`.
+27
View File
@@ -0,0 +1,27 @@
---
title: Docker-образ
icon: simple/docker
---
# Построение Docker-образа
Предполагается выполнение в директории с исходниками.
```
./build-docker-image.sh [<версия>]
```
где `<версия>` — необязательный тег версии в формате `vX.Y.Z`.
Если не указан, то будет взят последний.
Целевая платформа и архитектура меняется с помощью переменных `GOOS` и `GOARCH`:
```
GOOS=darwin GOARCH=arm64 ./build-docker-image.sh [<версия>]
```
Запуск:
```
docker run --pull always --name iptvc git.axenov.dev/iptv/iptvc КОМАНДА [АРГУМЕНТЫ]
```
+284
View File
@@ -0,0 +1,284 @@
# Песочница
## Мелочёвка
=== "Frontmatter"
```yaml
---
title: My Page
description: Some page description
icon: material/star
tags: [tag1, tag2]
hide: [navigation, toc, path]
status: new
#status: deprecated
#status: beta
---
# My Super-Duper Page
# Markdown content goes here
```
=== "Бейджи"
<!-- md:version 8.5.0 --> <!-- md:default true --> <!-- md:flag experimental -->
=== "Тултипы"
:material-information-outline:{ title="текст подсказки" }
[Hover me 1](https://example.com "I'm first tooltip!")
[Hover me 2][example]
[example]: https://example.com "I'm second tooltip!"
=== "Кнопки"
[Серая кнопка](https://example.com/){ .md-button }
[Синяя кнопка](https://example.com/){ .md-button .md-button--primary }
[:fontawesome-solid-paper-plane: Кнопка серая с иконкой и подсказкой](https://example.com/){ .md-button title="текст подсказки 2" }
[:fontawesome-solid-paper-plane: Кнопка синяя с иконкой](https://example.com/){ .md-button .md-button--primary }
---
=== "Простой блок"
```python
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С аннотациями"
```python
def _render_icon(shortcode: str, md) -> str: #(1)!
emoji_pattern = md.inlinePatterns.get("emoji") #(2)!
if emoji_pattern is None:
return escape(shortcode) #(3)!
```
1. сигнатура функции
2. инициализация переменной
3. возврат результата
=== "С заголовком"
```python title="example.py"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С нумерацией с 5"
```python linenums="5"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С выделением"
```python linenums="1" hl_lines="2-5 8 9 22"
def _render_icon(shortcode: str, md) -> str:
try:
emoji_pattern = md.inlinePatterns["emoji"]
except KeyError:
return escape(shortcode)
icon = emoji_pattern.emoji_index["emoji"].get(shortcode)
if icon is None:
return escape(shortcode)
element = emoji_pattern.generator(
emoji_pattern.emoji_index["name"],
shortcode,
None,
None,
shortcode,
"",
icon.get("category", ""),
emoji_pattern.options,
md,
)
return tostring(element, encoding="unicode", method="html")
```
---
## Врезки
=== "Полные"
!!! note "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! abstract "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! info "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! tip "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! success "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! question "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! warning "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! failure "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! danger "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! bug "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! example "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! quote "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
=== "Свёрнутые"
??? note "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? abstract "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? info "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? tip "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? success "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? question "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? warning "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? failure "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? danger "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? bug "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? example "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? quote "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Сворачиваемые"
???+ note "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ abstract "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ info "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ tip "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ success "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ question "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ warning "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ failure "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ danger "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ bug "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ example "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ quote "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Короткие"
!!! note
Дефолтный заголовок
!!! abstract ""
Пустой заголовок
!!! info "Пустое содержимое"
---
+103
View File
@@ -0,0 +1,103 @@
---
icon: material/book-cog-outline
---
# :material-book-cog-outline: Сборка документации
## Стилистика и правила оформления
Все исходники хранятся в директории `content/` в формате **Markdown** (формат файлов `.md`).
Структура проекта и его конфигурация описываются в файле `mkdocs.yml` в корне репозитория.
Структура исходных файлов документации и содержание должны быть согласованными.
Все ссылки на соседние страницы и изображения должны быть относительными.
**Каждое предложение должно быть на одной строке.**
Это даёт более наглядную разницу (diff) в тексте при работе с git.
Абзацы и списки должны отделяться 1 пустой строкой до и после.
Допустимо использовать любые стилистические возможности темы **Material for MkDocs** и самого **mkdocs**, но не следует визуально перегружать текст.
Документацию по ним см. по ссылкам ниже.
## Добавление изображений
**Все изображения хранятся только в директории `content/_assets/img/` и вложенных в неё.**
Общая суть такова:
* чем больше размеры, тем хуже должно быть качество;
* чем меньше размеры, тем чётче должен быть текст.
Каждое изображение должно:
* быть сохранено в формате jpg;
* иметь размер неболее 150 Кб;
* быть сжатым с качеством 65-80% от исходного;
* быть размером до 1500 px по наибольшей стороне.
Если на изображении есть текст, он должен оставаться различимым и читаемым.
Но если на изображении есть любые конфиденциальные данные и его невозможно кадрировать без потери смысла, то их необходимо скрыть.
Хорошей практикой будет использовать спойлеры для скрытия больших и/или идущих подряд нескольких изображений, например:
```
Совершенно любой текст, lorem ipsum dolor sit amet.
Совершенно любой текст, lorem ipsum dolor sit amet.
??? quote "В этом спойлере несколько больших картинок"
![Подпись-плейсхолдер1](../_assets/img/example1.jpg)
![Подпись-плейсхолдер2](../_assets/img/example2.jpg)
Продолжение текста, lorem ipsum dolor sit amet.
Продолжение текста, lorem ipsum dolor sit amet.
```
Эти простые правила позволят поддерживать репозиторий достаточно компактным, а страницы делать комфортными для чтения, экономя трафик для мобильных устройств.
## Стек
* make
* [docker](https://docker.com)
* [mkdocs](https://www.mkdocs.org/)
* [squidfunk/mkdocs-material](https://hub.docker.com/r/squidfunk/mkdocs-material)
* <https://squidfunk.github.io/mkdocs-material>
* <https://squidfunk.github.io/mkdocs-material/reference/admonitions/>
* <https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/>
## Запуск mkdocs в контейнере
```
make live
```
Перегенерирует документацию на лету сразу после сохранения файлов.
Документацию в реальном времени можно просматривать по адресу [localhost:3000](http://localhost:3000).
## Генерация статического сайта
```
make site
```
Генерирует статические файлы, которую можно версионировать, хранить,деплоить отдельно или просматривать на ПК через браузер.
Готовый скомпилированный статический сайт с документацией находится в директории `site/`.
## Генерация docker-образа
```
make image
```
Собирает docker-образ на основе nginx, генерируя перед этим статический сайт.
Запустить контейнер из этого образа по адресу [localhost:3001](http://localhost:3001) можно командой:
```
make run
```
+550
View File
@@ -0,0 +1,550 @@
| Иконка | Код для вставки в текст | Код для вставки в frontmatter |
| ------------------------------------------------ | -------------------------------------------------- | ------------------------------------------------ |
| :vscode-account: | `:vscode-account:` | `vscode/account` |
| :vscode-activate-breakpoints: | `:vscode-activate-breakpoints:` | `vscode/activate-breakpoints` |
| :vscode-add: | `:vscode-add:` | `vscode/add` |
| :vscode-add-small: | `:vscode-add-small:` | `vscode/add-small` |
| :vscode-agent: | `:vscode-agent:` | `vscode/agent` |
| :vscode-archive: | `:vscode-archive:` | `vscode/archive` |
| :vscode-arrow-both: | `:vscode-arrow-both:` | `vscode/arrow-both` |
| :vscode-arrow-circle-down: | `:vscode-arrow-circle-down:` | `vscode/arrow-circle-down` |
| :vscode-arrow-circle-left: | `:vscode-arrow-circle-left:` | `vscode/arrow-circle-left` |
| :vscode-arrow-circle-right: | `:vscode-arrow-circle-right:` | `vscode/arrow-circle-right` |
| :vscode-arrow-circle-up: | `:vscode-arrow-circle-up:` | `vscode/arrow-circle-up` |
| :vscode-arrow-down: | `:vscode-arrow-down:` | `vscode/arrow-down` |
| :vscode-arrow-left: | `:vscode-arrow-left:` | `vscode/arrow-left` |
| :vscode-arrow-right: | `:vscode-arrow-right:` | `vscode/arrow-right` |
| :vscode-arrow-small-down: | `:vscode-arrow-small-down:` | `vscode/arrow-small-down` |
| :vscode-arrow-small-left: | `:vscode-arrow-small-left:` | `vscode/arrow-small-left` |
| :vscode-arrow-small-right: | `:vscode-arrow-small-right:` | `vscode/arrow-small-right` |
| :vscode-arrow-small-up: | `:vscode-arrow-small-up:` | `vscode/arrow-small-up` |
| :vscode-arrow-swap: | `:vscode-arrow-swap:` | `vscode/arrow-swap` |
| :vscode-arrow-up: | `:vscode-arrow-up:` | `vscode/arrow-up` |
| :vscode-ask: | `:vscode-ask:` | `vscode/ask` |
| :vscode-attach: | `:vscode-attach:` | `vscode/attach` |
| :vscode-azure: | `:vscode-azure:` | `vscode/azure` |
| :vscode-azure-devops: | `:vscode-azure-devops:` | `vscode/azure-devops` |
| :vscode-beaker: | `:vscode-beaker:` | `vscode/beaker` |
| :vscode-beaker-stop: | `:vscode-beaker-stop:` | `vscode/beaker-stop` |
| :vscode-bell: | `:vscode-bell:` | `vscode/bell` |
| :vscode-bell-dot: | `:vscode-bell-dot:` | `vscode/bell-dot` |
| :vscode-bell-slash: | `:vscode-bell-slash:` | `vscode/bell-slash` |
| :vscode-bell-slash-dot: | `:vscode-bell-slash-dot:` | `vscode/bell-slash-dot` |
| :vscode-blank: | `:vscode-blank:` | `vscode/blank` |
| :vscode-bold: | `:vscode-bold:` | `vscode/bold` |
| :vscode-book: | `:vscode-book:` | `vscode/book` |
| :vscode-bookmark: | `:vscode-bookmark:` | `vscode/bookmark` |
| :vscode-bracket-dot: | `:vscode-bracket-dot:` | `vscode/bracket-dot` |
| :vscode-bracket-error: | `:vscode-bracket-error:` | `vscode/bracket-error` |
| :vscode-briefcase: | `:vscode-briefcase:` | `vscode/briefcase` |
| :vscode-broadcast: | `:vscode-broadcast:` | `vscode/broadcast` |
| :vscode-browser: | `:vscode-browser:` | `vscode/browser` |
| :vscode-bug: | `:vscode-bug:` | `vscode/bug` |
| :vscode-build: | `:vscode-build:` | `vscode/build` |
| :vscode-calendar: | `:vscode-calendar:` | `vscode/calendar` |
| :vscode-call-incoming: | `:vscode-call-incoming:` | `vscode/call-incoming` |
| :vscode-call-outgoing: | `:vscode-call-outgoing:` | `vscode/call-outgoing` |
| :vscode-case-sensitive: | `:vscode-case-sensitive:` | `vscode/case-sensitive` |
| :vscode-chat-export: | `:vscode-chat-export:` | `vscode/chat-export` |
| :vscode-chat-import: | `:vscode-chat-import:` | `vscode/chat-import` |
| :vscode-chat-sparkle: | `:vscode-chat-sparkle:` | `vscode/chat-sparkle` |
| :vscode-chat-sparkle-error: | `:vscode-chat-sparkle-error:` | `vscode/chat-sparkle-error` |
| :vscode-chat-sparkle-warning: | `:vscode-chat-sparkle-warning:` | `vscode/chat-sparkle-warning` |
| :vscode-check: | `:vscode-check:` | `vscode/check` |
| :vscode-check-all: | `:vscode-check-all:` | `vscode/check-all` |
| :vscode-checklist: | `:vscode-checklist:` | `vscode/checklist` |
| :vscode-chevron-down: | `:vscode-chevron-down:` | `vscode/chevron-down` |
| :vscode-chevron-left: | `:vscode-chevron-left:` | `vscode/chevron-left` |
| :vscode-chevron-right: | `:vscode-chevron-right:` | `vscode/chevron-right` |
| :vscode-chevron-up: | `:vscode-chevron-up:` | `vscode/chevron-up` |
| :vscode-chip: | `:vscode-chip:` | `vscode/chip` |
| :vscode-chrome-close: | `:vscode-chrome-close:` | `vscode/chrome-close` |
| :vscode-chrome-maximize: | `:vscode-chrome-maximize:` | `vscode/chrome-maximize` |
| :vscode-chrome-minimize: | `:vscode-chrome-minimize:` | `vscode/chrome-minimize` |
| :vscode-chrome-restore: | `:vscode-chrome-restore:` | `vscode/chrome-restore` |
| :vscode-circle: | `:vscode-circle:` | `vscode/circle` |
| :vscode-circle-filled: | `:vscode-circle-filled:` | `vscode/circle-filled` |
| :vscode-circle-large: | `:vscode-circle-large:` | `vscode/circle-large` |
| :vscode-circle-large-filled: | `:vscode-circle-large-filled:` | `vscode/circle-large-filled` |
| :vscode-circle-slash: | `:vscode-circle-slash:` | `vscode/circle-slash` |
| :vscode-circle-small: | `:vscode-circle-small:` | `vscode/circle-small` |
| :vscode-circle-small-filled: | `:vscode-circle-small-filled:` | `vscode/circle-small-filled` |
| :vscode-circuit-board: | `:vscode-circuit-board:` | `vscode/circuit-board` |
| :vscode-claude: | `:vscode-claude:` | `vscode/claude` |
| :vscode-clear-all: | `:vscode-clear-all:` | `vscode/clear-all` |
| :vscode-clippy: | `:vscode-clippy:` | `vscode/clippy` |
| :vscode-clockface: | `:vscode-clockface:` | `vscode/clockface` |
| :vscode-close: | `:vscode-close:` | `vscode/close` |
| :vscode-close-all: | `:vscode-close-all:` | `vscode/close-all` |
| :vscode-cloud: | `:vscode-cloud:` | `vscode/cloud` |
| :vscode-cloud-download: | `:vscode-cloud-download:` | `vscode/cloud-download` |
| :vscode-cloud-small: | `:vscode-cloud-small:` | `vscode/cloud-small` |
| :vscode-cloud-upload: | `:vscode-cloud-upload:` | `vscode/cloud-upload` |
| :vscode-code: | `:vscode-code:` | `vscode/code` |
| :vscode-code-oss: | `:vscode-code-oss:` | `vscode/code-oss` |
| :vscode-code-review: | `:vscode-code-review:` | `vscode/code-review` |
| :vscode-coffee: | `:vscode-coffee:` | `vscode/coffee` |
| :vscode-collapse-all: | `:vscode-collapse-all:` | `vscode/collapse-all` |
| :vscode-collection: | `:vscode-collection:` | `vscode/collection` |
| :vscode-collection-small: | `:vscode-collection-small:` | `vscode/collection-small` |
| :vscode-color-mode: | `:vscode-color-mode:` | `vscode/color-mode` |
| :vscode-combine: | `:vscode-combine:` | `vscode/combine` |
| :vscode-comment: | `:vscode-comment:` | `vscode/comment` |
| :vscode-comment-discussion: | `:vscode-comment-discussion:` | `vscode/comment-discussion` |
| :vscode-comment-discussion-quote: | `:vscode-comment-discussion-quote:` | `vscode/comment-discussion-quote` |
| :vscode-comment-discussion-sparkle: | `:vscode-comment-discussion-sparkle:` | `vscode/comment-discussion-sparkle` |
| :vscode-comment-draft: | `:vscode-comment-draft:` | `vscode/comment-draft` |
| :vscode-comment-unresolved: | `:vscode-comment-unresolved:` | `vscode/comment-unresolved` |
| :vscode-compass: | `:vscode-compass:` | `vscode/compass` |
| :vscode-compass-active: | `:vscode-compass-active:` | `vscode/compass-active` |
| :vscode-compass-dot: | `:vscode-compass-dot:` | `vscode/compass-dot` |
| :vscode-copilot: | `:vscode-copilot:` | `vscode/copilot` |
| :vscode-copilot-blocked: | `:vscode-copilot-blocked:` | `vscode/copilot-blocked` |
| :vscode-copilot-error: | `:vscode-copilot-error:` | `vscode/copilot-error` |
| :vscode-copilot-in-progress: | `:vscode-copilot-in-progress:` | `vscode/copilot-in-progress` |
| :vscode-copilot-large: | `:vscode-copilot-large:` | `vscode/copilot-large` |
| :vscode-copilot-not-connected: | `:vscode-copilot-not-connected:` | `vscode/copilot-not-connected` |
| :vscode-copilot-snooze: | `:vscode-copilot-snooze:` | `vscode/copilot-snooze` |
| :vscode-copilot-success: | `:vscode-copilot-success:` | `vscode/copilot-success` |
| :vscode-copilot-unavailable: | `:vscode-copilot-unavailable:` | `vscode/copilot-unavailable` |
| :vscode-copilot-warning: | `:vscode-copilot-warning:` | `vscode/copilot-warning` |
| :vscode-copilot-warning-large: | `:vscode-copilot-warning-large:` | `vscode/copilot-warning-large` |
| :vscode-copy: | `:vscode-copy:` | `vscode/copy` |
| :vscode-coverage: | `:vscode-coverage:` | `vscode/coverage` |
| :vscode-credit-card: | `:vscode-credit-card:` | `vscode/credit-card` |
| :vscode-cursor: | `:vscode-cursor:` | `vscode/cursor` |
| :vscode-dash: | `:vscode-dash:` | `vscode/dash` |
| :vscode-dashboard: | `:vscode-dashboard:` | `vscode/dashboard` |
| :vscode-database: | `:vscode-database:` | `vscode/database` |
| :vscode-debug: | `:vscode-debug:` | `vscode/debug` |
| :vscode-debug-all: | `:vscode-debug-all:` | `vscode/debug-all` |
| :vscode-debug-alt: | `:vscode-debug-alt:` | `vscode/debug-alt` |
| :vscode-debug-alt-small: | `:vscode-debug-alt-small:` | `vscode/debug-alt-small` |
| :vscode-debug-breakpoint-conditional: | `:vscode-debug-breakpoint-conditional:` | `vscode/debug-breakpoint-conditional` |
| :vscode-debug-breakpoint-conditional-unverified: | `:vscode-debug-breakpoint-conditional-unverified:` | `vscode/debug-breakpoint-conditional-unverified` |
| :vscode-debug-breakpoint-data: | `:vscode-debug-breakpoint-data:` | `vscode/debug-breakpoint-data` |
| :vscode-debug-breakpoint-data-unverified: | `:vscode-debug-breakpoint-data-unverified:` | `vscode/debug-breakpoint-data-unverified` |
| :vscode-debug-breakpoint-function: | `:vscode-debug-breakpoint-function:` | `vscode/debug-breakpoint-function` |
| :vscode-debug-breakpoint-function-unverified: | `:vscode-debug-breakpoint-function-unverified:` | `vscode/debug-breakpoint-function-unverified` |
| :vscode-debug-breakpoint-log: | `:vscode-debug-breakpoint-log:` | `vscode/debug-breakpoint-log` |
| :vscode-debug-breakpoint-log-unverified: | `:vscode-debug-breakpoint-log-unverified:` | `vscode/debug-breakpoint-log-unverified` |
| :vscode-debug-breakpoint-unsupported: | `:vscode-debug-breakpoint-unsupported:` | `vscode/debug-breakpoint-unsupported` |
| :vscode-debug-connected: | `:vscode-debug-connected:` | `vscode/debug-connected` |
| :vscode-debug-console: | `:vscode-debug-console:` | `vscode/debug-console` |
| :vscode-debug-continue: | `:vscode-debug-continue:` | `vscode/debug-continue` |
| :vscode-debug-continue-small: | `:vscode-debug-continue-small:` | `vscode/debug-continue-small` |
| :vscode-debug-coverage: | `:vscode-debug-coverage:` | `vscode/debug-coverage` |
| :vscode-debug-disconnect: | `:vscode-debug-disconnect:` | `vscode/debug-disconnect` |
| :vscode-debug-line-by-line: | `:vscode-debug-line-by-line:` | `vscode/debug-line-by-line` |
| :vscode-debug-pause: | `:vscode-debug-pause:` | `vscode/debug-pause` |
| :vscode-debug-rerun: | `:vscode-debug-rerun:` | `vscode/debug-rerun` |
| :vscode-debug-restart: | `:vscode-debug-restart:` | `vscode/debug-restart` |
| :vscode-debug-restart-frame: | `:vscode-debug-restart-frame:` | `vscode/debug-restart-frame` |
| :vscode-debug-reverse-continue: | `:vscode-debug-reverse-continue:` | `vscode/debug-reverse-continue` |
| :vscode-debug-stackframe: | `:vscode-debug-stackframe:` | `vscode/debug-stackframe` |
| :vscode-debug-stackframe-active: | `:vscode-debug-stackframe-active:` | `vscode/debug-stackframe-active` |
| :vscode-debug-start: | `:vscode-debug-start:` | `vscode/debug-start` |
| :vscode-debug-step-back: | `:vscode-debug-step-back:` | `vscode/debug-step-back` |
| :vscode-debug-step-into: | `:vscode-debug-step-into:` | `vscode/debug-step-into` |
| :vscode-debug-step-out: | `:vscode-debug-step-out:` | `vscode/debug-step-out` |
| :vscode-debug-step-over: | `:vscode-debug-step-over:` | `vscode/debug-step-over` |
| :vscode-debug-stop: | `:vscode-debug-stop:` | `vscode/debug-stop` |
| :vscode-desktop-download: | `:vscode-desktop-download:` | `vscode/desktop-download` |
| :vscode-device-camera: | `:vscode-device-camera:` | `vscode/device-camera` |
| :vscode-device-camera-video: | `:vscode-device-camera-video:` | `vscode/device-camera-video` |
| :vscode-device-mobile: | `:vscode-device-mobile:` | `vscode/device-mobile` |
| :vscode-diff: | `:vscode-diff:` | `vscode/diff` |
| :vscode-diff-added: | `:vscode-diff-added:` | `vscode/diff-added` |
| :vscode-diff-ignored: | `:vscode-diff-ignored:` | `vscode/diff-ignored` |
| :vscode-diff-modified: | `:vscode-diff-modified:` | `vscode/diff-modified` |
| :vscode-diff-multiple: | `:vscode-diff-multiple:` | `vscode/diff-multiple` |
| :vscode-diff-removed: | `:vscode-diff-removed:` | `vscode/diff-removed` |
| :vscode-diff-renamed: | `:vscode-diff-renamed:` | `vscode/diff-renamed` |
| :vscode-diff-single: | `:vscode-diff-single:` | `vscode/diff-single` |
| :vscode-discard: | `:vscode-discard:` | `vscode/discard` |
| :vscode-download: | `:vscode-download:` | `vscode/download` |
| :vscode-edit: | `:vscode-edit:` | `vscode/edit` |
| :vscode-edit-code: | `:vscode-edit-code:` | `vscode/edit-code` |
| :vscode-edit-session: | `:vscode-edit-session:` | `vscode/edit-session` |
| :vscode-edit-sparkle: | `:vscode-edit-sparkle:` | `vscode/edit-sparkle` |
| :vscode-editor-layout: | `:vscode-editor-layout:` | `vscode/editor-layout` |
| :vscode-ellipsis: | `:vscode-ellipsis:` | `vscode/ellipsis` |
| :vscode-empty-window: | `:vscode-empty-window:` | `vscode/empty-window` |
| :vscode-eraser: | `:vscode-eraser:` | `vscode/eraser` |
| :vscode-error: | `:vscode-error:` | `vscode/error` |
| :vscode-error-small: | `:vscode-error-small:` | `vscode/error-small` |
| :vscode-exclude: | `:vscode-exclude:` | `vscode/exclude` |
| :vscode-expand-all: | `:vscode-expand-all:` | `vscode/expand-all` |
| :vscode-export: | `:vscode-export:` | `vscode/export` |
| :vscode-extensions: | `:vscode-extensions:` | `vscode/extensions` |
| :vscode-extensions-large: | `:vscode-extensions-large:` | `vscode/extensions-large` |
| :vscode-eye: | `:vscode-eye:` | `vscode/eye` |
| :vscode-eye-closed: | `:vscode-eye-closed:` | `vscode/eye-closed` |
| :vscode-feedback: | `:vscode-feedback:` | `vscode/feedback` |
| :vscode-file: | `:vscode-file:` | `vscode/file` |
| :vscode-file-binary: | `:vscode-file-binary:` | `vscode/file-binary` |
| :vscode-file-code: | `:vscode-file-code:` | `vscode/file-code` |
| :vscode-file-media: | `:vscode-file-media:` | `vscode/file-media` |
| :vscode-file-pdf: | `:vscode-file-pdf:` | `vscode/file-pdf` |
| :vscode-file-submodule: | `:vscode-file-submodule:` | `vscode/file-submodule` |
| :vscode-file-symlink-directory: | `:vscode-file-symlink-directory:` | `vscode/file-symlink-directory` |
| :vscode-file-symlink-file: | `:vscode-file-symlink-file:` | `vscode/file-symlink-file` |
| :vscode-file-text: | `:vscode-file-text:` | `vscode/file-text` |
| :vscode-file-zip: | `:vscode-file-zip:` | `vscode/file-zip` |
| :vscode-files: | `:vscode-files:` | `vscode/files` |
| :vscode-filter: | `:vscode-filter:` | `vscode/filter` |
| :vscode-filter-filled: | `:vscode-filter-filled:` | `vscode/filter-filled` |
| :vscode-flag: | `:vscode-flag:` | `vscode/flag` |
| :vscode-flame: | `:vscode-flame:` | `vscode/flame` |
| :vscode-fold: | `:vscode-fold:` | `vscode/fold` |
| :vscode-fold-down: | `:vscode-fold-down:` | `vscode/fold-down` |
| :vscode-fold-up: | `:vscode-fold-up:` | `vscode/fold-up` |
| :vscode-folder: | `:vscode-folder:` | `vscode/folder` |
| :vscode-folder-active: | `:vscode-folder-active:` | `vscode/folder-active` |
| :vscode-folder-library: | `:vscode-folder-library:` | `vscode/folder-library` |
| :vscode-folder-opened: | `:vscode-folder-opened:` | `vscode/folder-opened` |
| :vscode-forward: | `:vscode-forward:` | `vscode/forward` |
| :vscode-game: | `:vscode-game:` | `vscode/game` |
| :vscode-gear: | `:vscode-gear:` | `vscode/gear` |
| :vscode-gift: | `:vscode-gift:` | `vscode/gift` |
| :vscode-gist: | `:vscode-gist:` | `vscode/gist` |
| :vscode-gist-secret: | `:vscode-gist-secret:` | `vscode/gist-secret` |
| :vscode-git-branch: | `:vscode-git-branch:` | `vscode/git-branch` |
| :vscode-git-branch-changes: | `:vscode-git-branch-changes:` | `vscode/git-branch-changes` |
| :vscode-git-branch-conflicts: | `:vscode-git-branch-conflicts:` | `vscode/git-branch-conflicts` |
| :vscode-git-branch-staged-changes: | `:vscode-git-branch-staged-changes:` | `vscode/git-branch-staged-changes` |
| :vscode-git-commit: | `:vscode-git-commit:` | `vscode/git-commit` |
| :vscode-git-compare: | `:vscode-git-compare:` | `vscode/git-compare` |
| :vscode-git-fetch: | `:vscode-git-fetch:` | `vscode/git-fetch` |
| :vscode-git-merge: | `:vscode-git-merge:` | `vscode/git-merge` |
| :vscode-git-pull-request: | `:vscode-git-pull-request:` | `vscode/git-pull-request` |
| :vscode-git-pull-request-closed: | `:vscode-git-pull-request-closed:` | `vscode/git-pull-request-closed` |
| :vscode-git-pull-request-create: | `:vscode-git-pull-request-create:` | `vscode/git-pull-request-create` |
| :vscode-git-pull-request-done: | `:vscode-git-pull-request-done:` | `vscode/git-pull-request-done` |
| :vscode-git-pull-request-draft: | `:vscode-git-pull-request-draft:` | `vscode/git-pull-request-draft` |
| :vscode-git-pull-request-go-to-changes: | `:vscode-git-pull-request-go-to-changes:` | `vscode/git-pull-request-go-to-changes` |
| :vscode-git-pull-request-new-changes: | `:vscode-git-pull-request-new-changes:` | `vscode/git-pull-request-new-changes` |
| :vscode-git-stash: | `:vscode-git-stash:` | `vscode/git-stash` |
| :vscode-git-stash-apply: | `:vscode-git-stash-apply:` | `vscode/git-stash-apply` |
| :vscode-git-stash-pop: | `:vscode-git-stash-pop:` | `vscode/git-stash-pop` |
| :vscode-github: | `:vscode-github:` | `vscode/github` |
| :vscode-github-action: | `:vscode-github-action:` | `vscode/github-action` |
| :vscode-github-alt: | `:vscode-github-alt:` | `vscode/github-alt` |
| :vscode-github-inverted: | `:vscode-github-inverted:` | `vscode/github-inverted` |
| :vscode-github-project: | `:vscode-github-project:` | `vscode/github-project` |
| :vscode-globe: | `:vscode-globe:` | `vscode/globe` |
| :vscode-go-to-editing-session: | `:vscode-go-to-editing-session:` | `vscode/go-to-editing-session` |
| :vscode-go-to-file: | `:vscode-go-to-file:` | `vscode/go-to-file` |
| :vscode-go-to-search: | `:vscode-go-to-search:` | `vscode/go-to-search` |
| :vscode-grabber: | `:vscode-grabber:` | `vscode/grabber` |
| :vscode-graph: | `:vscode-graph:` | `vscode/graph` |
| :vscode-graph-left: | `:vscode-graph-left:` | `vscode/graph-left` |
| :vscode-graph-line: | `:vscode-graph-line:` | `vscode/graph-line` |
| :vscode-graph-scatter: | `:vscode-graph-scatter:` | `vscode/graph-scatter` |
| :vscode-gripper: | `:vscode-gripper:` | `vscode/gripper` |
| :vscode-group-by-ref-type: | `:vscode-group-by-ref-type:` | `vscode/group-by-ref-type` |
| :vscode-heart: | `:vscode-heart:` | `vscode/heart` |
| :vscode-heart-filled: | `:vscode-heart-filled:` | `vscode/heart-filled` |
| :vscode-history: | `:vscode-history:` | `vscode/history` |
| :vscode-home: | `:vscode-home:` | `vscode/home` |
| :vscode-horizontal-rule: | `:vscode-horizontal-rule:` | `vscode/horizontal-rule` |
| :vscode-hubot: | `:vscode-hubot:` | `vscode/hubot` |
| :vscode-inbox: | `:vscode-inbox:` | `vscode/inbox` |
| :vscode-indent: | `:vscode-indent:` | `vscode/indent` |
| :vscode-index-zero: | `:vscode-index-zero:` | `vscode/index-zero` |
| :vscode-info: | `:vscode-info:` | `vscode/info` |
| :vscode-insert: | `:vscode-insert:` | `vscode/insert` |
| :vscode-inspect: | `:vscode-inspect:` | `vscode/inspect` |
| :vscode-issue-draft: | `:vscode-issue-draft:` | `vscode/issue-draft` |
| :vscode-issue-reopened: | `:vscode-issue-reopened:` | `vscode/issue-reopened` |
| :vscode-issues: | `:vscode-issues:` | `vscode/issues` |
| :vscode-italic: | `:vscode-italic:` | `vscode/italic` |
| :vscode-jersey: | `:vscode-jersey:` | `vscode/jersey` |
| :vscode-json: | `:vscode-json:` | `vscode/json` |
| :vscode-kebab-vertical: | `:vscode-kebab-vertical:` | `vscode/kebab-vertical` |
| :vscode-key: | `:vscode-key:` | `vscode/key` |
| :vscode-keyboard-tab: | `:vscode-keyboard-tab:` | `vscode/keyboard-tab` |
| :vscode-keyboard-tab-above: | `:vscode-keyboard-tab-above:` | `vscode/keyboard-tab-above` |
| :vscode-keyboard-tab-below: | `:vscode-keyboard-tab-below:` | `vscode/keyboard-tab-below` |
| :vscode-law: | `:vscode-law:` | `vscode/law` |
| :vscode-layers: | `:vscode-layers:` | `vscode/layers` |
| :vscode-layers-active: | `:vscode-layers-active:` | `vscode/layers-active` |
| :vscode-layers-dot: | `:vscode-layers-dot:` | `vscode/layers-dot` |
| :vscode-layout: | `:vscode-layout:` | `vscode/layout` |
| :vscode-layout-activitybar-left: | `:vscode-layout-activitybar-left:` | `vscode/layout-activitybar-left` |
| :vscode-layout-activitybar-right: | `:vscode-layout-activitybar-right:` | `vscode/layout-activitybar-right` |
| :vscode-layout-centered: | `:vscode-layout-centered:` | `vscode/layout-centered` |
| :vscode-layout-menubar: | `:vscode-layout-menubar:` | `vscode/layout-menubar` |
| :vscode-layout-panel: | `:vscode-layout-panel:` | `vscode/layout-panel` |
| :vscode-layout-panel-center: | `:vscode-layout-panel-center:` | `vscode/layout-panel-center` |
| :vscode-layout-panel-dock: | `:vscode-layout-panel-dock:` | `vscode/layout-panel-dock` |
| :vscode-layout-panel-justify: | `:vscode-layout-panel-justify:` | `vscode/layout-panel-justify` |
| :vscode-layout-panel-left: | `:vscode-layout-panel-left:` | `vscode/layout-panel-left` |
| :vscode-layout-panel-off: | `:vscode-layout-panel-off:` | `vscode/layout-panel-off` |
| :vscode-layout-panel-right: | `:vscode-layout-panel-right:` | `vscode/layout-panel-right` |
| :vscode-layout-sidebar-left: | `:vscode-layout-sidebar-left:` | `vscode/layout-sidebar-left` |
| :vscode-layout-sidebar-left-dock: | `:vscode-layout-sidebar-left-dock:` | `vscode/layout-sidebar-left-dock` |
| :vscode-layout-sidebar-left-off: | `:vscode-layout-sidebar-left-off:` | `vscode/layout-sidebar-left-off` |
| :vscode-layout-sidebar-right: | `:vscode-layout-sidebar-right:` | `vscode/layout-sidebar-right` |
| :vscode-layout-sidebar-right-dock: | `:vscode-layout-sidebar-right-dock:` | `vscode/layout-sidebar-right-dock` |
| :vscode-layout-sidebar-right-off: | `:vscode-layout-sidebar-right-off:` | `vscode/layout-sidebar-right-off` |
| :vscode-layout-statusbar: | `:vscode-layout-statusbar:` | `vscode/layout-statusbar` |
| :vscode-library: | `:vscode-library:` | `vscode/library` |
| :vscode-lightbulb: | `:vscode-lightbulb:` | `vscode/lightbulb` |
| :vscode-lightbulb-autofix: | `:vscode-lightbulb-autofix:` | `vscode/lightbulb-autofix` |
| :vscode-lightbulb-empty: | `:vscode-lightbulb-empty:` | `vscode/lightbulb-empty` |
| :vscode-lightbulb-sparkle: | `:vscode-lightbulb-sparkle:` | `vscode/lightbulb-sparkle` |
| :vscode-link: | `:vscode-link:` | `vscode/link` |
| :vscode-link-external: | `:vscode-link-external:` | `vscode/link-external` |
| :vscode-list-filter: | `:vscode-list-filter:` | `vscode/list-filter` |
| :vscode-list-flat: | `:vscode-list-flat:` | `vscode/list-flat` |
| :vscode-list-ordered: | `:vscode-list-ordered:` | `vscode/list-ordered` |
| :vscode-list-selection: | `:vscode-list-selection:` | `vscode/list-selection` |
| :vscode-list-tree: | `:vscode-list-tree:` | `vscode/list-tree` |
| :vscode-list-unordered: | `:vscode-list-unordered:` | `vscode/list-unordered` |
| :vscode-live-share: | `:vscode-live-share:` | `vscode/live-share` |
| :vscode-loading: | `:vscode-loading:` | `vscode/loading` |
| :vscode-location: | `:vscode-location:` | `vscode/location` |
| :vscode-lock: | `:vscode-lock:` | `vscode/lock` |
| :vscode-lock-small: | `:vscode-lock-small:` | `vscode/lock-small` |
| :vscode-magnet: | `:vscode-magnet:` | `vscode/magnet` |
| :vscode-mail: | `:vscode-mail:` | `vscode/mail` |
| :vscode-mail-read: | `:vscode-mail-read:` | `vscode/mail-read` |
| :vscode-map: | `:vscode-map:` | `vscode/map` |
| :vscode-map-filled: | `:vscode-map-filled:` | `vscode/map-filled` |
| :vscode-map-vertical: | `:vscode-map-vertical:` | `vscode/map-vertical` |
| :vscode-map-vertical-filled: | `:vscode-map-vertical-filled:` | `vscode/map-vertical-filled` |
| :vscode-markdown: | `:vscode-markdown:` | `vscode/markdown` |
| :vscode-mcp: | `:vscode-mcp:` | `vscode/mcp` |
| :vscode-megaphone: | `:vscode-megaphone:` | `vscode/megaphone` |
| :vscode-mention: | `:vscode-mention:` | `vscode/mention` |
| :vscode-menu: | `:vscode-menu:` | `vscode/menu` |
| :vscode-merge: | `:vscode-merge:` | `vscode/merge` |
| :vscode-merge-into: | `:vscode-merge-into:` | `vscode/merge-into` |
| :vscode-mic: | `:vscode-mic:` | `vscode/mic` |
| :vscode-mic-filled: | `:vscode-mic-filled:` | `vscode/mic-filled` |
| :vscode-milestone: | `:vscode-milestone:` | `vscode/milestone` |
| :vscode-mirror: | `:vscode-mirror:` | `vscode/mirror` |
| :vscode-mortar-board: | `:vscode-mortar-board:` | `vscode/mortar-board` |
| :vscode-move: | `:vscode-move:` | `vscode/move` |
| :vscode-multiple-windows: | `:vscode-multiple-windows:` | `vscode/multiple-windows` |
| :vscode-music: | `:vscode-music:` | `vscode/music` |
| :vscode-mute: | `:vscode-mute:` | `vscode/mute` |
| :vscode-new-collection: | `:vscode-new-collection:` | `vscode/new-collection` |
| :vscode-new-file: | `:vscode-new-file:` | `vscode/new-file` |
| :vscode-new-folder: | `:vscode-new-folder:` | `vscode/new-folder` |
| :vscode-new-session: | `:vscode-new-session:` | `vscode/new-session` |
| :vscode-newline: | `:vscode-newline:` | `vscode/newline` |
| :vscode-no-newline: | `:vscode-no-newline:` | `vscode/no-newline` |
| :vscode-note: | `:vscode-note:` | `vscode/note` |
| :vscode-notebook: | `:vscode-notebook:` | `vscode/notebook` |
| :vscode-notebook-template: | `:vscode-notebook-template:` | `vscode/notebook-template` |
| :vscode-octoface: | `:vscode-octoface:` | `vscode/octoface` |
| :vscode-open-in-product: | `:vscode-open-in-product:` | `vscode/open-in-product` |
| :vscode-open-in-window: | `:vscode-open-in-window:` | `vscode/open-in-window` |
| :vscode-open-preview: | `:vscode-open-preview:` | `vscode/open-preview` |
| :vscode-openai: | `:vscode-openai:` | `vscode/openai` |
| :vscode-organization: | `:vscode-organization:` | `vscode/organization` |
| :vscode-output: | `:vscode-output:` | `vscode/output` |
| :vscode-package: | `:vscode-package:` | `vscode/package` |
| :vscode-paintcan: | `:vscode-paintcan:` | `vscode/paintcan` |
| :vscode-pass: | `:vscode-pass:` | `vscode/pass` |
| :vscode-pass-filled: | `:vscode-pass-filled:` | `vscode/pass-filled` |
| :vscode-percentage: | `:vscode-percentage:` | `vscode/percentage` |
| :vscode-person: | `:vscode-person:` | `vscode/person` |
| :vscode-person-add: | `:vscode-person-add:` | `vscode/person-add` |
| :vscode-piano: | `:vscode-piano:` | `vscode/piano` |
| :vscode-pie-chart: | `:vscode-pie-chart:` | `vscode/pie-chart` |
| :vscode-pin: | `:vscode-pin:` | `vscode/pin` |
| :vscode-pinned: | `:vscode-pinned:` | `vscode/pinned` |
| :vscode-pinned-dirty: | `:vscode-pinned-dirty:` | `vscode/pinned-dirty` |
| :vscode-play: | `:vscode-play:` | `vscode/play` |
| :vscode-play-circle: | `:vscode-play-circle:` | `vscode/play-circle` |
| :vscode-plug: | `:vscode-plug:` | `vscode/plug` |
| :vscode-preserve-case: | `:vscode-preserve-case:` | `vscode/preserve-case` |
| :vscode-preview: | `:vscode-preview:` | `vscode/preview` |
| :vscode-primitive-square: | `:vscode-primitive-square:` | `vscode/primitive-square` |
| :vscode-project: | `:vscode-project:` | `vscode/project` |
| :vscode-pulse: | `:vscode-pulse:` | `vscode/pulse` |
| :vscode-python: | `:vscode-python:` | `vscode/python` |
| :vscode-question: | `:vscode-question:` | `vscode/question` |
| :vscode-quote: | `:vscode-quote:` | `vscode/quote` |
| :vscode-quotes: | `:vscode-quotes:` | `vscode/quotes` |
| :vscode-radio-tower: | `:vscode-radio-tower:` | `vscode/radio-tower` |
| :vscode-reactions: | `:vscode-reactions:` | `vscode/reactions` |
| :vscode-record: | `:vscode-record:` | `vscode/record` |
| :vscode-record-keys: | `:vscode-record-keys:` | `vscode/record-keys` |
| :vscode-record-small: | `:vscode-record-small:` | `vscode/record-small` |
| :vscode-redo: | `:vscode-redo:` | `vscode/redo` |
| :vscode-references: | `:vscode-references:` | `vscode/references` |
| :vscode-refresh: | `:vscode-refresh:` | `vscode/refresh` |
| :vscode-regex: | `:vscode-regex:` | `vscode/regex` |
| :vscode-remote: | `:vscode-remote:` | `vscode/remote` |
| :vscode-remote-explorer: | `:vscode-remote-explorer:` | `vscode/remote-explorer` |
| :vscode-remove: | `:vscode-remove:` | `vscode/remove` |
| :vscode-remove-small: | `:vscode-remove-small:` | `vscode/remove-small` |
| :vscode-rename: | `:vscode-rename:` | `vscode/rename` |
| :vscode-replace: | `:vscode-replace:` | `vscode/replace` |
| :vscode-replace-all: | `:vscode-replace-all:` | `vscode/replace-all` |
| :vscode-reply: | `:vscode-reply:` | `vscode/reply` |
| :vscode-repo: | `:vscode-repo:` | `vscode/repo` |
| :vscode-repo-clone: | `:vscode-repo-clone:` | `vscode/repo-clone` |
| :vscode-repo-fetch: | `:vscode-repo-fetch:` | `vscode/repo-fetch` |
| :vscode-repo-force-push: | `:vscode-repo-force-push:` | `vscode/repo-force-push` |
| :vscode-repo-forked: | `:vscode-repo-forked:` | `vscode/repo-forked` |
| :vscode-repo-pinned: | `:vscode-repo-pinned:` | `vscode/repo-pinned` |
| :vscode-repo-pull: | `:vscode-repo-pull:` | `vscode/repo-pull` |
| :vscode-repo-push: | `:vscode-repo-push:` | `vscode/repo-push` |
| :vscode-repo-selected: | `:vscode-repo-selected:` | `vscode/repo-selected` |
| :vscode-report: | `:vscode-report:` | `vscode/report` |
| :vscode-request-changes: | `:vscode-request-changes:` | `vscode/request-changes` |
| :vscode-robot: | `:vscode-robot:` | `vscode/robot` |
| :vscode-rocket: | `:vscode-rocket:` | `vscode/rocket` |
| :vscode-root-folder: | `:vscode-root-folder:` | `vscode/root-folder` |
| :vscode-root-folder-opened: | `:vscode-root-folder-opened:` | `vscode/root-folder-opened` |
| :vscode-rss: | `:vscode-rss:` | `vscode/rss` |
| :vscode-ruby: | `:vscode-ruby:` | `vscode/ruby` |
| :vscode-run-above: | `:vscode-run-above:` | `vscode/run-above` |
| :vscode-run-all: | `:vscode-run-all:` | `vscode/run-all` |
| :vscode-run-all-coverage: | `:vscode-run-all-coverage:` | `vscode/run-all-coverage` |
| :vscode-run-below: | `:vscode-run-below:` | `vscode/run-below` |
| :vscode-run-coverage: | `:vscode-run-coverage:` | `vscode/run-coverage` |
| :vscode-run-errors: | `:vscode-run-errors:` | `vscode/run-errors` |
| :vscode-run-with-deps: | `:vscode-run-with-deps:` | `vscode/run-with-deps` |
| :vscode-save: | `:vscode-save:` | `vscode/save` |
| :vscode-save-all: | `:vscode-save-all:` | `vscode/save-all` |
| :vscode-save-as: | `:vscode-save-as:` | `vscode/save-as` |
| :vscode-screen-cut: | `:vscode-screen-cut:` | `vscode/screen-cut` |
| :vscode-screen-full: | `:vscode-screen-full:` | `vscode/screen-full` |
| :vscode-screen-normal: | `:vscode-screen-normal:` | `vscode/screen-normal` |
| :vscode-search: | `:vscode-search:` | `vscode/search` |
| :vscode-search-fuzzy: | `:vscode-search-fuzzy:` | `vscode/search-fuzzy` |
| :vscode-search-large: | `:vscode-search-large:` | `vscode/search-large` |
| :vscode-search-sparkle: | `:vscode-search-sparkle:` | `vscode/search-sparkle` |
| :vscode-search-stop: | `:vscode-search-stop:` | `vscode/search-stop` |
| :vscode-send: | `:vscode-send:` | `vscode/send` |
| :vscode-send-to-remote-agent: | `:vscode-send-to-remote-agent:` | `vscode/send-to-remote-agent` |
| :vscode-server: | `:vscode-server:` | `vscode/server` |
| :vscode-server-environment: | `:vscode-server-environment:` | `vscode/server-environment` |
| :vscode-server-process: | `:vscode-server-process:` | `vscode/server-process` |
| :vscode-session-in-progress: | `:vscode-session-in-progress:` | `vscode/session-in-progress` |
| :vscode-settings: | `:vscode-settings:` | `vscode/settings` |
| :vscode-settings-gear: | `:vscode-settings-gear:` | `vscode/settings-gear` |
| :vscode-share: | `:vscode-share:` | `vscode/share` |
| :vscode-share-window: | `:vscode-share-window:` | `vscode/share-window` |
| :vscode-shield: | `:vscode-shield:` | `vscode/shield` |
| :vscode-sign-in: | `:vscode-sign-in:` | `vscode/sign-in` |
| :vscode-sign-out: | `:vscode-sign-out:` | `vscode/sign-out` |
| :vscode-skip: | `:vscode-skip:` | `vscode/skip` |
| :vscode-smiley: | `:vscode-smiley:` | `vscode/smiley` |
| :vscode-snake: | `:vscode-snake:` | `vscode/snake` |
| :vscode-sort-precedence: | `:vscode-sort-precedence:` | `vscode/sort-precedence` |
| :vscode-source-control: | `:vscode-source-control:` | `vscode/source-control` |
| :vscode-sparkle: | `:vscode-sparkle:` | `vscode/sparkle` |
| :vscode-sparkle-filled: | `:vscode-sparkle-filled:` | `vscode/sparkle-filled` |
| :vscode-split-horizontal: | `:vscode-split-horizontal:` | `vscode/split-horizontal` |
| :vscode-split-vertical: | `:vscode-split-vertical:` | `vscode/split-vertical` |
| :vscode-squirrel: | `:vscode-squirrel:` | `vscode/squirrel` |
| :vscode-star-empty: | `:vscode-star-empty:` | `vscode/star-empty` |
| :vscode-star-full: | `:vscode-star-full:` | `vscode/star-full` |
| :vscode-star-half: | `:vscode-star-half:` | `vscode/star-half` |
| :vscode-stop-circle: | `:vscode-stop-circle:` | `vscode/stop-circle` |
| :vscode-strikethrough: | `:vscode-strikethrough:` | `vscode/strikethrough` |
| :vscode-surround-with: | `:vscode-surround-with:` | `vscode/surround-with` |
| :vscode-symbol-array: | `:vscode-symbol-array:` | `vscode/symbol-array` |
| :vscode-symbol-boolean: | `:vscode-symbol-boolean:` | `vscode/symbol-boolean` |
| :vscode-symbol-class: | `:vscode-symbol-class:` | `vscode/symbol-class` |
| :vscode-symbol-color: | `:vscode-symbol-color:` | `vscode/symbol-color` |
| :vscode-symbol-constant: | `:vscode-symbol-constant:` | `vscode/symbol-constant` |
| :vscode-symbol-enum: | `:vscode-symbol-enum:` | `vscode/symbol-enum` |
| :vscode-symbol-enum-member: | `:vscode-symbol-enum-member:` | `vscode/symbol-enum-member` |
| :vscode-symbol-event: | `:vscode-symbol-event:` | `vscode/symbol-event` |
| :vscode-symbol-field: | `:vscode-symbol-field:` | `vscode/symbol-field` |
| :vscode-symbol-file: | `:vscode-symbol-file:` | `vscode/symbol-file` |
| :vscode-symbol-interface: | `:vscode-symbol-interface:` | `vscode/symbol-interface` |
| :vscode-symbol-key: | `:vscode-symbol-key:` | `vscode/symbol-key` |
| :vscode-symbol-keyword: | `:vscode-symbol-keyword:` | `vscode/symbol-keyword` |
| :vscode-symbol-method: | `:vscode-symbol-method:` | `vscode/symbol-method` |
| :vscode-symbol-method-arrow: | `:vscode-symbol-method-arrow:` | `vscode/symbol-method-arrow` |
| :vscode-symbol-misc: | `:vscode-symbol-misc:` | `vscode/symbol-misc` |
| :vscode-symbol-namespace: | `:vscode-symbol-namespace:` | `vscode/symbol-namespace` |
| :vscode-symbol-numeric: | `:vscode-symbol-numeric:` | `vscode/symbol-numeric` |
| :vscode-symbol-operator: | `:vscode-symbol-operator:` | `vscode/symbol-operator` |
| :vscode-symbol-parameter: | `:vscode-symbol-parameter:` | `vscode/symbol-parameter` |
| :vscode-symbol-property: | `:vscode-symbol-property:` | `vscode/symbol-property` |
| :vscode-symbol-ruler: | `:vscode-symbol-ruler:` | `vscode/symbol-ruler` |
| :vscode-symbol-snippet: | `:vscode-symbol-snippet:` | `vscode/symbol-snippet` |
| :vscode-symbol-string: | `:vscode-symbol-string:` | `vscode/symbol-string` |
| :vscode-symbol-structure: | `:vscode-symbol-structure:` | `vscode/symbol-structure` |
| :vscode-symbol-variable: | `:vscode-symbol-variable:` | `vscode/symbol-variable` |
| :vscode-sync: | `:vscode-sync:` | `vscode/sync` |
| :vscode-sync-ignored: | `:vscode-sync-ignored:` | `vscode/sync-ignored` |
| :vscode-table: | `:vscode-table:` | `vscode/table` |
| :vscode-tag: | `:vscode-tag:` | `vscode/tag` |
| :vscode-target: | `:vscode-target:` | `vscode/target` |
| :vscode-tasklist: | `:vscode-tasklist:` | `vscode/tasklist` |
| :vscode-telescope: | `:vscode-telescope:` | `vscode/telescope` |
| :vscode-terminal: | `:vscode-terminal:` | `vscode/terminal` |
| :vscode-terminal-bash: | `:vscode-terminal-bash:` | `vscode/terminal-bash` |
| :vscode-terminal-cmd: | `:vscode-terminal-cmd:` | `vscode/terminal-cmd` |
| :vscode-terminal-debian: | `:vscode-terminal-debian:` | `vscode/terminal-debian` |
| :vscode-terminal-git-bash: | `:vscode-terminal-git-bash:` | `vscode/terminal-git-bash` |
| :vscode-terminal-linux: | `:vscode-terminal-linux:` | `vscode/terminal-linux` |
| :vscode-terminal-powershell: | `:vscode-terminal-powershell:` | `vscode/terminal-powershell` |
| :vscode-terminal-secure: | `:vscode-terminal-secure:` | `vscode/terminal-secure` |
| :vscode-terminal-tmux: | `:vscode-terminal-tmux:` | `vscode/terminal-tmux` |
| :vscode-terminal-ubuntu: | `:vscode-terminal-ubuntu:` | `vscode/terminal-ubuntu` |
| :vscode-text-size: | `:vscode-text-size:` | `vscode/text-size` |
| :vscode-thinking: | `:vscode-thinking:` | `vscode/thinking` |
| :vscode-three-bars: | `:vscode-three-bars:` | `vscode/three-bars` |
| :vscode-thumbsdown: | `:vscode-thumbsdown:` | `vscode/thumbsdown` |
| :vscode-thumbsdown-filled: | `:vscode-thumbsdown-filled:` | `vscode/thumbsdown-filled` |
| :vscode-thumbsup: | `:vscode-thumbsup:` | `vscode/thumbsup` |
| :vscode-thumbsup-filled: | `:vscode-thumbsup-filled:` | `vscode/thumbsup-filled` |
| :vscode-tools: | `:vscode-tools:` | `vscode/tools` |
| :vscode-trash: | `:vscode-trash:` | `vscode/trash` |
| :vscode-triangle-down: | `:vscode-triangle-down:` | `vscode/triangle-down` |
| :vscode-triangle-left: | `:vscode-triangle-left:` | `vscode/triangle-left` |
| :vscode-triangle-right: | `:vscode-triangle-right:` | `vscode/triangle-right` |
| :vscode-triangle-up: | `:vscode-triangle-up:` | `vscode/triangle-up` |
| :vscode-twitter: | `:vscode-twitter:` | `vscode/twitter` |
| :vscode-type-hierarchy: | `:vscode-type-hierarchy:` | `vscode/type-hierarchy` |
| :vscode-type-hierarchy-sub: | `:vscode-type-hierarchy-sub:` | `vscode/type-hierarchy-sub` |
| :vscode-type-hierarchy-super: | `:vscode-type-hierarchy-super:` | `vscode/type-hierarchy-super` |
| :vscode-unarchive: | `:vscode-unarchive:` | `vscode/unarchive` |
| :vscode-unfold: | `:vscode-unfold:` | `vscode/unfold` |
| :vscode-ungroup-by-ref-type: | `:vscode-ungroup-by-ref-type:` | `vscode/ungroup-by-ref-type` |
| :vscode-unlock: | `:vscode-unlock:` | `vscode/unlock` |
| :vscode-unmute: | `:vscode-unmute:` | `vscode/unmute` |
| :vscode-unverified: | `:vscode-unverified:` | `vscode/unverified` |
| :vscode-variable-group: | `:vscode-variable-group:` | `vscode/variable-group` |
| :vscode-verified: | `:vscode-verified:` | `vscode/verified` |
| :vscode-verified-filled: | `:vscode-verified-filled:` | `vscode/verified-filled` |
| :vscode-versions: | `:vscode-versions:` | `vscode/versions` |
| :vscode-vm: | `:vscode-vm:` | `vscode/vm` |
| :vscode-vm-active: | `:vscode-vm-active:` | `vscode/vm-active` |
| :vscode-vm-connect: | `:vscode-vm-connect:` | `vscode/vm-connect` |
| :vscode-vm-outline: | `:vscode-vm-outline:` | `vscode/vm-outline` |
| :vscode-vm-pending: | `:vscode-vm-pending:` | `vscode/vm-pending` |
| :vscode-vm-running: | `:vscode-vm-running:` | `vscode/vm-running` |
| :vscode-vm-small: | `:vscode-vm-small:` | `vscode/vm-small` |
| :vscode-vr: | `:vscode-vr:` | `vscode/vr` |
| :vscode-vscode: | `:vscode-vscode:` | `vscode/vscode` |
| :vscode-vscode-insiders: | `:vscode-vscode-insiders:` | `vscode/vscode-insiders` |
| :vscode-wand: | `:vscode-wand:` | `vscode/wand` |
| :vscode-warning: | `:vscode-warning:` | `vscode/warning` |
| :vscode-watch: | `:vscode-watch:` | `vscode/watch` |
| :vscode-whitespace: | `:vscode-whitespace:` | `vscode/whitespace` |
| :vscode-whole-word: | `:vscode-whole-word:` | `vscode/whole-word` |
| :vscode-window: | `:vscode-window:` | `vscode/window` |
| :vscode-window-active: | `:vscode-window-active:` | `vscode/window-active` |
| :vscode-word-wrap: | `:vscode-word-wrap:` | `vscode/word-wrap` |
| :vscode-workspace-trusted: | `:vscode-workspace-trusted:` | `vscode/workspace-trusted` |
| :vscode-workspace-unknown: | `:vscode-workspace-unknown:` | `vscode/workspace-unknown` |
| :vscode-workspace-untrusted: | `:vscode-workspace-untrusted:` | `vscode/workspace-untrusted` |
| :vscode-worktree: | `:vscode-worktree:` | `vscode/worktree` |
| :vscode-worktree-small: | `:vscode-worktree-small:` | `vscode/worktree-small` |
| :vscode-zoom-in: | `:vscode-zoom-in:` | `vscode/zoom-in` |
| :vscode-zoom-out: | `:vscode-zoom-out:` | `vscode/zoom-out` |
+28
View File
@@ -0,0 +1,28 @@
---
icon: simple/devbox
hide: [toc]
---
# :simple-devbox: Для разработчиков
<div class="grid cards" markdown>
- [:material-application-brackets-outline: Среда разработки](local-dev.md)
---
Настройка ПО для работы с кодом проекта
- [:material-robot-outline: Telegram-бот](tgbot.md)
---
Специфика разработки и отладки Telegram-бота проекта
- [:material-book-cog-outline: Сборка документации](docs.md)
---
Как скорректировать, проверить и скомплировать эту документацию
- [:material-upload-network: Развёртывание и доставка обновлений](deploy.md)
---
О том, как опубликовать изменения на сервере
</div>
+221
View File
@@ -0,0 +1,221 @@
---
# icon: material/architecture
tags: ["iptvc", "разработка", "архитектура"]
---
# Архитектура iptvc
Внутреннее устройство программы для разработчиков.
## Стек технологий
- **Go 1.22+** — язык программирования;
- `net/http` — HTTP-сервер (Go 1.22 routing patterns);
- `html/template` — шаблоны HTML;
- `//go:embed` — встраивание шаблонов в бинарник;
- `github.com/spf13/cobra` — CLI-фреймворк;
- `gopkg.in/yaml.v3` — парсинг `config.yml`;
- `github.com/joho/godotenv` — загрузка `.env`;
- `github.com/redis/go-redis/v9` — клиент KeyDB/Redis.
## Структура проекта
```
iptvc/
├── main.go # точка входа
├── config.yml # конфигурация
├── .env # переменные окружения (опционально)
├── cmd/ # CLI-команды (Cobra)
│ ├── root.go # корневая команда, глобальные флаги
│ ├── check.go # команда check
│ ├── serve.go # команда serve
│ ├── flags.go # общие флаги check/serve
│ └── version.go # команда version
├── app/
│ ├── app.go # глобальные переменные: Args, Config, Cache
│ ├── config/
│ │ └── config.go # Config, Init(), validate(), IntRange, UserAgents
│ ├── checker/
│ │ └── checker.go # CheckPlaylists(), CheckChannels(), OnPlaylistChecked
│ ├── playlist/
│ │ └── playlist.go # Playlist, Channel, Parse(), Download()
│ ├── inifile/
│ │ └── inifile.go # чтение playlists.ini
│ ├── tagfile/
│ │ └── tagfile.go # чтение channels.json, назначение тегов
│ ├── cache/
│ │ └── cache.go # подключение к KeyDB/Redis
│ ├── logger/
│ │ └── logger.go # настройка логирования
│ ├── utils/
│ │ └── utils.go # Fetch(), ExpandPath(), ArrayUnique(), Md5str()
│ └── web/
│ ├── server.go # Server, Start(), StartBackgroundChecker()
│ ├── handlers.go # HTTP-обработчики
│ ├── templates.go # TemplateManager, //go:embed
│ └── views/ # HTML-шаблоны
│ ├── layout.html
│ ├── list.html
│ └── details.html
└── go.mod
```
## Пакеты
### `app`
Глобальный контейнер: `Args` (CLI-флаги), `Config` (конфигурация), `Cache` (Redis-клиент).
`Init()` загружает конфигурацию, инициализирует логгер и подключение к кешу.
### `app.config`
Структуры: `Config``AppConfig`, `ServerConfig`, `CheckConfig`, `CacheConfig`.
Кастомные YAML-типы:
- **`IntRange`** — скаляр или `[min, max]`. Метод `Value()` возвращает константу или случайное значение.
- **`UserAgents`** — строка или массив строк. Метод `Pick()` возвращает случайный элемент.
`Init(configPath)` — загружает `config.yml`, применяет env, валидирует.
`validate()` — проверяет все значения, исправляет некорректные с логированием.
### `app.checker`
Содержит логику проверки:
- **`PrepareListsToCheck(files, urls, codes)`** — формирует список плейлистов из файлов, URL и кодов ini-файла.
- **`CheckPlaylists(lists)`** — параллельная проверка плейлистов (семфор `per-routine`), загрузка, парсинг, вызов `CheckChannels` для каждого.
- **`CheckChannels(pls)`** — параллельная проверка каналов (семфор `per-routine`), HTTP-запрос с `Range` header.
- **`OnPlaylistChecked`** — глобальный callback, вызывается после проверки каждого плейлиста. Используется веб-сервером для обновления in-memory кеша.
- **`cachePlaylist(pls)`** — сохранение результата в Redis (если включён).
Параметры проверки берутся из `app.Config.Check.Playlists` и `app.Config.Check.Channels`.
### `app.playlist`
- **`Playlist`** — плейлист: код, URL, контент, каналы, статус.
- **`Channel`** — канал: ID, название, URL, статус, теги.
- **`Download(userAgent, timeout)`** — загрузка по URL.
- **`ReadFromFs()`** — чтение из файла.
- **`Parse()`** — парсинг m3u/m3u8 контента.
### `app.web`
Веб-сервер на `net/http` (Go 1.22 routing).
- **`Server`** — структура: конфиг, кеш, шаблоны, in-memory кеш (`memCache` с `sync.RWMutex`).
- **`Start()`** — запуск HTTP-сервера.
- **`StartBackgroundChecker(opts)`** — фоновая проверка в отдельной горутине.
- **`CheckOptions`** — параметры: Every, Repeat, Random, Files, Urls, Codes.
Маршруты (Go 1.22 patterns):
```
GET /api/playlists/{code} — JSON плейлиста
GET /api/version — версия
GET /api/health — здоровье сервиса
GET /api/stats — статистика
GET /{$} — главная (catch-all root)
GET /{path...} — все остальные маршруты (catch-all)
```
Catch-all `/{path...}` используется для избежания конфликтов паттернов в Go 1.22 mux.
In-memory кеш (`memCache`) обновляется через `OnPlaylistChecked` callback.
Это позволяет отображать результаты проверки в реальном времени без ожидания завершения цикла и без Redis.
ini-файл кешируется на 30 секунд, кеш сбрасывается при каждом обновлении `memCache`.
## Жизненный цикл `serve --check`
```
main → app.Init() → web.NewServer() → go StartBackgroundChecker() → server.Start()
StartBackgroundChecker:
loop:
runCheckerOnce()
→ checker.PrepareListsToCheck()
→ checker.CheckPlaylists()
→ for each playlist (parallel, per-routine):
→ Download() / ReadFromFs()
→ Parse()
→ CheckChannels()
→ for each channel (parallel, per-routine):
→ HTTP GET with Range header
→ check status + content type
→ OnPlaylistChecked(pls) → memCache update
→ one-cooldown sleep
→ all-cooldown sleep
sleep(every)
if repeat > 0 && iteration >= repeat: stop
```
## CLI-флаги
### Общие (`cmd/flags.go`)
Используются командами `check` и `serve --check`:
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-i, --ini` | `Args.IniPath` | Путь к playlists.ini |
| `-t, --tags` | `Args.TagsPath` | Путь к channels.json |
| `-r, --random` | `Args.RandomCount` | Случайные N плейлистов |
| `--repeat` | `Args.RepeatCount` | Количество циклов (0 = бесконечно) |
| `--every` | `Args.RepeatEverySec` | Секунд между циклами |
### Только `check` (`addCheckOnlyFlags`)
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-j, --json` | `Args.NeedJson` | Вывод в JSON |
| `-q, --quiet` | `Args.NeedQuiet` | Подавить логи |
| `-f, --file` | `Args.Files` | Локальные m3u файлы |
| `-u, --url` | `Args.Urls` | URL плейлистов |
| `-c, --code` | `Args.Codes` | Коды из ini-файла |
### Только `serve`
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-p, --port` | `Args.ServerPort` | Порт веб-сервера |
| `--host` | `Args.ServerHost` | Хост привязки |
| `--check` | `Args.NeedCheck` | Включить фоновую проверку |
### Глобальные (`cmd/root.go`)
| Флаг | Поле | Описание |
| --- | --- | --- |
| `--config` | `Args.ConfigPath` | Путь к config.yml |
| `-v, --verbose` | `Args.Verbose` | Подробный лог |
## Конфигурация
Подробное описание параметров — в разделе [config.yml](../config.md).
Приоритет: Defaults < `config.yml` < Env < CLI-флаги.
CLI-флаги переопределяют конфигурацию только если переданы явно (`cmd.Flags().Changed()`).
Для этого в Cobra используются zero-value defaults (0, "", false), чтобы отличить «не передан» от «передан со значением по умолчанию».
## Шаблоны
HTML-шаблоны встроены через `//go:embed`:
- `layout.html` — общий каркас (header, footer);
- `list.html` — список плейлистов с пагинацией;
- `details.html` — детали плейлиста и список каналов.
SVG-логотипы каналов передаются через `encodeURIComponent` в data-URI для корректной работы с кавычками в HTML.
При отсутствии `playlists.ini` рендерится пустое состояние с alert-блоком.
## Кеширование
Два уровня кеша:
1. **Redis/KeyDB** (опционально) — постоянный кеш результатов проверки. TTL из `cache.ttl`.
2. **In-memory** (`memCache`) — только при `serve --check`. Обновляется в реальном времени через callback. Не требует Redis.
In-memory кеш приоритетнее Redis при отображении в веб-интерфейсе.
+9
View File
@@ -0,0 +1,9 @@
---
icon: material/application-brackets-outline
---
# :material-application-brackets-outline: Среда разработки
Полная информация о развёртывании локальной среды расположена здесь:
https://git.axenov.dev/IPTV/iptv-docker/src/branch/master/README.md
+80
View File
@@ -0,0 +1,80 @@
---
icon: material/robot-outline
---
# :material-robot-outline: Telegram-бот
!!! info "Обрати внимание"
Локальная среда разработки должна быть [настроена и запущена](local-dev.md).
## Подготовка бота на стороне Telegram
1. Написать [@botfather](https://t.me/botfather), создать бота
2. Полученный токен установить значением переменной `TG_BOT_TOKEN` в файле `.env` репозитория `web`
3. Командой `/mybots` в [@botfather](https://t.me/botfather) выбрать свежесозданного бота, далее `Edit Bot` > `Edit Commands` и отправить текст:
```
list - Список плейлистов
info - Подробности о плейлисте по его коду
help - Помощь по командам бота
links - Ссылки на все страницы проекта
stats - Статистика по плейлистам и каналам
```
## Проброс внешних запросов на локальную машину
1. Установить [telebit](https://telebit.cloud) и пройти примитивную регистрацию.
В результате будет выдан уникальный адрес в формате `https://foo-bar-99.telebit.io`.
На email, указанный при регистрации, будет оформлен бесплатный SSL-серификат Let's Encrypt для этого домена.
Если адрес не используется месяц+, то сертификат протухнет, но он автоматически восстановится, если адрес начнёт использоваться вновь.
2. В терминале выполнить:
```
telebit http 8080
```
где `8080` — порт локальной машины, на который проброшен порт 80 из контейнера `iptv-nginx`.
Для выключения выполнить:
```
telebit http
```
3. Проверить работу адреса, перейдя по нему браузером.
Должен открыться твой локальный проект.
4. Полученный адрес установить значением переменной `APP_URL` в файле `.env` репозитория `web`
5. Установить веб-хук, отправив запрос браузером или любым HTTP-клиентом на адрес:
```
https://api.telegram.org/bot$BOT_TOKEN/setWebhook?url=$TELEBIT_URL/bot/webhook&secret_token=$SECRET_TOKEN
```
где:
* `$BOT_TOKEN` - авторизационный токен, который @botfather выдал твоему боту;
* `$TELEBIT_URL` - адрес, который telebit выдал тебе;
* `$SECRET_TOKEN` - секретный токен, опционален, см. ниже.
6. Проверить веб-хук, отправив запрос браузером или любым HTTP-клиентом на адрес:
```
https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo
```
где:
* `$BOT_TOKEN` - авторизационный токен, который @botfather выдал твоему боту.
7. После разработки нужно установить "боевой" адрес веб-хука аналогично п4.
## Что за секретный токен?
Telegram авторизует твоего бота по токену, который выдал ему сам.
Ты тоже можешь (не) авторизовать Telegram по токену, который ты выдашь ему.
Для этого нужно в значением переменной `TG_BOT_SECRET` в файле `.env` репозитория `web` установить любую строку.
Если ты это сделаешь, тогда ту же строку ты должен передать в параметре `secret_token` метода `setWebhook`.
В этом случае, все HTTP-запросы, которые приходят от Telegram, будут содержать заголовок `X-Telegram-Bot-Api-Secret-Token` со этой строкой в качестве значения.
Эта строка сверяется с той, что указана в `.env` проекта.
Если такого заголовка нет или его значение некорректно, входящий запрос отклоняется.
Если переменная `TG_BOT_SECRET` не задана, то заголовок проверяться не будет.
См. подробности в документации: [setWebhook](https://core.telegram.org/bots/api#setwebhook)
+113
View File
@@ -0,0 +1,113 @@
---
title: channels.json
icon: material/code-json
tags: ["iptvc", "теги", "каналы"]
---
# :material-code-json: Формат файла `channels.json`
Категории каналов указываются в файле `channels.json` в следующем формате:
```json
[
{
"tvg-id": "регулярное выражение для значения атрибута tvg-id",
"tvg-name": "регулярное выражение для значения атрибута tvg-name",
"title": "регулярное выражение для названия канала",
"tags": [
"список",
"тегов",
"(см. ниже)"
]
}
]
```
Приоритет параметров:
* `tvg-id`
* `tvg-name`
* `title`
Если указаны все или несколько, то применится только тот один, который по приоритету выше.
Параметр `tags` обязателен, список может быть (не)пустым.
Категории в списке указываются в двойных кавычках.
После каждой категории, кроме последней, ставится запятая.
Регулярные выражения должны быть PCRE-совместимыми.
Каналы сопоставляются в нижнем регистре.
<a id="warnings"></a>
## Рекомендации и предостережения
1. Если хочешь написать новое правило, будь осторожен с регулярками.
Старайся не охватывать несколько каналов сразу.
Если канал попадёт в несколько регулярок, то его **теги могут оказаться неожиданными** и найти проблему может быть сложно.
2. Указывай внутри `{}` только `tags` и один из параметров `tvg-id`, `tvg-name` или `title`.
Множество параметров на точность не влияет, зато будет проще уместить всё правило в одну строку и grep-ать файл.
3. Не забывай, что название на кириллице не всегда означает, что это российский канал или вещание на русском языке.
4. Разные каналы могут быть названы одинаково или похоже.
Например, `Первый канал`, `Первый Тульский`, `Первый городской`.
5. Один канал может быть назван по-разному.
Например, `Ю` или `Ю!`, `Россия 1 +5` или `Россия-1`.
6. Важно учитывать холдинги.
Например, российские Матч, ТНТ и НТВ имеют множество разных каналов, и тупо искать `^нтв$` — тупо.
7. У всех каналов есть `title`.
Не все каналы имеют `tvg-id`.
Некоторые каналы имеют `tvg-name`.
Все три параметра могут оказаться на кириллице.
<a id="доступные-теги">
## Доступные теги
| Ключевое слово | Описание |
| -------------- | ----------------------------------------------------------- |
| `untagged` | Неизвестно (ставится по умолчанию, если нет иных) |
| `unstable` | Нестабильные каналы |
| `hd` | Каналы в высоком качестве |
| `4k` | Каналы в супервысоком качестве |
| `8k` | Каналы в гигавысоком качестве (а вдруг?) |
| `adult` | Контент для взрослых 18+ |
| `army` | Каналы военные, об оружии, технике, армии, боевых действиях |
| `central` | Центральное ТВ (гос. каналы, федеральные) |
| `child` | Детские и подростковые каналы |
| `culture` | Каналы о культуре, театре |
| `crime` | Каналы с детективами и о преступлениях (true-crime) |
| `cyber` | Киберспортивные |
| `docs` | Каналы с документальными фильмами |
| `fashion` | Мода и стиль |
| `film` | Каналы с фильмами |
| `finance` | Бизнес, финансы, капитал |
| `food` | Кулинарные каналы |
| `fun` | Развлекательные каналы и шоу |
| `garden` | Сад, огород, фермерство, домашнее хозяйство |
| `health` | Каналы о здоровье |
| `history` | Исторические каналы |
| `house` | О строительстве, ремонте, интерьере и жилье |
| `humor` | Юмористические каналы, комедии |
| `hunt-fish` | Охота и рыбалка |
| `local` | Местные региональные каналы |
| `music` | Музыкальные |
| `mystic` | Каналы о мистике, потустороннем, НЛО и прочей пиздаболии |
| `nature` | Каналы о природе, животных |
| `news` | Новостные каналы |
| `politic` | Каналы политические, партийные, правительстенные |
| `radio` | Радиоканалы (радиостанции, подкасты, аудиоспектакли...) |
| `religion` | Религиозные каналы |
| `retro` | Ностальгия и ретро, музыка, старые передачи |
| `sci` | Научные и познавательные |
| `series` | Каналы с сериалами |
| `shopping` | Телемагазины |
| `sport` | Спортивные |
| `tech` | Каналы о технологиях |
| `transport` | Каналы о транспорте (авто, мото, ЖД...) |
| `travel` | Каналы о путешествиях |
| `webcam` | Уличные веб-камеры |
Также в категориях можно указывать страну вещания.
Это должен быть буквенный код АЛЬФА-2 из общероссийского классификатора стран мира (ОКСМ):
* [normativ.kontur.ru](https://normativ.kontur.ru/document?moduleId=1&documentId=22668#h1296)
* [classifikators.ru](https://classifikators.ru/oksm)
+23
View File
@@ -0,0 +1,23 @@
---
icon: material/file-code-outline
hide: [toc]
---
# :material-file-code-outline: Форматы файлов
<div class="grid cards" markdown>
- [:material-code-brackets: Формат файла `playlists.ini`](playlists.md)
---
Список плейлистов, которые отображаются на сайте и периодически проверяются
- [:material-code-json: Формат файла `channels.json`](channels.md)
---
Список правил для применения тегов к разным каналам
- [:material-playlist-play: Формат файлов `*.m3u` (`*.m3u8`)](m3u.md)
---
Плейлист — это вообще что?
</div>
+150
View File
@@ -0,0 +1,150 @@
---
title: "*.m3u (*.m3u8)"
icon: material/playlist-play
tags: ["плейлисты", "каналы"]
---
# :material-playlist-play: Формат файлов `*.m3u` (`*.m3u8`)
Формат применяется для составления мультимедиа плейлистов, как оффлайн, так и онлайн.
Разница между m3u и m3u8 уже давно отсутствует, но исторически так сложилось, то m3u8 должен был быть только в формате UTF-8.
Директивы начинаются с новой строки и символа `#`.
У каждой директивы могут (не) быть атрибуты, которые следуют в одну строку.
После директив с новой строки указывается ссылка на канал (или путь к файлу).
В свою чередь, по этой ссылке может быть:
* либо текстовое представление контента в формате m3u/m3u8/XMLTV/MPD с описанием непосредственно участки трансляции;
* либо непосредственно сама потоковая трансляция mp4 или т. п.
Рассмотрим на выдуманном примере плейлиста IPTV:
```m3u
#EXTM3U url-tvg="https://iptvx.one/EPG" catchup="append" catchup-days="3" catchup-source="?offset=-${offset}&utcstart=${timestamp}"
#EXTINF:-1 tvg-id="ntv" tvg-logo="http://epg.it999.ru/img2/2001.png",НТВ HD
#EXTGRP:🇷🇺 Эфирные
http://example.com/play-ntv.m3u
#EXTINF:-1 tvg-logo="http://tvoetv.space/tvoetv.png" tvg-id="tvoetv",Твоё ТВ HD v8 с VPN
http://example.com/play-tvoetv.m3u
#EXTINF:0 tvg-name="BBC" audio-track="eng" tvg-logo="http://mylogos.domain/BBC.png", BBC World
http://example.com/play-bbc.m3u
#EXTINF:-1 tvg-id="5TV.am" tvg-country="AM" tvg-language="Armenian" tvg-logo="https://i.imgur.com/yigw9dr.png" user-agent="Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148" group-title="General",5-րդ ալիք (480p)
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
#EXTVLCOPT:http-caching=1200
http://example.com/play-am.m3u8
```
<a id="EXTM3U"></a>
### Директива `#EXTM3U`
Заголовок файла (обязателен).
<a id="url-tvg"></a>
#### Атрибут `url-tvg` (он же `x-tvg-url`)
Ссылка программу передач в формате `*.xml` или `*.xml.gz`.
<a id="catchup"></a>
#### Атрибуты `catchup*`
Читай здесь: [Архив телепрограмм (SS IPTV)](https://ss-iptv.com/ru/operators/catchup)
<a id="EXTGRP"></a>
### Директива `#EXTGRP`
Название группы, к которой относится контент (звуковая дорожка или канал).
Указывается в формате `#EXTGRP:XXX`, где: `XXX` — название группы.
<a id="EXTINF"></a>
### Директива `#EXTINF`
Описывает контент (звуковую дорожку или канал).
Указывается в формате `#EXTINF:XXX YYY,ZZZ`, где:
* `XXX` — длительность в секундах (обязательно, но может быть `-1` или `0`);
* `YYY` — атрибуты (см. ниже);
* `ZZZ` — название контента;
<a id="tvg-shift"></a>
#### Атрибут `tvg-shift`
Cмещение телепрограммы в часах относительно указанного в программе.
<a id="tvg-id"></a>
<a id="tvg-name"></a>
#### Атрибуты `tvg-id` и `tvg-name`
Идентификатор телепрограммы канала.
По нему телепрограмма привязывается к трансляции с учётом смещения времени.
<a id="tvg-logo"></a>
#### Атрибут `tvg-logo`
Ссылка на логотип канала.
<a id="tvg-country"></a>
#### Атрибут `tvg-country`
Код Alpha-2 страны вещания согласно ISO 3166-1 или ОКСМ.
<a id="tvg-language"></a>
#### Атрибут `tvg-language`
Название языка телепередачи согласно ISO 639-2.
<a id="group-title"></a>
#### Атрибут `group-title`
Название группы.
По функционалу идентичен директиве `#EXTGRP`.
<a id="user-agent"></a>
#### Атрибут `user-agent`
Значение заголовка `User-Agent` для обращения к контенту по http.
<a id="audio-track"></a>
#### Атрибут `audio-track`
Языковой код (ISO 639-2) аудио дорожки канала, например: "eng,rus".
Допускается указание нескольких аудио дорожек через запятую: "rus,ukr,eng".
Дорожкой по умолчанию устанавливается первая указанная в списке.
<a id="aspect-ratio"></a>
#### Атрибут `aspect-ratio`
Определяет пропорции экрана (может быть недоступно для некоторых моделей телевизоров).
Допустимые значения: 16:9, 3:2, 4:3, 1,85:1, 2,39:1 (наиболее распространенное значение для фильмов)
<a id="EXTVLCOPT"></a>
### Директива `#EXTVLCOPT`
Специфична для VLC Player.
Директив может быть множество для одной дорожки (канала).
Указывается в формате `#EXTVLCOPT:XXX` или `#EXTVLCOPT--XXX=YYY`, где:
* `XXX` — параметр командной строки VLC Player ([полный список](https://wiki.videolan.org/VLC_command-line_help/));
* `YYY` — значения параметра.
## Дополнительные материалы
* [Инструкция по формату M3U (SS IPTV)](https://ss-iptv.com/ru/users/documents/m3u)
* [Архив телепрограмм (SS IPTV)](https://ss-iptv.com/ru/operators/catchup)
* [VLC command-line help](https://wiki.videolan.org/VLC_command-line_help/)
* [ISO 639-2 Codes for the Representation of Names of Languages](https://www.loc.gov/standards/iso639-2/php/code_list.php)
* [ISO 3166-1 Wikipedia](https://en.wikipedia.org/wiki/ISO_3166-1)
* ОКСМ:
* [normativ.kontur.ru](https://normativ.kontur.ru/document?moduleId=1&documentId=22668#h1296)
* [classifikators.ru](https://classifikators.ru/oksm)
+60
View File
@@ -0,0 +1,60 @@
---
title: playlists.ini
icon: material/code-brackets
tags: ["плейлисты"]
---
# :material-code-brackets: Формат файла `playlists.ini`
Рассмотрим на примере:
```ini
[code]
name = Рабочий автообновляемый IPTV плейлист M3U
desc = "В этом IPTV плейлисте есть каналы в высоком качестве"
pls = 'https://example.com/pls.m3u'
src = 'https://example.com/super-duper-playlist'
# комментарий 1
; ещё один комментарий
```
Перенос строк невозможен.
Комментарии игнорируются.
Для значений можно (не) использовать 'одинарные' или "двойные" кавычки.
Ради единообразия рекомендуется использовать 'одинарные'.
## `code`
Код плейлиста в рамках этого конфига (**обязательно**).
Должен быть коротким и уникальным.
Подставляется в короткую ссылку, по которой произойдёт переадресация на прямой адрес `pls`.
!!! note
Для удобства ввода с пульта, код рекомендуется задавать числом или короткой строкой без пробелов и др. спецсимволов.
Чем короче, тем лучше.
## `name`
Название плейлиста (необязательно).
По умолчанию: `Playlist #<code>`.
## `desc`
Краткое описание из источника или от себя (необязательно).
По умолчанию: пусто.
## `pls`
Прямая ссылка на m3u/m3u8 плейлист (**обязательно**).
## `src`
Ссылка на источник (страницу сайта), откуда был взят плейлист (необязательно).
По умолчанию: пусто.
+70
View File
@@ -0,0 +1,70 @@
---
icon: material/download
tags: ["iptvc"]
---
# Установка и запуск
## Запуск готовой программы
Достаточно скачать и распаковать архив с подходящим исполняемым файлом [со страницы последнего релиза][rel_page] в любую удобную директорию:
| ОС | Скачать для `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] |
[rel_page]: https://git.axenov.dev/IPTV/iptvc/releases/latest
[linux_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_amd64.zip
[linux_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_arm64.zip
[darwin_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_amd64.zip
[darwin_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_arm64.zip
[windows_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_amd64.zip
[windows_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_arm64.zip
## Запуск релизного образа
Релизные docker-образы строятся для платформы `linux` и архитектуры `amd64`.
Найти их можно здесь: <https://git.axenov.dev/IPTV/-/packages/container/iptvc>
Тег `latest` всегда соответствует последней версии, он подразумевается по умолчанию:
=== "Запуск последней версии"
```shell
docker run \
--pull always \
--name iptvc \
git.axenov.dev/iptv/iptvc \ #(1)!
КОМАНДА [АРГУМЕНТЫ]
```
1. Подразумевается `:latest`, можно указать явно
=== "Запуск другой версии"
```shell
docker run \
--pull always \
--name iptvc \
git.axenov.dev/iptv/iptvc:v1.0.6 \ #(1)!
КОМАНДА [АРГУМЕНТЫ]
```
1. Список версий доступен на [релизной странице][rel_page]
## Использование образа в Docker compose
```yaml title="compose.yml"
services:
#...
iptvc:
container_name: iptvc
image: git.axenov.dev/iptv/iptvc:latest
command: [serve] #(1)!
#...
```
1. Доступные команды и аргументы см. в [**Справочнике команд**](commands/index.md)
+73
View File
@@ -0,0 +1,73 @@
---
title: Введение
icon: octicons/sparkles-fill-16
hide: [toc]
---
# IPTV Checker (iptvc)
Это простая программа для проверки IPTV плейлистов, входящая в состав проекта m3u.su.
Поддерживает проверку как локальных файлов `*.m3u`/`*.m3u8`, так и удалённых плейлистов по прямым ссылкам, с возможностью кеширования результатов и его вывода в различных форматах.
> Программа не предназначена для хранения, воспроизведения или распространения пиратского контента.
<div class="grid cards" markdown>
- :material-flag-checkered: **Быстрый старт**
---
Коротко о главном, если не терпится
[Подробности :material-arrow-right:][start]{ .md-button .md-button--primary }
- :material-application-brackets-outline: **Запуск сайта**
---
Создайте свой агрегатор плейлистов
[Подробности :material-arrow-right:][site]{ .md-button .md-button--primary }
- :octicons-terminal-24: **Работа в терминале**
---
Как обрабатывать плейлисты без GUI
[Подробности :material-arrow-right:][cli]{ .md-button .md-button--primary }
- :material-file-cog: **Конфигурация**
---
Настройте `iptvc` для своих целей
[Подробности :material-arrow-right:][cfg]{ .md-button .md-button--primary }
</div>
[start]: quickstart.md "Перейти к разделу"
[site]: site/index.md "Перейти к разделу"
[cli]: commands/index.md "Перейти к разделу"
[cfg]: config/config.md "Перейти к разделу"
<!--
## :material-cog-sync-outline: Как работает `iptvc`
Принцип её работы очень простой:
1. Получить плейлист по ссылке.
2. Полученный плейлист распарсить:
обрабатывается полученный текст в формате [m3u](../formats/m3u.md), из него вычленется информация о каналах и их группировке.
3. Каждый найденный канал проверить:
получить информацию по ссылке и принять решение — работает ли канал (технически) или нет.
4. Если необходимо, каждому каналу [присвоить теги](../formats/channels.md#доступные-теги) согласно [правилам](../formats/channels.md).
5. Если необходимо, закешировать результаты проверок.
Во время работы программа пишет лог проверки плейлиста и каналов в достаточно компактном человекочитаемом виде, а в конце пишет результаты проверок.
При желании, можно включить более подробный вывод, чтобы тщательно следить за ходом проверки в реальном времени, а также вывод результатов в машиночитаемом виде (в формате json).
Подробности см. в разделе [Команда `check`](../iptvc/commands/check.md).
-->
+85
View File
@@ -0,0 +1,85 @@
---
icon: material/flag-checkered
tags: ["iptvc"]
---
# :material-flag-checkered: Быстрый старт
Для простоты представим, что программа скачана и распакована в любую директорию и вы находитесь в ней.
Используйте тот способ запуска, который выбрали на шаге [установки](install.md).
Ниже представлены лишь частые примеры запуска программы с разными аргументами под разные случаи.
## Проверить плейлист по прямой ссылке
```
./iptvc check -u https://example.com/pls.m3u
./iptvc check --url https://example.com/pls.m3u
```
## Проверить файл плейлиста с диска
```
./iptvc check -f /home/user/pls.m3u
./iptvc check --file /home/user/pls.m3u
```
## Проверить плейлист по короткому коду из [`playlists.ini`](formats/playlists.md)
```
./iptvc check -c X
./iptvc check --code X
```
Файл `playlists.ini` должен лежать рядом с `iptvc`.
Если файл лежит в другой директории, то можно явно указать путь к нему:
```
./iptvc check --ini /home/user/playlists.ini --code X
```
Если ini-файл не будет найден, программа предупредит об этом.
## Присвоить каналам тематические теги
Для этого рядом с `iptvc` должен лежать файл [channels.json](formats/channels.md).
Если файл лежит в другой директории, то можно указать её явно:
```
./iptvc check --tags /home/user/channels.json
```
Если json-файл не будет найден, то программа предупредит о том, что теги не будут присвоены, и продолжит работу.
## Проверить несколько плейлистов одновременно
Для этого можно комбинировать все аргументы, перечисленные выше, с учётом особенностей их работы:
```shell
./iptvc check \
--ini /home/user/p.ini \ #(1)!
--tags /home/user/c.json \ #(2)!
--code Y \ #(3)!
--file /home/user/tv.m3u \ #(4)!
--url https://example.com/pls1.m3u \ #(5)!
-u https://example.com/pls2.m3u #(6)!
```
1. Из этого файла будет взят список плейлистов
2. Из этого файла будут взяты правила для присвоения тегов каналам
3. Из ini-списка будет проверен только плейлист с кодом `Y`
4. Это отдельный файл плейлиста на диске, который будет проверен в дополнение к основному списку
5. Плейлист на каком-то удалённом сервере, который будет загружен и проверен вместе с прошлыми двумя
6. Ещё один по ссылке, просто через короткий вариант аргумента `--url`
Символ `\` нужен только для наглядного разделения аргументов на несколько строк.
Всё это можно писать в одну строку.
Переданные плейлисты будут обработаны в следующем порядке:
1. локальные файлы (`-f|--file`);
2. по ссылкам (`-u|--url`);
3. по кодам из ini-файла (`-i|--ini`, `-c|--code`).
+14
View File
@@ -0,0 +1,14 @@
---
icon: material/television-play
tags: ["плееры", "плейлисты"]
---
# :material-television-play: Как подключить плейлист
1. Найти какой-нибудь [плеер](./players.md)
2. Узнать как в него добавить плейлист по ссылке
3. Найти желаемый плелист из [списка](./list.md)
4. Найти на странице ["Ссылку для ТВ"](details.md#shortlink) и ввести (скопировать) её в поле ввода адреса в плеере
Для некоторых [плееров](./players.md) уже есть информация как добавить плейлист.
+137
View File
@@ -0,0 +1,137 @@
---
icon: material/table-eye
tags: ["сайт", "статусы", "каналы"]
---
# :material-table-eye: Страница плейлиста
Страница содержит подробности об одном конкретном плейлисте.
В её заголовке указано [название плейлиста](../formats/playlists.md#name).
Ниже страница разделена на две части: слева две вкладки с информацией и список каналов справа.
Рассмотрим всё это подробнее.
## Вкладка "Основная информация"
![Вкладка "Основная информация"](../_assets/img/pls-details/tab1.jpg)
На этой вкладке выводится таблица со следующими строками:
* **Код** — короткий уникальный [код плейлиста](../formats/playlists.md#code);
* **Описание** — [описание плейлиста](../formats/playlists.md#desc) (при наличии);
* **Ccылка для ТВ** — короткая ссылка, которую можно использовать для [подключения плейлиста](../common/connect.md), подробнее о ней см. ниже;
* **Источник** — [ссылка на ресурс](../formats/playlists.md#src), где была найдена ссылка на плейлист (при наличии);
* **Наполнение**:
* группы — количество групп, на которые поделены каналы;
* каналы — количества каналов общее, онлайн и оффлайн;
(всё по нулям, если плейлист <span class="badge offline">offline</span>)
* **Возможности** — наличие программы передач и перемотки каналов;
* **M3U** — [прямая ссылка](../formats/playlists.md#pls) на плейлист;
* **Проверка плейлиста** — дата и время последней [проверки](../common/checks.md) плейлиста с помощью [iptvc](../iptvc/index.md);
* **Ошибка проверки** — текст ошибки, которая возникла при проверке
(только если плейлист <span class="badge offline">offline</span>)
Если при проверке плейлиста возникла ошибка, то она будет отображена красным цветом сразу под заголовком:
??? quote "Скриншот страницы с ошибкой"
![Страница с ошибкой проверки плейлиста](../_assets/img/pls-details/error.jpg)
!!! info
Если в тексте ошибки фигурирует слово `Timeout` и плейлист <span class="badge offline">offline</span> — это ерунда.
Скорее всего, при следующей проверке статус позеленеет.
Просто в момент проверки сервер не получил файл плейлиста вовремя, а т. к. долго ждать он не может, поэтому плюнул и пошёл проверять другие.
!!! info "Обрати внимание"
Независимо от статуса плейлиста на сайте, его можно добавить в свой плеер по "Ссылке для ТВ" и проверить самостоятельно.
Проверка плейлиста не влияет на его работоспособность.
## Вкладка "Исходный текст"
![Вкладка "Исходный текст"](../_assets/img/pls-details/tab2.jpg)
Здесь выводится плейлист как он есть.
Над этим текстом — две кнопки:
* зелёная с кодом плейлиста для скачивания файла;
* нажатие на **QR-код** покажет, внезапно, QR-код, в который закодирована "Ссылка для ТВ".
## Список каналов
В заголовке пишется их общее количество.
![Cписок каналов](../_assets/img/pls-details/ch-list.jpg)
В списке всегда отображается не более 100 каналов.
Воспользуйтесь поиском, чтобы найти интересующий.
### Поиск каналов
Количество плейлистов в заголовке над списком учитывает найденные с помощью фильтров каналы.
Под заголовком есть **выпадающий список групп**.
Он отображается только если плейлист поделён на группы.
Справа — **кнопка сброса** для отображения всех каналов.
Под списком групп расположилась **строка поиска**.
Она есть вообще всегда.
Туда можно начать вводить название канала, и по мере ввода список будет сужаться.
Справа от строки поиска есть **кнопки фильтрации каналов по их статусу**.
Справа — **кнопка сброса** для отображения всех каналов.
Под строкой поиска есть [**облако тегов**](../formats/channels.md#доступные-теги).
!!! question inline end "Про теги"
Откуда они там появляются, можешь прочесть [здесь](../common/index.md) и [здесь](../iptvc/index.md).
На любой из них можно нажать, и тогда в списке останутся каналы только с выбранными тегами.
Выбранные теги подсвечиваются серым.
**Сбросить** выбор можно повторным нажатием на каждый, либо кнопкой сброса у строки поиска.
??? quote "Пример фильтрации"
![Скриншот используемого фильтра списка каналов](../_assets/img/pls-details/filter.jpg)
<a id="shortlink"></a>
## Ссылка для ТВ
Она может быть задана в нескольких форматах.
Поясню базовые принципы формирования адреса:
1. необязателен префикс протокола `http://` или `https://` перед доменом
2. обязателен домен `m3u.su`
3. обязателен `/код` плейлиста после домена
4. необязателен суффикс расширения после кода `.m3u` или `.m3u8`
На примере ниже я наглядно покажу все возможные ссылки на один и тот же плейлист с кодом `ru`:
```
https://m3u.su/ru.m3u8
https://m3u.su/ru.m3u
https://m3u.su/ru
http://m3u.su/ru.m3u8
http://m3u.su/ru.m3u
http://m3u.su/ru
m3u.su/ru.m3u8
m3u.su/ru.m3u
m3u.su/ru
```
!!! info ""
Запоминать их не надо.
Главное помнить как они формируются.
По идее, можешь использовать любую сылку из подобных, т. к. технически они отработают одинаково.
А вот твой [плеер](players.md) может не принять какую-то из них.
Так что, если не подойдёт один формат, используй другой — добавь префикс или суффикс.
Префикс плееру требуется чаще всего, потому что он при добавлении плейлиста проверяет — а ссылку ли мне вообще предоставил пользователь?
По наличию суффикса плеер может определить — а прямая ли это ссылка на файл плейлиста?
Технически — нет, непрямая, потому что файла плейлиста у меня на сервере нет физически и сервер должен сделать редирект уже на сам плейлист.
Но благодаря такой обманке плеер его наверняка подгрузит.
Или нет.
+36
View File
@@ -0,0 +1,36 @@
---
icon: fontawesome/solid/list-check
tags: ["сайт", "плейлисты"]
---
# :fontawesome-solid-list-check: Список плейлистов
Это главная страница сайта.
![Скриншот с примером главной страницы на десктопе](../_assets/img/pls-list/pc.jpg)
Наверху отображаются:
* дата последнего изменения файла [playlists.ini](../formats/playlists.md)
* общее количество плейлистов и с разделением по статусам.
Ниже — спиcок плейлистов.
## Из чего состоит список
* **Код** — короткий уникальный [код плейлиста](../formats/playlists.md#code)
* **Информация о плейлисте**
* [статус плейлиста](../common/checks.md#playlists)
* может быть [значок 18+](../common/checks.md#adult)
* [название плейлиста](../formats/playlists.md#name) — ссылка на [страницу плейлиста](../common/details.md)
под ним:
* [иконки возможностей плейлиста](../common/checks.md#extra) (только при статусе <span class="badge online">online</span>)
* [описание плейлиста](../formats/playlists.md#desc) (при наличии)
* [список тегов](../formats/channels.md#доступные-теги), собранный со всех каналов после их проверки (только при статусе <span class="badge online">online</span>)
* ещё одна ссылка на [страницу плейлиста](../common/details.md)
* **Каналов** — фактическое количество каналов в плейлисте (только при статусе <span class="badge online">online</span>) или 0 (при других статусах)
* **Ссылка для ТВ** — [короткая ссылка](details.md#shortlink), которую можно использовать для [подключения плейлиста](../common/connect.md).
В зависимости от ширины экрана, для экономии места может быть скрыто описание с иконками возможностей и короткая ссылка.
![Скриншот с примером главной страницы на смартфоне](../_assets/img/pls-list/mobile.jpg)
+392
View File
@@ -0,0 +1,392 @@
---
title: Запуск своего сайта
icon: material/rocket-launch
tags: ["iptvc", "serve", "сайт"]
---
# :material-rocket-launch: Запуск своего сайта
В этой статье мы шаг за шагом запустим собственный сайт-агрегатор IPTV-плейлистов — от простейшего варианта до полной конфигурации с кешем и тонкой настройкой проверки.
Программа `iptvc` уже должна быть [установлена](../install.md). Все команды ниже выполняются в терминале из директории, где лежит бинарник.
---
## Шаг 1. Запускаем пустой сайт
Минимальный запуск — одна команда:
```bash
./iptvc serve
```
Сайт откроется на `http://localhost:8080`. Это пустая страница: нет ни одного плейлиста, потому что программе пока неоткуда их взять.
Чтобы изменить порт или хост, не трогая файл конфигурации:
```bash
./iptvc serve -p 3000 --host 0.0.0.0
```
Подробнее об этих флагах — в [документации команды `serve`](../commands/serve.md).
---
## Шаг 2. Добавляем плейлисты
Список плейлистов описывается в файле [`playlists.ini`](../formats/playlists.md). Создадим его рядом с `iptvc`:
```ini title="playlists.ini"
[ru]
name = Российские каналы
desc = Основные федеральные каналы
pls = 'https://example.com/ru.m3u'
src = 'https://example.com/ru-playlist'
[movies]
name = Фильмы
pls = 'https://example.com/movies.m3u'
```
Каждая секция `[code]` — это плейлист. Код используется в коротких ссылках вида `http://localhost:8080/ru`. Параметр `pls` обязателен, остальные — по желанию.
Теперь запустим:
```bash
./iptvc serve
```
Сайт покажет оба плейлиста, но их статус — `unknown` (неизвестно). Это нормально: программа знает о них, но ещё не проверяла. Чтобы статусы появились, нужно включить фоновую проверку.
!!! tip "Путь к ini-файлу"
Если файл лежит не рядом с программой, укажите путь через флаг [`-i`](../commands/serve.md#ini) или в [`config.yml`](../config/config.md) → `app.playlists`.
---
## Шаг 3. Включаем фоновую проверку
Без проверки сайт просто показывает список. Чтобы плейлисты и каналы проверялись автоматически, добавим флаг `--check`:
```bash
./iptvc serve --check
```
Теперь программа в фоне загружает каждый плейлист, парсит каналы и проверяет их доступность. Результаты сразу попадают в оперативную память и отображаются на сайте.
Можно настроить интервал между циклами проверки:
```bash
# проверять каждые 120 секунд, бесконечно
./iptvc serve --check --every 120
# проверить один раз и остановить
./iptvc serve --check --repeat 1
```
Если не хочется каждый раз писать `--check`, можно включить проверку через [`config.yml`](../config/config.md):
```yaml title="config.yml"
check:
start-on-serve: true
```
Тогда обычный `./iptvc serve` автоматически запустит фоновую проверку.
---
## Шаг 4. Настраиваем внешний вид сайта
Сайт можно настроить под себя: заголовок, иконку, навигацию в шапке и ссылки в подвале. Всё это — в секции [`site`](../config/config.md#секция-site) файла `config.yml`.
```yaml title="config.yml"
site:
base-url: http://localhost:8080
repo-url: https://git.axenov.dev/IPTV
page-size: 20 # пагинация по 20 плейлистов на страницу (0 — без пагинации)
favicon: /favicon.ico # путь к иконке
header:
title: Мой IPTV # заголовок в шапке и вкладке браузера
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/my_channel
icon: megaphone-outline
- title: Чат
url: https://t.me/my_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: Мой сайт
url: https://example.com
icon: person-outline
```
--8<-- "ionicons-name.md"
!!! note "base-url"
Параметр `base-url` используется для формирования внутренних ссылок. Если публикуете сайт на домене, укажите его здесь, например `https://my-iptv.ru`.
---
## Шаг 5. Добавляем теги каналам
Теги помогают посетителям находить каналы по темам: спорт, фильмы, музыка и так далее. Правила описываются в файле [`channels.json`](../formats/channels.md).
```json title="channels.json"
[
{
"tvg-id": "^ru-",
"tags": ["russian"]
},
{
"title": "спорт",
"tags": ["sport"]
},
{
"title": "кино|фильм",
"tags": ["film"]
}
]
```
Путь к файлу указывается в [`config.yml`](../config/config.md) → `app.tags` или через флаг [`-t`](../commands/serve.md#tags):
```bash
./iptvc serve --check -t /path/to/channels.json
```
Полный список доступных тегов — в [справочнике по channels.json](../formats/channels.md#доступные-теги).
---
## Шаг 6. Подключаем кеш (KeyDB/Redis)
По умолчанию результаты проверки хранятся только в оперативной памяти. Если программу перезапустить — все результаты пропадут, и плейлисты снова станут `unknown` до следующей проверки.
Кеш решает эту проблему: результаты сохраняются в KeyDB (или Redis) и переживают перезапуск. Включается одной строкой в [`config.yml`](../config/config.md#секция-cache):
```yaml title="config.yml"
cache:
enabled: true
host: localhost
port: 6379
ttl: 1800 # секунды (30 минут)
```
Или через переменные окружения:
```bash
CACHE_ENABLED=true CACHE_TTL=3600 ./iptvc serve --check
```
Или через флаги:
```bash
./iptvc serve --check --cache-enabled --cache-host 192.168.1.10 --cache-ttl 3600
```
!!! tip "KeyDB или Redis"
KeyDB — это форк Redis, полностью совместимый по протоколу. Подойдёт любой из них. Если кеш включён, но сервер недоступен — сайт продолжит работать, просто без кеширования.
---
## Шаг 7. Тонкая настройка проверки
Когда плейлистов много, полезно управлять параллелизмом, таймаутами и задержками. Все параметры — в секции [`check`](../config/config.md#секция-check) файла `config.yml`.
### Параллелизм
```yaml title="config.yml"
check:
playlists:
max-routines: 10 # сколько плейлистов проверять одновременно
per-routine: 5 # сколько плейлистов в одной процедуре
channels:
max-routines: 100 # сколько каналов проверять одновременно
per-routine: 20 # сколько каналов в одной процедуре
```
Чем больше значения — тем быстрее проверка, но выше нагрузка на процессор и сеть. Начните со значений по умолчанию и увеличивайте при необходимости.
### Таймауты
```yaml title="config.yml"
check:
playlists:
timeout: 10000 # мс на загрузку плейлиста
channels:
timeout: 10000 # мс на проверку одного канала
byte-range: 512 # сколько байт скачать от сервера канала
```
Если плейлисты или каналы медленные, увеличьте `timeout`. Если сервер блокирует большие запросы — уменьшите `byte-range`.
### Задержки (cooldown)
Чтобы не перегружать серверы-источники, между проверками можно делать паузы. Параметры поддерживают как фиксированное значение, так и диапазон `[min, max]` — тогда пауза будет случайной при каждом проходе:
```yaml title="config.yml"
check:
playlists:
all-cooldown: 5000 # 5 секунд после всех плейлистов
one-cooldown: [1000, 3000] # 1–3 секунды после каждого плейлиста
channels:
cooldown: [100, 500] # 100–500 мс после каждого канала
```
### User-Agent
Некоторые серверы блокируют запросы без правильного User-Agent. Можно указать один или несколько — тогда при каждом запросе будет выбран случайный:
```yaml title="config.yml"
check:
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
- Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
channels:
user-agent: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
```
Все эти параметры можно также задавать через [переменные окружения](../config/env.md) или [CLI-флаги](../commands/serve.md) — они имеют наивысший приоритет.
---
## Полный пример
Соберём всё вместе в одном `config.yml`:
```yaml title="config.yml"
app:
timezone: GMT+3
debug: false
log_level: info
playlists: ./playlists.ini
tags: ./channels.json
server:
host: 0.0.0.0
port: 8080
site:
base-url: https://my-iptv.ru
repo-url: https://git.axenov.dev/IPTV
page-size: 20
favicon: /favicon.ico
header:
title: Мой IPTV
navigation:
- title: Статус
url: https://status.my-iptv.ru
icon: pulse-outline
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/my_channel
icon: megaphone-outline
- title: Чат
url: https://t.me/my_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: Контакты
url: https://example.com
icon: person-outline
check:
start-on-serve: true
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
- Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
timeout: 10000
all-cooldown: 5000
one-cooldown: [1000, 3000]
max-routines: 10
per-routine: 5
channels:
user-agent: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
timeout: 10000
byte-range: 512
cooldown: [100, 500]
max-routines: 100
per-routine: 20
cache:
enabled: true
host: localhost
port: 6379
ttl: 3600
```
Запуск:
```bash
./iptvc serve
```
Поскольку `start-on-serve: true`, фоновая проверка запустится автоматически. Кеш включён, так что результаты переживут перезапуск. Сайт доступен на `http://0.0.0.0:8080`.
---
## Docker
Удобно запускать сайт в контейнере. Образ `iptvc` уже включает бинарник:
```yaml title="compose.yml"
services:
iptvc:
image: git.axenov.dev/iptv/iptvc:latest
command: [serve]
ports:
- "8080:8080"
volumes:
- ./config.yml:/app/config.yml:ro
- ./playlists.ini:/app/playlists.ini:ro
- ./channels.json:/app/channels.json:ro
environment:
- CHECK_START_ON_SERVE=true
- CACHE_ENABLED=true
- CACHE_HOST=keydb
- CACHE_PORT=6379
keydb:
image: eqalpha/keydb:latest
restart: unless-stopped
```
```bash
docker compose up -d
```
Подробнее об установке образа — в [документации по установке](../install.md).
---
## Краткая шпаргалка
| Задача | Как |
| --- | --- |
| Запустить сайт | `./iptvc serve` |
| С проверкой плейлистов | `./iptvc serve --check` |
| На другом порту | `./iptvc serve -p 3000` |
| С ini-файлом из другого места | `./iptvc serve -i /path/to/playlists.ini` |
| С кешем | `./iptvc serve --check --cache-enabled` |
| Один цикл проверки | `./iptvc serve --check --repeat 1` |
| Проверять каждые 2 минуты | `./iptvc serve --check --every 120` |
| Подробные логи | `./iptvc serve --check --verbose` |
Полный список параметров — в [справочнике по `config.yml`](../config/config.md), [переменным окружения](../config/env.md) и [команде `serve`](../commands/serve.md).