Миграция на zensical, актуализация под iptvc после рефакторинга

This commit is contained in:
2026-07-22 01:11:07 +08:00
parent 7d61aadc5d
commit 8d0f3ebcce
665 changed files with 7626 additions and 922 deletions
+384
View File
@@ -0,0 +1,384 @@
---
title: check
tags: [iptvc]
---
# Команда `check`
Команда поддерживает множество аргументов для разных целей.
Они могут дополнять друг друга.
Порядок аргументов не имеет значения.
## `-i`, `--ini` { id=ini }
Указывает путь к локальному [ini-файлу](../../common/formats/playlists.md) с описанием плейлистов.
Можно указать только однажды.
Значение по умолчанию: `./playlists.ini`
Если файл не найден, проверка плейлистов будет доступна только по ссылкам ([`--url`](#url)) или из локальных файлов ([`--file`](#file)).
```shell title="Пример"
./iptvc check -i ~/my.ini
```
## `-t`, `--tags` { id=tags }
Указывает путь к локальному [json-файлу](../../common/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](../../common/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
```
## `--playlists-all-cooldown` { id=playlists-all-cooldown }
Указывает паузу между полными циклами проверки в секундах. Параметр переопределяет `check.playlists.all-cooldown` из конфигурации.
Пауза применяется после завершения полного цикла и перед началом следующего. Внутри цикла этот параметр не используется: для задержки между плейлистами применяется [`check.playlists.one-cooldown`](#playlists-one-cooldown).
Значение по умолчанию: значение `check.playlists.all-cooldown` из конфигурации, обычно `1800` (30 минут).
```shell title="Пример"
# проверить 5 раз с паузой 5 секунд между циклами
./iptvc check -i ~/my.ini -c xx --code yy --repeat 5 --playlists-all-cooldown 5
# бесконечно проверять все плейлисты из my.ini каждый час
./iptvc check -i ~/my.ini --repeat 0 --playlists-all-cooldown 3600
# бесконечно проверять плейлист из файла с паузой 10 секунд
./iptvc check -f test.m3u --repeat 0 --playlists-all-cooldown 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` (по умолчанию `10`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-timeout 5
```
### `--playlists-all-cooldown` { id=playlists-all-cooldown }
Задержка в секундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `1800`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-all-cooldown 10
```
### `--playlists-one-cooldown` { id=playlists-one-cooldown }
Задержка в секундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `2`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-one-cooldown 2
```
### `--playlists-max-routines` { id=playlists-max-routines }
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `1`).
```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` (по умолчанию `10`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-timeout 5
```
### `--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 1
```
### `--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` (по умолчанию `30`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-ttl 3600
```
+53
View File
@@ -0,0 +1,53 @@
---
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
serve Start web interface
version Show version
Flags:
--config string path to config file (default "config.yml")
--debug enable debug mode (overrides config.yml)
-h, --help help for iptvc
--log-level string log level: debug, info, warn, error (overrides config.yml)
-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 | — | Подробное логирование |
+383
View File
@@ -0,0 +1,383 @@
---
title: serve
tags: [iptvc]
---
# Команда `serve`
Запускает встроенный веб-сервер для просмотра плейлистов и результатов их проверки в браузере.
```bash
iptvc serve [flags]
```
## Веб-сервер
### `-p`, `--port` { id="port" }
Порт для веб-сервера.
Переопределяет `server.port` из `config.yml` и переменную `SERVER_PORT`.
Если не указан, используется значение из `config.yml` (по умолчанию `8800`).
```bash
iptvc serve -p 3000
```
### `--host` { id="host" }
Хост для привязки веб-сервера.
Переопределяет `server.host` из `config.yml` и переменную `SERVER_HOST`.
Если не указан, используется значение из `config.yml` (по умолчанию — все интерфейсы).
```bash
iptvc serve --host 127.0.0.1
```
## Фоновая проверка
### `--check` { id="check" }
Включает фоновую проверку плейлистов. По умолчанию выключена.
Без этого флага веб-сервер работает standalone — отображает данные из кеша (если включён) или статус `unknown` для всех плейлистов.
```bash
iptvc serve --check
```
При `--check` доступны следующие флаги:
### `-i`, `--ini` { id="ini" }
Путь к локальному [ini-файлу](../../common/formats/playlists.md) с описанием плейлистов.
Значение по умолчанию: `./playlists.ini`
```bash
iptvc serve --check -i ~/my.ini
```
### `-t`, `--tags` { id="tags" }
Путь к [json-файлу](../../common/formats/channels.md) с описанием тегов каналов.
Значение по умолчанию: `./channels.json`
```bash
iptvc serve --check -t ~/tags.json
```
### `--playlists-all-cooldown` { id=playlists-all-cooldown }
Пауза между полными циклами фоновой проверки в секундах. Параметр переопределяет `check.playlists.all-cooldown` из конфигурации.
Значение по умолчанию: значение `check.playlists.all-cooldown` из конфигурации, обычно `1800` (30 минут).
```bash
# пауза 2 минуты между циклами
iptvc serve --check --playlists-all-cooldown 120
```
### `--repeat` { id="repeat" }
Количество циклов фоновой проверки.
Значение по умолчанию: `0` (бесконечно)
```bash
# проверить один раз и остановить фоновую проверку
iptvc serve --check --repeat 1
```
### `-r`, `--random` { id="random" }
Максимальное количество случайных плейлистов из ini-файла для проверки.
```bash
iptvc serve --check -r 10
```
## Глобальные флаги
### `--config` { id="config" }
Путь к файлу конфигурации `config.yml`.
Значение по умолчанию: `config.yml`
```bash
iptvc serve --config /etc/iptvc/config.yml
```
### `--debug` { id="debug" }
Включает режим отладки. Переопределяет `app.debug` из `config.yml` и переменную `APP_DEBUG`.
```bash
iptvc serve --debug
```
### `--log-level` { id="log-level" }
Устанавливает уровень логирования. Переопределяет `app.log_level` из `config.yml`.
Доступные значения: `debug`, `info`, `warn`, `error`.
```bash
iptvc serve --log-level debug
```
### `-v`, `--verbose` { id="verbose" }
Включает подробное логирование.
## Флаги проверки плейлистов
Эти флаги переопределяют параметры секции `check.playlists` из `config.yml`. Доступны для команд `check` и `serve`. Имеют смысл только при включённой фоновой проверке (`--check` или `check.start-on-serve: true`).
### `--playlists-timeout` { id="playlists-timeout" }
Таймаут HTTP-запроса плейлиста в секундах.
Переопределяет `check.playlists.timeout` (по умолчанию `10`).
```bash
iptvc serve --check --playlists-timeout 5
```
### `--playlists-all-cooldown` { id="playlists-all-cooldown" }
Задержка в секундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `1800`).
```bash
iptvc serve --check --playlists-all-cooldown 10
```
### `--playlists-one-cooldown` { id="playlists-one-cooldown" }
Задержка в секундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `2`).
```bash
iptvc serve --check --playlists-one-cooldown 2
```
### `--playlists-max-routines` { id="playlists-max-routines" }
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `1`).
```bash
iptvc serve --check --playlists-max-routines 10
```
### `--playlists-per-routine` { id="playlists-per-routine" }
Количество плейлистов на одну процедуру проверки.
Переопределяет `check.playlists.per-routine` (по умолчанию `1`).
```bash
iptvc serve --check --playlists-per-routine 3
```
### `--playlists-user-agent` { id="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`. Имеют смысл только при включённой фоновой проверке.
### `--channels-timeout` { id="channels-timeout" }
Таймаут HTTP-запроса канала в секундах.
Переопределяет `check.channels.timeout` (по умолчанию `10`).
```bash
iptvc serve --check --channels-timeout 8
```
### `--channels-byte-range` { id="channels-byte-range" }
Объём данных в байтах для загрузки от сервера при проверке канала.
Переопределяет `check.channels.byte-range` (по умолчанию `512`).
```bash
iptvc serve --check --channels-byte-range 1024
```
### `--channels-cooldown` { id="channels-cooldown" }
Задержка в секундах после проверки каждого канала.
Переопределяет `check.channels.cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --channels-cooldown 1
```
### `--channels-max-routines` { id="channels-max-routines" }
Максимум одновременно проверяемых каналов.
Переопределяет `check.channels.max-routines` (по умолчанию `50`).
```bash
iptvc serve --check --channels-max-routines 100
```
### `--channels-per-routine` { id="channels-per-routine" }
Количество каналов на одну процедуру проверки.
Переопределяет `check.channels.per-routine` (по умолчанию `10`).
```bash
iptvc serve --check --channels-per-routine 20
```
### `--channels-user-agent` { id="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`.
### `--cache-enabled` { id="cache-enabled" }
Включает кеширование результатов в KeyDB/Redis.
Переопределяет `cache.enabled` (по умолчанию `false`).
```bash
iptvc serve --cache-enabled
```
### `--cache-host` { id="cache-host" }
Хост KeyDB/Redis.
Переопределяет `cache.host` (по умолчанию `localhost`).
```bash
iptvc serve --cache-enabled --cache-host 192.168.1.10
```
### `--cache-port` { id="cache-port" }
Порт KeyDB/Redis.
Переопределяет `cache.port` (по умолчанию `6379`).
```bash
iptvc serve --cache-enabled --cache-port 6380
```
### `--cache-username` { id="cache-username" }
Логин для подключения к KeyDB/Redis.
Переопределяет `cache.username`.
```bash
iptvc serve --cache-enabled --cache-username myuser
```
### `--cache-password` { id="cache-password" }
Пароль для подключения к KeyDB/Redis.
Переопределяет `cache.password`.
```bash
iptvc serve --cache-enabled --cache-password secret
```
### `--cache-db` { id="cache-db" }
Номер базы данных KeyDB/Redis.
Переопределяет `cache.db` (по умолчанию `0`).
```bash
iptvc serve --cache-enabled --cache-db 2
```
### `--cache-ttl` { id="cache-ttl" }
TTL записей кеша в секундах.
Переопределяет `cache.ttl` (по умолчанию `30`).
```bash
iptvc serve --cache-enabled --cache-ttl 3600
```
## Примеры
```bash
# просто веб-сервер без проверки
iptvc serve
# веб-сервер с фоновой проверкой каждые 2 минуты
iptvc serve --check --playlists-all-cooldown 120
# веб-сервер на порту 3000 с проверкой 10 случайных плейлистов
iptvc serve -p 3000 --check -r 10
# один цикл проверки, затем только веб-сервер
iptvc serve --check --repeat 1
# веб-сервер с кешем и фоновой проверкой, увеличенные лимиты параллелизма
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` | Редирект на `/api/` (Swagger UI) |
| GET | `/api/` | Swagger UI с описанием REST API |
| GET | `/api/openapi.json` | OpenAPI-схема в формате JSON |
| GET | `/api/playlists` | JSON: массив всех плейлистов |
| GET | `/api/playlists/{code}` | JSON: информация о плейлисте |
| GET | `/api/playlists/{code}/channels` | JSON: список каналов плейлиста |
| GET | `/api/version` | JSON: версии компонентов |
| GET | `/api/health` | JSON: состояние сервиса |
| GET | `/api/stats` | JSON: статистика по плейлистам и каналам |
+20
View File
@@ -0,0 +1,20 @@
---
title: version
tags: [iptvc]
---
# Команда `version`
Выводит версию программы:
```bash
./iptvc version
```
Пример результата:
```
iptvc v1.1.3
```
Версия также доступна через [API](serve.md) — endpoint `GET /api/version`.
+228
View File
@@ -0,0 +1,228 @@
---
# icon: material/architecture
tags: ["iptvc", "разработка", "архитектура"]
---
# Архитектура iptvc
Внутреннее устройство программы для разработчиков.
## Стек технологий
- **Go 1.23+** — язык программирования;
- `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-шаблоны
│ ├── base.html
│ ├── list.html
│ ├── details.html
│ └── notfound.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 — редирект на /api/
GET /api/ — Swagger UI
GET /api/openapi.json — OpenAPI-схема
GET /api/playlists — JSON: массив плейлистов
GET /api/playlists/{code} — JSON плейлиста
GET /api/playlists/{code}/channels — 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 = бесконечно) |
| `--playlists-all-cooldown` | `Args.PlAllCooldown` | Секунд между циклами |
### Только `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](../../common/config/config.md).
Приоритет: Defaults < `config.yml` < Env < CLI-флаги.
CLI-флаги переопределяют конфигурацию только если переданы явно (`cmd.Flags().Changed()`).
Для этого в Cobra используются zero-value defaults (0, "", false), чтобы отличить «не передан» от «передан со значением по умолчанию».
## Шаблоны
HTML-шаблоны встроены через `//go:embed`:
- `base.html` — общий каркас (header, footer);
- `list.html` — список плейлистов с пагинацией;
- `details.html` — детали плейлиста и список каналов;
- `notfound.html` — страница 404.
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 при отображении в веб-интерфейсе.
+59
View File
@@ -0,0 +1,59 @@
---
title: Компиляция
icon: material/cog
tags: ["iptvc", "сборка"]
---
# Компиляция из исходного кода
Для компиляции потребуется [Go](https://go.dev/dl/) 1.23.6 и выше.
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
make linux
# или make help для получения справки по рецептам
```
## Доступные рецепты Makefile
| Команда | Назначение |
| --- | --- |
| `make linux` | Сборка под Linux (amd64 по умолчанию) |
| `make win` | Сборка под Windows |
| `make darwin` | Сборка под macOS |
| `make release` | Сборка под все платформы (linux/windows/darwin × amd64/arm64) |
| `make clean` | Удаление скомпилированных бинарников |
| `make help` | Вывод списка доступных рецептов |
## Управление архитектурой
Архитектура целевой платформы задаётся переменной `GOARCH`:
```bash
make darwin GOARCH=arm64 # macOS на Apple Silicon
make linux GOARCH=arm64 # Linux ARM (Raspberry Pi и т.п.)
```
## Результат
Скомпилированные бинарники и ZIP-архивы помещаются в директорию `bin/`:
```
bin/
├── linux_amd64/iptvc # или linux_arm64/
├── linux_amd64.zip
├── windows_amd64/iptvc.exe # или windows_arm64/
├── windows_amd64.zip
├── darwin_amd64/iptvc # или darwin_arm64/
└── darwin_amd64.zip
```
## Сборка без Make
Можно скомпилировать напрямую через `go build`:
```bash
go build -o iptvc .
go build -trimpath -ldflags="-s -w" -o iptvc .
```
+58
View File
@@ -0,0 +1,58 @@
---
title: Docker-образ
icon: simple/docker
tags: ["iptvc", "docker"]
---
# Построение Docker-образа
## Сборка
Образ собирается из [Dockerfile](https://git.axenov.dev/IPTV/iptvc/src/branch/master/Dockerfile) — multi-stage сборка на базе `golang:1.25-alpine` с финальным образом `alpine:3.22`.
```bash
docker build -t iptvc ./iptvc
```
Можно передать версию через build-arg:
```bash
docker build --build-arg IPTVC_VERSION=1.1.3 -t iptvc:v1.1.3 ./iptvc
```
Целевая платформа и архитектура задаются через `GOOS` и `GOARCH`:
```bash
docker build --build-arg GOOS=linux --build-arg GOARCH=arm64 -t iptvc:linux-arm64 ./iptvc
```
## Запуск
```bash
# Проверка плейлистов
docker run --rm \
-v ./playlists.ini:/app/playlists.ini \
-v ./channels.json:/app/channels.json \
iptvc check -i playlists.ini --repeat 0 --playlists-all-cooldown 60
# Веб-сервер с фоновой проверкой
docker run --rm -p 8800:8800 \
-v ./playlists.ini:/app/playlists.ini \
-v ./channels.json:/app/channels.json \
iptvc serve -i playlists.ini -p 8800 --check
```
## Использование в compose
В `compose.yml` сервиса `iptvc` образ собирается автоматически:
```yaml
iptvc:
image: git.axenov.dev/iptv/iptvc:latest
build:
context: ./iptvc
dockerfile: Dockerfile
command: ["serve", "--check", "--repeat", "0"]
```
Подробнее о полном окружении — в разделе [Развёртывание](../site/deploy.md).
+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**, но не следует визуально перегружать текст.
Документацию по ним см. по ссылкам ниже.
## Добавление изображений
**Все изображения хранятся в директории `_assets/` рядом с документом.**
Общая суть такова:
* чем больше размеры, тем хуже должно быть качество;
* чем меньше размеры, тем чётче должен быть текст.
Каждое изображение должно:
* быть сохранено в формате jpg;
* иметь размер неболее 150 Кб;
* быть сжатым с качеством 65-80% от исходного;
* быть размером до 1500 px по наибольшей стороне.
Если на изображении есть текст, он должен оставаться различимым и читаемым.
Но если на изображении есть любые конфиденциальные данные и его невозможно кадрировать без потери смысла, то их необходимо скрыть.
Хорошей практикой будет использовать спойлеры для скрытия больших и/или идущих подряд нескольких изображений, например:
```
Совершенно любой текст, lorem ipsum dolor sit amet.
Совершенно любой текст, lorem ipsum dolor sit amet.
??? quote "В этом спойлере несколько больших картинок"
![Подпись-плейсхолдер1](_assets/example1.jpg)
![Подпись-плейсхолдер2](_assets/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 @@
Информацию о лицензии см. на странице [Свободное ПО](../../../legal/license.md)
| Иконка | Код для вставки в текст | Код для вставки в 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` |
+306
View File
@@ -0,0 +1,306 @@
# Компоненты
Здесь показаны примеры компонентов, которые можно внедрять в документацию.
Для синтаксиса обращайся в [документацию Zensical][1] и репозиторий [этой документации][2].
[1]: https://zensical.org/docs
[2]: https://git.axenov.dev/IPTV/docs
## Мелочёвка
=== "Бейджи"
Это кастомный функционал, в основе которого лежит `inline_badges.py` в корне репозитория.
<!-- md:env SOME_VAR -->
<!-- md:arg --arg -->
<!-- md:config server.host -->
<!-- md:version 1.2.3 -->
<!-- md:default default_value -->
<!-- md:beta -->
=== "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
```
=== "Тултипы"
: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 "Пустое содержимое"
=== "Кастомные"
??? image "Врезка для скриншота"
Тут должна быть картинка
!!! boosty "Врезка Boosty"
Тут должна быть релевантная информация
!!! yoomoney "Врезка Yoomoney"
Тут должна быть релевантная информация
---
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
---
icon: material/application-brackets-outline
tags: ["iptvc", "разработка"]
---
# :material-application-brackets-outline: Среда разработки
## Быстрый старт
Для локальной разработки `iptvc` достаточно установить [Go 1.23.6+][1] и клонировать репозиторий:
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
go run . serve -i playlists.ini -p 8800 --check
```
Веб-интерфейс будет доступен по адресу <http://localhost:8800>.
## Требования
| ПО | Версия | Назначение |
| ----------- | --------------------- | ------------------------------------- |
| [Go][1] | 1.23.6+ | Компиляция и запуск |
| [Make][2] | любая | Сборка через `Makefile` (опционально) |
| [Docker][3] | с `docker compose` v2 | Полное окружение (опционально) |
[1]: https://go.dev/dl/
[2]: https://www.gnu.org/software/make
[3]: https://docs.docker.com/engine/install
## Команды для разработки
```bash
# Запуск веб-сервера с фоновой проверкой
go run . serve -i playlists.ini -p 8800 --check
# Запуск проверки без веб-сервера
go run . check -i playlists.ini --repeat 1
# Сборка бинарника
go build -o iptvc .
# Проверка кода
go vet ./...
# Сборка под все платформы
make release
```
## Файлы конфигурации
Для работы `iptvc` нужны два файла рядом с бинарником:
- `playlists.ini` — список плейлистов. Формат описан в [справочнике форматов](../../common/formats/playlists.md).
- `channels.json` — правила тегов каналов. Формат описан в [справочнике форматов](../../common/formats/channels.md).
- `config.yml` — конфигурация программы. Описание — в [разделе config.yml](../../common/config/config.md).
## Полное Docker-окружение
Полная инфраструктура проекта (nginx, KeyDB, checker, docs) развёртывается через Docker.
Подробнее — в разделе [Развёртывание](../site/deploy.md).
## Дополнительные материалы
- [Компиляция из исходного кода](compile.md)
- [Построение Docker-образа](docker.md)
- [Архитектура iptvc](arch.md)
- [Развёртывание и доставка обновлений](../site/deploy.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)
+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.1.3 \ #(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/first-steps.md "Перейти к разделу"
[cli]: commands/index.md "Перейти к разделу"
[cfg]: ../common/config/config.md "Перейти к разделу"
<!--
## :material-cog-sync-outline: Как работает `iptvc`
Принцип её работы очень простой:
1. Получить плейлист по ссылке.
2. Полученный плейлист распарсить:
обрабатывается полученный текст в формате [m3u](../common/formats/m3u.md), из него вычленется информация о каналах и их группировке.
3. Каждый найденный канал проверить:
получить информацию по ссылке и принять решение — работает ли канал (технически) или нет.
4. Если необходимо, каждому каналу [присвоить теги](../common/formats/channels.md#доступные-теги) согласно [правилам](../common/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`](../common/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](../common/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`).
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

+14
View File
@@ -0,0 +1,14 @@
---
icon: material/television-play
tags: ["плееры", "плейлисты"]
---
# :material-television-play: Как подключить плейлист
1. Найти какой-нибудь [плеер](../../common/players.md)
2. Узнать как в него добавить плейлист по ссылке
3. Найти желаемый плелист из [списка](./list.md)
4. Найти на странице ["Ссылку для ТВ"](details.md#shortlink) и ввести (скопировать) её в поле ввода адреса в плеере
Для некоторых [плееров](../../common/players.md) уже есть информация как добавить плейлист.
+191
View File
@@ -0,0 +1,191 @@
---
icon: material/upload-network
tags: ["iptvc", "docker", "deploy"]
---
# :material-upload-network: Развёртывание и доставка обновлений
В этом разделе описан фактический порядок настройки окружения `iptv` с приложением `iptvc`, хранилищем KeyDB и сайтом документации.
## Требования { id="requirements" }
- [Docker](https://docs.docker.com/engine/install/) с плагином `docker compose` версии 2
- Git и доступ к репозиторию `git.axenov.dev/IPTV`
- Права на запись в каталог данных KeyDB
- Доступ к registry `git.axenov.dev`, если вы публикуете образ `iptvc`
!!! warning "Только docker compose v2"
Используйте команду `docker compose`.
Устаревшая команда `docker-compose` в этом сценарии не поддерживается.
## Подготовка репозитория { id="repository" }
Клонируйте основной репозиторий и перейдите в его каталог:
```bash
git clone https://git.axenov.dev/IPTV.git
cd IPTV
```
В корне проекта должны находиться `compose.yml`, `.env`, `config.yml`, `playlists.ini`, `channels.json` и каталог `docker/keydb`.
Исходный код приложения располагается в `iptvc/`, а документации — в `docs/`.
## Настройка файлов { id="configuration" }
Создайте файлы локальной конфигурации на основе примеров:
```bash
cp .env.example .env
cp iptvc/config.yml.example config.yml
```
Отредактируйте `config.yml`.
В контейнере `iptvc` он подключается как `/app/config.yml`.
Минимально проверьте следующие параметры:
- `app.playlists` — путь к файлу плейлистов;
- `app.tags` — путь к файлу тегов каналов;
- `server.host` и `server.port` — адрес и порт веб-интерфейса;
- `cache.enabled` и параметры `cache` — использование KeyDB;
- `site.base-url` — внешний адрес приложения.
Скопируйте входные данные плейлистов в корень окружения:
```bash
cp /path/to/playlists.ini ./playlists.ini
cp /path/to/channels.json ./channels.json
```
Эти файлы монтируются в контейнер как `/app/playlists.ini` и `/app/channels.json`.
В `.env` задаются параметры приложения из `iptvc/.env.example`, включая `SITE_BASE_URL`, `SERVER_PORT`, `CACHE_ENABLED`, `CACHE_HOST`, `CACHE_PORT`, `CACHE_DB` и `CACHE_TTL`.
Для подключения к KeyDB из контейнера укажите имя сервиса `keydb` в `CACHE_HOST`, а не `localhost`.
## Состав окружения { id="services" }
Файл `compose.yml` запускает три сервиса:
| Сервис | Образ | Назначение |
| --- | --- | --- |
| `iptvc` | `git.axenov.dev/iptv/iptvc:latest` | Веб-интерфейс и фоновая проверка плейлистов |
| `keydb` | `eqalpha/keydb:latest` | Кеш результатов проверки |
| `docs` | `git.axenov.dev/iptv/iptv-docs:latest` | Сайт документации |
Сервис `iptvc` публикует порт `8800`, а `docs` — порт `8801`.
KeyDB публикует порт, заданный `KEYDB_PORT`, по умолчанию `6379`.
Сервис `iptvc` зависит от `keydb` и запускается с командой `serve --check --repeat 0`.
Это поднимает веб-сервер и запускает бесконечную фоновую проверку.
## Запуск { id="start" }
После подготовки файлов соберите и запустите окружение из корня проекта:
```bash
docker compose up -d --build
```
Проверьте состояние контейнеров и журналы:
```bash
docker compose ps
docker compose logs -f iptvc
```
Веб-интерфейс приложения доступен на `http://localhost:8800`.
Документация доступна на `http://localhost:8801`.
Для остановки окружения выполните:
```bash
docker compose down
```
## Сборка образа iptvc { id="image" }
Для публикации образа используйте скрипт `iptvc/build-docker-image.sh`.
Скрипт вычисляет версию из Git, собирает образы с тегами `iptvc:<тег>` и `git.axenov.dev/iptv/iptvc:<тег>`, а затем отправляет registry-тег в registry:
```bash
cd iptvc
./build-docker-image.sh latest
```
Для публикации релизного тега:
```bash
./build-docker-image.sh v1.2.3
```
По умолчанию скрипт собирает Linux-образ для `amd64`.
Платформу можно изменить переменными окружения:
```bash
GOOS=linux GOARCH=arm64 ./build-docker-image.sh v1.2.3
```
Перед публикацией войдите в registry, если это требуется вашей настройкой:
```bash
docker login git.axenov.dev
```
!!! warning "Рабочее дерево Git"
Скрипт переключается на Git-тег, переданный первым аргументом, или на тег, возвращённый `git describe`.
Перед запуском сохраните локальные изменения и убедитесь, что нужный тег существует.
## Обновление { id="update" }
После изменения конфигурации или исходного кода пересоберите и перезапустите сервисы:
```bash
docker compose up -d --build
```
Чтобы пересобрать только приложение `iptvc`:
```bash
docker compose build iptvc
docker compose up -d iptvc
```
Чтобы использовать опубликованный образ вместо локальной сборки, загрузите его и пересоздайте сервис:
```bash
docker compose pull iptvc
docker compose up -d iptvc
```
Обновление документации выполняется пересборкой сервиса `docs`:
```bash
docker compose build docs
docker compose up -d docs
```
## Диагностика { id="diagnostics" }
Для просмотра журналов отдельных сервисов используйте:
```bash
docker compose logs -f iptvc
docker compose logs -f keydb
docker compose logs -f docs
```
Для проверки конфигурации Compose выполните:
```bash
docker compose config
```
Если `iptvc` не подключается к кешу, проверьте, что в `.env` параметр `CACHE_HOST` имеет значение `keydb`, а сервис `keydb` запущен.
+136
View File
@@ -0,0 +1,136 @@
---
icon: material/table-eye
tags: ["сайт", "статусы", "каналы"]
---
# :material-table-eye: Страница плейлиста
Страница содержит подробности об одном конкретном плейлисте.
В её заголовке указано [название плейлиста](../../common/formats/playlists.md#name).
Ниже страница разделена на две части: слева две вкладки с информацией и список каналов справа.
Рассмотрим всё это подробнее.
## Вкладка "Основная информация"
![Вкладка "Основная информация"](_assets/pls-details/tab1.jpg)
На этой вкладке выводится таблица со следующими строками:
* **Код** — короткий уникальный [код плейлиста](../../common/formats/playlists.md#code);
* **Описание** — [описание плейлиста](../../common/formats/playlists.md#desc) (при наличии);
* **Ccылка для ТВ** — короткая ссылка, которую можно использовать для [подключения плейлиста](connect.md), подробнее о ней см. ниже;
* **Источник** — [ссылка на ресурс](../../common/formats/playlists.md#src), где была найдена ссылка на плейлист (при наличии);
* **Наполнение**:
* группы — количество групп, на которые поделены каналы;
* каналы — количества каналов общее, онлайн и оффлайн;
(всё по нулям, если плейлист <span class="badge offline">offline</span>)
* **Возможности** — наличие программы передач и перемотки каналов;
* **M3U** — [прямая ссылка](../../common/formats/playlists.md#pls) на плейлист;
* **Проверка плейлиста** — дата и время последней [проверки](../../aggregator/checks.md) плейлиста с помощью [iptvc](../overview.md);
* **Ошибка проверки** — текст ошибки, которая возникла при проверке
(только если плейлист <span class="badge offline">offline</span>)
Если при проверке плейлиста возникла ошибка, то она будет отображена красным цветом сразу под заголовком:
??? quote "Скриншот страницы с ошибкой"
![Страница с ошибкой проверки плейлиста](_assets/pls-details/error.jpg)
!!! info
Если в тексте ошибки фигурирует слово `Timeout` и плейлист <span class="badge offline">offline</span> — это ерунда.
Скорее всего, при следующей проверке статус позеленеет.
Просто в момент проверки сервер не получил файл плейлиста вовремя, а т. к. долго ждать он не может, поэтому плюнул и пошёл проверять другие.
!!! info "Обрати внимание"
Независимо от статуса плейлиста на сайте, его можно добавить в свой плеер по "Ссылке для ТВ" и проверить самостоятельно.
Проверка плейлиста не влияет на его работоспособность.
## Вкладка "Исходный текст"
![Вкладка "Исходный текст"](_assets/pls-details/tab2.jpg)
Здесь выводится плейлист как он есть.
Над этим текстом — две кнопки:
* зелёная с кодом плейлиста для скачивания файла;
* нажатие на **QR-код** покажет, внезапно, QR-код, в который закодирована "Ссылка для ТВ".
## Список каналов
В заголовке пишется их общее количество.
![Cписок каналов](_assets/pls-details/ch-list.jpg)
В списке всегда отображается не более 100 каналов.
Воспользуйтесь поиском, чтобы найти интересующий.
### Поиск каналов
Количество плейлистов в заголовке над списком учитывает найденные с помощью фильтров каналы.
Под заголовком есть **выпадающий список групп**.
Он отображается только если плейлист поделён на группы.
Справа — **кнопка сброса** для отображения всех каналов.
Под списком групп расположилась **строка поиска**.
Она есть вообще всегда.
Туда можно начать вводить название канала, и по мере ввода список будет сужаться.
Справа от строки поиска есть **кнопки фильтрации каналов по их статусу**.
Справа — **кнопка сброса** для отображения всех каналов.
Под строкой поиска есть [**облако тегов**](../../common/formats/channels.md#доступные-теги).
!!! question inline end "Про теги"
Откуда они там появляются, можешь прочесть [здесь](../../common/index.md) и [здесь](../overview.md).
На любой из них можно нажать, и тогда в списке останутся каналы только с выбранными тегами.
Выбранные теги подсвечиваются серым.
**Сбросить** выбор можно повторным нажатием на каждый, либо кнопкой сброса у строки поиска.
??? quote "Пример фильтрации"
![Скриншот используемого фильтра списка каналов](_assets/pls-details/filter.jpg)
## Ссылка для ТВ { id="shortlink" }
Она может быть задана в нескольких форматах.
Поясню базовые принципы формирования адреса:
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 "Адрес может быть любым"
Смотря как будет развёрнут сайт, ты можешь заходить на localhost, либо по прямому IP-адресу или доменному имения.
См. раздел [**Развёртывание**](deploy.md) для подробностей.
По идее, можешь использовать любую ссылку из подобных, т. к. технически они отработают одинаково.
А вот твой [плеер](../../common/players.md) может не принять какую-то из них.
Так что, если не подойдёт один формат, используй другой — добавь префикс или суффикс.
Префикс плееру требуется чаще всего, потому что он при добавлении плейлиста проверяет — а ссылку ли мне вообще предоставил пользователь?
По наличию суффикса плеер может определить — а прямая ли это ссылка на файл плейлиста?
Технически — нет, непрямая, потому что файла плейлиста у меня на сервере нет физически и сервер должен сделать редирект уже на сам плейлист.
Но благодаря такой обманке плеер его наверняка подгрузит.
Или нет.
+417
View File
@@ -0,0 +1,417 @@
---
title: Первые шаги
icon: material/rocket-launch
tags: ["iptvc", "serve", "сайт"]
---
# :material-rocket-launch: Первые шаги для запуска сайта
В этой статье мы шаг за шагом запустим собственный сайт-агрегатор IPTV-плейлистов — от простейшего варианта до полной конфигурации с кешем и тонкой настройкой проверки.
Программа `iptvc` уже должна быть [установлена](../install.md).
Все команды ниже выполняются в терминале из директории, где лежит бинарник.
---
## Шаг 1. Запускаем пустой сайт
Минимальный запуск — одна команда:
```bash
./iptvc serve
```
Сайт откроется на `http://localhost:8800`.
Это пустая страница: нет ни одного плейлиста, потому что программе пока неоткуда их взять.
Чтобы изменить порт или хост, не трогая файл конфигурации:
```bash
./iptvc serve -p 3000 --host 0.0.0.0
```
Подробнее об этих флагах — в [документации команды `serve`](../commands/serve.md).
---
## Шаг 2. Добавляем плейлисты
Список плейлистов описывается в файле [`playlists.ini`](../../common/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:8800/ru`.
Параметр `pls` обязателен, остальные — по желанию.
Теперь запустим:
```bash
./iptvc serve
```
Сайт покажет оба плейлиста, но их статус — `unknown` (неизвестно).
Это нормально: программа знает о них, но ещё не проверяла.
Чтобы статусы появились, нужно включить фоновую проверку.
!!! tip "Путь к ini-файлу"
Если файл лежит не рядом с программой, укажите путь через флаг [`-i`](../commands/serve.md#ini) или в [`config.yml`](../../common/config/config.md) → `app.playlists`.
---
## Шаг 3. Включаем фоновую проверку
Без проверки сайт просто показывает список.
Чтобы плейлисты и каналы проверялись автоматически, добавим флаг `--check`:
```bash
./iptvc serve --check
```
Теперь программа в фоне загружает каждый плейлист, парсит каналы и проверяет их доступность.
Результаты сразу попадают в оперативную память и отображаются на сайте.
Можно настроить интервал между циклами проверки:
```bash
# пауза 120 секунд между циклами, бесконечно
./iptvc serve --check --playlists-all-cooldown 120
# проверить один раз и остановить
./iptvc serve --check --repeat 1
```
Если не хочется каждый раз писать `--check`, можно включить проверку через [`config.yml`](../../common/config/config.md):
```yaml title="config.yml"
check:
start-on-serve: true
```
Тогда обычный `./iptvc serve` автоматически запустит фоновую проверку.
---
## Шаг 4. Настраиваем внешний вид сайта
Сайт можно настроить под себя: заголовок, иконку, навигацию в шапке и ссылки в подвале.
Всё это — в секции [`site`](../../common/config/config.md) файла `config.yml`.
```yaml title="config.yml"
site:
base-url: http://localhost:8800
repo-url: https://git.axenov.dev/IPTV
page-size: 20 # пагинация по 20 плейлистов на страницу (0 — без пагинации)
favicon: /favicon.ico # путь к иконке
header:
title: Мой IPTV # заголовок в шапке и вкладке браузера
menu:
- 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`](../../common/formats/channels.md).
```json title="channels.json"
[
{
"tvg-id": "^ru-",
"tags": ["russian"]
},
{
"title": "спорт",
"tags": ["sport"]
},
{
"title": "кино|фильм",
"tags": ["film"]
}
]
```
Путь к файлу указывается в [`config.yml`](../../common/config/config.md) → `app.tags` или через флаг [`-t`](../commands/serve.md#tags):
```bash
./iptvc serve --check -t /path/to/channels.json
```
Полный список доступных тегов — в [справочнике по channels.json](../../common/formats/channels.md#доступные-теги).
---
## Шаг 6. Подключаем кеш (KeyDB/Redis)
По умолчанию результаты проверки хранятся только в оперативной памяти.
Если программу перезапустить — все результаты пропадут, и плейлисты снова станут `unknown` до следующей проверки.
Кеш решает эту проблему: результаты сохраняются в KeyDB (или Redis) и переживают перезапуск.
Включается одной строкой в [`config.yml`](../../common/config/config.md):
```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`](../../common/config/config.md) файла `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: 10 # секунд на загрузку плейлиста
channels:
timeout: 10 # секунд на проверку одного канала
byte-range: 512 # сколько байт скачать от сервера канала
```
Если плейлисты или каналы медленные, увеличьте `timeout`.
Если сервер блокирует большие запросы — уменьшите `byte-range`.
### Задержки (cooldown)
Чтобы не перегружать серверы-источники, между проверками можно делать паузы.
Параметры поддерживают как фиксированное значение, так и диапазон `[min, max]` — тогда пауза будет случайной при каждом проходе:
```yaml title="config.yml"
check:
playlists:
all-cooldown: 5 # 5 секунд после всех плейлистов
one-cooldown: [1, 3] # 1–3 секунды после каждого плейлиста
channels:
cooldown: 0 # 0 секунд после каждого канала
```
### 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
```
Все эти параметры можно также задавать через [переменные окружения](../../common/config/config.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: 8800
site:
base-url: https://my-iptv.ru
repo-url: https://git.axenov.dev/IPTV
page-size: 20
favicon: /favicon.ico
header:
title: Мой IPTV
menu:
- 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: 10
all-cooldown: 5
one-cooldown: [1, 3]
max-routines: 10
per-routine: 5
channels:
user-agent: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
timeout: 10
byte-range: 512
cooldown: 0
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:8800`.
---
## Docker
Удобно запускать сайт в контейнере. Образ `iptvc` уже включает бинарник:
```yaml title="compose.yml"
services:
iptvc:
image: git.axenov.dev/iptv/iptvc:latest
command: [serve]
ports:
- "8800:8800"
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 --playlists-all-cooldown 120` |
| Подробные логи | `./iptvc serve --check --verbose` |
Полный список параметров — в [справочнике по `config.yml`](../../common/config/config.md), [переменным окружения](../../common/config/config.md) и [команде `serve`](../commands/serve.md).
+16
View File
@@ -0,0 +1,16 @@
---
icon: material/cogs
tags: ["плейлисты", "каналы", "теги", "iptvc", "плееры", "сайт"]
---
# :material-cogs: Как работает сервис
1. В специальном файле [playlists.ini](../../common/formats/playlists.md) описываются плейлисты, которые кем-то опубликованы в интернете.
Каждому плейлисту присваивается свой уникальный **короткий код**.
2. В специальном файле [channels.json](../../common/formats/channels.md) описываются **ключевые слова** (метки, теги), которые характеризуют каналы.
3. В фоновом режиме [работает ПО](../overview.md), которое периодически [проверяет все плейлисты](../../aggregator/checks.md) из п. 1 и **присваивает теги** каналам из п. 2.
4. На главной странице сайта выводится [весь список плейлистов](list.md), которые описаны в п. 1: с тегами, описаниями и короткими кодами.
5. Каждому плейлисту на сайте посвящена [своя страничка](details.md), где отображаются результаты его проверки, проверки его каналов (с присвоенными тегами) и пр.
6. Когда пользователь [обращается к плейлисту](connect.md) по короткому коду (например, `https://m3u.su/xyz`), то происходит **переадресация** на исходный плейлист.
Более подробную информацию ты можешь прочесть в соответствующих разделах документации.
+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](../../common/formats/playlists.md)
* общее количество плейлистов и с разделением по статусам.
Ниже — спиcок плейлистов.
## Из чего состоит список
* **Код** — короткий уникальный [код плейлиста](../../common/formats/playlists.md#code)
* **Информация о плейлисте**
* [статус плейлиста](../../aggregator/checks.md#playlists)
* может быть [значок 18+](../../aggregator/checks.md#adult)
* [название плейлиста](../../common/formats/playlists.md#name) — ссылка на [страницу плейлиста](details.md)
под ним:
* [иконки возможностей плейлиста](../../aggregator/checks.md#extra) (только при статусе <span class="badge online">online</span>)
* [описание плейлиста](../../common/formats/playlists.md#desc) (при наличии)
* [список тегов](../../common/formats/channels.md#доступные-теги), собранный со всех каналов после их проверки (только при статусе <span class="badge online">online</span>)
* ещё одна ссылка на [страницу плейлиста](details.md)
* **Каналов** — фактическое количество каналов в плейлисте (только при статусе <span class="badge online">online</span>) или 0 (при других статусах)
* **Ссылка для ТВ** — [короткая ссылка](details.md#shortlink), которую можно использовать для [подключения плейлиста](connect.md).
В зависимости от ширины экрана, для экономии места может быть скрыто описание с иконками возможностей и короткая ссылка.
![Скриншот с примером главной страницы на смартфоне](../_assets/img/pls-list/mobile.jpg)