This commit is contained in:
2026-07-13 12:30:05 +08:00
parent 7d61aadc5d
commit f1849722c8
655 changed files with 4903 additions and 636 deletions
+387
View File
@@ -0,0 +1,387 @@
---
name: koda-zensical
description: Навык, который необходим для работы в этом репозитории и должен закружаться безусловно. Этот навык содержит инструкции, которые позволят правильно писать и форматировать исходные файлы документации.
---
# Документирование
## Принципы написания документации
### Язык и стиль
- Пиши простым и понятным языком
- Избегай просторечий и сложных технических терминов без объяснения
- Используй активный залог
- Обращайся к пользователю на "вы"
- Поддерживай единый стиль во всех документах
- Не злоупотребляй emoji
### Примеры
- Приводи примеры конфигурации
- Показывай скриншоты для важных шагов
- Добавляй таблицы для сравнения опций
### Исправление и избегание ошибок
- Синтаксические и грамматические ошибки должны исправляться в соответствии с правиламии и нормами естественного языка
- При изменении структуры документации:
- в новый документ следует добавить ссылки на другие релевантные документы или якоря
- ссылки на перемещённый или удалённый документ/якорь следует обновить в каждом существующем документе
- следует проверять вывод команды `make site` на наличие ошибок и предупреждений компилятора
## Синтаксис файлов
В основе документации лежит расширенный markdown.
Ниже описаны правила оформления и синтаксиса, которые отличаются от стандартного markdown и github-flavoured markdown.
Расширения синтаксиса предоставляются связкой:
- `zensical` (документация: <https://zensical.org/docs/>)
- `pymdown-extensions` (документация: <https://facelessuser.github.io/pymdown-extensions>)
Ниже только необходимые и достаточные правила:
- для качественной документации;
- хорошего человеческого восприятия;
- корректного формирования документации без ошибок.
Применение этих подходов необязательно, но требования к каждому требуется соблюдать строго.
Если приведены ссылки на документацию, можешь использовать их для получения актуальной информации.
Там так же могут быть описаные дополнительные приёмы для работы с документами.
### Frontmatter
Каждый документ должен начинаться с этого блока метаданных.
После frontmatter должна быть пустая строка.
Внутри должен быть валидный yaml.
Часто используются следующие опциональные параметры (* — желательны):
- *`title` — укорочечнное название документа для отображения в навигации (умолчание — заголовок 1 уровня)
- *`description` — небольшое осмысленное описание документа (умолчание — пусто)
- *`icon` — код иконки для отображения в навигации рядом с названием (умолчание — пусто)
- *`tags` — массив ключевых слов (тегов), описывающих документ (умолчание — пусто)
- `hide` — массив кодов элементов, которые нужно скрыть на странице документа:
- `navigation` — главная навигация (слева)
- `toc` — содержание страницы (справа)
- `path` — хлебные крошки (сверху)
- `status` — статус страницы (добавляет к пункту навигации слева иконку с подсказкой)
- `new` — новая информация
- `deprecated` — устаревшая информация
- `beta` — информация о нестабильном функционале
Параметр `title` не должен быть равен заголовку первого уровня.
В таком случае `title` следует убрать или не добавлять.
### Заголовки
На странице должен быть только один заголовок 1 уровня — сразу после Frontmatter.
До и после каждого заголовка должна быть 1 пустая строка.
В конце строки заголовка должно быть объявление в формате:
`{ id="header-slug" }`
### Абзацы
До и после каждого абзаца должна быть 1 пустая строка.
Каждое предложение внутри абзаца должно быть на новой строке.
### Списки
Вложенные уровни отступаются на 4 пробела слева.
До и после каждого списка должна быть 1 пустая строка.
Ненумерованные списки начинаются с `-`.
### Многострочные блоки кода
До и после каждого блока кода должна быть 1 пустая строка.
Каждый блок кода в заголовке может иметь атрибуты:
- `title="..."` — заголовок блока (например, название файла)
- `hl_lines="..."` — подсветка срок: номера через пробел и/или диапазоны через `-`
- `linenums="N"` — включить нумерацию строк, отсчитывая с указанного числа `N`
В конце строк внутри блока может быть любое число в формате `#(X)!` — это кликальбельные аннотации, содержимое которых будет взято из ближайшего нумерованного списка.
Полный пример:
```yaml title="config.yaml" hl_lines="6 8-10 13 17-20" linenums="1"
context:
- provider: code
# - provider: docs # сломан
- provider: diff #(2)!
- provider: terminal
- provider: problems
- provider: folder
- provider: codebase
params:
nFinal: 10
# - provider: file
# - provider: url
# - provider: search #(1)!
```
1. Подсказка 1
2. **Подсказка** 2
### Врезки
Позволяют акцентировать внимание на ключевых моментах, выделяя блок цветом и иконкой.
Документация: <https://raw.githubusercontent.com/zensical/docs/master/docs/authoring/admonitions.md>
Синтаксис:
```
!!! <тип> "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
??? <тип> "Заголовок разворачиваемой врезки"
Содержимое, которое может быть многострочным
и свёрнуто по умолчанию, но разворачивается по клику на заголовке
???+ <тип> "Заголовок сворачиваемой врезки"
Содержимое, которое может быть многострочным
и развёрнуто по умолчанию, но сворачивается по клику на заголовке
```
Типы, их цвета и пиктограммы:
| Тип | Цвет | Пиктограмма |
| ---------- | ------- | ------------------ |
| `note` | #448aff | карандаш в круге |
| `abstract` | #00b0ff | планшет для бумаги |
| `info` | #00b8d4 | `i` в круге |
| `tip` | #00bfa5 | пламя |
| `success` | #00c853 | галочка |
| `question` | #64dd17 | `?` в круге |
| `warning` | #ff9100 | `!` в треугольнике |
| `failure` | #ff5252 | крестик |
| `danger` | #ff1744 | молния в круге |
| `bug` | #f50057 | жук на щите |
| `example` | #7c4dff | пробирка |
| `quote` | #9e9e9e | двойная кавычка |
Заголовок может быть пустым, в этом случае:
- после типа указываются пустые двойные кавычки (иначе подставится название типа с заглавной буквы на английском языке)
- содержимое внутри блока обрамлён цветом своего типа
Если текста внутри врезки нет, отображается только яркий заголовок с иконкой.
Содержимое врезки отступается минимум на 4 пробела.
Содержимое без отступа (в начале строки) находится вне врезки.
Для содержимого врезки распространяются все те же markdown-правила, включая указанные в этом документе.
До и после каждой врезки должна быть 1 пустая строка.
### Сниппеты
Это переиспользуемые блоки markdown/html, хранящиеся в файлах.
- Директория: `snippets` в корне проекта
- Документация: <https://raw.githubusercontent.com/facelessuser/pymdown-extensions/refs/heads/main/pymdownx/snippets.py>
Как использовать:
1. создать файл с директории
2. наполнить содержимым
3. во всех местах документации вставить:
- пустая строка
- `--8<-- "filename.md"`
- пустая строка
4. если файлов несколько, вставить следующим образом:
- пустая строка
- `--8<--`
- `"filename1.md"`
- пустая строка
- `"filename2.md"`
- `--8<--`
- пустая строка
### Иконки
Каждая иконка определяется своим идентификатором, который делится на две части: код набора и код иконки.
Внутри frontmatter (параметр `icon`) используется формат: `набор/иконка`
В тексте документа используется формат: `:набор-иконка:`
Если иконка в начале строки, пробел ставится только после неё.
Если иконка в середине строки, пробелы ставятся до и после неё.
Если иконка в конце строки, пробел ставятся только до неё.
Доступны 4 встроенных набора иконок:
| Название | Код набора | Ссылка | Путь в проекте |
| --------------- | ------------- | ---------------------------------------------- | --------------------------- |
| Lucide | `lucide` | <https://lucide.dev/icons/> | - |
| Material Design | `material` | <https://pictogrammers.com/library/mdi/> | - |
| FontAwesome | `fontawesome` | <https://fontawesome.com/search> | - |
| Octicons | `octicons` | <https://primer.style/octicons/> | - |
| Simple Icons | `material` | <https://simpleicons.org/> | - |
| VSCode Codicons | `vscode` | <https://github.com/microsoft/vscode-codicons> | `./overrides/.icons/vscode` |
Полный список названий иконок здесь: <https://squidfunk.github.io/mkdocs-material/assets/javascripts/iconsearch_index.json>
Получить полный список иконок в этих наборах можно прочитав содержимое указанных директорий в проекте.
### Гриды (карточки)
Грид позволяет разместить короткие предложения в формате динамических карточек.
Он выглядит как markdown-список, обрамлённый в `<div>`.
До открывающего и после закрывающего тегов должна быть 1 пустая строка.
Пример простого грида с компактными карточками:
```
<div class="grid cards" markdown>
- :fontawesome-brands-html5: Карточка №1
- :fontawesome-brands-js: Карточка №2
- :fontawesome-brands-css3: Карточка №3
- :fontawesome-brands-internet-explorer: Карточка №4
</div>
```
Пример грида с многострочными карточками:
```
<div class="grid cards" markdown>
- :fontawesome-brands-html5: **Заголовок карточки №1**
---
Многострочное содержимое карточки №1
- :fontawesome-brands-js: **Заголовок карточки №2**
---
Многострочное содержимое карточки №2
- :fontawesome-brands-css3: **Заголовок карточки №3**
---
Многострочное содержимое карточки №3
- :fontawesome-brands-internet-explorer: **Заголовок карточки №4**
---
Многострочное содержимое карточки №4
</div>
```
### Вкладки (табы)
Позволяют уместить информацию на одном уровне, не растягивая страницу по высоте.
Синтаксис:
```
=== "Заголовок вкладки 1"
Содержимое вкладки 1
=== "Заголовок вкладки 2"
Содержимое вкладки 2
```
Содержимое вкладки отступается минимум на 4 пробела.
Содержимое без отступа (в начале строки) находится вне вкладки.
Для содержимого вкладки распространяются все те же markdown-правила, включая указанные в этом документе.
До и после каждого заголовка вкладки должна быть 1 пустая строка.
После содержимого последней вкладки должна быть 1 пустая строка.
### Сноски
Сноска позволяет добавить надстрочный индекс к слову, чтобы вынести пояснения в конец страницы, быстро переместиться к нему по клику на индекс и вернуться обратно.
Синтаксис:
```
Lorem[^1] ipsum[^2] dolor sit amet, consectetur adipiscing elit.
[^1]: однострочная сноска
[^2]:
многострочная сноска
с отступом 4 пробела слева
на каждой строке
```
### Подсказки (тултипы) и аббревиатуры
Они появляются при наведении мыши на какой-либо элемент на странице документа.
Пример 1: иконка с подсказкой:
`:material-information-outline:{ title="текст подсказки" }`
Пример 2: ссылка с подсказкой:
`[Hover me](https://example.com "I'm a tooltip!")`
Пример 3: альтернативная ссылка с подсказкой:
```
[Hover me][example]
[example]: https://example.com "I'm a tooltip!"
```
### Горячие клавиши
В общем случае, для указания корячих клавиш следует использовать тег `<kbd>`.
Примеры: `<kbd>B</kbd>`, `<kbd>Esc</kbd>`
Для описания комбинаций клавиш следует вставлять между каждой клавишей знак `+`, обрамлённый пробелами.
Примеры: `<kbd>Shift</kbd> + <kbd>A</kbd>`, `<kbd>Ctrl</kbd> + <kbd>K</kbd> + <kbd>4</kbd>`
Для MacOS-специфичных тем вставлять `+` не нужно.
Примеры: `<kbd>⌘</kbd><kbd>C</kbd>`, `<kbd>⇧</kbd><kbd>⌘</kbd><kbd>P</kbd>`
Сопоставление пиктограмм с названиями клавиш (служебных и модификаторов) MacOS:
- Базовые модификаторы:
- `` - `Command` (`Cmd`)
- `` - `Option` (`Alt`)
- `` - `Control` (`Ctrl`)
- `` - `Shift`
- `` - `Caps Lock`
- Навигация и управление:
- `` - `Delete` (Backspace, удаление символа слева)
- `` - `Forward Delete` (удаление символа справа, `Fn` + D`elete)
- `⏎` - `Return` (`Enter`)
- `⌕` - `Enter` на цифровой клавиатуре (в некоторых шрифтах)
- `⎋` - `Escape` (`Esc`)
- `⇥` - `Tab` (Табуляция)
- `⇤` - `Backtab` (`Shift` + `Tab`)
- `␣` - `Space` (Пробел)
- Перемещение по тексту:
- `↖` - `Home` (Начало документа, `Fn` + `←`)
- `↘` - `End` (Конец документа, `Fn` + `→`)
- `⇞` - `Page Up` (Страница вверх, `Fn` + `↑`)
- `⇟` - `Page Down` (Страница вниз, `Fn` + `↓`)
- Специальные и системные:
- `🌐` / `fn` — Функция (`Fn` / Кнопка смены языка/вызова эмодзи)
- `⏏``Eject` (Извлечение диска)
### Кнопки
- `[Серая кнопка](https://example.com/){ .md-button }`
- `[Синяя кнопка](https://example.com/){ .md-button .md-button--primary }`
- `[:fontawesome-solid-paper-plane: Кнопка серая с иконкой](https://example.com/){ .md-button }`
- `[:fontawesome-solid-paper-plane: Кнопка синяя с иконкой](https://example.com/){ .md-button .md-button--primary }`
+6
View File
@@ -0,0 +1,6 @@
/.git
/.gitea
/.gitignore
/.cache
Makefile
.DS_Store
+18
View File
@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yaml,yml}]
indent_size = 2
[Makefile]
indent_style = tab
+4 -2
View File
@@ -1,2 +1,4 @@
/.cache
/site
/.cache/
/site/
/__pycache__/
.DS_Store
+25
View File
@@ -0,0 +1,25 @@
- Всегда используй навык koda-zensical
- Всегда перечитывай файлы перед каждым изменением - пользователь уже мог внести изменения
- При изменениях структуры проекта всегда актуализируй AGENTS.md
- |
В структуре навигации файла `zensical.toml` всегда должен присутствовать закомментированный раздел `Debug`.
Это отладочные страницы с примерами, которые не должны попасть в итоговую документацию.
Этот раздел раскомментируется вручную по необходимости.
- |
Для отладки используй команду `make site`.
В выводе команды могу быть предупреждения и ошибки в следующем формате:
```
Warning: page does not exist
╭─[ test.md:695:10 ]
695 │ - [Edit](../edit/how-to-use-it.md) — редактирование кода
│ ────────────┬───────────
│ ╰───────────── page does not exist
─────╯
```
что означает буквально следующее: "в файле content/test.md в строке 695 в позиции 10 есть ссылка на несуществующую страницу."
Эти предупреждения надо всегда исправлять.
- При изменениях структуры проекта и только при корректной сборке (без предупреждений и ошибок) всегда актуализируй `./AGENTS.md`
- При доработке или расширении функционала этого проекта или синтаксиса всегда актуализируй скилл `./.agents/skills/koda-zensical`
+5
View File
@@ -0,0 +1,5 @@
{
"recommendations": [
"0x10.mkdocs-material-preview"
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"files.associations": {
"*.md": "python-markdown"
}
}
+22
View File
@@ -0,0 +1,22 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Живой предпросмотр (localhost:8000)",
"type": "shell",
"command": "make live"
},
{
"label": "Собрать статический сайт (./site)",
"type": "shell",
"command": "make site"
},
{
"label": "Собрать docker-образ со стат. сайтом",
"type": "shell",
"command": "make image"
}
]
}
+12 -2
View File
@@ -1,9 +1,19 @@
FROM squidfunk/mkdocs-material AS builder
FROM zensical/zensical:latest AS builder
ENV PYTHONPATH=/docs
COPY . /docs
RUN mkdocs build
RUN zensical build --clean
FROM nginx:alpine AS iptv-docs
LABEL org.opencontainers.image.title="IPTV Checker"
LABEL org.opencontainers.image.description="Documentation for our services and products"
LABEL org.opencontainers.image.authors="Anthony Axenov <anthonyaxenov@gmail.com>"
LABEL org.opencontainers.image.url="https://m3u.su"
LABEL org.opencontainers.image.vendor="Anthony Axenov"
LABEL org.opencontainers.image.documentation="https://m3u.su/docs"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.source="https://git.axenov.dev/IPTV/docs"
COPY --from=builder /docs/site /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
USER root
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Антон Аксенов (Anthony Axenov)
Copyright (c) 2025-2026 Антон Аксенов (Anthony Axenov)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+33 -28
View File
@@ -1,49 +1,54 @@
## image: Run mkdocs with live-reloading (localhost:3000)
.DEFAULT_GOAL := help
.PHONY: live site image push run help
## live = Run zensical with live-reloading on http://localhost:8000
live:
@echo "Wait until container starts and open http://localhost:3000 to see live preview"
@docker run \
--pull always \
@echo "*** Wait until container starts and open http://localhost:8000 to see live preview"
@docker stop iptv-docs-dev 2>/dev/null; \
docker run \
--rm \
--interactive \
--tty \
--publish 3000:8000 \
--env PYTHONPATH=/docs \
--publish 8000:8000 \
--volume ${PWD}:/docs \
--name iptv-docs-dev \
squidfunk/mkdocs-material:9.6.20
zensical/zensical:latest
## image: Build local static site
## site = Build a local static site
site:
@echo "Wait until mkdocs finish"
@docker run \
@echo "*** Wait until zensical finish"
@docker stop iptv-docs-dev 2>/dev/null; \
docker run \
--pull always \
--rm \
--interactive \
--tty \
--env PYTHONPATH=/docs \
--volume ${PWD}:/docs \
--name iptv-docs-dev \
squidfunk/mkdocs-material:9.6.20 build
zensical/zensical:latest \
build \
--clean
## image: Build docker image
## image = Build a docker image
image:
@docker build \
--tag iptv-docs:latest \
--tag git.axenov.dev/iptv/iptv-docs:latest \
.
@docker build --tag git.axenov.dev/iptv/iptv-docs:latest:latest .
## push: Push docker image to registry
push:
@docker push git.axenov.dev/iptv/iptv-docs:latest
## run: Run docker image (localhost:3001)
## run = Run docker container from image built with `make image` on http://localhost:8001
run:
@echo "Wait until container starts and open http://localhost:3001 to see ready static website"
@docker run \
@echo "*** Wait until container starts and open http://localhost:8001 to see ready static website"
@docker stop iptv-docs 2>/dev/null; \
docker run \
--rm \
--publish 3001:80 \
--publish 8001:80 \
--name iptv-docs \
git.axenov.dev/iptv/iptv-docs:latest
git.axenov.dev/iptv/iptv-docs:latest:latest
## help: Show this message and exit
# push = Push docker image built with `make image` to git.axenov.dev
push:
@docker login git.axenov.dev; \
docker push git.axenov.dev/iptv/iptv-docs:latest:latest
## help = Show this message and exit (default)
help: Makefile
@echo "Available recipes:"
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
@sed -n 's/^##/ /p' $< | column -t -s '='
+2 -2
View File
@@ -1,6 +1,6 @@
# Документация m3u.su
# Документация iptvc
Как работать с документацией: [src/dev/docs.md](src/dev/docs.md)
Как работать с документацией: [content/dev/docs.md](content/dev/docs.md)
## Лицензия

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+6 -6
View File
@@ -262,23 +262,23 @@ tags: ["сайт", "каналы", "плейлисты", "epg", "плееры",
### Просят денег и/или подписку
??? quote "[Скриншот] Уважаемый клиент! Для возобновления просмотра Вам необходимо использовать не более 2 устройств"
![](assets/img/paywalls/1.jpg)
![](_assets/img/paywalls/1.jpg)
> Уважаемый клиент! Для возобновления просмотра Вам необходимо использовать не более 2 устройств.
> Обратитесь к поставщику контента для уточнения.
??? quote "[Скриншот] Ваша подписка не активна"
![](assets/img/paywalls/2.jpg)
![](_assets/img/paywalls/2.jpg)
> Ваша подписка не активна
> Your subscription is not active
??? quote "[Скриншот] Мы обнаружили систематическое нарушение правил использования нашего сервиса"
![](assets/img/paywalls/3.jpg)
![](_assets/img/paywalls/3.jpg)
> Мы обнаружили систематическое нарушение правил использования нашего сервиса и заблокировали возможность просмотра контента.
> Для возобновления просмотра необходимо сменить OTTID в личном кабинете, и обновить плейлисты на ваших устройствах.
> Если вы считаете, что произошла какая-то ошибка - пожалуйста, обратитесь в техподдержку.
??? quote "[Скриншот] Вы превысили разрешённое количество одновременных поджключений"
![](assets/img/paywalls/4.jpg)
![](_assets/img/paywalls/4.jpg)
> Вы превысили разрешённое количество одновременных подключений
> Для возобновления просмотра ограничьте количество подключённых устройств
@@ -300,11 +300,11 @@ tags: ["сайт", "каналы", "плейлисты", "epg", "плееры",
### Wink
??? quote "[Скриншот] Просмотр ТВ-каналов, фильмов и сериалов доступен только в официальных приложениях Wink и на территории России"
![](assets/img/paywalls/wink.jpg)
![](_assets/img/paywalls/wink.jpg)
> Просмотр ТВ-каналов, фильмов и сериалов доступен только в официальных приложениях Wink и на территории России
??? quote "[Скриншот] Wink ещё не показывает видео на этой территории"
![](assets/img/paywalls/wink2.jpg)
![](_assets/img/paywalls/wink2.jpg)
> Wink ещё не показывает видео на этой территории
**Решение 1:** купить подписку Wink и использовать официальные приложения.
+2 -3
View File
@@ -1,5 +1,6 @@
---
icon: material/home
hide: [navigation, toc]
---
# :material-home: Введение
@@ -9,11 +10,9 @@ icon: material/home
!!! info "Все необходимые адреса"
**Веб-сайт:** [m3u.su](https://m3u.su)
**Документация:** [m3u.su/docs](https://m3u.su/docs)
Документация: [m3u.su/docs](https://m3u.su/docs)
Исходный код: [git.axenov.dev/IPTV](https://git.axenov.dev/IPTV)
Telegram-канал: [@iptv_aggregator](https://t.me/iptv_aggregator)
Обсуждение: [@iptv_aggregator_chat](https://t.me/iptv_aggregator_chat)
Бот: [@iptv_aggregator_bot](https://t.me/iptv_aggregator_bot)
Далеко не все пользователи, желающие использовать цифровое ТВ, могут позволить себе подключение IPTV у своего провайдера или поставщиков контента.
@@ -8,7 +8,7 @@ tags: ["сайт"]
Так выглядит статусная страница сервиса.
Попасть на неё можно по ссылке "[Аптайм](https://status.m3u.su)" в шапке сайта.
![Скриншот с примером главной страницы на десктопе](../assets/img/status/main.jpg)
![Скриншот с примером главной страницы на десктопе](../_assets/img/status/main.jpg)
Здесь отображается состояние компонентов сервиса:
@@ -22,7 +22,7 @@ tags: ["сайт"]
Можно нажать на незвание сервиса и посмотреть детальную информацию:
![Скриншот с примером страницы компонента на десктопе](../assets/img/status/details.jpg)
![Скриншот с примером страницы компонента на десктопе](../_assets/img/status/details.jpg)
Если на шкале появляется красное деление, значит был кратковременный сбой.
+1 -1
View File
@@ -27,7 +27,7 @@ icon: material/book-open-page-variant-outline
4. справа — содержание конкретной страницы.
??? info end "На мобильниках содержание страницы спрятано за этой кнопкой в боковом меню:"
![Скриншот бокового меню с мобильной версии](assets/img/mobile-toc-btn.jpg)
![Скриншот бокового меню с мобильной версии](_assets/img/mobile-toc-btn.jpg)
## Связанные статьи
@@ -33,17 +33,17 @@ tags: ["плееры"]
Универсальный плеер практически для любого мультимедиа-контента.
??? quote "[Скриншот] Главное окно"
![](../assets/img/players/vlc/main.jpg)
![](../_assets/img/players/vlc/main.jpg)
??? quote "[Скриншот] Добавление плейлиста на десктопе"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/vlc/add1.jpg)
![](../assets/img/players/vlc/add2.jpg)
![](../_assets/img/players/vlc/add1.jpg)
![](../_assets/img/players/vlc/add2.jpg)
??? quote "[Скриншот] Добавление плейлиста на андроиде"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/vlc/add1-mob.jpg)
![](../assets/img/players/vlc/add2-mob.jpg)
![](../_assets/img/players/vlc/add1-mob.jpg)
![](../_assets/img/players/vlc/add2-mob.jpg)
### :thumbsup: IPTVnator
@@ -58,12 +58,12 @@ tags: ["плееры"]
Если использовать веб-версию, то настройки сохраняются в браузере.
??? quote "[Скриншот] Главное окно"
![](../assets/img/players/iptvnator/main.jpg)
![](../_assets/img/players/iptvnator/main.jpg)
??? quote "[Скриншот] Добавление плейлиста"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/iptvnator/add1.jpg)
![](../assets/img/players/iptvnator/add2.jpg)
![](../_assets/img/players/iptvnator/add1.jpg)
![](../_assets/img/players/iptvnator/add2.jpg)
### IPTV Web Player
@@ -75,11 +75,11 @@ tags: ["плееры"]
Подгрузка и отображение телепрограммы (используется https://cdn.epg.one/epg2.xml).
??? quote "[Скриншот] Главное окно"
![](../assets/img/players/iptv-web-player/main.jpg)
![](../_assets/img/players/iptv-web-player/main.jpg)
??? quote "[Скриншот] Добавление плейлиста"
!!! success "Указание протокола `https://` необязательно!"
![](../assets/img/players/iptv-web-player/add.jpg)
![](../_assets/img/players/iptv-web-player/add.jpg)
### Kodi
@@ -136,11 +136,11 @@ tags: ["плееры"]
Поддерживает плейлисты по ссылкам, сторонние телепрограммы, группировку каналов, изменение плейлистов и многое другое.
??? quote "[Скриншот] Главное окно"
![](../assets/img/players/yuki-iptv/main.jpg)
![](../_assets/img/players/yuki-iptv/main.jpg)
??? quote "[Скриншот] Добавление плейлиста"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/yuki-iptv/add.jpg)
![](../_assets/img/players/yuki-iptv/add.jpg)
---
@@ -174,18 +174,18 @@ tags: ["плееры"]
* Скачать: [play.google.com](https://play.google.com/store/apps/details?id=com.ottplay.ottplay)
??? quote "[Скриншот] Главный экран"
![](../assets/img/players/televizo/main1.jpg)
![](../assets/img/players/televizo/main2.jpg)
![](../_assets/img/players/televizo/main1.jpg)
![](../_assets/img/players/televizo/main2.jpg)
??? quote "[Скриншот] Добавление плейлиста"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/televizo/add1.jpg)
![](../assets/img/players/televizo/add2.jpg)
![](../_assets/img/players/televizo/add1.jpg)
![](../_assets/img/players/televizo/add2.jpg)
Из настроек:
![](../assets/img/players/televizo/add21.jpg)
![](../assets/img/players/televizo/add22.jpg)
![](../_assets/img/players/televizo/add21.jpg)
![](../_assets/img/players/televizo/add22.jpg)
И дальше те же шаги 3-5 на скриншотах выше.
@@ -202,16 +202,16 @@ tags: ["плееры"]
Программу передач нужно [подключать отдельной ссылкой](../faq.md#epg), из плейлиста не тянет.
??? quote "[Скриншот] Главный экран"
![](../assets/img/players/m3u/main.jpg)
![](../_assets/img/players/m3u/main.jpg)
??? quote "[Скриншот] Добавление плейлиста"
!!! warning "Указание протокола `https://` обязательно!"
![](../assets/img/players/m3u/add1.jpg)
![](../assets/img/players/m3u/add2.jpg)
![](../_assets/img/players/m3u/add1.jpg)
![](../_assets/img/players/m3u/add2.jpg)
??? quote "[Скриншот] Установка User-Agent"
![](../assets/img/players/m3u/ua1.jpg)
![](../assets/img/players/m3u/ua2.jpg)
![](../_assets/img/players/m3u/ua1.jpg)
![](../_assets/img/players/m3u/ua2.jpg)
#### IPTV (Александр Софронов)
+384
View File
@@ -0,0 +1,384 @@
---
title: check
tags: [./iptvc]
---
# Команда `check`
Команда поддерживает множество аргументов для разных целей.
Они могут дополнять друг друга.
Порядок аргументов не имеет значения.
## `-i`, `--ini` { id=ini }
Указывает путь к локальному [ini-файлу](../../formats/playlists.md) с описанием плейлистов.
Можно указать только однажды.
Значение по умолчанию: `./playlists.ini`
Если файл не найден, проверка плейлистов будет доступна только по ссылкам ([`--url`](#url)) или из локальных файлов ([`--file`](#file)).
```shell title="Пример"
./iptvc check -i ~/my.ini
```
## `-t`, `--tags` { id=tags }
Указывает путь к локальному [json-файлу](../../formats/channels.md) с описанием тегов каналов.
Можно указать только однажды.
Значение по умолчанию: `./channels.json`
Если файл не найден, то будет выведено предупреждение о том, что каналы не будут помечены тегами.
```shell title="Пример"
./iptvc check -t ~/tags.json
```
## `-f`, `--file` { id=file }
Указывает путь к локальному файлу плейлиста `*.m3u`/`*.m3u8`.
Можно указать несколько разных.
```shell title="Пример"
./iptvc check -f playlist.m3u
./iptvc check -f playlist1.m3u --file playlist2.m3u8
./iptvc check --file /path/to/playlist.m3u
```
## `-u`, `--url` { id=url }
Указывает URL удалённого плейлиста (поддерживаются протоколы http/https).
Можно указать несколько разных.
```shell title="Пример"
./iptvc check -u http://example.com/playlist.m3u
./iptvc check -u https://site.com/playlist.m3u8 --url http://other.com/list.m3u
```
## `-c`, `--code` { id=code }
Указывает код плейлиста из файла [playlists.ini](../../formats/playlists.md).
Можно указать несколько разных.
!!! warning "Работает только вместе с [`--ini`](#ini)."
Если не указан ни разу, то будут проверены все плейлисты, которые указаны в ini-файле.
Если используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
./iptvc check -i ~/my.ini -c RU_BASIC --code MOVIE_PREMIUM
```
## `--repeat` { id=repeat }
Указывает количество повторений (итераций) команды.
Значение по умолчанию: `1`
Если указано `0`, тогда:
* повторение будет бесконечным;
* если переданы [`--url`](#url), [`--file`](#file) или [`--code`](#code), то на каждой итерации будут проверяться только указанные плейлисты;
* если не переданы [`--url`](#url), [`--file`](#file) или [`--code`](#code), то на каждой итерации список плейлистов будет подготавливаться заново.
Если при этом используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
# проверить 5 раз плейлисты с кодами xx и yy из my.ini
./iptvc check -i ~/my.ini -c xx --code yy --repeat 5
# бесконечно проверять все плейлисты из my.ini, без учёта проверенных
./iptvc check -i ~/my.ini --repeat 0
# бесконечно проверять плейлист из файла
./iptvc check -f test.m3u --repeat 0
```
## `--every` { id=every }
Указывает количество секунд между повторениями (итерациями) команды.
Значение по умолчанию: `5`
Если указано `0`, то задержки не будет.
```shell title="Пример"
# проверить 5 раз плейлисты с кодами xx и yy из my.ini каждые 5 секунд
./iptvc check -i ~/my.ini -c xx --code yy --repeat 5 --every 5
# бесконечно проверять все плейлисты из my.ini, без учёта проверенных, каждый час
./iptvc check -i ~/my.ini --repeat 0 --every 3600
# бесконечно проверять плейлист из файла каждые 10 секунд
./iptvc check -f test.m3u --repeat 0 --every 10
```
## `-r`, `--random` { id=random }
Указывает максимальное количество случайных плейлистов из ini-файла для проверки.
!!! warning "Работает только вместе с [`--ini`](#ini)."
Если не указан ни разу, то будут проверены все плейлисты, которые указаны в ini-файле.
Если используется кеширование, то проверенные плейлисты (результаты проверки которых ещё находятся в кеше) проверяться не будут.
```shell title="Пример"
./iptvc check -i ~/my.ini -r 10
```
## `-j`, `--json` { id=json }
Если указано, то подробные результаты проверки будут выводиться в формате JSON.
```shell title="Пример"
./iptvc check -f playlist.m3u --json
```
## `-q`, `--quiet` { id=quiet }
Подавляет вывод всех логов.
!!! info "Не влияет на [`--json`](#json) (JSON-данные будут выведены в stdout), но перекрывает [`--verbose`](#verbose) (логов не будет вовсе, независимо от повышенной подробности)."
```shell title="Пример"
./iptvc check -i ~/my.ini --random 10 --quiet --json
```
## `-v`, `--verbose` { id=verbose }
Включает подробное логирование.
```shell title="Пример"
./iptvc check --random 10 --verbose
```
## Глобальные флаги { id=global }
Эти флаги доступны для всех команд и переопределяют значения из `config.yml`.
### `--debug` { id=debug }
Включает режим отладки. Переопределяет `app.debug` из `config.yml` и переменную `APP_DEBUG`.
```shell title="Пример"
./iptvc check -i ~/my.ini --debug
```
### `--log-level` { id=log-level }
Устанавливает уровень логирования. Переопределяет `app.log_level` из `config.yml`.
Доступные значения: `debug`, `info`, `warn`, `error`.
```shell title="Пример"
./iptvc check -i ~/my.ini --log-level debug
```
## Флаги проверки плейлистов { id=check-playlists }
Эти флаги переопределяют параметры секции `check.playlists` из `config.yml`. Доступны для команд `check` и `serve`.
### `--playlists-timeout` { id=playlists-timeout }
Таймаут HTTP-запроса плейлиста в миллисекундах.
Переопределяет `check.playlists.timeout` (по умолчанию `10000`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-timeout 5000
```
### `--playlists-all-cooldown` { id=playlists-all-cooldown }
Задержка в миллисекундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-all-cooldown 10000
```
### `--playlists-one-cooldown` { id=playlists-one-cooldown }
Задержка в миллисекундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-one-cooldown 2000
```
### `--playlists-max-routines` { id=playlists-max-routines }
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `5`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-max-routines 10
```
### `--playlists-per-routine` { id=playlists-per-routine }
Количество плейлистов на одну процедуру проверки.
Переопределяет `check.playlists.per-routine` (по умолчанию `1`).
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-per-routine 3
```
### `--playlists-user-agent` { id=playlists-user-agent }
User-Agent для HTTP-запросов плейлистов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.playlists.user-agent`.
```shell title="Пример"
./iptvc check -i ~/my.ini --playlists-user-agent "Mozilla/5.0" "curl/8.0"
```
## Флаги проверки каналов { id=check-channels }
Эти флаги переопределяют параметры секции `check.channels` из `config.yml`. Доступны для команд `check` и `serve`.
### `--channels-timeout` { id=channels-timeout }
Таймаут HTTP-запроса канала в миллисекундах.
Переопределяет `check.channels.timeout` (по умолчанию `10000`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-timeout 8000
```
### `--channels-byte-range` { id=channels-byte-range }
Объём данных в байтах для загрузки от сервера при проверке канала.
Переопределяет `check.channels.byte-range` (по умолчанию `512`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-byte-range 1024
```
### `--channels-cooldown` { id=channels-cooldown }
Задержка в миллисекундах после проверки каждого канала.
Переопределяет `check.channels.cooldown` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-cooldown 100
```
### `--channels-max-routines` { id=channels-max-routines }
Максимум одновременно проверяемых каналов.
Переопределяет `check.channels.max-routines` (по умолчанию `50`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-max-routines 100
```
### `--channels-per-routine` { id=channels-per-routine }
Количество каналов на одну процедуру проверки.
Переопределяет `check.channels.per-routine` (по умолчанию `10`).
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-per-routine 20
```
### `--channels-user-agent` { id=channels-user-agent }
User-Agent для HTTP-запросов каналов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.channels.user-agent`.
```shell title="Пример"
./iptvc check -i ~/my.ini --channels-user-agent "Mozilla/5.0" "VLC/3.0"
```
## Флаги кеша { id=cache-flags }
Эти флаги переопределяют параметры секции `cache` из `config.yml`. Доступны для команд `check` и `serve`.
### `--cache-enabled` { id=cache-enabled }
Включает кеширование результатов в KeyDB/Redis.
Переопределяет `cache.enabled` (по умолчанию `false`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled
```
### `--cache-host` { id=cache-host }
Хост KeyDB/Redis.
Переопределяет `cache.host` (по умолчанию `localhost`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-host 192.168.1.10
```
### `--cache-port` { id=cache-port }
Порт KeyDB/Redis.
Переопределяет `cache.port` (по умолчанию `6379`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-port 6380
```
### `--cache-username` { id=cache-username }
Логин для подключения к KeyDB/Redis.
Переопределяет `cache.username`.
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-username myuser
```
### `--cache-password` { id=cache-password }
Пароль для подключения к KeyDB/Redis.
Переопределяет `cache.password`.
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-password secret
```
### `--cache-db` { id=cache-db }
Номер базы данных KeyDB/Redis.
Переопределяет `cache.db` (по умолчанию `0`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-db 2
```
### `--cache-ttl` { id=cache-ttl }
TTL записей кеша в секундах.
Переопределяет `cache.ttl` (по умолчанию `1800`).
```shell title="Пример"
./iptvc check -i ~/my.ini --cache-enabled --cache-ttl 3600
```
@@ -1,5 +1,6 @@
---
tags: ["iptvc"]
title: help
tags: [iptvc]
---
# Команда `help`
+23
View File
@@ -0,0 +1,23 @@
---
icon: octicons/terminal-24
hide: [toc]
---
# :octicons-terminal-24: Справочник команд
* [`check`](check.md) — проверка плейлистов
* [`serve`](serve.md) — запуск веб-интерфейса
* [`version`](version.md) — получение версии и выход
* [`help`](help.md) — получение справки о программе и выход
Каждая команда отвечает за конкретную операцию и имеет свои настройки (аргументы), которыми можно влиять на логику выполнения операции.
Также есть глобальные аргументы, которые доступны для всех команд:
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| ----------------- | ------ | ---------------------------- | ----------------------------------------------------- |
| `--config` | string | — | Путь к файлу конфигурации (по умолчанию `config.yml`) |
| `--debug` | bool | `app.debug` | Включить режим отладки |
| `--log-level` | string | `app.log_level` | Уровень логирования: `debug`, `info`, `warn`, `error` |
| `-v`, `--verbose` | bool | — | Подробное логирование |
+408
View File
@@ -0,0 +1,408 @@
---
title: serve
tags: [iptvc]
---
# Команда `serve`
Запускает встроенный веб-сервер для просмотра плейлистов и результатов их проверки в браузере.
```bash
iptvc serve [flags]
```
## Веб-сервер
<a id="port"></a>
### `-p`, `--port`
Порт для веб-сервера.
Переопределяет `server.port` из `config.yml` и переменную `WEB_PORT`.
Если не указан, используется значение из `config.yml` (по умолчанию `8080`).
```bash
iptvc serve -p 3000
```
<a id="host"></a>
### `--host`
Хост для привязки веб-сервера.
Переопределяет `server.host` из `config.yml` и переменную `WEB_HOST`.
Если не указан, используется значение из `config.yml` (по умолчанию — все интерфейсы).
```bash
iptvc serve --host 127.0.0.1
```
## Фоновая проверка
<a id="check"></a>
### `--check`
Включает фоновую проверку плейлистов. По умолчанию выключена.
Без этого флага веб-сервер работает standalone — отображает данные из кеша (если включён) или статус `unknown` для всех плейлистов.
```bash
iptvc serve --check
```
При `--check` доступны следующие флаги:
<a id="ini"></a>
### `-i`, `--ini`
Путь к локальному [ini-файлу](../../formats/playlists.md) с описанием плейлистов.
Значение по умолчанию: `./playlists.ini`
```bash
iptvc serve --check -i ~/my.ini
```
<a id="tags"></a>
### `-t`, `--tags`
Путь к [json-файлу](../../formats/channels.md) с описанием тегов каналов.
Значение по умолчанию: `./channels.json`
```bash
iptvc serve --check -t ~/tags.json
```
<a id="every"></a>
### `--every`
Интервал между циклами фоновой проверки в секундах.
Значение по умолчанию: `60`
```bash
iptvc serve --check --every 120
```
<a id="repeat"></a>
### `--repeat`
Количество циклов фоновой проверки.
Значение по умолчанию: `0` (бесконечно)
```bash
# проверить один раз и остановить фоновую проверку
iptvc serve --check --repeat 1
```
<a id="random"></a>
### `-r`, `--random`
Максимальное количество случайных плейлистов из ini-файла для проверки.
```bash
iptvc serve --check -r 10
```
## Глобальные флаги
<a id="config"></a>
### `--config`
Путь к файлу конфигурации `config.yml`.
Значение по умолчанию: `config.yml`
```bash
iptvc serve --config /etc/iptvc/config.yml
```
<a id="debug"></a>
### `--debug`
Включает режим отладки. Переопределяет `app.debug` из `config.yml` и переменную `APP_DEBUG`.
```bash
iptvc serve --debug
```
<a id="log-level"></a>
### `--log-level`
Устанавливает уровень логирования. Переопределяет `app.log_level` из `config.yml`.
Доступные значения: `debug`, `info`, `warn`, `error`.
```bash
iptvc serve --log-level debug
```
<a id="verbose"></a>
### `-v`, `--verbose`
Включает подробное логирование.
## Флаги проверки плейлистов
Эти флаги переопределяют параметры секции `check.playlists` из `config.yml`. Доступны для команд `check` и `serve`. Имеют смысл только при включённой фоновой проверке (`--check` или `check.start-on-serve: true`).
<a id="playlists-timeout"></a>
### `--playlists-timeout`
Таймаут HTTP-запроса плейлиста в миллисекундах.
Переопределяет `check.playlists.timeout` (по умолчанию `10000`).
```bash
iptvc serve --check --playlists-timeout 5000
```
<a id="playlists-all-cooldown"></a>
### `--playlists-all-cooldown`
Задержка в миллисекундах после проверки всех плейлистов.
Переопределяет `check.playlists.all-cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --playlists-all-cooldown 10000
```
<a id="playlists-one-cooldown"></a>
### `--playlists-one-cooldown`
Задержка в миллисекундах после проверки каждого плейлиста.
Переопределяет `check.playlists.one-cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --playlists-one-cooldown 2000
```
<a id="playlists-max-routines"></a>
### `--playlists-max-routines`
Максимум одновременно проверяемых плейлистов.
Переопределяет `check.playlists.max-routines` (по умолчанию `5`).
```bash
iptvc serve --check --playlists-max-routines 10
```
<a id="playlists-per-routine"></a>
### `--playlists-per-routine`
Количество плейлистов на одну процедуру проверки.
Переопределяет `check.playlists.per-routine` (по умолчанию `1`).
```bash
iptvc serve --check --playlists-per-routine 3
```
<a id="playlists-user-agent"></a>
### `--playlists-user-agent`
User-Agent для HTTP-запросов плейлистов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.playlists.user-agent`.
```bash
iptvc serve --check --playlists-user-agent "Mozilla/5.0" "curl/8.0"
```
## Флаги проверки каналов
Эти флаги переопределяют параметры секции `check.channels` из `config.yml`. Доступны для команд `check` и `serve`. Имеют смысл только при включённой фоновой проверке.
<a id="channels-timeout"></a>
### `--channels-timeout`
Таймаут HTTP-запроса канала в миллисекундах.
Переопределяет `check.channels.timeout` (по умолчанию `10000`).
```bash
iptvc serve --check --channels-timeout 8000
```
<a id="channels-byte-range"></a>
### `--channels-byte-range`
Объём данных в байтах для загрузки от сервера при проверке канала.
Переопределяет `check.channels.byte-range` (по умолчанию `512`).
```bash
iptvc serve --check --channels-byte-range 1024
```
<a id="channels-cooldown"></a>
### `--channels-cooldown`
Задержка в миллисекундах после проверки каждого канала.
Переопределяет `check.channels.cooldown` (по умолчанию `0`).
```bash
iptvc serve --check --channels-cooldown 100
```
<a id="channels-max-routines"></a>
### `--channels-max-routines`
Максимум одновременно проверяемых каналов.
Переопределяет `check.channels.max-routines` (по умолчанию `50`).
```bash
iptvc serve --check --channels-max-routines 100
```
<a id="channels-per-routine"></a>
### `--channels-per-routine`
Количество каналов на одну процедуру проверки.
Переопределяет `check.channels.per-routine` (по умолчанию `10`).
```bash
iptvc serve --check --channels-per-routine 20
```
<a id="channels-user-agent"></a>
### `--channels-user-agent`
User-Agent для HTTP-запросов каналов. Можно указать несколько — будет выбран случайный при каждом запросе.
Переопределяет `check.channels.user-agent`.
```bash
iptvc serve --check --channels-user-agent "Mozilla/5.0" "VLC/3.0"
```
## Флаги кеша
Эти флаги переопределяют параметры секции `cache` из `config.yml`. Доступны для команд `check` и `serve`.
<a id="cache-enabled"></a>
### `--cache-enabled`
Включает кеширование результатов в KeyDB/Redis.
Переопределяет `cache.enabled` (по умолчанию `false`).
```bash
iptvc serve --cache-enabled
```
<a id="cache-host"></a>
### `--cache-host`
Хост KeyDB/Redis.
Переопределяет `cache.host` (по умолчанию `localhost`).
```bash
iptvc serve --cache-enabled --cache-host 192.168.1.10
```
<a id="cache-port"></a>
### `--cache-port`
Порт KeyDB/Redis.
Переопределяет `cache.port` (по умолчанию `6379`).
```bash
iptvc serve --cache-enabled --cache-port 6380
```
<a id="cache-username"></a>
### `--cache-username`
Логин для подключения к KeyDB/Redis.
Переопределяет `cache.username`.
```bash
iptvc serve --cache-enabled --cache-username myuser
```
<a id="cache-password"></a>
### `--cache-password`
Пароль для подключения к KeyDB/Redis.
Переопределяет `cache.password`.
```bash
iptvc serve --cache-enabled --cache-password secret
```
<a id="cache-db"></a>
### `--cache-db`
Номер базы данных KeyDB/Redis.
Переопределяет `cache.db` (по умолчанию `0`).
```bash
iptvc serve --cache-enabled --cache-db 2
```
<a id="cache-ttl"></a>
### `--cache-ttl`
TTL записей кеша в секундах.
Переопределяет `cache.ttl` (по умолчанию `1800`).
```bash
iptvc serve --cache-enabled --cache-ttl 3600
```
## Примеры
```bash
# просто веб-сервер без проверки
iptvc serve
# веб-сервер с фоновой проверкой каждые 2 минуты
iptvc serve --check --every 120
# веб-сервер на порту 3000 с проверкой 10 случайных плейлистов
iptvc serve -p 3000 --check -r 10
# один цикл проверки, затем только веб-сервер
iptvc serve --check --repeat 1 --every 0
# веб-сервер с кешем и фоновой проверкой, увеличенные лимиты параллелизма
iptvc serve --check --cache-enabled \
--playlists-max-routines 10 \
--channels-max-routines 100
# веб-сервер с отладкой и кастомным user-agent
iptvc serve --check --debug \
--playlists-user-agent "Mozilla/5.0" \
--channels-user-agent "VLC/3.0"
```
## Веб-маршруты
| Метод | Путь | Описание |
| --- | --- | --- |
| GET | `/` | Главная страница со списком плейлистов |
| GET | `/page/{N}` | Страница N списка плейлистов |
| GET | `/{code}` | Редирект на прямую ссылку плейлиста |
| GET | `/{code}.m3u[8]` | Редирект на прямую ссылку плейлиста |
| GET | `/{code}/details` | Страница с описанием плейлиста |
| GET | `/api/playlists/{code}` | JSON: информация о плейлисте |
| GET | `/api/version` | JSON: версии компонентов |
| GET | `/api/health` | JSON: состояние сервиса |
| GET | `/api/stats` | JSON: статистика по плейлистам и каналам |
@@ -1,5 +1,6 @@
---
tags: ["iptvc"]
title: version
tags: [iptvc]
---
# Команда `version`
@@ -7,7 +8,7 @@ tags: ["iptvc"]
Выводит версию программы:
```bash
iptvc version
./iptvc version
```
Пример результата:
+436
View File
@@ -0,0 +1,436 @@
---
title: config.yml
icon: material/file-cog
tags: ["iptvc", "конфигурация"]
---
# :material-file-cog: Конфигурация config.yml
Программа читает настройки из YAML-файла `config.yml` в корне проекта.
Путь к файлу можно задать через глобальный флаг `--config`.
## Приоритет настроек
От низшего к высшему:
1. **Значения по умолчанию** — встроены в код;
2. **`config.yml`** — YAML-файл;
3. **Переменные окружения** — переопределяют `config.yml` (если заданы);
4. **CLI-флаги** — переопределяют переменные окружения и `config.yml` (если заданы явно).
Файл `.env` загружается автоматически, переменные из него применяются как переменные окружения.
## Структура файла
```yaml
app:
timezone: GMT
debug: false
log_level: info
playlists: ./playlists.ini
tags: ./channels.json
server:
host: localhost
port: 8080
site:
base-url: http://localhost:8080
repo-url: https://git.axenov.dev/IPTV
page-size: 0
favicon:
header:
title: IPTV Checker
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/iptv_aggregator
icon: megaphone-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
check:
start-on-serve: false
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
timeout: 10000
all-cooldown: 0
one-cooldown: 0
max-routines: 5
per-routine: 1
channels:
user-agent: Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
timeout: 10000
byte-range: 512
cooldown: 0
max-routines: 50
per-routine: 10
cache:
enabled: false
host: localhost
port: 6379
username:
password:
db: 0
ttl: 1800
```
## Секция `app`
| Параметр | Тип | По умолчанию | Описание |
| ----------- | ------ | ----------------- | ---------------------- |
| `timezone` | string | `GMT` | Часовой пояс |
| `debug` | bool | `false` | Режим отладки |
| `log_level` | string | `info` | Уровень логирования |
| `playlists` | string | `./playlists.ini` | Путь к `playlists.ini` |
| `tags` | string | `./channels.json` | Путь к `channels.json` |
## Секция `server`
| Параметр | Тип | По умолчанию | Описание |
| -------- | ------ | ------------ | ----------------- |
| `host` | string | (пусто) | Хост для привязки |
| `port` | uint | `8080` | Порт веб-сервера |
## Секция `site`
Настройки сайта: ссылки, заголовок, навигация, пагинация.
| Параметр | Тип | По умолчанию | Описание |
| ----------- | ------ | ----------------------------- | ----------------------------------- |
| `base-url` | string | `http://localhost:8080` | Базовый URL для формирования ссылок |
| `repo-url` | string | `https://git.axenov.dev/IPTV` | Ссылка на репозиторий |
| `page-size` | uint | `0` | Размер страницы (0 — без пагинации) |
| `favicon` | string | (пусто) | Путь к иконке сайта |
### `site.header`
Настройки шапки сайта.
| Параметр | Тип | По умолчанию | Описание |
| ------------ | ------ | -------------- | -------------------------------------- |
| `title` | string | `IPTV Checker` | Заголовок сайта (в navbar и `<title>`) |
| `navigation` | [Link] | (см. ниже) | Ссылки в шапке сайта |
### `site.footer-links`
Ссылки в подвале сайта. Массив элементов `Link`.
### Тип `Link`
Элемент навигации или подвала. Если задано `children`, рендерится как выпадающее меню.
| Параметр | Тип | Описание |
| ---------- | ------ | ----------------------------------------------------------- |
| `title` | string | Текст ссылки |
| `url` | string | URL ссылки (можно опустить, если есть `children`) |
| `icon` | string | Имя иконки |
| `children` | [Link] | Дочерние ссылки (выпадающее меню, один уровень вложенности) |
--8<-- "ionicons-name.md"
Пример:
```yaml
site:
header:
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/iptv_aggregator
icon: megaphone-outline
- title: Чат
url: https://t.me/iptv_aggregator_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: axenov.dev
url: https://axenov.dev
icon: person-outline
```
## Секция `check`
Параметры проверки плейлистов и каналов. Поддерживаются скаляры и массивы.
### `check.start-on-serve`
| Параметр | Тип | По умолчанию | Описание |
| ---------------- | ---- | ------------ | ---------------------------------------------------------- |
| `start-on-serve` | bool | `false` | Запустить фоновую проверку при `serve` без флага `--check` |
### Типы значений
!!! info "timeout, max-routines, per-routine, byte-range"
Эти параметры — всегда целые числа, не массивы.
!!! info "cooldown (all-cooldown, one-cooldown, channels.cooldown)"
Эти параметры могут быть заданы:
- **скаляром** — фиксированное значение, например `all-cooldown: 10`;
- **массивом `[min, max]`** — случайное значение в диапазоне при каждой проверке, например `all-cooldown: [5, 15]`.
!!! info "user-agent"
Параметр `user-agent` может быть задан:
- **строкой** — используется всегда одно значение;
- **массивом строк** — случайный выбор при каждом запросе.
!!! info "byte-range"
Параметр `byte-range` — всегда целое число (не массив).
### `check.playlists`
Параметры проверки плейлистов (загрузка m3u-файлов по URL или из ФС).
| Параметр | Тип | По умолчанию | Единица | Описание |
| -------------- | ------------------ | ----------------- | ------- | ------------------------------------------------ |
| `user-agent` | string \| string[] | `Mozilla/5.0 ...` | — | User-Agent для HTTP-запросов |
| `timeout` | int | `10000` | мс | Таймаут запроса плейлиста |
| `all-cooldown` | int \| int[] | `0` | мс | Задержка после проверки всех плейлистов |
| `one-cooldown` | int \| int[] | `0` | мс | Задержка после проверки каждого плейлиста |
| `max-routines` | int | `5` | шт | Максимум одновременно проверяемых плейлистов |
| `per-routine` | int | `1` | шт | Количество плейлистов на одну процедуру проверки |
### `check.channels`
Параметры проверки каналов внутри плейлиста.
| Параметр | Тип | По умолчанию | Единица | Описание |
| -------------- | ------------------ | ----------------- | ------- | --------------------------------------------- |
| `user-agent` | string \| string[] | `Mozilla/5.0 ...` | — | User-Agent для HTTP-запросов |
| `timeout` | int | `10000` | мс | Таймаут запроса канала |
| `byte-range` | int | `512` | байт | Объём данных для загрузки от сервера |
| `cooldown` | int \| int[] | `0` | мс | Задержка после проверки каждого канала |
| `max-routines` | int | `50` | шт | Максимум одновременно проверяемых каналов |
| `per-routine` | int | `10` | шт | Количество каналов на одну процедуру проверки |
## Секция `cache`
| Параметр | Тип | По умолчанию | Описание |
| ---------- | ------ | ------------ | ---------------------------------- |
| `enabled` | bool | `false` | Включить кеширование (KeyDB/Redis) |
| `host` | string | `localhost` | Хост KeyDB/Redis |
| `port` | uint | `6379` | Порт KeyDB/Redis |
| `username` | string | (пусто) | Логин |
| `password` | string | (пусто) | Пароль |
| `db` | uint | `0` | Номер БД |
| `ttl` | uint | `1800` | TTL записей (сек) |
## Валидация
При запуске конфигурация валидируется.
Некорректные значения исправляются автоматически, каждое исправление логируется:
| Проверка | Действие |
| ---------------------------------------------------- | --------------------------------------- |
| `server.port` = 0 или > 65535 | сброс в `8080` |
| `site.base-url` пусто | автогенерация `http://localhost:{port}` |
| `cache.host` пусто (если cache включён) | `localhost` |
| `cache.port` = 0 (если cache включён) | `6379` |
| `cache.ttl` = 0 (если cache включён) | `1800` |
| `check.playlists.timeout` <= 0 | `10000` |
| `check.channels.timeout` <= 0 | `10000` |
| `check.*.cooldown``min > max` | swap |
| `check.*.cooldown` — выход за границы `[0, 3600000]` | clamp |
| `check.playlists.max-routines` < 1 | `5` |
| `check.channels.max-routines` < 1 | `50` |
| `check.playlists.per-routine` < 1 | `1` |
| `check.channels.per-routine` < 1 | `10` |
| `check.channels.byte-range` <= 0 | `512` |
| `check.*.user-agent` пусто | дефолтный User-Agent |
## Переменные окружения
Переменные окружения переопределяют значения из `config.yml`.
### Приложение
| Переменная | Соответствует в `config.yml` |
| --------------- | ---------------------------- |
| `APP_DEBUG` | `app.debug` |
| `APP_LOG_LEVEL` | `app.log_level` |
| `APP_TIMEZONE` | `app.timezone` |
| `APP_PLAYLISTS` | `app.playlists` |
| `APP_TAGS` | `app.tags` |
### Веб-сервер и сайт
| Переменная | Соответствует в `config.yml` |
| -------------- | ---------------------------- |
| `WEB_PORT` | `server.port` |
| `WEB_HOST` | `server.host` |
| `APP_URL` | `site.base-url` |
| `PAGE_SIZE` | `site.page-size` |
| `REPO_URL` | `site.repo-url` |
| `SITE_FAVICON` | `site.favicon` |
| `APP_TITLE` | `site.header.title` |
### Проверка
| Переменная | Соответствует в `config.yml` |
| ---------------------- | ---------------------------- |
| `CHECK_START_ON_SERVE` | `check.start-on-serve` |
#### `check.playlists`
| Переменная | Соответствует в `config.yml` |
| ---------------------------------- | --------------------------------------------------- |
| `CHECK_PLAYLISTS_TIMEOUT` | `check.playlists.timeout` |
| `CHECK_PLAYLISTS_ALL_COOLDOWN` | `check.playlists.all-cooldown` (скаляр) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MIN` | `check.playlists.all-cooldown` (минимум диапазона) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MAX` | `check.playlists.all-cooldown` (максимум диапазона) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN` | `check.playlists.one-cooldown` (скаляр) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MIN` | `check.playlists.one-cooldown` (минимум диапазона) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MAX` | `check.playlists.one-cooldown` (максимум диапазона) |
| `CHECK_PLAYLISTS_MAX_ROUTINES` | `check.playlists.max-routines` |
| `CHECK_PLAYLISTS_PER_ROUTINE` | `check.playlists.per-routine` |
| `CHECK_PLAYLISTS_USER_AGENT_1` | `check.playlists.user-agent` (первый элемент) |
| `CHECK_PLAYLISTS_USER_AGENT_2` | `check.playlists.user-agent` (второй элемент) |
| `CHECK_PLAYLISTS_USER_AGENT_N` | `check.playlists.user-agent` (N-й элемент) |
#### `check.channels`
| Переменная | Соответствует в `config.yml` |
| ----------------------------- | ---------------------------------------------- |
| `CHECK_CHANNELS_TIMEOUT` | `check.channels.timeout` |
| `CHECK_CHANNELS_BYTE_RANGE` | `check.channels.byte-range` |
| `CHECK_CHANNELS_COOLDOWN` | `check.channels.cooldown` (скаляр) |
| `CHECK_CHANNELS_COOLDOWN_MIN` | `check.channels.cooldown` (минимум диапазона) |
| `CHECK_CHANNELS_COOLDOWN_MAX` | `check.channels.cooldown` (максимум диапазона) |
| `CHECK_CHANNELS_MAX_ROUTINES` | `check.channels.max-routines` |
| `CHECK_CHANNELS_PER_ROUTINE` | `check.channels.per-routine` |
| `CHECK_CHANNELS_USER_AGENT_1` | `check.channels.user-agent` (первый элемент) |
| `CHECK_CHANNELS_USER_AGENT_2` | `check.channels.user-agent` (второй элемент) |
| `CHECK_CHANNELS_USER_AGENT_N` | `check.channels.user-agent` (N-й элемент) |
!!! info "Диапазоны cooldown через env"
Если заданы обе переменные `_MIN` и `_MAX` — используется диапазон.
Если задана только скалярная переменная (без `_MIN`/`_MAX`) — используется фиксированное значение.
Если задана только одна из `_MIN`/`_MAX` — переменная игнорируется.
!!! info "Массивы user-agent через env"
Переменные читаются последовательно: `_1`, `_2`, `_3`, …
Первая отсутствующая переменная останавливает чтение.
Пустые значения пропускаются.
### Кеш
| Переменная | Соответствует в `config.yml` |
| ---------------- | ---------------------------- |
| `CACHE_ENABLED` | `cache.enabled` |
| `CACHE_HOST` | `cache.host` |
| `CACHE_PORT` | `cache.port` |
| `CACHE_USERNAME` | `cache.username` |
| `CACHE_PASSWORD` | `cache.password` |
| `CACHE_DB` | `cache.db` |
| `CACHE_TTL` | `cache.ttl` |
## CLI-флаги
CLI-флаги имеют наивысший приоритет и переопределяют значения из `config.yml` и переменных окружения. Все флаги используют zero-value по умолчанию: переопределение срабатывает, только если флаг задан явно (через `cmd.Flags().Changed()`).
Порядок применения в обработчиках команд:
```
app.Init() → defaults → config.yml → env → logger
applyAppOverrides(cmd) → app.* через Changed()
applyCacheOverrides(cmd) → cache.* через Changed()
applyCheckOverrides(cmd) → check.* через Changed()
app.InitCache() → подключение к KeyDB/Redis
```
### Глобальные флаги
### Флаги путей
Доступны для `check` и `serve`.
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| -------------- | ------ | ---------------------------- | ---------------------- |
| `-i`, `--ini` | string | `app.playlists` | Путь к `playlists.ini` |
| `-t`, `--tags` | string | `app.tags` | Путь к `channels.json` |
### Флаги итерации
Доступны для `check` и `serve`.
| Флаг | Тип | По умолчанию | Описание |
| ---------------- | ---- | ------------------------------ | --------------------------------------------- |
| `-r`, `--random` | uint | `0` | Проверить N случайных плейлистов из ini-файла |
| `--repeat` | uint | `1` (`check`) / `0` (`serve`) | Количество циклов (0 = бесконечно) |
| `--every` | uint | `5` (`check`) / `60` (`serve`) | Секунд между циклами |
### Флаги проверки плейлистов
Доступны для `check` и `serve`. Переопределяют секцию `check.playlists`.
| Флаг | Тип | Соответствует в `config.yml` | Единица | Описание |
| -------------------------- | -------- | ------------------------------ | ------- | -------------------------------- |
| `--playlists-timeout` | int | `check.playlists.timeout` | мс | Таймаут запроса плейлиста |
| `--playlists-all-cooldown` | int | `check.playlists.all-cooldown` | мс | Задержка после всех плейлистов |
| `--playlists-one-cooldown` | int | `check.playlists.one-cooldown` | мс | Задержка после каждого плейлиста |
| `--playlists-max-routines` | int | `check.playlists.max-routines` | шт | Максимум параллельных проверок |
| `--playlists-per-routine` | int | `check.playlists.per-routine` | шт | Плейлистов на процедуру |
| `--playlists-user-agent` | string[] | `check.playlists.user-agent` | — | User-Agent (можно несколько) |
### Флаги проверки каналов
Доступны для `check` и `serve`. Переопределяют секцию `check.channels`.
| Флаг | Тип | Соответствует в `config.yml` | Единица | Описание |
| ------------------------- | -------- | ----------------------------- | ------- | ------------------------------ |
| `--channels-timeout` | int | `check.channels.timeout` | мс | Таймаут запроса канала |
| `--channels-byte-range` | int | `check.channels.byte-range` | байт | Объём данных от сервера |
| `--channels-cooldown` | int | `check.channels.cooldown` | мс | Задержка после каждого канала |
| `--channels-max-routines` | int | `check.channels.max-routines` | шт | Максимум параллельных проверок |
| `--channels-per-routine` | int | `check.channels.per-routine` | шт | Каналов на процедуру |
| `--channels-user-agent` | string[] | `check.channels.user-agent` | — | User-Agent (можно несколько) |
### Флаги кеша
Доступны для `check` и `serve`. Переопределяют секцию `cache`.
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| ------------------ | ------ | ---------------------------- | ----------------- |
| `--cache-enabled` | bool | `cache.enabled` | Включить кеш |
| `--cache-host` | string | `cache.host` | Хост KeyDB/Redis |
| `--cache-port` | uint | `cache.port` | Порт KeyDB/Redis |
| `--cache-username` | string | `cache.username` | Логин |
| `--cache-password` | string | `cache.password` | Пароль |
| `--cache-db` | uint | `cache.db` | Номер БД |
| `--cache-ttl` | uint | `cache.ttl` | TTL записей (сек) |
### Флаги только для `serve`
| Флаг | Тип | Соответствует в `config.yml` | Описание |
| -------------- | ------ | ---------------------------- | ------------------------- |
| `-p`, `--port` | uint | `server.port` | Порт веб-сервера |
| `--host` | string | `server.host` | Хост привязки |
| `--check` | bool | — | Включить фоновую проверку |
### Флаги только для `check`
| Флаг | Тип | Описание |
| --------------- | -------- | -------------------------- |
| `-j`, `--json` | bool | Вывод результатов в JSON |
| `-q`, `--quiet` | bool | Подавить логи |
| `-f`, `--file` | string[] | Локальный m3u-файл |
| `-u`, `--url` | string[] | URL удалённого плейлиста |
| `-c`, `--code` | string[] | Код плейлиста из ini-файла |
+116
View File
@@ -0,0 +1,116 @@
---
icon: simple/dotenv
tags: ["iptvc", "переменные окружения"]
---
# :simple-dotenv: Переменные окружения
Переменные окружения переопределяют значения из [`config.yml`](config.md).
Файл `.env` загружается автоматически при запуске.
Приоритет: **defaults → config.yml → env → CLI-флаги**.
## Приложение
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------------------- | ----------------------------------------- |
| `APP_DEBUG` | bool | `false` | Режим отладки |
| `APP_LOG_LEVEL` | string | `info` | Уровень логирования (`debug`, `info`, `warn`, `error`) |
| `APP_TIMEZONE` | string | `GMT` | Часовой пояс |
| `APP_PLAYLISTS` | string | `./playlists.ini` | Путь к `playlists.ini` |
| `APP_TAGS` | string | `./channels.json` | Путь к `channels.json` |
## Веб-сервер и сайт
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------------------------- | ----------------------------------- |
| `WEB_PORT` | uint | `8080` | Порт веб-сервера |
| `WEB_HOST` | string | (пусто) | Хост для привязки |
| `APP_URL` | string | `http://localhost:8080` | Базовый URL для ссылок |
| `PAGE_SIZE` | uint | `0` | Размер страницы (0 — без пагинации) |
| `REPO_URL` | string | `https://git.axenov.dev/IPTV` | Ссылка на репозиторий |
| `SITE_FAVICON` | string | (пусто) | Путь к иконке сайта |
| `APP_TITLE` | string | `IPTV Checker` | Заголовок сайта |
## Проверка
| Имя | Тип | Умолчание | Назначение |
| ----------------------- | --- | --------- | ------------------------------------------------------- |
| `CHECK_START_ON_SERVE` | bool | `false` | Запустить фоновую проверку при `serve` без флага `--check` |
### `check.playlists`
| Имя | Тип | Умолчание | Назначение |
| --- | --- | --- | --- |
| `CHECK_PLAYLISTS_TIMEOUT` | int | `10000` | Таймаут запроса плейлиста (мс) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN` | int | `0` | Задержка после всех плейлистов (мс, скаляр) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MIN` | int | `0` | Минимум задержки после всех плейлистов (мс) |
| `CHECK_PLAYLISTS_ALL_COOLDOWN_MAX` | int | `0` | Максимум задержки после всех плейлистов (мс) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN` | int | `0` | Задержка после каждого плейлиста (мс, скаляр) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MIN` | int | `0` | Минимум задержки после каждого плейлиста (мс) |
| `CHECK_PLAYLISTS_ONE_COOLDOWN_MAX` | int | `0` | Максимум задержки после каждого плейлиста (мс) |
| `CHECK_PLAYLISTS_MAX_ROUTINES` | int | `5` | Максимум параллельных проверок плейлистов |
| `CHECK_PLAYLISTS_PER_ROUTINE` | int | `1` | Плейлистов на процедуру |
| `CHECK_PLAYLISTS_USER_AGENT_1` | string | `Mozilla/5.0 …` | Первый User-Agent для запросов плейлистов |
| `CHECK_PLAYLISTS_USER_AGENT_2` | string | — | Второй User-Agent (и т.д.) |
### `check.channels`
| Имя | Тип | Умолчание | Назначение |
| --- | --- | --- | --- |
| `CHECK_CHANNELS_TIMEOUT` | int | `10000` | Таймаут запроса канала (мс) |
| `CHECK_CHANNELS_BYTE_RANGE` | int | `512` | Объём данных от сервера (байт) |
| `CHECK_CHANNELS_COOLDOWN` | int | `0` | Задержка после каждого канала (мс, скаляр) |
| `CHECK_CHANNELS_COOLDOWN_MIN` | int | `0` | Минимум задержки после каждого канала (мс) |
| `CHECK_CHANNELS_COOLDOWN_MAX` | int | `0` | Максимум задержки после каждого канала (мс) |
| `CHECK_CHANNELS_MAX_ROUTINES` | int | `50` | Максимум параллельных проверок каналов |
| `CHECK_CHANNELS_PER_ROUTINE` | int | `10` | Каналов на процедуру |
| `CHECK_CHANNELS_USER_AGENT_1` | string | `Mozilla/5.0 …` | Первый User-Agent для запросов каналов |
| `CHECK_CHANNELS_USER_AGENT_2` | string | — | Второй User-Agent (и т.д.) |
## Кеширование
Кеш хранится в СУБД redis или keydb.
| Имя | Тип | Умолчание | Назначение |
| ---------------- | ------ | ----------- | ---------------------------------- |
| `CACHE_ENABLED` | bool | `false` | Включает кеширование |
| `CACHE_HOST` | string | `localhost` | Имя хоста СУБД |
| `CACHE_PORT` | uint | `6379` | Порт СУБД |
| `CACHE_USERNAME` | string | (пусто) | Логин пользователя в СУБД |
| `CACHE_PASSWORD` | string | (пусто) | Пароль пользователя в СУБД |
| `CACHE_DB` | uint | `0` | Номер БД в СУБД для кеша |
| `CACHE_TTL` | uint | `1800` | Время жизни ключей кеша в секундах |
## Правила
### Диапазоны cooldown
Поля `all-cooldown`, `one-cooldown` и `channels.cooldown` поддерживают скаляр и диапазон `[min, max]`.
Через env-переменные:
- Заданы **обе** `_MIN` и `_MAX` → диапазон (случайное значение при каждой проверке).
- Задана только **скалярная** переменная (без суффикса) → фиксированное значение.
- Задана только **одна** из `_MIN` / `_MAX` → переменная игнорируется.
```shell title="Скаляр"
CHECK_PLAYLISTS_ALL_COOLDOWN=500
```
```shell title="Диапазон"
CHECK_PLAYLISTS_ALL_COOLDOWN_MIN=100
CHECK_PLAYLISTS_ALL_COOLDOWN_MAX=2000
```
### Массивы user-agent
Поля `user-agent` поддерживают массив строк. Через env задаются индексированными переменными `_1`, `_2`, `_3`, …
- Чтение останавливается на первой отсутствующей переменной.
- Пустые значения пропускаются.
```shell title="Два User-Agent"
CHECK_PLAYLISTS_USER_AGENT_1=Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
CHECK_PLAYLISTS_USER_AGENT_2=curl/8.0
```
+24
View File
@@ -0,0 +1,24 @@
---
title: Компиляция
icon: material/cog
---
# Компиляция из исходного кода
Для компиляции потребуется golang v1.23.6 и выше.
На версиях ниже не проверялось.
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
make linux
# или make help для получения помощи по компиляции
```
Поддерживается передача переменной `GOARCH`:
```shell
make darwin GOARCH=arm64
```
Скомпилированные файлы находятся в директории `bin/`.
+27
View File
@@ -0,0 +1,27 @@
---
title: Docker-образ
icon: simple/docker
---
# Построение Docker-образа
Предполагается выполнение в директории с исходниками.
```
./build-docker-image.sh [<версия>]
```
где `<версия>` — необязательный тег версии в формате `vX.Y.Z`.
Если не указан, то будет взят последний.
Целевая платформа и архитектура меняется с помощью переменных `GOOS` и `GOARCH`:
```
GOOS=darwin GOARCH=arm64 ./build-docker-image.sh [<версия>]
```
Запуск:
```
docker run --pull always --name iptvc git.axenov.dev/iptv/iptvc КОМАНДА [АРГУМЕНТЫ]
```
+284
View File
@@ -0,0 +1,284 @@
# Песочница
## Мелочёвка
=== "Frontmatter"
```yaml
---
title: My Page
description: Some page description
icon: material/star
tags: [tag1, tag2]
hide: [navigation, toc, path]
status: new
#status: deprecated
#status: beta
---
# My Super-Duper Page
# Markdown content goes here
```
=== "Бейджи"
<!-- md:version 8.5.0 --> <!-- md:default true --> <!-- md:flag experimental -->
=== "Тултипы"
:material-information-outline:{ title="текст подсказки" }
[Hover me 1](https://example.com "I'm first tooltip!")
[Hover me 2][example]
[example]: https://example.com "I'm second tooltip!"
=== "Кнопки"
[Серая кнопка](https://example.com/){ .md-button }
[Синяя кнопка](https://example.com/){ .md-button .md-button--primary }
[:fontawesome-solid-paper-plane: Кнопка серая с иконкой и подсказкой](https://example.com/){ .md-button title="текст подсказки 2" }
[:fontawesome-solid-paper-plane: Кнопка синяя с иконкой](https://example.com/){ .md-button .md-button--primary }
---
=== "Простой блок"
```python
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С аннотациями"
```python
def _render_icon(shortcode: str, md) -> str: #(1)!
emoji_pattern = md.inlinePatterns.get("emoji") #(2)!
if emoji_pattern is None:
return escape(shortcode) #(3)!
```
1. сигнатура функции
2. инициализация переменной
3. возврат результата
=== "С заголовком"
```python title="example.py"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С нумерацией с 5"
```python linenums="5"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С выделением"
```python linenums="1" hl_lines="2-5 8 9 22"
def _render_icon(shortcode: str, md) -> str:
try:
emoji_pattern = md.inlinePatterns["emoji"]
except KeyError:
return escape(shortcode)
icon = emoji_pattern.emoji_index["emoji"].get(shortcode)
if icon is None:
return escape(shortcode)
element = emoji_pattern.generator(
emoji_pattern.emoji_index["name"],
shortcode,
None,
None,
shortcode,
"",
icon.get("category", ""),
emoji_pattern.options,
md,
)
return tostring(element, encoding="unicode", method="html")
```
---
## Врезки
=== "Полные"
!!! note "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! abstract "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! info "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! tip "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! success "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! question "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! warning "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! failure "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! danger "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! bug "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! example "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! quote "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
=== "Свёрнутые"
??? note "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? abstract "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? info "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? tip "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? success "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? question "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? warning "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? failure "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? danger "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? bug "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? example "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? quote "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Сворачиваемые"
???+ note "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ abstract "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ info "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ tip "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ success "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ question "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ warning "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ failure "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ danger "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ bug "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ example "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ quote "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Короткие"
!!! note
Дефолтный заголовок
!!! abstract ""
Пустой заголовок
!!! info "Пустое содержимое"
---
@@ -6,7 +6,7 @@ icon: material/book-cog-outline
## Стилистика и правила оформления
Все исходники хранятся в директории `src/` в формате **Markdown** (формат файлов `.md`).
Все исходники хранятся в директории `content/` в формате **Markdown** (формат файлов `.md`).
Структура проекта и его конфигурация описываются в файле `mkdocs.yml` в корне репозитория.
@@ -24,7 +24,7 @@ icon: material/book-cog-outline
## Добавление изображений
**Все изображения хранятся только в директории `src/assets/img/` и вложенных в неё.**
**Все изображения хранятся только в директории `content/_assets/img/` и вложенных в неё.**
Общая суть такова:
@@ -49,8 +49,8 @@ icon: material/book-cog-outline
Совершенно любой текст, lorem ipsum dolor sit amet.
??? quote "В этом спойлере несколько больших картинок"
![Подпись-плейсхолдер1](../assets/img/example1.jpg)
![Подпись-плейсхолдер2](../assets/img/example2.jpg)
![Подпись-плейсхолдер1](../_assets/img/example1.jpg)
![Подпись-плейсхолдер2](../_assets/img/example2.jpg)
Продолжение текста, lorem ipsum dolor sit amet.
Продолжение текста, lorem ipsum dolor sit amet.
+550
View File
@@ -0,0 +1,550 @@
| Иконка | Код для вставки в текст | Код для вставки в frontmatter |
| ------------------------------------------------ | -------------------------------------------------- | ------------------------------------------------ |
| :vscode-account: | `:vscode-account:` | `vscode/account` |
| :vscode-activate-breakpoints: | `:vscode-activate-breakpoints:` | `vscode/activate-breakpoints` |
| :vscode-add: | `:vscode-add:` | `vscode/add` |
| :vscode-add-small: | `:vscode-add-small:` | `vscode/add-small` |
| :vscode-agent: | `:vscode-agent:` | `vscode/agent` |
| :vscode-archive: | `:vscode-archive:` | `vscode/archive` |
| :vscode-arrow-both: | `:vscode-arrow-both:` | `vscode/arrow-both` |
| :vscode-arrow-circle-down: | `:vscode-arrow-circle-down:` | `vscode/arrow-circle-down` |
| :vscode-arrow-circle-left: | `:vscode-arrow-circle-left:` | `vscode/arrow-circle-left` |
| :vscode-arrow-circle-right: | `:vscode-arrow-circle-right:` | `vscode/arrow-circle-right` |
| :vscode-arrow-circle-up: | `:vscode-arrow-circle-up:` | `vscode/arrow-circle-up` |
| :vscode-arrow-down: | `:vscode-arrow-down:` | `vscode/arrow-down` |
| :vscode-arrow-left: | `:vscode-arrow-left:` | `vscode/arrow-left` |
| :vscode-arrow-right: | `:vscode-arrow-right:` | `vscode/arrow-right` |
| :vscode-arrow-small-down: | `:vscode-arrow-small-down:` | `vscode/arrow-small-down` |
| :vscode-arrow-small-left: | `:vscode-arrow-small-left:` | `vscode/arrow-small-left` |
| :vscode-arrow-small-right: | `:vscode-arrow-small-right:` | `vscode/arrow-small-right` |
| :vscode-arrow-small-up: | `:vscode-arrow-small-up:` | `vscode/arrow-small-up` |
| :vscode-arrow-swap: | `:vscode-arrow-swap:` | `vscode/arrow-swap` |
| :vscode-arrow-up: | `:vscode-arrow-up:` | `vscode/arrow-up` |
| :vscode-ask: | `:vscode-ask:` | `vscode/ask` |
| :vscode-attach: | `:vscode-attach:` | `vscode/attach` |
| :vscode-azure: | `:vscode-azure:` | `vscode/azure` |
| :vscode-azure-devops: | `:vscode-azure-devops:` | `vscode/azure-devops` |
| :vscode-beaker: | `:vscode-beaker:` | `vscode/beaker` |
| :vscode-beaker-stop: | `:vscode-beaker-stop:` | `vscode/beaker-stop` |
| :vscode-bell: | `:vscode-bell:` | `vscode/bell` |
| :vscode-bell-dot: | `:vscode-bell-dot:` | `vscode/bell-dot` |
| :vscode-bell-slash: | `:vscode-bell-slash:` | `vscode/bell-slash` |
| :vscode-bell-slash-dot: | `:vscode-bell-slash-dot:` | `vscode/bell-slash-dot` |
| :vscode-blank: | `:vscode-blank:` | `vscode/blank` |
| :vscode-bold: | `:vscode-bold:` | `vscode/bold` |
| :vscode-book: | `:vscode-book:` | `vscode/book` |
| :vscode-bookmark: | `:vscode-bookmark:` | `vscode/bookmark` |
| :vscode-bracket-dot: | `:vscode-bracket-dot:` | `vscode/bracket-dot` |
| :vscode-bracket-error: | `:vscode-bracket-error:` | `vscode/bracket-error` |
| :vscode-briefcase: | `:vscode-briefcase:` | `vscode/briefcase` |
| :vscode-broadcast: | `:vscode-broadcast:` | `vscode/broadcast` |
| :vscode-browser: | `:vscode-browser:` | `vscode/browser` |
| :vscode-bug: | `:vscode-bug:` | `vscode/bug` |
| :vscode-build: | `:vscode-build:` | `vscode/build` |
| :vscode-calendar: | `:vscode-calendar:` | `vscode/calendar` |
| :vscode-call-incoming: | `:vscode-call-incoming:` | `vscode/call-incoming` |
| :vscode-call-outgoing: | `:vscode-call-outgoing:` | `vscode/call-outgoing` |
| :vscode-case-sensitive: | `:vscode-case-sensitive:` | `vscode/case-sensitive` |
| :vscode-chat-export: | `:vscode-chat-export:` | `vscode/chat-export` |
| :vscode-chat-import: | `:vscode-chat-import:` | `vscode/chat-import` |
| :vscode-chat-sparkle: | `:vscode-chat-sparkle:` | `vscode/chat-sparkle` |
| :vscode-chat-sparkle-error: | `:vscode-chat-sparkle-error:` | `vscode/chat-sparkle-error` |
| :vscode-chat-sparkle-warning: | `:vscode-chat-sparkle-warning:` | `vscode/chat-sparkle-warning` |
| :vscode-check: | `:vscode-check:` | `vscode/check` |
| :vscode-check-all: | `:vscode-check-all:` | `vscode/check-all` |
| :vscode-checklist: | `:vscode-checklist:` | `vscode/checklist` |
| :vscode-chevron-down: | `:vscode-chevron-down:` | `vscode/chevron-down` |
| :vscode-chevron-left: | `:vscode-chevron-left:` | `vscode/chevron-left` |
| :vscode-chevron-right: | `:vscode-chevron-right:` | `vscode/chevron-right` |
| :vscode-chevron-up: | `:vscode-chevron-up:` | `vscode/chevron-up` |
| :vscode-chip: | `:vscode-chip:` | `vscode/chip` |
| :vscode-chrome-close: | `:vscode-chrome-close:` | `vscode/chrome-close` |
| :vscode-chrome-maximize: | `:vscode-chrome-maximize:` | `vscode/chrome-maximize` |
| :vscode-chrome-minimize: | `:vscode-chrome-minimize:` | `vscode/chrome-minimize` |
| :vscode-chrome-restore: | `:vscode-chrome-restore:` | `vscode/chrome-restore` |
| :vscode-circle: | `:vscode-circle:` | `vscode/circle` |
| :vscode-circle-filled: | `:vscode-circle-filled:` | `vscode/circle-filled` |
| :vscode-circle-large: | `:vscode-circle-large:` | `vscode/circle-large` |
| :vscode-circle-large-filled: | `:vscode-circle-large-filled:` | `vscode/circle-large-filled` |
| :vscode-circle-slash: | `:vscode-circle-slash:` | `vscode/circle-slash` |
| :vscode-circle-small: | `:vscode-circle-small:` | `vscode/circle-small` |
| :vscode-circle-small-filled: | `:vscode-circle-small-filled:` | `vscode/circle-small-filled` |
| :vscode-circuit-board: | `:vscode-circuit-board:` | `vscode/circuit-board` |
| :vscode-claude: | `:vscode-claude:` | `vscode/claude` |
| :vscode-clear-all: | `:vscode-clear-all:` | `vscode/clear-all` |
| :vscode-clippy: | `:vscode-clippy:` | `vscode/clippy` |
| :vscode-clockface: | `:vscode-clockface:` | `vscode/clockface` |
| :vscode-close: | `:vscode-close:` | `vscode/close` |
| :vscode-close-all: | `:vscode-close-all:` | `vscode/close-all` |
| :vscode-cloud: | `:vscode-cloud:` | `vscode/cloud` |
| :vscode-cloud-download: | `:vscode-cloud-download:` | `vscode/cloud-download` |
| :vscode-cloud-small: | `:vscode-cloud-small:` | `vscode/cloud-small` |
| :vscode-cloud-upload: | `:vscode-cloud-upload:` | `vscode/cloud-upload` |
| :vscode-code: | `:vscode-code:` | `vscode/code` |
| :vscode-code-oss: | `:vscode-code-oss:` | `vscode/code-oss` |
| :vscode-code-review: | `:vscode-code-review:` | `vscode/code-review` |
| :vscode-coffee: | `:vscode-coffee:` | `vscode/coffee` |
| :vscode-collapse-all: | `:vscode-collapse-all:` | `vscode/collapse-all` |
| :vscode-collection: | `:vscode-collection:` | `vscode/collection` |
| :vscode-collection-small: | `:vscode-collection-small:` | `vscode/collection-small` |
| :vscode-color-mode: | `:vscode-color-mode:` | `vscode/color-mode` |
| :vscode-combine: | `:vscode-combine:` | `vscode/combine` |
| :vscode-comment: | `:vscode-comment:` | `vscode/comment` |
| :vscode-comment-discussion: | `:vscode-comment-discussion:` | `vscode/comment-discussion` |
| :vscode-comment-discussion-quote: | `:vscode-comment-discussion-quote:` | `vscode/comment-discussion-quote` |
| :vscode-comment-discussion-sparkle: | `:vscode-comment-discussion-sparkle:` | `vscode/comment-discussion-sparkle` |
| :vscode-comment-draft: | `:vscode-comment-draft:` | `vscode/comment-draft` |
| :vscode-comment-unresolved: | `:vscode-comment-unresolved:` | `vscode/comment-unresolved` |
| :vscode-compass: | `:vscode-compass:` | `vscode/compass` |
| :vscode-compass-active: | `:vscode-compass-active:` | `vscode/compass-active` |
| :vscode-compass-dot: | `:vscode-compass-dot:` | `vscode/compass-dot` |
| :vscode-copilot: | `:vscode-copilot:` | `vscode/copilot` |
| :vscode-copilot-blocked: | `:vscode-copilot-blocked:` | `vscode/copilot-blocked` |
| :vscode-copilot-error: | `:vscode-copilot-error:` | `vscode/copilot-error` |
| :vscode-copilot-in-progress: | `:vscode-copilot-in-progress:` | `vscode/copilot-in-progress` |
| :vscode-copilot-large: | `:vscode-copilot-large:` | `vscode/copilot-large` |
| :vscode-copilot-not-connected: | `:vscode-copilot-not-connected:` | `vscode/copilot-not-connected` |
| :vscode-copilot-snooze: | `:vscode-copilot-snooze:` | `vscode/copilot-snooze` |
| :vscode-copilot-success: | `:vscode-copilot-success:` | `vscode/copilot-success` |
| :vscode-copilot-unavailable: | `:vscode-copilot-unavailable:` | `vscode/copilot-unavailable` |
| :vscode-copilot-warning: | `:vscode-copilot-warning:` | `vscode/copilot-warning` |
| :vscode-copilot-warning-large: | `:vscode-copilot-warning-large:` | `vscode/copilot-warning-large` |
| :vscode-copy: | `:vscode-copy:` | `vscode/copy` |
| :vscode-coverage: | `:vscode-coverage:` | `vscode/coverage` |
| :vscode-credit-card: | `:vscode-credit-card:` | `vscode/credit-card` |
| :vscode-cursor: | `:vscode-cursor:` | `vscode/cursor` |
| :vscode-dash: | `:vscode-dash:` | `vscode/dash` |
| :vscode-dashboard: | `:vscode-dashboard:` | `vscode/dashboard` |
| :vscode-database: | `:vscode-database:` | `vscode/database` |
| :vscode-debug: | `:vscode-debug:` | `vscode/debug` |
| :vscode-debug-all: | `:vscode-debug-all:` | `vscode/debug-all` |
| :vscode-debug-alt: | `:vscode-debug-alt:` | `vscode/debug-alt` |
| :vscode-debug-alt-small: | `:vscode-debug-alt-small:` | `vscode/debug-alt-small` |
| :vscode-debug-breakpoint-conditional: | `:vscode-debug-breakpoint-conditional:` | `vscode/debug-breakpoint-conditional` |
| :vscode-debug-breakpoint-conditional-unverified: | `:vscode-debug-breakpoint-conditional-unverified:` | `vscode/debug-breakpoint-conditional-unverified` |
| :vscode-debug-breakpoint-data: | `:vscode-debug-breakpoint-data:` | `vscode/debug-breakpoint-data` |
| :vscode-debug-breakpoint-data-unverified: | `:vscode-debug-breakpoint-data-unverified:` | `vscode/debug-breakpoint-data-unverified` |
| :vscode-debug-breakpoint-function: | `:vscode-debug-breakpoint-function:` | `vscode/debug-breakpoint-function` |
| :vscode-debug-breakpoint-function-unverified: | `:vscode-debug-breakpoint-function-unverified:` | `vscode/debug-breakpoint-function-unverified` |
| :vscode-debug-breakpoint-log: | `:vscode-debug-breakpoint-log:` | `vscode/debug-breakpoint-log` |
| :vscode-debug-breakpoint-log-unverified: | `:vscode-debug-breakpoint-log-unverified:` | `vscode/debug-breakpoint-log-unverified` |
| :vscode-debug-breakpoint-unsupported: | `:vscode-debug-breakpoint-unsupported:` | `vscode/debug-breakpoint-unsupported` |
| :vscode-debug-connected: | `:vscode-debug-connected:` | `vscode/debug-connected` |
| :vscode-debug-console: | `:vscode-debug-console:` | `vscode/debug-console` |
| :vscode-debug-continue: | `:vscode-debug-continue:` | `vscode/debug-continue` |
| :vscode-debug-continue-small: | `:vscode-debug-continue-small:` | `vscode/debug-continue-small` |
| :vscode-debug-coverage: | `:vscode-debug-coverage:` | `vscode/debug-coverage` |
| :vscode-debug-disconnect: | `:vscode-debug-disconnect:` | `vscode/debug-disconnect` |
| :vscode-debug-line-by-line: | `:vscode-debug-line-by-line:` | `vscode/debug-line-by-line` |
| :vscode-debug-pause: | `:vscode-debug-pause:` | `vscode/debug-pause` |
| :vscode-debug-rerun: | `:vscode-debug-rerun:` | `vscode/debug-rerun` |
| :vscode-debug-restart: | `:vscode-debug-restart:` | `vscode/debug-restart` |
| :vscode-debug-restart-frame: | `:vscode-debug-restart-frame:` | `vscode/debug-restart-frame` |
| :vscode-debug-reverse-continue: | `:vscode-debug-reverse-continue:` | `vscode/debug-reverse-continue` |
| :vscode-debug-stackframe: | `:vscode-debug-stackframe:` | `vscode/debug-stackframe` |
| :vscode-debug-stackframe-active: | `:vscode-debug-stackframe-active:` | `vscode/debug-stackframe-active` |
| :vscode-debug-start: | `:vscode-debug-start:` | `vscode/debug-start` |
| :vscode-debug-step-back: | `:vscode-debug-step-back:` | `vscode/debug-step-back` |
| :vscode-debug-step-into: | `:vscode-debug-step-into:` | `vscode/debug-step-into` |
| :vscode-debug-step-out: | `:vscode-debug-step-out:` | `vscode/debug-step-out` |
| :vscode-debug-step-over: | `:vscode-debug-step-over:` | `vscode/debug-step-over` |
| :vscode-debug-stop: | `:vscode-debug-stop:` | `vscode/debug-stop` |
| :vscode-desktop-download: | `:vscode-desktop-download:` | `vscode/desktop-download` |
| :vscode-device-camera: | `:vscode-device-camera:` | `vscode/device-camera` |
| :vscode-device-camera-video: | `:vscode-device-camera-video:` | `vscode/device-camera-video` |
| :vscode-device-mobile: | `:vscode-device-mobile:` | `vscode/device-mobile` |
| :vscode-diff: | `:vscode-diff:` | `vscode/diff` |
| :vscode-diff-added: | `:vscode-diff-added:` | `vscode/diff-added` |
| :vscode-diff-ignored: | `:vscode-diff-ignored:` | `vscode/diff-ignored` |
| :vscode-diff-modified: | `:vscode-diff-modified:` | `vscode/diff-modified` |
| :vscode-diff-multiple: | `:vscode-diff-multiple:` | `vscode/diff-multiple` |
| :vscode-diff-removed: | `:vscode-diff-removed:` | `vscode/diff-removed` |
| :vscode-diff-renamed: | `:vscode-diff-renamed:` | `vscode/diff-renamed` |
| :vscode-diff-single: | `:vscode-diff-single:` | `vscode/diff-single` |
| :vscode-discard: | `:vscode-discard:` | `vscode/discard` |
| :vscode-download: | `:vscode-download:` | `vscode/download` |
| :vscode-edit: | `:vscode-edit:` | `vscode/edit` |
| :vscode-edit-code: | `:vscode-edit-code:` | `vscode/edit-code` |
| :vscode-edit-session: | `:vscode-edit-session:` | `vscode/edit-session` |
| :vscode-edit-sparkle: | `:vscode-edit-sparkle:` | `vscode/edit-sparkle` |
| :vscode-editor-layout: | `:vscode-editor-layout:` | `vscode/editor-layout` |
| :vscode-ellipsis: | `:vscode-ellipsis:` | `vscode/ellipsis` |
| :vscode-empty-window: | `:vscode-empty-window:` | `vscode/empty-window` |
| :vscode-eraser: | `:vscode-eraser:` | `vscode/eraser` |
| :vscode-error: | `:vscode-error:` | `vscode/error` |
| :vscode-error-small: | `:vscode-error-small:` | `vscode/error-small` |
| :vscode-exclude: | `:vscode-exclude:` | `vscode/exclude` |
| :vscode-expand-all: | `:vscode-expand-all:` | `vscode/expand-all` |
| :vscode-export: | `:vscode-export:` | `vscode/export` |
| :vscode-extensions: | `:vscode-extensions:` | `vscode/extensions` |
| :vscode-extensions-large: | `:vscode-extensions-large:` | `vscode/extensions-large` |
| :vscode-eye: | `:vscode-eye:` | `vscode/eye` |
| :vscode-eye-closed: | `:vscode-eye-closed:` | `vscode/eye-closed` |
| :vscode-feedback: | `:vscode-feedback:` | `vscode/feedback` |
| :vscode-file: | `:vscode-file:` | `vscode/file` |
| :vscode-file-binary: | `:vscode-file-binary:` | `vscode/file-binary` |
| :vscode-file-code: | `:vscode-file-code:` | `vscode/file-code` |
| :vscode-file-media: | `:vscode-file-media:` | `vscode/file-media` |
| :vscode-file-pdf: | `:vscode-file-pdf:` | `vscode/file-pdf` |
| :vscode-file-submodule: | `:vscode-file-submodule:` | `vscode/file-submodule` |
| :vscode-file-symlink-directory: | `:vscode-file-symlink-directory:` | `vscode/file-symlink-directory` |
| :vscode-file-symlink-file: | `:vscode-file-symlink-file:` | `vscode/file-symlink-file` |
| :vscode-file-text: | `:vscode-file-text:` | `vscode/file-text` |
| :vscode-file-zip: | `:vscode-file-zip:` | `vscode/file-zip` |
| :vscode-files: | `:vscode-files:` | `vscode/files` |
| :vscode-filter: | `:vscode-filter:` | `vscode/filter` |
| :vscode-filter-filled: | `:vscode-filter-filled:` | `vscode/filter-filled` |
| :vscode-flag: | `:vscode-flag:` | `vscode/flag` |
| :vscode-flame: | `:vscode-flame:` | `vscode/flame` |
| :vscode-fold: | `:vscode-fold:` | `vscode/fold` |
| :vscode-fold-down: | `:vscode-fold-down:` | `vscode/fold-down` |
| :vscode-fold-up: | `:vscode-fold-up:` | `vscode/fold-up` |
| :vscode-folder: | `:vscode-folder:` | `vscode/folder` |
| :vscode-folder-active: | `:vscode-folder-active:` | `vscode/folder-active` |
| :vscode-folder-library: | `:vscode-folder-library:` | `vscode/folder-library` |
| :vscode-folder-opened: | `:vscode-folder-opened:` | `vscode/folder-opened` |
| :vscode-forward: | `:vscode-forward:` | `vscode/forward` |
| :vscode-game: | `:vscode-game:` | `vscode/game` |
| :vscode-gear: | `:vscode-gear:` | `vscode/gear` |
| :vscode-gift: | `:vscode-gift:` | `vscode/gift` |
| :vscode-gist: | `:vscode-gist:` | `vscode/gist` |
| :vscode-gist-secret: | `:vscode-gist-secret:` | `vscode/gist-secret` |
| :vscode-git-branch: | `:vscode-git-branch:` | `vscode/git-branch` |
| :vscode-git-branch-changes: | `:vscode-git-branch-changes:` | `vscode/git-branch-changes` |
| :vscode-git-branch-conflicts: | `:vscode-git-branch-conflicts:` | `vscode/git-branch-conflicts` |
| :vscode-git-branch-staged-changes: | `:vscode-git-branch-staged-changes:` | `vscode/git-branch-staged-changes` |
| :vscode-git-commit: | `:vscode-git-commit:` | `vscode/git-commit` |
| :vscode-git-compare: | `:vscode-git-compare:` | `vscode/git-compare` |
| :vscode-git-fetch: | `:vscode-git-fetch:` | `vscode/git-fetch` |
| :vscode-git-merge: | `:vscode-git-merge:` | `vscode/git-merge` |
| :vscode-git-pull-request: | `:vscode-git-pull-request:` | `vscode/git-pull-request` |
| :vscode-git-pull-request-closed: | `:vscode-git-pull-request-closed:` | `vscode/git-pull-request-closed` |
| :vscode-git-pull-request-create: | `:vscode-git-pull-request-create:` | `vscode/git-pull-request-create` |
| :vscode-git-pull-request-done: | `:vscode-git-pull-request-done:` | `vscode/git-pull-request-done` |
| :vscode-git-pull-request-draft: | `:vscode-git-pull-request-draft:` | `vscode/git-pull-request-draft` |
| :vscode-git-pull-request-go-to-changes: | `:vscode-git-pull-request-go-to-changes:` | `vscode/git-pull-request-go-to-changes` |
| :vscode-git-pull-request-new-changes: | `:vscode-git-pull-request-new-changes:` | `vscode/git-pull-request-new-changes` |
| :vscode-git-stash: | `:vscode-git-stash:` | `vscode/git-stash` |
| :vscode-git-stash-apply: | `:vscode-git-stash-apply:` | `vscode/git-stash-apply` |
| :vscode-git-stash-pop: | `:vscode-git-stash-pop:` | `vscode/git-stash-pop` |
| :vscode-github: | `:vscode-github:` | `vscode/github` |
| :vscode-github-action: | `:vscode-github-action:` | `vscode/github-action` |
| :vscode-github-alt: | `:vscode-github-alt:` | `vscode/github-alt` |
| :vscode-github-inverted: | `:vscode-github-inverted:` | `vscode/github-inverted` |
| :vscode-github-project: | `:vscode-github-project:` | `vscode/github-project` |
| :vscode-globe: | `:vscode-globe:` | `vscode/globe` |
| :vscode-go-to-editing-session: | `:vscode-go-to-editing-session:` | `vscode/go-to-editing-session` |
| :vscode-go-to-file: | `:vscode-go-to-file:` | `vscode/go-to-file` |
| :vscode-go-to-search: | `:vscode-go-to-search:` | `vscode/go-to-search` |
| :vscode-grabber: | `:vscode-grabber:` | `vscode/grabber` |
| :vscode-graph: | `:vscode-graph:` | `vscode/graph` |
| :vscode-graph-left: | `:vscode-graph-left:` | `vscode/graph-left` |
| :vscode-graph-line: | `:vscode-graph-line:` | `vscode/graph-line` |
| :vscode-graph-scatter: | `:vscode-graph-scatter:` | `vscode/graph-scatter` |
| :vscode-gripper: | `:vscode-gripper:` | `vscode/gripper` |
| :vscode-group-by-ref-type: | `:vscode-group-by-ref-type:` | `vscode/group-by-ref-type` |
| :vscode-heart: | `:vscode-heart:` | `vscode/heart` |
| :vscode-heart-filled: | `:vscode-heart-filled:` | `vscode/heart-filled` |
| :vscode-history: | `:vscode-history:` | `vscode/history` |
| :vscode-home: | `:vscode-home:` | `vscode/home` |
| :vscode-horizontal-rule: | `:vscode-horizontal-rule:` | `vscode/horizontal-rule` |
| :vscode-hubot: | `:vscode-hubot:` | `vscode/hubot` |
| :vscode-inbox: | `:vscode-inbox:` | `vscode/inbox` |
| :vscode-indent: | `:vscode-indent:` | `vscode/indent` |
| :vscode-index-zero: | `:vscode-index-zero:` | `vscode/index-zero` |
| :vscode-info: | `:vscode-info:` | `vscode/info` |
| :vscode-insert: | `:vscode-insert:` | `vscode/insert` |
| :vscode-inspect: | `:vscode-inspect:` | `vscode/inspect` |
| :vscode-issue-draft: | `:vscode-issue-draft:` | `vscode/issue-draft` |
| :vscode-issue-reopened: | `:vscode-issue-reopened:` | `vscode/issue-reopened` |
| :vscode-issues: | `:vscode-issues:` | `vscode/issues` |
| :vscode-italic: | `:vscode-italic:` | `vscode/italic` |
| :vscode-jersey: | `:vscode-jersey:` | `vscode/jersey` |
| :vscode-json: | `:vscode-json:` | `vscode/json` |
| :vscode-kebab-vertical: | `:vscode-kebab-vertical:` | `vscode/kebab-vertical` |
| :vscode-key: | `:vscode-key:` | `vscode/key` |
| :vscode-keyboard-tab: | `:vscode-keyboard-tab:` | `vscode/keyboard-tab` |
| :vscode-keyboard-tab-above: | `:vscode-keyboard-tab-above:` | `vscode/keyboard-tab-above` |
| :vscode-keyboard-tab-below: | `:vscode-keyboard-tab-below:` | `vscode/keyboard-tab-below` |
| :vscode-law: | `:vscode-law:` | `vscode/law` |
| :vscode-layers: | `:vscode-layers:` | `vscode/layers` |
| :vscode-layers-active: | `:vscode-layers-active:` | `vscode/layers-active` |
| :vscode-layers-dot: | `:vscode-layers-dot:` | `vscode/layers-dot` |
| :vscode-layout: | `:vscode-layout:` | `vscode/layout` |
| :vscode-layout-activitybar-left: | `:vscode-layout-activitybar-left:` | `vscode/layout-activitybar-left` |
| :vscode-layout-activitybar-right: | `:vscode-layout-activitybar-right:` | `vscode/layout-activitybar-right` |
| :vscode-layout-centered: | `:vscode-layout-centered:` | `vscode/layout-centered` |
| :vscode-layout-menubar: | `:vscode-layout-menubar:` | `vscode/layout-menubar` |
| :vscode-layout-panel: | `:vscode-layout-panel:` | `vscode/layout-panel` |
| :vscode-layout-panel-center: | `:vscode-layout-panel-center:` | `vscode/layout-panel-center` |
| :vscode-layout-panel-dock: | `:vscode-layout-panel-dock:` | `vscode/layout-panel-dock` |
| :vscode-layout-panel-justify: | `:vscode-layout-panel-justify:` | `vscode/layout-panel-justify` |
| :vscode-layout-panel-left: | `:vscode-layout-panel-left:` | `vscode/layout-panel-left` |
| :vscode-layout-panel-off: | `:vscode-layout-panel-off:` | `vscode/layout-panel-off` |
| :vscode-layout-panel-right: | `:vscode-layout-panel-right:` | `vscode/layout-panel-right` |
| :vscode-layout-sidebar-left: | `:vscode-layout-sidebar-left:` | `vscode/layout-sidebar-left` |
| :vscode-layout-sidebar-left-dock: | `:vscode-layout-sidebar-left-dock:` | `vscode/layout-sidebar-left-dock` |
| :vscode-layout-sidebar-left-off: | `:vscode-layout-sidebar-left-off:` | `vscode/layout-sidebar-left-off` |
| :vscode-layout-sidebar-right: | `:vscode-layout-sidebar-right:` | `vscode/layout-sidebar-right` |
| :vscode-layout-sidebar-right-dock: | `:vscode-layout-sidebar-right-dock:` | `vscode/layout-sidebar-right-dock` |
| :vscode-layout-sidebar-right-off: | `:vscode-layout-sidebar-right-off:` | `vscode/layout-sidebar-right-off` |
| :vscode-layout-statusbar: | `:vscode-layout-statusbar:` | `vscode/layout-statusbar` |
| :vscode-library: | `:vscode-library:` | `vscode/library` |
| :vscode-lightbulb: | `:vscode-lightbulb:` | `vscode/lightbulb` |
| :vscode-lightbulb-autofix: | `:vscode-lightbulb-autofix:` | `vscode/lightbulb-autofix` |
| :vscode-lightbulb-empty: | `:vscode-lightbulb-empty:` | `vscode/lightbulb-empty` |
| :vscode-lightbulb-sparkle: | `:vscode-lightbulb-sparkle:` | `vscode/lightbulb-sparkle` |
| :vscode-link: | `:vscode-link:` | `vscode/link` |
| :vscode-link-external: | `:vscode-link-external:` | `vscode/link-external` |
| :vscode-list-filter: | `:vscode-list-filter:` | `vscode/list-filter` |
| :vscode-list-flat: | `:vscode-list-flat:` | `vscode/list-flat` |
| :vscode-list-ordered: | `:vscode-list-ordered:` | `vscode/list-ordered` |
| :vscode-list-selection: | `:vscode-list-selection:` | `vscode/list-selection` |
| :vscode-list-tree: | `:vscode-list-tree:` | `vscode/list-tree` |
| :vscode-list-unordered: | `:vscode-list-unordered:` | `vscode/list-unordered` |
| :vscode-live-share: | `:vscode-live-share:` | `vscode/live-share` |
| :vscode-loading: | `:vscode-loading:` | `vscode/loading` |
| :vscode-location: | `:vscode-location:` | `vscode/location` |
| :vscode-lock: | `:vscode-lock:` | `vscode/lock` |
| :vscode-lock-small: | `:vscode-lock-small:` | `vscode/lock-small` |
| :vscode-magnet: | `:vscode-magnet:` | `vscode/magnet` |
| :vscode-mail: | `:vscode-mail:` | `vscode/mail` |
| :vscode-mail-read: | `:vscode-mail-read:` | `vscode/mail-read` |
| :vscode-map: | `:vscode-map:` | `vscode/map` |
| :vscode-map-filled: | `:vscode-map-filled:` | `vscode/map-filled` |
| :vscode-map-vertical: | `:vscode-map-vertical:` | `vscode/map-vertical` |
| :vscode-map-vertical-filled: | `:vscode-map-vertical-filled:` | `vscode/map-vertical-filled` |
| :vscode-markdown: | `:vscode-markdown:` | `vscode/markdown` |
| :vscode-mcp: | `:vscode-mcp:` | `vscode/mcp` |
| :vscode-megaphone: | `:vscode-megaphone:` | `vscode/megaphone` |
| :vscode-mention: | `:vscode-mention:` | `vscode/mention` |
| :vscode-menu: | `:vscode-menu:` | `vscode/menu` |
| :vscode-merge: | `:vscode-merge:` | `vscode/merge` |
| :vscode-merge-into: | `:vscode-merge-into:` | `vscode/merge-into` |
| :vscode-mic: | `:vscode-mic:` | `vscode/mic` |
| :vscode-mic-filled: | `:vscode-mic-filled:` | `vscode/mic-filled` |
| :vscode-milestone: | `:vscode-milestone:` | `vscode/milestone` |
| :vscode-mirror: | `:vscode-mirror:` | `vscode/mirror` |
| :vscode-mortar-board: | `:vscode-mortar-board:` | `vscode/mortar-board` |
| :vscode-move: | `:vscode-move:` | `vscode/move` |
| :vscode-multiple-windows: | `:vscode-multiple-windows:` | `vscode/multiple-windows` |
| :vscode-music: | `:vscode-music:` | `vscode/music` |
| :vscode-mute: | `:vscode-mute:` | `vscode/mute` |
| :vscode-new-collection: | `:vscode-new-collection:` | `vscode/new-collection` |
| :vscode-new-file: | `:vscode-new-file:` | `vscode/new-file` |
| :vscode-new-folder: | `:vscode-new-folder:` | `vscode/new-folder` |
| :vscode-new-session: | `:vscode-new-session:` | `vscode/new-session` |
| :vscode-newline: | `:vscode-newline:` | `vscode/newline` |
| :vscode-no-newline: | `:vscode-no-newline:` | `vscode/no-newline` |
| :vscode-note: | `:vscode-note:` | `vscode/note` |
| :vscode-notebook: | `:vscode-notebook:` | `vscode/notebook` |
| :vscode-notebook-template: | `:vscode-notebook-template:` | `vscode/notebook-template` |
| :vscode-octoface: | `:vscode-octoface:` | `vscode/octoface` |
| :vscode-open-in-product: | `:vscode-open-in-product:` | `vscode/open-in-product` |
| :vscode-open-in-window: | `:vscode-open-in-window:` | `vscode/open-in-window` |
| :vscode-open-preview: | `:vscode-open-preview:` | `vscode/open-preview` |
| :vscode-openai: | `:vscode-openai:` | `vscode/openai` |
| :vscode-organization: | `:vscode-organization:` | `vscode/organization` |
| :vscode-output: | `:vscode-output:` | `vscode/output` |
| :vscode-package: | `:vscode-package:` | `vscode/package` |
| :vscode-paintcan: | `:vscode-paintcan:` | `vscode/paintcan` |
| :vscode-pass: | `:vscode-pass:` | `vscode/pass` |
| :vscode-pass-filled: | `:vscode-pass-filled:` | `vscode/pass-filled` |
| :vscode-percentage: | `:vscode-percentage:` | `vscode/percentage` |
| :vscode-person: | `:vscode-person:` | `vscode/person` |
| :vscode-person-add: | `:vscode-person-add:` | `vscode/person-add` |
| :vscode-piano: | `:vscode-piano:` | `vscode/piano` |
| :vscode-pie-chart: | `:vscode-pie-chart:` | `vscode/pie-chart` |
| :vscode-pin: | `:vscode-pin:` | `vscode/pin` |
| :vscode-pinned: | `:vscode-pinned:` | `vscode/pinned` |
| :vscode-pinned-dirty: | `:vscode-pinned-dirty:` | `vscode/pinned-dirty` |
| :vscode-play: | `:vscode-play:` | `vscode/play` |
| :vscode-play-circle: | `:vscode-play-circle:` | `vscode/play-circle` |
| :vscode-plug: | `:vscode-plug:` | `vscode/plug` |
| :vscode-preserve-case: | `:vscode-preserve-case:` | `vscode/preserve-case` |
| :vscode-preview: | `:vscode-preview:` | `vscode/preview` |
| :vscode-primitive-square: | `:vscode-primitive-square:` | `vscode/primitive-square` |
| :vscode-project: | `:vscode-project:` | `vscode/project` |
| :vscode-pulse: | `:vscode-pulse:` | `vscode/pulse` |
| :vscode-python: | `:vscode-python:` | `vscode/python` |
| :vscode-question: | `:vscode-question:` | `vscode/question` |
| :vscode-quote: | `:vscode-quote:` | `vscode/quote` |
| :vscode-quotes: | `:vscode-quotes:` | `vscode/quotes` |
| :vscode-radio-tower: | `:vscode-radio-tower:` | `vscode/radio-tower` |
| :vscode-reactions: | `:vscode-reactions:` | `vscode/reactions` |
| :vscode-record: | `:vscode-record:` | `vscode/record` |
| :vscode-record-keys: | `:vscode-record-keys:` | `vscode/record-keys` |
| :vscode-record-small: | `:vscode-record-small:` | `vscode/record-small` |
| :vscode-redo: | `:vscode-redo:` | `vscode/redo` |
| :vscode-references: | `:vscode-references:` | `vscode/references` |
| :vscode-refresh: | `:vscode-refresh:` | `vscode/refresh` |
| :vscode-regex: | `:vscode-regex:` | `vscode/regex` |
| :vscode-remote: | `:vscode-remote:` | `vscode/remote` |
| :vscode-remote-explorer: | `:vscode-remote-explorer:` | `vscode/remote-explorer` |
| :vscode-remove: | `:vscode-remove:` | `vscode/remove` |
| :vscode-remove-small: | `:vscode-remove-small:` | `vscode/remove-small` |
| :vscode-rename: | `:vscode-rename:` | `vscode/rename` |
| :vscode-replace: | `:vscode-replace:` | `vscode/replace` |
| :vscode-replace-all: | `:vscode-replace-all:` | `vscode/replace-all` |
| :vscode-reply: | `:vscode-reply:` | `vscode/reply` |
| :vscode-repo: | `:vscode-repo:` | `vscode/repo` |
| :vscode-repo-clone: | `:vscode-repo-clone:` | `vscode/repo-clone` |
| :vscode-repo-fetch: | `:vscode-repo-fetch:` | `vscode/repo-fetch` |
| :vscode-repo-force-push: | `:vscode-repo-force-push:` | `vscode/repo-force-push` |
| :vscode-repo-forked: | `:vscode-repo-forked:` | `vscode/repo-forked` |
| :vscode-repo-pinned: | `:vscode-repo-pinned:` | `vscode/repo-pinned` |
| :vscode-repo-pull: | `:vscode-repo-pull:` | `vscode/repo-pull` |
| :vscode-repo-push: | `:vscode-repo-push:` | `vscode/repo-push` |
| :vscode-repo-selected: | `:vscode-repo-selected:` | `vscode/repo-selected` |
| :vscode-report: | `:vscode-report:` | `vscode/report` |
| :vscode-request-changes: | `:vscode-request-changes:` | `vscode/request-changes` |
| :vscode-robot: | `:vscode-robot:` | `vscode/robot` |
| :vscode-rocket: | `:vscode-rocket:` | `vscode/rocket` |
| :vscode-root-folder: | `:vscode-root-folder:` | `vscode/root-folder` |
| :vscode-root-folder-opened: | `:vscode-root-folder-opened:` | `vscode/root-folder-opened` |
| :vscode-rss: | `:vscode-rss:` | `vscode/rss` |
| :vscode-ruby: | `:vscode-ruby:` | `vscode/ruby` |
| :vscode-run-above: | `:vscode-run-above:` | `vscode/run-above` |
| :vscode-run-all: | `:vscode-run-all:` | `vscode/run-all` |
| :vscode-run-all-coverage: | `:vscode-run-all-coverage:` | `vscode/run-all-coverage` |
| :vscode-run-below: | `:vscode-run-below:` | `vscode/run-below` |
| :vscode-run-coverage: | `:vscode-run-coverage:` | `vscode/run-coverage` |
| :vscode-run-errors: | `:vscode-run-errors:` | `vscode/run-errors` |
| :vscode-run-with-deps: | `:vscode-run-with-deps:` | `vscode/run-with-deps` |
| :vscode-save: | `:vscode-save:` | `vscode/save` |
| :vscode-save-all: | `:vscode-save-all:` | `vscode/save-all` |
| :vscode-save-as: | `:vscode-save-as:` | `vscode/save-as` |
| :vscode-screen-cut: | `:vscode-screen-cut:` | `vscode/screen-cut` |
| :vscode-screen-full: | `:vscode-screen-full:` | `vscode/screen-full` |
| :vscode-screen-normal: | `:vscode-screen-normal:` | `vscode/screen-normal` |
| :vscode-search: | `:vscode-search:` | `vscode/search` |
| :vscode-search-fuzzy: | `:vscode-search-fuzzy:` | `vscode/search-fuzzy` |
| :vscode-search-large: | `:vscode-search-large:` | `vscode/search-large` |
| :vscode-search-sparkle: | `:vscode-search-sparkle:` | `vscode/search-sparkle` |
| :vscode-search-stop: | `:vscode-search-stop:` | `vscode/search-stop` |
| :vscode-send: | `:vscode-send:` | `vscode/send` |
| :vscode-send-to-remote-agent: | `:vscode-send-to-remote-agent:` | `vscode/send-to-remote-agent` |
| :vscode-server: | `:vscode-server:` | `vscode/server` |
| :vscode-server-environment: | `:vscode-server-environment:` | `vscode/server-environment` |
| :vscode-server-process: | `:vscode-server-process:` | `vscode/server-process` |
| :vscode-session-in-progress: | `:vscode-session-in-progress:` | `vscode/session-in-progress` |
| :vscode-settings: | `:vscode-settings:` | `vscode/settings` |
| :vscode-settings-gear: | `:vscode-settings-gear:` | `vscode/settings-gear` |
| :vscode-share: | `:vscode-share:` | `vscode/share` |
| :vscode-share-window: | `:vscode-share-window:` | `vscode/share-window` |
| :vscode-shield: | `:vscode-shield:` | `vscode/shield` |
| :vscode-sign-in: | `:vscode-sign-in:` | `vscode/sign-in` |
| :vscode-sign-out: | `:vscode-sign-out:` | `vscode/sign-out` |
| :vscode-skip: | `:vscode-skip:` | `vscode/skip` |
| :vscode-smiley: | `:vscode-smiley:` | `vscode/smiley` |
| :vscode-snake: | `:vscode-snake:` | `vscode/snake` |
| :vscode-sort-precedence: | `:vscode-sort-precedence:` | `vscode/sort-precedence` |
| :vscode-source-control: | `:vscode-source-control:` | `vscode/source-control` |
| :vscode-sparkle: | `:vscode-sparkle:` | `vscode/sparkle` |
| :vscode-sparkle-filled: | `:vscode-sparkle-filled:` | `vscode/sparkle-filled` |
| :vscode-split-horizontal: | `:vscode-split-horizontal:` | `vscode/split-horizontal` |
| :vscode-split-vertical: | `:vscode-split-vertical:` | `vscode/split-vertical` |
| :vscode-squirrel: | `:vscode-squirrel:` | `vscode/squirrel` |
| :vscode-star-empty: | `:vscode-star-empty:` | `vscode/star-empty` |
| :vscode-star-full: | `:vscode-star-full:` | `vscode/star-full` |
| :vscode-star-half: | `:vscode-star-half:` | `vscode/star-half` |
| :vscode-stop-circle: | `:vscode-stop-circle:` | `vscode/stop-circle` |
| :vscode-strikethrough: | `:vscode-strikethrough:` | `vscode/strikethrough` |
| :vscode-surround-with: | `:vscode-surround-with:` | `vscode/surround-with` |
| :vscode-symbol-array: | `:vscode-symbol-array:` | `vscode/symbol-array` |
| :vscode-symbol-boolean: | `:vscode-symbol-boolean:` | `vscode/symbol-boolean` |
| :vscode-symbol-class: | `:vscode-symbol-class:` | `vscode/symbol-class` |
| :vscode-symbol-color: | `:vscode-symbol-color:` | `vscode/symbol-color` |
| :vscode-symbol-constant: | `:vscode-symbol-constant:` | `vscode/symbol-constant` |
| :vscode-symbol-enum: | `:vscode-symbol-enum:` | `vscode/symbol-enum` |
| :vscode-symbol-enum-member: | `:vscode-symbol-enum-member:` | `vscode/symbol-enum-member` |
| :vscode-symbol-event: | `:vscode-symbol-event:` | `vscode/symbol-event` |
| :vscode-symbol-field: | `:vscode-symbol-field:` | `vscode/symbol-field` |
| :vscode-symbol-file: | `:vscode-symbol-file:` | `vscode/symbol-file` |
| :vscode-symbol-interface: | `:vscode-symbol-interface:` | `vscode/symbol-interface` |
| :vscode-symbol-key: | `:vscode-symbol-key:` | `vscode/symbol-key` |
| :vscode-symbol-keyword: | `:vscode-symbol-keyword:` | `vscode/symbol-keyword` |
| :vscode-symbol-method: | `:vscode-symbol-method:` | `vscode/symbol-method` |
| :vscode-symbol-method-arrow: | `:vscode-symbol-method-arrow:` | `vscode/symbol-method-arrow` |
| :vscode-symbol-misc: | `:vscode-symbol-misc:` | `vscode/symbol-misc` |
| :vscode-symbol-namespace: | `:vscode-symbol-namespace:` | `vscode/symbol-namespace` |
| :vscode-symbol-numeric: | `:vscode-symbol-numeric:` | `vscode/symbol-numeric` |
| :vscode-symbol-operator: | `:vscode-symbol-operator:` | `vscode/symbol-operator` |
| :vscode-symbol-parameter: | `:vscode-symbol-parameter:` | `vscode/symbol-parameter` |
| :vscode-symbol-property: | `:vscode-symbol-property:` | `vscode/symbol-property` |
| :vscode-symbol-ruler: | `:vscode-symbol-ruler:` | `vscode/symbol-ruler` |
| :vscode-symbol-snippet: | `:vscode-symbol-snippet:` | `vscode/symbol-snippet` |
| :vscode-symbol-string: | `:vscode-symbol-string:` | `vscode/symbol-string` |
| :vscode-symbol-structure: | `:vscode-symbol-structure:` | `vscode/symbol-structure` |
| :vscode-symbol-variable: | `:vscode-symbol-variable:` | `vscode/symbol-variable` |
| :vscode-sync: | `:vscode-sync:` | `vscode/sync` |
| :vscode-sync-ignored: | `:vscode-sync-ignored:` | `vscode/sync-ignored` |
| :vscode-table: | `:vscode-table:` | `vscode/table` |
| :vscode-tag: | `:vscode-tag:` | `vscode/tag` |
| :vscode-target: | `:vscode-target:` | `vscode/target` |
| :vscode-tasklist: | `:vscode-tasklist:` | `vscode/tasklist` |
| :vscode-telescope: | `:vscode-telescope:` | `vscode/telescope` |
| :vscode-terminal: | `:vscode-terminal:` | `vscode/terminal` |
| :vscode-terminal-bash: | `:vscode-terminal-bash:` | `vscode/terminal-bash` |
| :vscode-terminal-cmd: | `:vscode-terminal-cmd:` | `vscode/terminal-cmd` |
| :vscode-terminal-debian: | `:vscode-terminal-debian:` | `vscode/terminal-debian` |
| :vscode-terminal-git-bash: | `:vscode-terminal-git-bash:` | `vscode/terminal-git-bash` |
| :vscode-terminal-linux: | `:vscode-terminal-linux:` | `vscode/terminal-linux` |
| :vscode-terminal-powershell: | `:vscode-terminal-powershell:` | `vscode/terminal-powershell` |
| :vscode-terminal-secure: | `:vscode-terminal-secure:` | `vscode/terminal-secure` |
| :vscode-terminal-tmux: | `:vscode-terminal-tmux:` | `vscode/terminal-tmux` |
| :vscode-terminal-ubuntu: | `:vscode-terminal-ubuntu:` | `vscode/terminal-ubuntu` |
| :vscode-text-size: | `:vscode-text-size:` | `vscode/text-size` |
| :vscode-thinking: | `:vscode-thinking:` | `vscode/thinking` |
| :vscode-three-bars: | `:vscode-three-bars:` | `vscode/three-bars` |
| :vscode-thumbsdown: | `:vscode-thumbsdown:` | `vscode/thumbsdown` |
| :vscode-thumbsdown-filled: | `:vscode-thumbsdown-filled:` | `vscode/thumbsdown-filled` |
| :vscode-thumbsup: | `:vscode-thumbsup:` | `vscode/thumbsup` |
| :vscode-thumbsup-filled: | `:vscode-thumbsup-filled:` | `vscode/thumbsup-filled` |
| :vscode-tools: | `:vscode-tools:` | `vscode/tools` |
| :vscode-trash: | `:vscode-trash:` | `vscode/trash` |
| :vscode-triangle-down: | `:vscode-triangle-down:` | `vscode/triangle-down` |
| :vscode-triangle-left: | `:vscode-triangle-left:` | `vscode/triangle-left` |
| :vscode-triangle-right: | `:vscode-triangle-right:` | `vscode/triangle-right` |
| :vscode-triangle-up: | `:vscode-triangle-up:` | `vscode/triangle-up` |
| :vscode-twitter: | `:vscode-twitter:` | `vscode/twitter` |
| :vscode-type-hierarchy: | `:vscode-type-hierarchy:` | `vscode/type-hierarchy` |
| :vscode-type-hierarchy-sub: | `:vscode-type-hierarchy-sub:` | `vscode/type-hierarchy-sub` |
| :vscode-type-hierarchy-super: | `:vscode-type-hierarchy-super:` | `vscode/type-hierarchy-super` |
| :vscode-unarchive: | `:vscode-unarchive:` | `vscode/unarchive` |
| :vscode-unfold: | `:vscode-unfold:` | `vscode/unfold` |
| :vscode-ungroup-by-ref-type: | `:vscode-ungroup-by-ref-type:` | `vscode/ungroup-by-ref-type` |
| :vscode-unlock: | `:vscode-unlock:` | `vscode/unlock` |
| :vscode-unmute: | `:vscode-unmute:` | `vscode/unmute` |
| :vscode-unverified: | `:vscode-unverified:` | `vscode/unverified` |
| :vscode-variable-group: | `:vscode-variable-group:` | `vscode/variable-group` |
| :vscode-verified: | `:vscode-verified:` | `vscode/verified` |
| :vscode-verified-filled: | `:vscode-verified-filled:` | `vscode/verified-filled` |
| :vscode-versions: | `:vscode-versions:` | `vscode/versions` |
| :vscode-vm: | `:vscode-vm:` | `vscode/vm` |
| :vscode-vm-active: | `:vscode-vm-active:` | `vscode/vm-active` |
| :vscode-vm-connect: | `:vscode-vm-connect:` | `vscode/vm-connect` |
| :vscode-vm-outline: | `:vscode-vm-outline:` | `vscode/vm-outline` |
| :vscode-vm-pending: | `:vscode-vm-pending:` | `vscode/vm-pending` |
| :vscode-vm-running: | `:vscode-vm-running:` | `vscode/vm-running` |
| :vscode-vm-small: | `:vscode-vm-small:` | `vscode/vm-small` |
| :vscode-vr: | `:vscode-vr:` | `vscode/vr` |
| :vscode-vscode: | `:vscode-vscode:` | `vscode/vscode` |
| :vscode-vscode-insiders: | `:vscode-vscode-insiders:` | `vscode/vscode-insiders` |
| :vscode-wand: | `:vscode-wand:` | `vscode/wand` |
| :vscode-warning: | `:vscode-warning:` | `vscode/warning` |
| :vscode-watch: | `:vscode-watch:` | `vscode/watch` |
| :vscode-whitespace: | `:vscode-whitespace:` | `vscode/whitespace` |
| :vscode-whole-word: | `:vscode-whole-word:` | `vscode/whole-word` |
| :vscode-window: | `:vscode-window:` | `vscode/window` |
| :vscode-window-active: | `:vscode-window-active:` | `vscode/window-active` |
| :vscode-word-wrap: | `:vscode-word-wrap:` | `vscode/word-wrap` |
| :vscode-workspace-trusted: | `:vscode-workspace-trusted:` | `vscode/workspace-trusted` |
| :vscode-workspace-unknown: | `:vscode-workspace-unknown:` | `vscode/workspace-unknown` |
| :vscode-workspace-untrusted: | `:vscode-workspace-untrusted:` | `vscode/workspace-untrusted` |
| :vscode-worktree: | `:vscode-worktree:` | `vscode/worktree` |
| :vscode-worktree-small: | `:vscode-worktree-small:` | `vscode/worktree-small` |
| :vscode-zoom-in: | `:vscode-zoom-in:` | `vscode/zoom-in` |
| :vscode-zoom-out: | `:vscode-zoom-out:` | `vscode/zoom-out` |
+221
View File
@@ -0,0 +1,221 @@
---
# icon: material/architecture
tags: ["iptvc", "разработка", "архитектура"]
---
# Архитектура iptvc
Внутреннее устройство программы для разработчиков.
## Стек технологий
- **Go 1.22+** — язык программирования;
- `net/http` — HTTP-сервер (Go 1.22 routing patterns);
- `html/template` — шаблоны HTML;
- `//go:embed` — встраивание шаблонов в бинарник;
- `github.com/spf13/cobra` — CLI-фреймворк;
- `gopkg.in/yaml.v3` — парсинг `config.yml`;
- `github.com/joho/godotenv` — загрузка `.env`;
- `github.com/redis/go-redis/v9` — клиент KeyDB/Redis.
## Структура проекта
```
iptvc/
├── main.go # точка входа
├── config.yml # конфигурация
├── .env # переменные окружения (опционально)
├── cmd/ # CLI-команды (Cobra)
│ ├── root.go # корневая команда, глобальные флаги
│ ├── check.go # команда check
│ ├── serve.go # команда serve
│ ├── flags.go # общие флаги check/serve
│ └── version.go # команда version
├── app/
│ ├── app.go # глобальные переменные: Args, Config, Cache
│ ├── config/
│ │ └── config.go # Config, Init(), validate(), IntRange, UserAgents
│ ├── checker/
│ │ └── checker.go # CheckPlaylists(), CheckChannels(), OnPlaylistChecked
│ ├── playlist/
│ │ └── playlist.go # Playlist, Channel, Parse(), Download()
│ ├── inifile/
│ │ └── inifile.go # чтение playlists.ini
│ ├── tagfile/
│ │ └── tagfile.go # чтение channels.json, назначение тегов
│ ├── cache/
│ │ └── cache.go # подключение к KeyDB/Redis
│ ├── logger/
│ │ └── logger.go # настройка логирования
│ ├── utils/
│ │ └── utils.go # Fetch(), ExpandPath(), ArrayUnique(), Md5str()
│ └── web/
│ ├── server.go # Server, Start(), StartBackgroundChecker()
│ ├── handlers.go # HTTP-обработчики
│ ├── templates.go # TemplateManager, //go:embed
│ └── views/ # HTML-шаблоны
│ ├── layout.html
│ ├── list.html
│ └── details.html
└── go.mod
```
## Пакеты
### `app`
Глобальный контейнер: `Args` (CLI-флаги), `Config` (конфигурация), `Cache` (Redis-клиент).
`Init()` загружает конфигурацию, инициализирует логгер и подключение к кешу.
### `app.config`
Структуры: `Config``AppConfig`, `ServerConfig`, `CheckConfig`, `CacheConfig`.
Кастомные YAML-типы:
- **`IntRange`** — скаляр или `[min, max]`. Метод `Value()` возвращает константу или случайное значение.
- **`UserAgents`** — строка или массив строк. Метод `Pick()` возвращает случайный элемент.
`Init(configPath)` — загружает `config.yml`, применяет env, валидирует.
`validate()` — проверяет все значения, исправляет некорректные с логированием.
### `app.checker`
Содержит логику проверки:
- **`PrepareListsToCheck(files, urls, codes)`** — формирует список плейлистов из файлов, URL и кодов ini-файла.
- **`CheckPlaylists(lists)`** — параллельная проверка плейлистов (семфор `per-routine`), загрузка, парсинг, вызов `CheckChannels` для каждого.
- **`CheckChannels(pls)`** — параллельная проверка каналов (семфор `per-routine`), HTTP-запрос с `Range` header.
- **`OnPlaylistChecked`** — глобальный callback, вызывается после проверки каждого плейлиста. Используется веб-сервером для обновления in-memory кеша.
- **`cachePlaylist(pls)`** — сохранение результата в Redis (если включён).
Параметры проверки берутся из `app.Config.Check.Playlists` и `app.Config.Check.Channels`.
### `app.playlist`
- **`Playlist`** — плейлист: код, URL, контент, каналы, статус.
- **`Channel`** — канал: ID, название, URL, статус, теги.
- **`Download(userAgent, timeout)`** — загрузка по URL.
- **`ReadFromFs()`** — чтение из файла.
- **`Parse()`** — парсинг m3u/m3u8 контента.
### `app.web`
Веб-сервер на `net/http` (Go 1.22 routing).
- **`Server`** — структура: конфиг, кеш, шаблоны, in-memory кеш (`memCache` с `sync.RWMutex`).
- **`Start()`** — запуск HTTP-сервера.
- **`StartBackgroundChecker(opts)`** — фоновая проверка в отдельной горутине.
- **`CheckOptions`** — параметры: Every, Repeat, Random, Files, Urls, Codes.
Маршруты (Go 1.22 patterns):
```
GET /api/playlists/{code} — JSON плейлиста
GET /api/version — версия
GET /api/health — здоровье сервиса
GET /api/stats — статистика
GET /{$} — главная (catch-all root)
GET /{path...} — все остальные маршруты (catch-all)
```
Catch-all `/{path...}` используется для избежания конфликтов паттернов в Go 1.22 mux.
In-memory кеш (`memCache`) обновляется через `OnPlaylistChecked` callback.
Это позволяет отображать результаты проверки в реальном времени без ожидания завершения цикла и без Redis.
ini-файл кешируется на 30 секунд, кеш сбрасывается при каждом обновлении `memCache`.
## Жизненный цикл `serve --check`
```
main → app.Init() → web.NewServer() → go StartBackgroundChecker() → server.Start()
StartBackgroundChecker:
loop:
runCheckerOnce()
→ checker.PrepareListsToCheck()
→ checker.CheckPlaylists()
→ for each playlist (parallel, per-routine):
→ Download() / ReadFromFs()
→ Parse()
→ CheckChannels()
→ for each channel (parallel, per-routine):
→ HTTP GET with Range header
→ check status + content type
→ OnPlaylistChecked(pls) → memCache update
→ one-cooldown sleep
→ all-cooldown sleep
sleep(every)
if repeat > 0 && iteration >= repeat: stop
```
## CLI-флаги
### Общие (`cmd/flags.go`)
Используются командами `check` и `serve --check`:
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-i, --ini` | `Args.IniPath` | Путь к playlists.ini |
| `-t, --tags` | `Args.TagsPath` | Путь к channels.json |
| `-r, --random` | `Args.RandomCount` | Случайные N плейлистов |
| `--repeat` | `Args.RepeatCount` | Количество циклов (0 = бесконечно) |
| `--every` | `Args.RepeatEverySec` | Секунд между циклами |
### Только `check` (`addCheckOnlyFlags`)
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-j, --json` | `Args.NeedJson` | Вывод в JSON |
| `-q, --quiet` | `Args.NeedQuiet` | Подавить логи |
| `-f, --file` | `Args.Files` | Локальные m3u файлы |
| `-u, --url` | `Args.Urls` | URL плейлистов |
| `-c, --code` | `Args.Codes` | Коды из ini-файла |
### Только `serve`
| Флаг | Поле | Описание |
| --- | --- | --- |
| `-p, --port` | `Args.ServerPort` | Порт веб-сервера |
| `--host` | `Args.ServerHost` | Хост привязки |
| `--check` | `Args.NeedCheck` | Включить фоновую проверку |
### Глобальные (`cmd/root.go`)
| Флаг | Поле | Описание |
| --- | --- | --- |
| `--config` | `Args.ConfigPath` | Путь к config.yml |
| `-v, --verbose` | `Args.Verbose` | Подробный лог |
## Конфигурация
Подробное описание параметров — в разделе [config.yml](../config.md).
Приоритет: Defaults < `config.yml` < Env < CLI-флаги.
CLI-флаги переопределяют конфигурацию только если переданы явно (`cmd.Flags().Changed()`).
Для этого в Cobra используются zero-value defaults (0, "", false), чтобы отличить «не передан» от «передан со значением по умолчанию».
## Шаблоны
HTML-шаблоны встроены через `//go:embed`:
- `layout.html` — общий каркас (header, footer);
- `list.html` — список плейлистов с пагинацией;
- `details.html` — детали плейлиста и список каналов.
SVG-логотипы каналов передаются через `encodeURIComponent` в data-URI для корректной работы с кавычками в HTML.
При отсутствии `playlists.ini` рендерится пустое состояние с alert-блоком.
## Кеширование
Два уровня кеша:
1. **Redis/KeyDB** (опционально) — постоянный кеш результатов проверки. TTL из `cache.ttl`.
2. **In-memory** (`memCache`) — только при `serve --check`. Обновляется в реальном времени через callback. Не требует Redis.
In-memory кеш приоритетнее Redis при отображении в веб-интерфейсе.
@@ -1,4 +1,5 @@
---
title: channels.json
icon: material/code-json
tags: ["iptvc", "теги", "каналы"]
---
@@ -1,4 +1,5 @@
---
title: "*.m3u (*.m3u8)"
icon: material/playlist-play
tags: ["плейлисты", "каналы"]
---
@@ -15,6 +16,7 @@ tags: ["плейлисты", "каналы"]
После директив с новой строки указывается ссылка на канал (или путь к файлу).
В свою чередь, по этой ссылке может быть:
* либо текстовое представление контента в формате m3u/m3u8/XMLTV/MPD с описанием непосредственно участки трансляции;
* либо непосредственно сама потоковая трансляция mp4 или т. п.
@@ -1,4 +1,5 @@
---
title: playlists.ini
icon: material/code-brackets
tags: ["плейлисты"]
---
+70
View File
@@ -0,0 +1,70 @@
---
icon: material/download
tags: ["iptvc"]
---
# Установка и запуск
## Запуск готовой программы
Достаточно скачать и распаковать архив с подходящим исполняемым файлом [со страницы последнего релиза][rel_page] в любую удобную директорию:
| ОС | Скачать для `amd64` | Скачать для `arm64` |
| ------- | ---------------------------------- | ---------------------------------- |
| Linux | [linux_amd64.zip][linux_amd64] | [linux_arm64.zip][linux_arm64] |
| MacOS | [darwin_amd64.zip][darwin_amd64] | [darwin_arm64.zip][darwin_arm64] |
| Windows | [windows_amd64.zip][windows_amd64] | [windows_arm64.zip][windows_arm64] |
[rel_page]: https://git.axenov.dev/IPTV/iptvc/releases/latest
[linux_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_amd64.zip
[linux_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/linux_arm64.zip
[darwin_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_amd64.zip
[darwin_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/darwin_arm64.zip
[windows_amd64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_amd64.zip
[windows_arm64]: https://git.axenov.dev/IPTV/iptvc/releases/download/latest/windows_arm64.zip
## Запуск релизного образа
Релизные docker-образы строятся для платформы `linux` и архитектуры `amd64`.
Найти их можно здесь: <https://git.axenov.dev/IPTV/-/packages/container/iptvc>
Тег `latest` всегда соответствует последней версии, он подразумевается по умолчанию:
=== "Запуск последней версии"
```shell
docker run \
--pull always \
--name iptvc \
git.axenov.dev/iptv/iptvc \ #(1)!
КОМАНДА [АРГУМЕНТЫ]
```
1. Подразумевается `:latest`, можно указать явно
=== "Запуск другой версии"
```shell
docker run \
--pull always \
--name iptvc \
git.axenov.dev/iptv/iptvc:v1.0.6 \ #(1)!
КОМАНДА [АРГУМЕНТЫ]
```
1. Список версий доступен на [релизной странице][rel_page]
## Использование образа в Docker compose
```yaml title="compose.yml"
services:
#...
iptvc:
container_name: iptvc
image: git.axenov.dev/iptv/iptvc:latest
command: [serve] #(1)!
#...
```
1. Доступные команды и аргументы см. в [**Справочнике команд**](commands/index.md)
@@ -1,9 +1,10 @@
---
icon: material/check-network
title: Введение
icon: octicons/sparkles-fill-16
hide: [toc]
---
# :material-check-network: IPTV Checker (iptvc)
# IPTV Checker (iptvc)
Это простая программа для проверки IPTV плейлистов, входящая в состав проекта m3u.su.
@@ -11,26 +12,47 @@ hide: [toc]
> Программа не предназначена для хранения, воспроизведения или распространения пиратского контента.
Программа не имеет графического интерфейса и работает в терминале.
<div class="grid cards" markdown>
- [:material-flag-checkered: Быстрый старт](quickstart.md)
- :material-flag-checkered: **Быстрый старт**
---
Коротко о главном, если не терпится
- [:octicons-terminal-24: Доступные команды](commands/index.md)
[Подробности :material-arrow-right:][start]{ .md-button .md-button--primary }
- :material-application-brackets-outline: **Запуск сайта**
---
Как правильно с ней работать
- [:simple-dotenv: Переменные окружения](env.md)
Создайте свой агрегатор плейлистов
[Подробности :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>
## :material-cog-sync-outline: Как работает iptvc
[start]: quickstart.md "Перейти к разделу"
[site]: site/index.md "Перейти к разделу"
[cli]: commands/index.md "Перейти к разделу"
[cfg]: config/config.md "Перейти к разделу"
<!--
## :material-cog-sync-outline: Как работает `iptvc`
Принцип её работы очень простой:
@@ -48,3 +70,4 @@ hide: [toc]
Подробности см. в разделе [Команда `check`](../iptvc/commands/check.md).
-->
+85
View File
@@ -0,0 +1,85 @@
---
icon: material/flag-checkered
tags: ["iptvc"]
---
# :material-flag-checkered: Быстрый старт
Для простоты представим, что программа скачана и распакована в любую директорию и вы находитесь в ней.
Используйте тот способ запуска, который выбрали на шаге [установки](install.md).
Ниже представлены лишь частые примеры запуска программы с разными аргументами под разные случаи.
## Проверить плейлист по прямой ссылке
```
./iptvc check -u https://example.com/pls.m3u
./iptvc check --url https://example.com/pls.m3u
```
## Проверить файл плейлиста с диска
```
./iptvc check -f /home/user/pls.m3u
./iptvc check --file /home/user/pls.m3u
```
## Проверить плейлист по короткому коду из [`playlists.ini`](formats/playlists.md)
```
./iptvc check -c X
./iptvc check --code X
```
Файл `playlists.ini` должен лежать рядом с `iptvc`.
Если файл лежит в другой директории, то можно явно указать путь к нему:
```
./iptvc check --ini /home/user/playlists.ini --code X
```
Если ini-файл не будет найден, программа предупредит об этом.
## Присвоить каналам тематические теги
Для этого рядом с `iptvc` должен лежать файл [channels.json](formats/channels.md).
Если файл лежит в другой директории, то можно указать её явно:
```
./iptvc check --tags /home/user/channels.json
```
Если json-файл не будет найден, то программа предупредит о том, что теги не будут присвоены, и продолжит работу.
## Проверить несколько плейлистов одновременно
Для этого можно комбинировать все аргументы, перечисленные выше, с учётом особенностей их работы:
```shell
./iptvc check \
--ini /home/user/p.ini \ #(1)!
--tags /home/user/c.json \ #(2)!
--code Y \ #(3)!
--file /home/user/tv.m3u \ #(4)!
--url https://example.com/pls1.m3u \ #(5)!
-u https://example.com/pls2.m3u #(6)!
```
1. Из этого файла будет взят список плейлистов
2. Из этого файла будут взяты правила для присвоения тегов каналам
3. Из ini-списка будет проверен только плейлист с кодом `Y`
4. Это отдельный файл плейлиста на диске, который будет проверен в дополнение к основному списку
5. Плейлист на каком-то удалённом сервере, который будет загружен и проверен вместе с прошлыми двумя
6. Ещё один по ссылке, просто через короткий вариант аргумента `--url`
Символ `\` нужен только для наглядного разделения аргументов на несколько строк.
Всё это можно писать в одну строку.
Переданные плейлисты будут обработаны в следующем порядке:
1. локальные файлы (`-f|--file`);
2. по ссылкам (`-u|--url`);
3. по кодам из ini-файла (`-i|--ini`, `-c|--code`).
@@ -15,7 +15,7 @@ tags: ["сайт", "статусы", "каналы"]
## Вкладка "Основная информация"
![Вкладка "Основная информация"](../assets/img/pls-details/tab1.jpg)
![Вкладка "Основная информация"](../_assets/img/pls-details/tab1.jpg)
На этой вкладке выводится таблица со следующими строками:
@@ -36,7 +36,7 @@ tags: ["сайт", "статусы", "каналы"]
Если при проверке плейлиста возникла ошибка, то она будет отображена красным цветом сразу под заголовком:
??? quote "Скриншот страницы с ошибкой"
![Страница с ошибкой проверки плейлиста](../assets/img/pls-details/error.jpg)
![Страница с ошибкой проверки плейлиста](../_assets/img/pls-details/error.jpg)
!!! info
Если в тексте ошибки фигурирует слово `Timeout` и плейлист <span class="badge offline">offline</span> — это ерунда.
@@ -49,7 +49,7 @@ tags: ["сайт", "статусы", "каналы"]
## Вкладка "Исходный текст"
![Вкладка "Исходный текст"](../assets/img/pls-details/tab2.jpg)
![Вкладка "Исходный текст"](../_assets/img/pls-details/tab2.jpg)
Здесь выводится плейлист как он есть.
Над этим текстом — две кнопки:
@@ -61,7 +61,7 @@ tags: ["сайт", "статусы", "каналы"]
В заголовке пишется их общее количество.
![Cписок каналов](../assets/img/pls-details/ch-list.jpg)
![Cписок каналов](../_assets/img/pls-details/ch-list.jpg)
В списке всегда отображается не более 100 каналов.
@@ -92,7 +92,7 @@ tags: ["сайт", "статусы", "каналы"]
**Сбросить** выбор можно повторным нажатием на каждый, либо кнопкой сброса у строки поиска.
??? quote "Пример фильтрации"
![Скриншот используемого фильтра списка каналов](../assets/img/pls-details/filter.jpg)
![Скриншот используемого фильтра списка каналов](../_assets/img/pls-details/filter.jpg)
<a id="shortlink"></a>
## Ссылка для ТВ
@@ -7,7 +7,7 @@ tags: ["сайт", "плейлисты"]
Это главная страница сайта.
![Скриншот с примером главной страницы на десктопе](../assets/img/pls-list/pc.jpg)
![Скриншот с примером главной страницы на десктопе](../_assets/img/pls-list/pc.jpg)
Наверху отображаются:
@@ -33,4 +33,4 @@ tags: ["сайт", "плейлисты"]
В зависимости от ширины экрана, для экономии места может быть скрыто описание с иконками возможностей и короткая ссылка.
![Скриншот с примером главной страницы на смартфоне](../assets/img/pls-list/mobile.jpg)
![Скриншот с примером главной страницы на смартфоне](../_assets/img/pls-list/mobile.jpg)
+392
View File
@@ -0,0 +1,392 @@
---
title: Запуск своего сайта
icon: material/rocket-launch
tags: ["iptvc", "serve", "сайт"]
---
# :material-rocket-launch: Запуск своего сайта
В этой статье мы шаг за шагом запустим собственный сайт-агрегатор IPTV-плейлистов — от простейшего варианта до полной конфигурации с кешем и тонкой настройкой проверки.
Программа `iptvc` уже должна быть [установлена](../install.md). Все команды ниже выполняются в терминале из директории, где лежит бинарник.
---
## Шаг 1. Запускаем пустой сайт
Минимальный запуск — одна команда:
```bash
./iptvc serve
```
Сайт откроется на `http://localhost:8080`. Это пустая страница: нет ни одного плейлиста, потому что программе пока неоткуда их взять.
Чтобы изменить порт или хост, не трогая файл конфигурации:
```bash
./iptvc serve -p 3000 --host 0.0.0.0
```
Подробнее об этих флагах — в [документации команды `serve`](../commands/serve.md).
---
## Шаг 2. Добавляем плейлисты
Список плейлистов описывается в файле [`playlists.ini`](../formats/playlists.md). Создадим его рядом с `iptvc`:
```ini title="playlists.ini"
[ru]
name = Российские каналы
desc = Основные федеральные каналы
pls = 'https://example.com/ru.m3u'
src = 'https://example.com/ru-playlist'
[movies]
name = Фильмы
pls = 'https://example.com/movies.m3u'
```
Каждая секция `[code]` — это плейлист. Код используется в коротких ссылках вида `http://localhost:8080/ru`. Параметр `pls` обязателен, остальные — по желанию.
Теперь запустим:
```bash
./iptvc serve
```
Сайт покажет оба плейлиста, но их статус — `unknown` (неизвестно). Это нормально: программа знает о них, но ещё не проверяла. Чтобы статусы появились, нужно включить фоновую проверку.
!!! tip "Путь к ini-файлу"
Если файл лежит не рядом с программой, укажите путь через флаг [`-i`](../commands/serve.md#ini) или в [`config.yml`](../config/config.md) → `app.playlists`.
---
## Шаг 3. Включаем фоновую проверку
Без проверки сайт просто показывает список. Чтобы плейлисты и каналы проверялись автоматически, добавим флаг `--check`:
```bash
./iptvc serve --check
```
Теперь программа в фоне загружает каждый плейлист, парсит каналы и проверяет их доступность. Результаты сразу попадают в оперативную память и отображаются на сайте.
Можно настроить интервал между циклами проверки:
```bash
# проверять каждые 120 секунд, бесконечно
./iptvc serve --check --every 120
# проверить один раз и остановить
./iptvc serve --check --repeat 1
```
Если не хочется каждый раз писать `--check`, можно включить проверку через [`config.yml`](../config/config.md):
```yaml title="config.yml"
check:
start-on-serve: true
```
Тогда обычный `./iptvc serve` автоматически запустит фоновую проверку.
---
## Шаг 4. Настраиваем внешний вид сайта
Сайт можно настроить под себя: заголовок, иконку, навигацию в шапке и ссылки в подвале. Всё это — в секции [`site`](../config/config.md#секция-site) файла `config.yml`.
```yaml title="config.yml"
site:
base-url: http://localhost:8080
repo-url: https://git.axenov.dev/IPTV
page-size: 20 # пагинация по 20 плейлистов на страницу (0 — без пагинации)
favicon: /favicon.ico # путь к иконке
header:
title: Мой IPTV # заголовок в шапке и вкладке браузера
navigation:
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/my_channel
icon: megaphone-outline
- title: Чат
url: https://t.me/my_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: Мой сайт
url: https://example.com
icon: person-outline
```
--8<-- "ionicons-name.md"
!!! note "base-url"
Параметр `base-url` используется для формирования внутренних ссылок. Если публикуете сайт на домене, укажите его здесь, например `https://my-iptv.ru`.
---
## Шаг 5. Добавляем теги каналам
Теги помогают посетителям находить каналы по темам: спорт, фильмы, музыка и так далее. Правила описываются в файле [`channels.json`](../formats/channels.md).
```json title="channels.json"
[
{
"tvg-id": "^ru-",
"tags": ["russian"]
},
{
"title": "спорт",
"tags": ["sport"]
},
{
"title": "кино|фильм",
"tags": ["film"]
}
]
```
Путь к файлу указывается в [`config.yml`](../config/config.md) → `app.tags` или через флаг [`-t`](../commands/serve.md#tags):
```bash
./iptvc serve --check -t /path/to/channels.json
```
Полный список доступных тегов — в [справочнике по channels.json](../formats/channels.md#доступные-теги).
---
## Шаг 6. Подключаем кеш (KeyDB/Redis)
По умолчанию результаты проверки хранятся только в оперативной памяти. Если программу перезапустить — все результаты пропадут, и плейлисты снова станут `unknown` до следующей проверки.
Кеш решает эту проблему: результаты сохраняются в KeyDB (или Redis) и переживают перезапуск. Включается одной строкой в [`config.yml`](../config/config.md#секция-cache):
```yaml title="config.yml"
cache:
enabled: true
host: localhost
port: 6379
ttl: 1800 # секунды (30 минут)
```
Или через переменные окружения:
```bash
CACHE_ENABLED=true CACHE_TTL=3600 ./iptvc serve --check
```
Или через флаги:
```bash
./iptvc serve --check --cache-enabled --cache-host 192.168.1.10 --cache-ttl 3600
```
!!! tip "KeyDB или Redis"
KeyDB — это форк Redis, полностью совместимый по протоколу. Подойдёт любой из них. Если кеш включён, но сервер недоступен — сайт продолжит работать, просто без кеширования.
---
## Шаг 7. Тонкая настройка проверки
Когда плейлистов много, полезно управлять параллелизмом, таймаутами и задержками. Все параметры — в секции [`check`](../config/config.md#секция-check) файла `config.yml`.
### Параллелизм
```yaml title="config.yml"
check:
playlists:
max-routines: 10 # сколько плейлистов проверять одновременно
per-routine: 5 # сколько плейлистов в одной процедуре
channels:
max-routines: 100 # сколько каналов проверять одновременно
per-routine: 20 # сколько каналов в одной процедуре
```
Чем больше значения — тем быстрее проверка, но выше нагрузка на процессор и сеть. Начните со значений по умолчанию и увеличивайте при необходимости.
### Таймауты
```yaml title="config.yml"
check:
playlists:
timeout: 10000 # мс на загрузку плейлиста
channels:
timeout: 10000 # мс на проверку одного канала
byte-range: 512 # сколько байт скачать от сервера канала
```
Если плейлисты или каналы медленные, увеличьте `timeout`. Если сервер блокирует большие запросы — уменьшите `byte-range`.
### Задержки (cooldown)
Чтобы не перегружать серверы-источники, между проверками можно делать паузы. Параметры поддерживают как фиксированное значение, так и диапазон `[min, max]` — тогда пауза будет случайной при каждом проходе:
```yaml title="config.yml"
check:
playlists:
all-cooldown: 5000 # 5 секунд после всех плейлистов
one-cooldown: [1000, 3000] # 1–3 секунды после каждого плейлиста
channels:
cooldown: [100, 500] # 100–500 мс после каждого канала
```
### User-Agent
Некоторые серверы блокируют запросы без правильного User-Agent. Можно указать один или несколько — тогда при каждом запросе будет выбран случайный:
```yaml title="config.yml"
check:
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
- Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
channels:
user-agent: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
```
Все эти параметры можно также задавать через [переменные окружения](../config/env.md) или [CLI-флаги](../commands/serve.md) — они имеют наивысший приоритет.
---
## Полный пример
Соберём всё вместе в одном `config.yml`:
```yaml title="config.yml"
app:
timezone: GMT+3
debug: false
log_level: info
playlists: ./playlists.ini
tags: ./channels.json
server:
host: 0.0.0.0
port: 8080
site:
base-url: https://my-iptv.ru
repo-url: https://git.axenov.dev/IPTV
page-size: 20
favicon: /favicon.ico
header:
title: Мой IPTV
navigation:
- title: Статус
url: https://status.my-iptv.ru
icon: pulse-outline
- title: Документация
url: /docs
icon: document-text-outline
- title: Telegram
icon: paper-plane-outline
children:
- title: Канал
url: https://t.me/my_channel
icon: megaphone-outline
- title: Чат
url: https://t.me/my_chat
icon: chatbubbles-outline
footer-links:
- title: Исходники
url: https://git.axenov.dev/IPTV
icon: code-slash-outline
- title: Контакты
url: https://example.com
icon: person-outline
check:
start-on-serve: true
playlists:
user-agent:
- Mozilla/5.0 WINK/1.31.1 (AndroidTV/9) HlsWinkPlayer
- Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
timeout: 10000
all-cooldown: 5000
one-cooldown: [1000, 3000]
max-routines: 10
per-routine: 5
channels:
user-agent: Mozilla/5.0 (Linux; Android 11) AppleWebKit/537.36
timeout: 10000
byte-range: 512
cooldown: [100, 500]
max-routines: 100
per-routine: 20
cache:
enabled: true
host: localhost
port: 6379
ttl: 3600
```
Запуск:
```bash
./iptvc serve
```
Поскольку `start-on-serve: true`, фоновая проверка запустится автоматически. Кеш включён, так что результаты переживут перезапуск. Сайт доступен на `http://0.0.0.0:8080`.
---
## Docker
Удобно запускать сайт в контейнере. Образ `iptvc` уже включает бинарник:
```yaml title="compose.yml"
services:
iptvc:
image: git.axenov.dev/iptv/iptvc:latest
command: [serve]
ports:
- "8080:8080"
volumes:
- ./config.yml:/app/config.yml:ro
- ./playlists.ini:/app/playlists.ini:ro
- ./channels.json:/app/channels.json:ro
environment:
- CHECK_START_ON_SERVE=true
- CACHE_ENABLED=true
- CACHE_HOST=keydb
- CACHE_PORT=6379
keydb:
image: eqalpha/keydb:latest
restart: unless-stopped
```
```bash
docker compose up -d
```
Подробнее об установке образа — в [документации по установке](../install.md).
---
## Краткая шпаргалка
| Задача | Как |
| --- | --- |
| Запустить сайт | `./iptvc serve` |
| С проверкой плейлистов | `./iptvc serve --check` |
| На другом порту | `./iptvc serve -p 3000` |
| С ini-файлом из другого места | `./iptvc serve -i /path/to/playlists.ini` |
| С кешем | `./iptvc serve --check --cache-enabled` |
| Один цикл проверки | `./iptvc serve --check --repeat 1` |
| Проверять каждые 2 минуты | `./iptvc serve --check --every 120` |
| Подробные логи | `./iptvc serve --check --verbose` |
Полный список параметров — в [справочнике по `config.yml`](../config/config.md), [переменным окружения](../config/env.md) и [команде `serve`](../commands/serve.md).
View File
+49
View File
@@ -0,0 +1,49 @@
from __future__ import annotations
import re
from html import escape
from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
SHORTCODE_RE = re.compile(r"<!--\s*md:(version|default|flag)\s*(.*?)\s*-->", re.I)
class BadgePreprocessor(Preprocessor):
def run(self, lines: list[str]) -> list[str]:
return [SHORTCODE_RE.sub(self._replace, line) for line in lines]
def _replace(self, match: re.Match[str]) -> str:
kind, args = match.groups()
args = args.strip()
if kind == "version":
return _badge(":material-tag-outline:", escape(args))
if kind == "default":
value = escape(args or "none")
return _badge(":material-water:", f"<code>{value}</code>")
if kind == "flag" and args == "experimental":
return _badge(":material-test-tube:")
return match.group(0)
# Формирует HTML-разметку бейджа. Иконки остаются Zensical-шорткодами
# и рендерятся штатным `pymdownx.emoji` на следующем этапе обработки Markdown.
def _badge(icon: str, text: str = "") -> str:
return "".join([
'<span class="mdx-badge">',
f'<span class="mdx-badge__icon">{icon}</span>',
*([f'<span class="mdx-badge__text">{text}</span>'] if text else []),
'</span>',
])
class InlineBadgeExtension(Extension):
def extendMarkdown(self, md):
md.preprocessors.register(BadgePreprocessor(md), "inline_badges", 175)
def makeExtension(**kwargs):
return InlineBadgeExtension(**kwargs)
-144
View File
@@ -1,144 +0,0 @@
site_name: Документация m3u.su
site_description: Описание сервиса m3u.su и его компонентов
site_author: Антон Аксенов
copyright: Антон Аксенов &copy; 2025 MIT License
repo_name: Репозиторий
repo_url: https://git.axenov.dev/IPTV/docs
edit_uri: src/branch/master/src/
remote_branch: master
docs_dir: ./src
use_directory_urls: false
watch:
- src
extra_css:
- assets/css/custom.css
extra_javascript:
- https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js
extra:
homepage: /docs
logo: assets/img/favicon/logo.png
favicon: assets/img/favicon/logo.png
social:
- name: Канал @iptv_aggregator
icon: simple/telegram
link: https://t.me/iptv_aggregator
- name: Чат @iptv_aggregator_chat
icon: simple/telegram
link: https://t.me/iptv_aggregator_chat
- name: Бот @iptv_aggregator_bot
icon: simple/telegram
link: https://t.me/iptv_aggregator_bot
theme:
name: 'material'
language: ru
features:
- toc.follow
- search.suggest
- navigation.top
- navigation.footer
- navigation.indexes
- content.action.edit
icon:
repo: simple/gitea
edit: material/pencil
view: material/eye
palette:
# Автотема
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
# Тёмная тема
- scheme: slate
media: "(prefers-color-scheme: light)"
accent: teal
primary: black
toggle:
name: Светлая тема
icon: material/weather-sunny
# Светлая тема
- scheme: default
media: "(prefers-color-scheme: dark)"
accent: teal
primary: teal
toggle:
name: Тёмная тема
icon: material/weather-night
plugins:
- minify:
minify_html: true
- tags:
tags: true
listings: true
- social:
cards_layout_options:
background_color: teal
background_image: null
- search:
separator: '[\s\-\.]+'
# indexing: 'full'
validation:
omitted_files: warn
absolute_links: warn
unrecognized_links: warn
anchors: warn
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- attr_list
- md_in_html
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- toc:
permalink: true
nav:
- index.md
- docs.md
- support.md
- legal.md
- 'Общая информация':
- common/index.md
- common/how-it-works.md
- common/selection.md
- common/checks.md
- common/list.md
- common/details.md
- common/connect.md
- common/players.md
- 'IPTV Checker (iptvc)':
- iptvc/index.md
- iptvc/install.md
- iptvc/quickstart.md
- iptvc/env.md
- 'Доступные команды':
- iptvc/commands/index.md
- iptvc/commands/help.md
- iptvc/commands/check.md
- iptvc/commands/version.md
- statuspage.md
- 'Для разработчиков':
- dev/index.md
- dev/local-dev.md
- dev/tgbot.md
- dev/docs.md
- dev/deploy.md
- 'Форматы файлов':
- formats/index.md
- 'playlists.ini': 'formats/playlists.md'
- 'channels.json': 'formats/channels.md'
- '*.m3u (*.m3u8)': 'formats/m3u.md'
- 'Telegram':
- tg/index.md
- 'Бот': tg/bot.md
- 'Чат': tg/chat.md
- 'FAQ (ЧаВо)': 'faq.md'
+399
View File
@@ -0,0 +1,399 @@
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More_considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the "Licensor." The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
---
Git Logo by [Jason Long](https://bsky.app/profile/jasonlong.me) is licensed under the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/).
+1
View File
@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M6 5C6 3.89543 6.89543 3 8 3C9.10457 3 10 3.89543 10 5C10 6.10457 9.10457 7 8 7C6.89543 7 6 6.10457 6 5ZM5.49998 8L10.5 8C11.3284 8 12 8.67157 12 9.5C12 10.6161 11.541 11.5103 10.7879 12.1148C10.0466 12.7098 9.05308 13 8 13C6.94692 13 5.95342 12.7098 5.21215 12.1148C4.45897 11.5103 4 10.6161 4 9.5C4 8.67161 4.67156 8 5.49998 8ZM8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0ZM1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8Z"/></svg>

After

Width:  |  Height:  |  Size: 660 B

Some files were not shown because too many files have changed in this diff Show More