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

This commit is contained in:
2026-07-22 01:11:07 +08:00
parent 7d61aadc5d
commit 8d0f3ebcce
665 changed files with 7626 additions and 922 deletions
+228
View File
@@ -0,0 +1,228 @@
---
# icon: material/architecture
tags: ["iptvc", "разработка", "архитектура"]
---
# Архитектура iptvc
Внутреннее устройство программы для разработчиков.
## Стек технологий
- **Go 1.23+** — язык программирования;
- `net/http` — HTTP-сервер (Go 1.22+ routing patterns);
- `html/template` — шаблоны HTML;
- `//go:embed` — встраивание шаблонов в бинарник;
- `github.com/spf13/cobra` — CLI-фреймворк;
- `gopkg.in/yaml.v3` — парсинг `config.yml`;
- `github.com/joho/godotenv` — загрузка `.env`;
- `github.com/redis/go-redis/v9` — клиент KeyDB/Redis.
## Структура проекта
```
iptvc/
├── main.go # точка входа
├── config.yml # конфигурация
├── .env # переменные окружения (опционально)
├── cmd/ # CLI-команды (Cobra)
│ ├── root.go # корневая команда, глобальные флаги
│ ├── check.go # команда check
│ ├── serve.go # команда serve
│ ├── flags.go # общие флаги check/serve
│ └── version.go # команда version
├── app/
│ ├── app.go # глобальные переменные: Args, Config, Cache
│ ├── config/
│ │ └── config.go # Config, Init(), validate(), IntRange, UserAgents
│ ├── checker/
│ │ └── checker.go # CheckPlaylists(), CheckChannels(), OnPlaylistChecked
│ ├── playlist/
│ │ └── playlist.go # Playlist, Channel, Parse(), Download()
│ ├── inifile/
│ │ └── inifile.go # чтение playlists.ini
│ ├── tagfile/
│ │ └── tagfile.go # чтение channels.json, назначение тегов
│ ├── cache/
│ │ └── cache.go # подключение к KeyDB/Redis
│ ├── logger/
│ │ └── logger.go # настройка логирования
│ ├── utils/
│ │ └── utils.go # Fetch(), ExpandPath(), ArrayUnique(), Md5str()
│ └── web/
│ ├── server.go # Server, Start(), StartBackgroundChecker()
│ ├── handlers.go # HTTP-обработчики
│ ├── templates.go # TemplateManager, //go:embed
│ └── views/ # HTML-шаблоны
│ ├── base.html
│ ├── list.html
│ ├── details.html
│ └── notfound.html
└── go.mod
```
## Пакеты
### `app`
Глобальный контейнер: `Args` (CLI-флаги), `Config` (конфигурация), `Cache` (Redis-клиент).
`Init()` загружает конфигурацию, инициализирует логгер и подключение к кешу.
### `app.config`
Структуры: `Config``AppConfig`, `ServerConfig`, `CheckConfig`, `CacheConfig`.
Кастомные YAML-типы:
- **`IntRange`** — скаляр или `[min, max]`. Метод `Value()` возвращает константу или случайное значение.
- **`UserAgents`** — строка или массив строк. Метод `Pick()` возвращает случайный элемент.
`Init(configPath)` — загружает `config.yml`, применяет env, валидирует.
`validate()` — проверяет все значения, исправляет некорректные с логированием.
### `app.checker`
Содержит логику проверки:
- **`PrepareListsToCheck(files, urls, codes)`** — формирует список плейлистов из файлов, URL и кодов ini-файла.
- **`CheckPlaylists(lists)`** — параллельная проверка плейлистов (семфор `per-routine`), загрузка, парсинг, вызов `CheckChannels` для каждого.
- **`CheckChannels(pls)`** — параллельная проверка каналов (семфор `per-routine`), HTTP-запрос с `Range` header.
- **`OnPlaylistChecked`** — глобальный callback, вызывается после проверки каждого плейлиста. Используется веб-сервером для обновления in-memory кеша.
- **`cachePlaylist(pls)`** — сохранение результата в Redis (если включён).
Параметры проверки берутся из `app.Config.Check.Playlists` и `app.Config.Check.Channels`.
### `app.playlist`
- **`Playlist`** — плейлист: код, URL, контент, каналы, статус.
- **`Channel`** — канал: ID, название, URL, статус, теги.
- **`Download(userAgent, timeout)`** — загрузка по URL.
- **`ReadFromFs()`** — чтение из файла.
- **`Parse()`** — парсинг m3u/m3u8 контента.
### `app.web`
Веб-сервер на `net/http` (Go 1.22 routing).
- **`Server`** — структура: конфиг, кеш, шаблоны, in-memory кеш (`memCache` с `sync.RWMutex`).
- **`Start()`** — запуск HTTP-сервера.
- **`StartBackgroundChecker(opts)`** — фоновая проверка в отдельной горутине.
- **`CheckOptions`** — параметры: Every, Repeat, Random, Files, Urls, Codes.
Маршруты (Go 1.22 patterns):
```
GET /api — редирект на /api/
GET /api/ — Swagger UI
GET /api/openapi.json — OpenAPI-схема
GET /api/playlists — JSON: массив плейлистов
GET /api/playlists/{code} — JSON плейлиста
GET /api/playlists/{code}/channels — JSON каналов плейлиста
GET /api/version — версия
GET /api/health — здоровье сервиса
GET /api/stats — статистика
GET /{$} — главная (catch-all root)
GET /{path...} — все остальные маршруты (catch-all)
```
Catch-all `/{path...}` используется для избежания конфликтов паттернов в Go 1.22 mux.
In-memory кеш (`memCache`) обновляется через `OnPlaylistChecked` callback.
Это позволяет отображать результаты проверки в реальном времени без ожидания завершения цикла и без Redis.
ini-файл кешируется на 30 секунд, кеш сбрасывается при каждом обновлении `memCache`.
## Жизненный цикл `serve --check`
```
main → app.Init() → web.NewServer() → go StartBackgroundChecker() → server.Start()
StartBackgroundChecker:
loop:
runCheckerOnce()
→ checker.PrepareListsToCheck()
→ checker.CheckPlaylists()
→ for each playlist (parallel, per-routine):
→ Download() / ReadFromFs()
→ Parse()
→ CheckChannels()
→ for each channel (parallel, per-routine):
→ HTTP GET with Range header
→ check status + content type
→ OnPlaylistChecked(pls) → memCache update
→ one-cooldown sleep
→ all-cooldown sleep
sleep(every)
if repeat > 0 && iteration >= repeat: stop
```
## CLI-флаги
### Общие (`cmd/flags.go`)
Используются командами `check` и `serve --check`:
| Флаг | Поле | Описание |
| -------------------------- | -------------------- | ---------------------------------- |
| `-i, --ini` | `Args.IniPath` | Путь к playlists.ini |
| `-t, --tags` | `Args.TagsPath` | Путь к channels.json |
| `-r, --random` | `Args.RandomCount` | Случайные N плейлистов |
| `--repeat` | `Args.RepeatCount` | Количество циклов (0 = бесконечно) |
| `--playlists-all-cooldown` | `Args.PlAllCooldown` | Секунд между циклами |
### Только `check` (`addCheckOnlyFlags`)
| Флаг | Поле | Описание |
| ------------- | ---------------- | ------------------- |
| `-j, --json` | `Args.NeedJson` | Вывод в JSON |
| `-q, --quiet` | `Args.NeedQuiet` | Подавить логи |
| `-f, --file` | `Args.Files` | Локальные m3u файлы |
| `-u, --url` | `Args.Urls` | URL плейлистов |
| `-c, --code` | `Args.Codes` | Коды из ini-файла |
### Только `serve`
| Флаг | Поле | Описание |
| ------------ | ----------------- | ------------------------- |
| `-p, --port` | `Args.ServerPort` | Порт веб-сервера |
| `--host` | `Args.ServerHost` | Хост привязки |
| `--check` | `Args.NeedCheck` | Включить фоновую проверку |
### Глобальные (`cmd/root.go`)
| Флаг | Поле | Описание |
| --------------- | ----------------- | ----------------- |
| `--config` | `Args.ConfigPath` | Путь к config.yml |
| `-v, --verbose` | `Args.Verbose` | Подробный лог |
## Конфигурация
Подробное описание параметров — в разделе [config.yml](../../common/config/config.md).
Приоритет: Defaults < `config.yml` < Env < CLI-флаги.
CLI-флаги переопределяют конфигурацию только если переданы явно (`cmd.Flags().Changed()`).
Для этого в Cobra используются zero-value defaults (0, "", false), чтобы отличить «не передан» от «передан со значением по умолчанию».
## Шаблоны
HTML-шаблоны встроены через `//go:embed`:
- `base.html` — общий каркас (header, footer);
- `list.html` — список плейлистов с пагинацией;
- `details.html` — детали плейлиста и список каналов;
- `notfound.html` — страница 404.
SVG-логотипы каналов передаются через `encodeURIComponent` в data-URI для корректной работы с кавычками в HTML.
При отсутствии `playlists.ini` рендерится пустое состояние с alert-блоком.
## Кеширование
Два уровня кеша:
1. **Redis/KeyDB** (опционально) — постоянный кеш результатов проверки. TTL из `cache.ttl`.
2. **In-memory** (`memCache`) — только при `serve --check`. Обновляется в реальном времени через callback. Не требует Redis.
In-memory кеш приоритетнее Redis при отображении в веб-интерфейсе.
+59
View File
@@ -0,0 +1,59 @@
---
title: Компиляция
icon: material/cog
tags: ["iptvc", "сборка"]
---
# Компиляция из исходного кода
Для компиляции потребуется [Go](https://go.dev/dl/) 1.23.6 и выше.
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
make linux
# или make help для получения справки по рецептам
```
## Доступные рецепты Makefile
| Команда | Назначение |
| --- | --- |
| `make linux` | Сборка под Linux (amd64 по умолчанию) |
| `make win` | Сборка под Windows |
| `make darwin` | Сборка под macOS |
| `make release` | Сборка под все платформы (linux/windows/darwin × amd64/arm64) |
| `make clean` | Удаление скомпилированных бинарников |
| `make help` | Вывод списка доступных рецептов |
## Управление архитектурой
Архитектура целевой платформы задаётся переменной `GOARCH`:
```bash
make darwin GOARCH=arm64 # macOS на Apple Silicon
make linux GOARCH=arm64 # Linux ARM (Raspberry Pi и т.п.)
```
## Результат
Скомпилированные бинарники и ZIP-архивы помещаются в директорию `bin/`:
```
bin/
├── linux_amd64/iptvc # или linux_arm64/
├── linux_amd64.zip
├── windows_amd64/iptvc.exe # или windows_arm64/
├── windows_amd64.zip
├── darwin_amd64/iptvc # или darwin_arm64/
└── darwin_amd64.zip
```
## Сборка без Make
Можно скомпилировать напрямую через `go build`:
```bash
go build -o iptvc .
go build -trimpath -ldflags="-s -w" -o iptvc .
```
+58
View File
@@ -0,0 +1,58 @@
---
title: Docker-образ
icon: simple/docker
tags: ["iptvc", "docker"]
---
# Построение Docker-образа
## Сборка
Образ собирается из [Dockerfile](https://git.axenov.dev/IPTV/iptvc/src/branch/master/Dockerfile) — multi-stage сборка на базе `golang:1.25-alpine` с финальным образом `alpine:3.22`.
```bash
docker build -t iptvc ./iptvc
```
Можно передать версию через build-arg:
```bash
docker build --build-arg IPTVC_VERSION=1.1.3 -t iptvc:v1.1.3 ./iptvc
```
Целевая платформа и архитектура задаются через `GOOS` и `GOARCH`:
```bash
docker build --build-arg GOOS=linux --build-arg GOARCH=arm64 -t iptvc:linux-arm64 ./iptvc
```
## Запуск
```bash
# Проверка плейлистов
docker run --rm \
-v ./playlists.ini:/app/playlists.ini \
-v ./channels.json:/app/channels.json \
iptvc check -i playlists.ini --repeat 0 --playlists-all-cooldown 60
# Веб-сервер с фоновой проверкой
docker run --rm -p 8800:8800 \
-v ./playlists.ini:/app/playlists.ini \
-v ./channels.json:/app/channels.json \
iptvc serve -i playlists.ini -p 8800 --check
```
## Использование в compose
В `compose.yml` сервиса `iptvc` образ собирается автоматически:
```yaml
iptvc:
image: git.axenov.dev/iptv/iptvc:latest
build:
context: ./iptvc
dockerfile: Dockerfile
command: ["serve", "--check", "--repeat", "0"]
```
Подробнее о полном окружении — в разделе [Развёртывание](../site/deploy.md).
+103
View File
@@ -0,0 +1,103 @@
---
icon: material/book-cog-outline
---
# :material-book-cog-outline: Сборка документации
## Стилистика и правила оформления
Все исходники хранятся в директории `content/` в формате **Markdown** (формат файлов `.md`).
Структура проекта и его конфигурация описываются в файле `mkdocs.yml` в корне репозитория.
Структура исходных файлов документации и содержание должны быть согласованными.
Все ссылки на соседние страницы и изображения должны быть относительными.
**Каждое предложение должно быть на одной строке.**
Это даёт более наглядную разницу (diff) в тексте при работе с git.
Абзацы и списки должны отделяться 1 пустой строкой до и после.
Допустимо использовать любые стилистические возможности темы **Material for MkDocs** и самого **mkdocs**, но не следует визуально перегружать текст.
Документацию по ним см. по ссылкам ниже.
## Добавление изображений
**Все изображения хранятся в директории `_assets/` рядом с документом.**
Общая суть такова:
* чем больше размеры, тем хуже должно быть качество;
* чем меньше размеры, тем чётче должен быть текст.
Каждое изображение должно:
* быть сохранено в формате jpg;
* иметь размер неболее 150 Кб;
* быть сжатым с качеством 65-80% от исходного;
* быть размером до 1500 px по наибольшей стороне.
Если на изображении есть текст, он должен оставаться различимым и читаемым.
Но если на изображении есть любые конфиденциальные данные и его невозможно кадрировать без потери смысла, то их необходимо скрыть.
Хорошей практикой будет использовать спойлеры для скрытия больших и/или идущих подряд нескольких изображений, например:
```
Совершенно любой текст, lorem ipsum dolor sit amet.
Совершенно любой текст, lorem ipsum dolor sit amet.
??? quote "В этом спойлере несколько больших картинок"
![Подпись-плейсхолдер1](_assets/example1.jpg)
![Подпись-плейсхолдер2](_assets/example2.jpg)
Продолжение текста, lorem ipsum dolor sit amet.
Продолжение текста, lorem ipsum dolor sit amet.
```
Эти простые правила позволят поддерживать репозиторий достаточно компактным, а страницы делать комфортными для чтения, экономя трафик для мобильных устройств.
## Стек
* make
* [docker](https://docker.com)
* [mkdocs](https://www.mkdocs.org/)
* [squidfunk/mkdocs-material](https://hub.docker.com/r/squidfunk/mkdocs-material)
* <https://squidfunk.github.io/mkdocs-material>
* <https://squidfunk.github.io/mkdocs-material/reference/admonitions/>
* <https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/>
## Запуск mkdocs в контейнере
```
make live
```
Перегенерирует документацию на лету сразу после сохранения файлов.
Документацию в реальном времени можно просматривать по адресу [localhost:3000](http://localhost:3000).
## Генерация статического сайта
```
make site
```
Генерирует статические файлы, которую можно версионировать, хранить,деплоить отдельно или просматривать на ПК через браузер.
Готовый скомпилированный статический сайт с документацией находится в директории `site/`.
## Генерация docker-образа
```
make image
```
Собирает docker-образ на основе nginx, генерируя перед этим статический сайт.
Запустить контейнер из этого образа по адресу [localhost:3001](http://localhost:3001) можно командой:
```
make run
```
+550
View File
@@ -0,0 +1,550 @@
Информацию о лицензии см. на странице [Свободное ПО](../../../legal/license.md)
| Иконка | Код для вставки в текст | Код для вставки в frontmatter |
| ------------------------------------------------ | -------------------------------------------------- | ------------------------------------------------ |
| :vscode-account: | `:vscode-account:` | `vscode/account` |
| :vscode-activate-breakpoints: | `:vscode-activate-breakpoints:` | `vscode/activate-breakpoints` |
| :vscode-add: | `:vscode-add:` | `vscode/add` |
| :vscode-add-small: | `:vscode-add-small:` | `vscode/add-small` |
| :vscode-agent: | `:vscode-agent:` | `vscode/agent` |
| :vscode-archive: | `:vscode-archive:` | `vscode/archive` |
| :vscode-arrow-both: | `:vscode-arrow-both:` | `vscode/arrow-both` |
| :vscode-arrow-circle-down: | `:vscode-arrow-circle-down:` | `vscode/arrow-circle-down` |
| :vscode-arrow-circle-left: | `:vscode-arrow-circle-left:` | `vscode/arrow-circle-left` |
| :vscode-arrow-circle-right: | `:vscode-arrow-circle-right:` | `vscode/arrow-circle-right` |
| :vscode-arrow-circle-up: | `:vscode-arrow-circle-up:` | `vscode/arrow-circle-up` |
| :vscode-arrow-down: | `:vscode-arrow-down:` | `vscode/arrow-down` |
| :vscode-arrow-left: | `:vscode-arrow-left:` | `vscode/arrow-left` |
| :vscode-arrow-right: | `:vscode-arrow-right:` | `vscode/arrow-right` |
| :vscode-arrow-small-down: | `:vscode-arrow-small-down:` | `vscode/arrow-small-down` |
| :vscode-arrow-small-left: | `:vscode-arrow-small-left:` | `vscode/arrow-small-left` |
| :vscode-arrow-small-right: | `:vscode-arrow-small-right:` | `vscode/arrow-small-right` |
| :vscode-arrow-small-up: | `:vscode-arrow-small-up:` | `vscode/arrow-small-up` |
| :vscode-arrow-swap: | `:vscode-arrow-swap:` | `vscode/arrow-swap` |
| :vscode-arrow-up: | `:vscode-arrow-up:` | `vscode/arrow-up` |
| :vscode-ask: | `:vscode-ask:` | `vscode/ask` |
| :vscode-attach: | `:vscode-attach:` | `vscode/attach` |
| :vscode-azure: | `:vscode-azure:` | `vscode/azure` |
| :vscode-azure-devops: | `:vscode-azure-devops:` | `vscode/azure-devops` |
| :vscode-beaker: | `:vscode-beaker:` | `vscode/beaker` |
| :vscode-beaker-stop: | `:vscode-beaker-stop:` | `vscode/beaker-stop` |
| :vscode-bell: | `:vscode-bell:` | `vscode/bell` |
| :vscode-bell-dot: | `:vscode-bell-dot:` | `vscode/bell-dot` |
| :vscode-bell-slash: | `:vscode-bell-slash:` | `vscode/bell-slash` |
| :vscode-bell-slash-dot: | `:vscode-bell-slash-dot:` | `vscode/bell-slash-dot` |
| :vscode-blank: | `:vscode-blank:` | `vscode/blank` |
| :vscode-bold: | `:vscode-bold:` | `vscode/bold` |
| :vscode-book: | `:vscode-book:` | `vscode/book` |
| :vscode-bookmark: | `:vscode-bookmark:` | `vscode/bookmark` |
| :vscode-bracket-dot: | `:vscode-bracket-dot:` | `vscode/bracket-dot` |
| :vscode-bracket-error: | `:vscode-bracket-error:` | `vscode/bracket-error` |
| :vscode-briefcase: | `:vscode-briefcase:` | `vscode/briefcase` |
| :vscode-broadcast: | `:vscode-broadcast:` | `vscode/broadcast` |
| :vscode-browser: | `:vscode-browser:` | `vscode/browser` |
| :vscode-bug: | `:vscode-bug:` | `vscode/bug` |
| :vscode-build: | `:vscode-build:` | `vscode/build` |
| :vscode-calendar: | `:vscode-calendar:` | `vscode/calendar` |
| :vscode-call-incoming: | `:vscode-call-incoming:` | `vscode/call-incoming` |
| :vscode-call-outgoing: | `:vscode-call-outgoing:` | `vscode/call-outgoing` |
| :vscode-case-sensitive: | `:vscode-case-sensitive:` | `vscode/case-sensitive` |
| :vscode-chat-export: | `:vscode-chat-export:` | `vscode/chat-export` |
| :vscode-chat-import: | `:vscode-chat-import:` | `vscode/chat-import` |
| :vscode-chat-sparkle: | `:vscode-chat-sparkle:` | `vscode/chat-sparkle` |
| :vscode-chat-sparkle-error: | `:vscode-chat-sparkle-error:` | `vscode/chat-sparkle-error` |
| :vscode-chat-sparkle-warning: | `:vscode-chat-sparkle-warning:` | `vscode/chat-sparkle-warning` |
| :vscode-check: | `:vscode-check:` | `vscode/check` |
| :vscode-check-all: | `:vscode-check-all:` | `vscode/check-all` |
| :vscode-checklist: | `:vscode-checklist:` | `vscode/checklist` |
| :vscode-chevron-down: | `:vscode-chevron-down:` | `vscode/chevron-down` |
| :vscode-chevron-left: | `:vscode-chevron-left:` | `vscode/chevron-left` |
| :vscode-chevron-right: | `:vscode-chevron-right:` | `vscode/chevron-right` |
| :vscode-chevron-up: | `:vscode-chevron-up:` | `vscode/chevron-up` |
| :vscode-chip: | `:vscode-chip:` | `vscode/chip` |
| :vscode-chrome-close: | `:vscode-chrome-close:` | `vscode/chrome-close` |
| :vscode-chrome-maximize: | `:vscode-chrome-maximize:` | `vscode/chrome-maximize` |
| :vscode-chrome-minimize: | `:vscode-chrome-minimize:` | `vscode/chrome-minimize` |
| :vscode-chrome-restore: | `:vscode-chrome-restore:` | `vscode/chrome-restore` |
| :vscode-circle: | `:vscode-circle:` | `vscode/circle` |
| :vscode-circle-filled: | `:vscode-circle-filled:` | `vscode/circle-filled` |
| :vscode-circle-large: | `:vscode-circle-large:` | `vscode/circle-large` |
| :vscode-circle-large-filled: | `:vscode-circle-large-filled:` | `vscode/circle-large-filled` |
| :vscode-circle-slash: | `:vscode-circle-slash:` | `vscode/circle-slash` |
| :vscode-circle-small: | `:vscode-circle-small:` | `vscode/circle-small` |
| :vscode-circle-small-filled: | `:vscode-circle-small-filled:` | `vscode/circle-small-filled` |
| :vscode-circuit-board: | `:vscode-circuit-board:` | `vscode/circuit-board` |
| :vscode-claude: | `:vscode-claude:` | `vscode/claude` |
| :vscode-clear-all: | `:vscode-clear-all:` | `vscode/clear-all` |
| :vscode-clippy: | `:vscode-clippy:` | `vscode/clippy` |
| :vscode-clockface: | `:vscode-clockface:` | `vscode/clockface` |
| :vscode-close: | `:vscode-close:` | `vscode/close` |
| :vscode-close-all: | `:vscode-close-all:` | `vscode/close-all` |
| :vscode-cloud: | `:vscode-cloud:` | `vscode/cloud` |
| :vscode-cloud-download: | `:vscode-cloud-download:` | `vscode/cloud-download` |
| :vscode-cloud-small: | `:vscode-cloud-small:` | `vscode/cloud-small` |
| :vscode-cloud-upload: | `:vscode-cloud-upload:` | `vscode/cloud-upload` |
| :vscode-code: | `:vscode-code:` | `vscode/code` |
| :vscode-code-oss: | `:vscode-code-oss:` | `vscode/code-oss` |
| :vscode-code-review: | `:vscode-code-review:` | `vscode/code-review` |
| :vscode-coffee: | `:vscode-coffee:` | `vscode/coffee` |
| :vscode-collapse-all: | `:vscode-collapse-all:` | `vscode/collapse-all` |
| :vscode-collection: | `:vscode-collection:` | `vscode/collection` |
| :vscode-collection-small: | `:vscode-collection-small:` | `vscode/collection-small` |
| :vscode-color-mode: | `:vscode-color-mode:` | `vscode/color-mode` |
| :vscode-combine: | `:vscode-combine:` | `vscode/combine` |
| :vscode-comment: | `:vscode-comment:` | `vscode/comment` |
| :vscode-comment-discussion: | `:vscode-comment-discussion:` | `vscode/comment-discussion` |
| :vscode-comment-discussion-quote: | `:vscode-comment-discussion-quote:` | `vscode/comment-discussion-quote` |
| :vscode-comment-discussion-sparkle: | `:vscode-comment-discussion-sparkle:` | `vscode/comment-discussion-sparkle` |
| :vscode-comment-draft: | `:vscode-comment-draft:` | `vscode/comment-draft` |
| :vscode-comment-unresolved: | `:vscode-comment-unresolved:` | `vscode/comment-unresolved` |
| :vscode-compass: | `:vscode-compass:` | `vscode/compass` |
| :vscode-compass-active: | `:vscode-compass-active:` | `vscode/compass-active` |
| :vscode-compass-dot: | `:vscode-compass-dot:` | `vscode/compass-dot` |
| :vscode-copilot: | `:vscode-copilot:` | `vscode/copilot` |
| :vscode-copilot-blocked: | `:vscode-copilot-blocked:` | `vscode/copilot-blocked` |
| :vscode-copilot-error: | `:vscode-copilot-error:` | `vscode/copilot-error` |
| :vscode-copilot-in-progress: | `:vscode-copilot-in-progress:` | `vscode/copilot-in-progress` |
| :vscode-copilot-large: | `:vscode-copilot-large:` | `vscode/copilot-large` |
| :vscode-copilot-not-connected: | `:vscode-copilot-not-connected:` | `vscode/copilot-not-connected` |
| :vscode-copilot-snooze: | `:vscode-copilot-snooze:` | `vscode/copilot-snooze` |
| :vscode-copilot-success: | `:vscode-copilot-success:` | `vscode/copilot-success` |
| :vscode-copilot-unavailable: | `:vscode-copilot-unavailable:` | `vscode/copilot-unavailable` |
| :vscode-copilot-warning: | `:vscode-copilot-warning:` | `vscode/copilot-warning` |
| :vscode-copilot-warning-large: | `:vscode-copilot-warning-large:` | `vscode/copilot-warning-large` |
| :vscode-copy: | `:vscode-copy:` | `vscode/copy` |
| :vscode-coverage: | `:vscode-coverage:` | `vscode/coverage` |
| :vscode-credit-card: | `:vscode-credit-card:` | `vscode/credit-card` |
| :vscode-cursor: | `:vscode-cursor:` | `vscode/cursor` |
| :vscode-dash: | `:vscode-dash:` | `vscode/dash` |
| :vscode-dashboard: | `:vscode-dashboard:` | `vscode/dashboard` |
| :vscode-database: | `:vscode-database:` | `vscode/database` |
| :vscode-debug: | `:vscode-debug:` | `vscode/debug` |
| :vscode-debug-all: | `:vscode-debug-all:` | `vscode/debug-all` |
| :vscode-debug-alt: | `:vscode-debug-alt:` | `vscode/debug-alt` |
| :vscode-debug-alt-small: | `:vscode-debug-alt-small:` | `vscode/debug-alt-small` |
| :vscode-debug-breakpoint-conditional: | `:vscode-debug-breakpoint-conditional:` | `vscode/debug-breakpoint-conditional` |
| :vscode-debug-breakpoint-conditional-unverified: | `:vscode-debug-breakpoint-conditional-unverified:` | `vscode/debug-breakpoint-conditional-unverified` |
| :vscode-debug-breakpoint-data: | `:vscode-debug-breakpoint-data:` | `vscode/debug-breakpoint-data` |
| :vscode-debug-breakpoint-data-unverified: | `:vscode-debug-breakpoint-data-unverified:` | `vscode/debug-breakpoint-data-unverified` |
| :vscode-debug-breakpoint-function: | `:vscode-debug-breakpoint-function:` | `vscode/debug-breakpoint-function` |
| :vscode-debug-breakpoint-function-unverified: | `:vscode-debug-breakpoint-function-unverified:` | `vscode/debug-breakpoint-function-unverified` |
| :vscode-debug-breakpoint-log: | `:vscode-debug-breakpoint-log:` | `vscode/debug-breakpoint-log` |
| :vscode-debug-breakpoint-log-unverified: | `:vscode-debug-breakpoint-log-unverified:` | `vscode/debug-breakpoint-log-unverified` |
| :vscode-debug-breakpoint-unsupported: | `:vscode-debug-breakpoint-unsupported:` | `vscode/debug-breakpoint-unsupported` |
| :vscode-debug-connected: | `:vscode-debug-connected:` | `vscode/debug-connected` |
| :vscode-debug-console: | `:vscode-debug-console:` | `vscode/debug-console` |
| :vscode-debug-continue: | `:vscode-debug-continue:` | `vscode/debug-continue` |
| :vscode-debug-continue-small: | `:vscode-debug-continue-small:` | `vscode/debug-continue-small` |
| :vscode-debug-coverage: | `:vscode-debug-coverage:` | `vscode/debug-coverage` |
| :vscode-debug-disconnect: | `:vscode-debug-disconnect:` | `vscode/debug-disconnect` |
| :vscode-debug-line-by-line: | `:vscode-debug-line-by-line:` | `vscode/debug-line-by-line` |
| :vscode-debug-pause: | `:vscode-debug-pause:` | `vscode/debug-pause` |
| :vscode-debug-rerun: | `:vscode-debug-rerun:` | `vscode/debug-rerun` |
| :vscode-debug-restart: | `:vscode-debug-restart:` | `vscode/debug-restart` |
| :vscode-debug-restart-frame: | `:vscode-debug-restart-frame:` | `vscode/debug-restart-frame` |
| :vscode-debug-reverse-continue: | `:vscode-debug-reverse-continue:` | `vscode/debug-reverse-continue` |
| :vscode-debug-stackframe: | `:vscode-debug-stackframe:` | `vscode/debug-stackframe` |
| :vscode-debug-stackframe-active: | `:vscode-debug-stackframe-active:` | `vscode/debug-stackframe-active` |
| :vscode-debug-start: | `:vscode-debug-start:` | `vscode/debug-start` |
| :vscode-debug-step-back: | `:vscode-debug-step-back:` | `vscode/debug-step-back` |
| :vscode-debug-step-into: | `:vscode-debug-step-into:` | `vscode/debug-step-into` |
| :vscode-debug-step-out: | `:vscode-debug-step-out:` | `vscode/debug-step-out` |
| :vscode-debug-step-over: | `:vscode-debug-step-over:` | `vscode/debug-step-over` |
| :vscode-debug-stop: | `:vscode-debug-stop:` | `vscode/debug-stop` |
| :vscode-desktop-download: | `:vscode-desktop-download:` | `vscode/desktop-download` |
| :vscode-device-camera: | `:vscode-device-camera:` | `vscode/device-camera` |
| :vscode-device-camera-video: | `:vscode-device-camera-video:` | `vscode/device-camera-video` |
| :vscode-device-mobile: | `:vscode-device-mobile:` | `vscode/device-mobile` |
| :vscode-diff: | `:vscode-diff:` | `vscode/diff` |
| :vscode-diff-added: | `:vscode-diff-added:` | `vscode/diff-added` |
| :vscode-diff-ignored: | `:vscode-diff-ignored:` | `vscode/diff-ignored` |
| :vscode-diff-modified: | `:vscode-diff-modified:` | `vscode/diff-modified` |
| :vscode-diff-multiple: | `:vscode-diff-multiple:` | `vscode/diff-multiple` |
| :vscode-diff-removed: | `:vscode-diff-removed:` | `vscode/diff-removed` |
| :vscode-diff-renamed: | `:vscode-diff-renamed:` | `vscode/diff-renamed` |
| :vscode-diff-single: | `:vscode-diff-single:` | `vscode/diff-single` |
| :vscode-discard: | `:vscode-discard:` | `vscode/discard` |
| :vscode-download: | `:vscode-download:` | `vscode/download` |
| :vscode-edit: | `:vscode-edit:` | `vscode/edit` |
| :vscode-edit-code: | `:vscode-edit-code:` | `vscode/edit-code` |
| :vscode-edit-session: | `:vscode-edit-session:` | `vscode/edit-session` |
| :vscode-edit-sparkle: | `:vscode-edit-sparkle:` | `vscode/edit-sparkle` |
| :vscode-editor-layout: | `:vscode-editor-layout:` | `vscode/editor-layout` |
| :vscode-ellipsis: | `:vscode-ellipsis:` | `vscode/ellipsis` |
| :vscode-empty-window: | `:vscode-empty-window:` | `vscode/empty-window` |
| :vscode-eraser: | `:vscode-eraser:` | `vscode/eraser` |
| :vscode-error: | `:vscode-error:` | `vscode/error` |
| :vscode-error-small: | `:vscode-error-small:` | `vscode/error-small` |
| :vscode-exclude: | `:vscode-exclude:` | `vscode/exclude` |
| :vscode-expand-all: | `:vscode-expand-all:` | `vscode/expand-all` |
| :vscode-export: | `:vscode-export:` | `vscode/export` |
| :vscode-extensions: | `:vscode-extensions:` | `vscode/extensions` |
| :vscode-extensions-large: | `:vscode-extensions-large:` | `vscode/extensions-large` |
| :vscode-eye: | `:vscode-eye:` | `vscode/eye` |
| :vscode-eye-closed: | `:vscode-eye-closed:` | `vscode/eye-closed` |
| :vscode-feedback: | `:vscode-feedback:` | `vscode/feedback` |
| :vscode-file: | `:vscode-file:` | `vscode/file` |
| :vscode-file-binary: | `:vscode-file-binary:` | `vscode/file-binary` |
| :vscode-file-code: | `:vscode-file-code:` | `vscode/file-code` |
| :vscode-file-media: | `:vscode-file-media:` | `vscode/file-media` |
| :vscode-file-pdf: | `:vscode-file-pdf:` | `vscode/file-pdf` |
| :vscode-file-submodule: | `:vscode-file-submodule:` | `vscode/file-submodule` |
| :vscode-file-symlink-directory: | `:vscode-file-symlink-directory:` | `vscode/file-symlink-directory` |
| :vscode-file-symlink-file: | `:vscode-file-symlink-file:` | `vscode/file-symlink-file` |
| :vscode-file-text: | `:vscode-file-text:` | `vscode/file-text` |
| :vscode-file-zip: | `:vscode-file-zip:` | `vscode/file-zip` |
| :vscode-files: | `:vscode-files:` | `vscode/files` |
| :vscode-filter: | `:vscode-filter:` | `vscode/filter` |
| :vscode-filter-filled: | `:vscode-filter-filled:` | `vscode/filter-filled` |
| :vscode-flag: | `:vscode-flag:` | `vscode/flag` |
| :vscode-flame: | `:vscode-flame:` | `vscode/flame` |
| :vscode-fold: | `:vscode-fold:` | `vscode/fold` |
| :vscode-fold-down: | `:vscode-fold-down:` | `vscode/fold-down` |
| :vscode-fold-up: | `:vscode-fold-up:` | `vscode/fold-up` |
| :vscode-folder: | `:vscode-folder:` | `vscode/folder` |
| :vscode-folder-active: | `:vscode-folder-active:` | `vscode/folder-active` |
| :vscode-folder-library: | `:vscode-folder-library:` | `vscode/folder-library` |
| :vscode-folder-opened: | `:vscode-folder-opened:` | `vscode/folder-opened` |
| :vscode-forward: | `:vscode-forward:` | `vscode/forward` |
| :vscode-game: | `:vscode-game:` | `vscode/game` |
| :vscode-gear: | `:vscode-gear:` | `vscode/gear` |
| :vscode-gift: | `:vscode-gift:` | `vscode/gift` |
| :vscode-gist: | `:vscode-gist:` | `vscode/gist` |
| :vscode-gist-secret: | `:vscode-gist-secret:` | `vscode/gist-secret` |
| :vscode-git-branch: | `:vscode-git-branch:` | `vscode/git-branch` |
| :vscode-git-branch-changes: | `:vscode-git-branch-changes:` | `vscode/git-branch-changes` |
| :vscode-git-branch-conflicts: | `:vscode-git-branch-conflicts:` | `vscode/git-branch-conflicts` |
| :vscode-git-branch-staged-changes: | `:vscode-git-branch-staged-changes:` | `vscode/git-branch-staged-changes` |
| :vscode-git-commit: | `:vscode-git-commit:` | `vscode/git-commit` |
| :vscode-git-compare: | `:vscode-git-compare:` | `vscode/git-compare` |
| :vscode-git-fetch: | `:vscode-git-fetch:` | `vscode/git-fetch` |
| :vscode-git-merge: | `:vscode-git-merge:` | `vscode/git-merge` |
| :vscode-git-pull-request: | `:vscode-git-pull-request:` | `vscode/git-pull-request` |
| :vscode-git-pull-request-closed: | `:vscode-git-pull-request-closed:` | `vscode/git-pull-request-closed` |
| :vscode-git-pull-request-create: | `:vscode-git-pull-request-create:` | `vscode/git-pull-request-create` |
| :vscode-git-pull-request-done: | `:vscode-git-pull-request-done:` | `vscode/git-pull-request-done` |
| :vscode-git-pull-request-draft: | `:vscode-git-pull-request-draft:` | `vscode/git-pull-request-draft` |
| :vscode-git-pull-request-go-to-changes: | `:vscode-git-pull-request-go-to-changes:` | `vscode/git-pull-request-go-to-changes` |
| :vscode-git-pull-request-new-changes: | `:vscode-git-pull-request-new-changes:` | `vscode/git-pull-request-new-changes` |
| :vscode-git-stash: | `:vscode-git-stash:` | `vscode/git-stash` |
| :vscode-git-stash-apply: | `:vscode-git-stash-apply:` | `vscode/git-stash-apply` |
| :vscode-git-stash-pop: | `:vscode-git-stash-pop:` | `vscode/git-stash-pop` |
| :vscode-github: | `:vscode-github:` | `vscode/github` |
| :vscode-github-action: | `:vscode-github-action:` | `vscode/github-action` |
| :vscode-github-alt: | `:vscode-github-alt:` | `vscode/github-alt` |
| :vscode-github-inverted: | `:vscode-github-inverted:` | `vscode/github-inverted` |
| :vscode-github-project: | `:vscode-github-project:` | `vscode/github-project` |
| :vscode-globe: | `:vscode-globe:` | `vscode/globe` |
| :vscode-go-to-editing-session: | `:vscode-go-to-editing-session:` | `vscode/go-to-editing-session` |
| :vscode-go-to-file: | `:vscode-go-to-file:` | `vscode/go-to-file` |
| :vscode-go-to-search: | `:vscode-go-to-search:` | `vscode/go-to-search` |
| :vscode-grabber: | `:vscode-grabber:` | `vscode/grabber` |
| :vscode-graph: | `:vscode-graph:` | `vscode/graph` |
| :vscode-graph-left: | `:vscode-graph-left:` | `vscode/graph-left` |
| :vscode-graph-line: | `:vscode-graph-line:` | `vscode/graph-line` |
| :vscode-graph-scatter: | `:vscode-graph-scatter:` | `vscode/graph-scatter` |
| :vscode-gripper: | `:vscode-gripper:` | `vscode/gripper` |
| :vscode-group-by-ref-type: | `:vscode-group-by-ref-type:` | `vscode/group-by-ref-type` |
| :vscode-heart: | `:vscode-heart:` | `vscode/heart` |
| :vscode-heart-filled: | `:vscode-heart-filled:` | `vscode/heart-filled` |
| :vscode-history: | `:vscode-history:` | `vscode/history` |
| :vscode-home: | `:vscode-home:` | `vscode/home` |
| :vscode-horizontal-rule: | `:vscode-horizontal-rule:` | `vscode/horizontal-rule` |
| :vscode-hubot: | `:vscode-hubot:` | `vscode/hubot` |
| :vscode-inbox: | `:vscode-inbox:` | `vscode/inbox` |
| :vscode-indent: | `:vscode-indent:` | `vscode/indent` |
| :vscode-index-zero: | `:vscode-index-zero:` | `vscode/index-zero` |
| :vscode-info: | `:vscode-info:` | `vscode/info` |
| :vscode-insert: | `:vscode-insert:` | `vscode/insert` |
| :vscode-inspect: | `:vscode-inspect:` | `vscode/inspect` |
| :vscode-issue-draft: | `:vscode-issue-draft:` | `vscode/issue-draft` |
| :vscode-issue-reopened: | `:vscode-issue-reopened:` | `vscode/issue-reopened` |
| :vscode-issues: | `:vscode-issues:` | `vscode/issues` |
| :vscode-italic: | `:vscode-italic:` | `vscode/italic` |
| :vscode-jersey: | `:vscode-jersey:` | `vscode/jersey` |
| :vscode-json: | `:vscode-json:` | `vscode/json` |
| :vscode-kebab-vertical: | `:vscode-kebab-vertical:` | `vscode/kebab-vertical` |
| :vscode-key: | `:vscode-key:` | `vscode/key` |
| :vscode-keyboard-tab: | `:vscode-keyboard-tab:` | `vscode/keyboard-tab` |
| :vscode-keyboard-tab-above: | `:vscode-keyboard-tab-above:` | `vscode/keyboard-tab-above` |
| :vscode-keyboard-tab-below: | `:vscode-keyboard-tab-below:` | `vscode/keyboard-tab-below` |
| :vscode-law: | `:vscode-law:` | `vscode/law` |
| :vscode-layers: | `:vscode-layers:` | `vscode/layers` |
| :vscode-layers-active: | `:vscode-layers-active:` | `vscode/layers-active` |
| :vscode-layers-dot: | `:vscode-layers-dot:` | `vscode/layers-dot` |
| :vscode-layout: | `:vscode-layout:` | `vscode/layout` |
| :vscode-layout-activitybar-left: | `:vscode-layout-activitybar-left:` | `vscode/layout-activitybar-left` |
| :vscode-layout-activitybar-right: | `:vscode-layout-activitybar-right:` | `vscode/layout-activitybar-right` |
| :vscode-layout-centered: | `:vscode-layout-centered:` | `vscode/layout-centered` |
| :vscode-layout-menubar: | `:vscode-layout-menubar:` | `vscode/layout-menubar` |
| :vscode-layout-panel: | `:vscode-layout-panel:` | `vscode/layout-panel` |
| :vscode-layout-panel-center: | `:vscode-layout-panel-center:` | `vscode/layout-panel-center` |
| :vscode-layout-panel-dock: | `:vscode-layout-panel-dock:` | `vscode/layout-panel-dock` |
| :vscode-layout-panel-justify: | `:vscode-layout-panel-justify:` | `vscode/layout-panel-justify` |
| :vscode-layout-panel-left: | `:vscode-layout-panel-left:` | `vscode/layout-panel-left` |
| :vscode-layout-panel-off: | `:vscode-layout-panel-off:` | `vscode/layout-panel-off` |
| :vscode-layout-panel-right: | `:vscode-layout-panel-right:` | `vscode/layout-panel-right` |
| :vscode-layout-sidebar-left: | `:vscode-layout-sidebar-left:` | `vscode/layout-sidebar-left` |
| :vscode-layout-sidebar-left-dock: | `:vscode-layout-sidebar-left-dock:` | `vscode/layout-sidebar-left-dock` |
| :vscode-layout-sidebar-left-off: | `:vscode-layout-sidebar-left-off:` | `vscode/layout-sidebar-left-off` |
| :vscode-layout-sidebar-right: | `:vscode-layout-sidebar-right:` | `vscode/layout-sidebar-right` |
| :vscode-layout-sidebar-right-dock: | `:vscode-layout-sidebar-right-dock:` | `vscode/layout-sidebar-right-dock` |
| :vscode-layout-sidebar-right-off: | `:vscode-layout-sidebar-right-off:` | `vscode/layout-sidebar-right-off` |
| :vscode-layout-statusbar: | `:vscode-layout-statusbar:` | `vscode/layout-statusbar` |
| :vscode-library: | `:vscode-library:` | `vscode/library` |
| :vscode-lightbulb: | `:vscode-lightbulb:` | `vscode/lightbulb` |
| :vscode-lightbulb-autofix: | `:vscode-lightbulb-autofix:` | `vscode/lightbulb-autofix` |
| :vscode-lightbulb-empty: | `:vscode-lightbulb-empty:` | `vscode/lightbulb-empty` |
| :vscode-lightbulb-sparkle: | `:vscode-lightbulb-sparkle:` | `vscode/lightbulb-sparkle` |
| :vscode-link: | `:vscode-link:` | `vscode/link` |
| :vscode-link-external: | `:vscode-link-external:` | `vscode/link-external` |
| :vscode-list-filter: | `:vscode-list-filter:` | `vscode/list-filter` |
| :vscode-list-flat: | `:vscode-list-flat:` | `vscode/list-flat` |
| :vscode-list-ordered: | `:vscode-list-ordered:` | `vscode/list-ordered` |
| :vscode-list-selection: | `:vscode-list-selection:` | `vscode/list-selection` |
| :vscode-list-tree: | `:vscode-list-tree:` | `vscode/list-tree` |
| :vscode-list-unordered: | `:vscode-list-unordered:` | `vscode/list-unordered` |
| :vscode-live-share: | `:vscode-live-share:` | `vscode/live-share` |
| :vscode-loading: | `:vscode-loading:` | `vscode/loading` |
| :vscode-location: | `:vscode-location:` | `vscode/location` |
| :vscode-lock: | `:vscode-lock:` | `vscode/lock` |
| :vscode-lock-small: | `:vscode-lock-small:` | `vscode/lock-small` |
| :vscode-magnet: | `:vscode-magnet:` | `vscode/magnet` |
| :vscode-mail: | `:vscode-mail:` | `vscode/mail` |
| :vscode-mail-read: | `:vscode-mail-read:` | `vscode/mail-read` |
| :vscode-map: | `:vscode-map:` | `vscode/map` |
| :vscode-map-filled: | `:vscode-map-filled:` | `vscode/map-filled` |
| :vscode-map-vertical: | `:vscode-map-vertical:` | `vscode/map-vertical` |
| :vscode-map-vertical-filled: | `:vscode-map-vertical-filled:` | `vscode/map-vertical-filled` |
| :vscode-markdown: | `:vscode-markdown:` | `vscode/markdown` |
| :vscode-mcp: | `:vscode-mcp:` | `vscode/mcp` |
| :vscode-megaphone: | `:vscode-megaphone:` | `vscode/megaphone` |
| :vscode-mention: | `:vscode-mention:` | `vscode/mention` |
| :vscode-menu: | `:vscode-menu:` | `vscode/menu` |
| :vscode-merge: | `:vscode-merge:` | `vscode/merge` |
| :vscode-merge-into: | `:vscode-merge-into:` | `vscode/merge-into` |
| :vscode-mic: | `:vscode-mic:` | `vscode/mic` |
| :vscode-mic-filled: | `:vscode-mic-filled:` | `vscode/mic-filled` |
| :vscode-milestone: | `:vscode-milestone:` | `vscode/milestone` |
| :vscode-mirror: | `:vscode-mirror:` | `vscode/mirror` |
| :vscode-mortar-board: | `:vscode-mortar-board:` | `vscode/mortar-board` |
| :vscode-move: | `:vscode-move:` | `vscode/move` |
| :vscode-multiple-windows: | `:vscode-multiple-windows:` | `vscode/multiple-windows` |
| :vscode-music: | `:vscode-music:` | `vscode/music` |
| :vscode-mute: | `:vscode-mute:` | `vscode/mute` |
| :vscode-new-collection: | `:vscode-new-collection:` | `vscode/new-collection` |
| :vscode-new-file: | `:vscode-new-file:` | `vscode/new-file` |
| :vscode-new-folder: | `:vscode-new-folder:` | `vscode/new-folder` |
| :vscode-new-session: | `:vscode-new-session:` | `vscode/new-session` |
| :vscode-newline: | `:vscode-newline:` | `vscode/newline` |
| :vscode-no-newline: | `:vscode-no-newline:` | `vscode/no-newline` |
| :vscode-note: | `:vscode-note:` | `vscode/note` |
| :vscode-notebook: | `:vscode-notebook:` | `vscode/notebook` |
| :vscode-notebook-template: | `:vscode-notebook-template:` | `vscode/notebook-template` |
| :vscode-octoface: | `:vscode-octoface:` | `vscode/octoface` |
| :vscode-open-in-product: | `:vscode-open-in-product:` | `vscode/open-in-product` |
| :vscode-open-in-window: | `:vscode-open-in-window:` | `vscode/open-in-window` |
| :vscode-open-preview: | `:vscode-open-preview:` | `vscode/open-preview` |
| :vscode-openai: | `:vscode-openai:` | `vscode/openai` |
| :vscode-organization: | `:vscode-organization:` | `vscode/organization` |
| :vscode-output: | `:vscode-output:` | `vscode/output` |
| :vscode-package: | `:vscode-package:` | `vscode/package` |
| :vscode-paintcan: | `:vscode-paintcan:` | `vscode/paintcan` |
| :vscode-pass: | `:vscode-pass:` | `vscode/pass` |
| :vscode-pass-filled: | `:vscode-pass-filled:` | `vscode/pass-filled` |
| :vscode-percentage: | `:vscode-percentage:` | `vscode/percentage` |
| :vscode-person: | `:vscode-person:` | `vscode/person` |
| :vscode-person-add: | `:vscode-person-add:` | `vscode/person-add` |
| :vscode-piano: | `:vscode-piano:` | `vscode/piano` |
| :vscode-pie-chart: | `:vscode-pie-chart:` | `vscode/pie-chart` |
| :vscode-pin: | `:vscode-pin:` | `vscode/pin` |
| :vscode-pinned: | `:vscode-pinned:` | `vscode/pinned` |
| :vscode-pinned-dirty: | `:vscode-pinned-dirty:` | `vscode/pinned-dirty` |
| :vscode-play: | `:vscode-play:` | `vscode/play` |
| :vscode-play-circle: | `:vscode-play-circle:` | `vscode/play-circle` |
| :vscode-plug: | `:vscode-plug:` | `vscode/plug` |
| :vscode-preserve-case: | `:vscode-preserve-case:` | `vscode/preserve-case` |
| :vscode-preview: | `:vscode-preview:` | `vscode/preview` |
| :vscode-primitive-square: | `:vscode-primitive-square:` | `vscode/primitive-square` |
| :vscode-project: | `:vscode-project:` | `vscode/project` |
| :vscode-pulse: | `:vscode-pulse:` | `vscode/pulse` |
| :vscode-python: | `:vscode-python:` | `vscode/python` |
| :vscode-question: | `:vscode-question:` | `vscode/question` |
| :vscode-quote: | `:vscode-quote:` | `vscode/quote` |
| :vscode-quotes: | `:vscode-quotes:` | `vscode/quotes` |
| :vscode-radio-tower: | `:vscode-radio-tower:` | `vscode/radio-tower` |
| :vscode-reactions: | `:vscode-reactions:` | `vscode/reactions` |
| :vscode-record: | `:vscode-record:` | `vscode/record` |
| :vscode-record-keys: | `:vscode-record-keys:` | `vscode/record-keys` |
| :vscode-record-small: | `:vscode-record-small:` | `vscode/record-small` |
| :vscode-redo: | `:vscode-redo:` | `vscode/redo` |
| :vscode-references: | `:vscode-references:` | `vscode/references` |
| :vscode-refresh: | `:vscode-refresh:` | `vscode/refresh` |
| :vscode-regex: | `:vscode-regex:` | `vscode/regex` |
| :vscode-remote: | `:vscode-remote:` | `vscode/remote` |
| :vscode-remote-explorer: | `:vscode-remote-explorer:` | `vscode/remote-explorer` |
| :vscode-remove: | `:vscode-remove:` | `vscode/remove` |
| :vscode-remove-small: | `:vscode-remove-small:` | `vscode/remove-small` |
| :vscode-rename: | `:vscode-rename:` | `vscode/rename` |
| :vscode-replace: | `:vscode-replace:` | `vscode/replace` |
| :vscode-replace-all: | `:vscode-replace-all:` | `vscode/replace-all` |
| :vscode-reply: | `:vscode-reply:` | `vscode/reply` |
| :vscode-repo: | `:vscode-repo:` | `vscode/repo` |
| :vscode-repo-clone: | `:vscode-repo-clone:` | `vscode/repo-clone` |
| :vscode-repo-fetch: | `:vscode-repo-fetch:` | `vscode/repo-fetch` |
| :vscode-repo-force-push: | `:vscode-repo-force-push:` | `vscode/repo-force-push` |
| :vscode-repo-forked: | `:vscode-repo-forked:` | `vscode/repo-forked` |
| :vscode-repo-pinned: | `:vscode-repo-pinned:` | `vscode/repo-pinned` |
| :vscode-repo-pull: | `:vscode-repo-pull:` | `vscode/repo-pull` |
| :vscode-repo-push: | `:vscode-repo-push:` | `vscode/repo-push` |
| :vscode-repo-selected: | `:vscode-repo-selected:` | `vscode/repo-selected` |
| :vscode-report: | `:vscode-report:` | `vscode/report` |
| :vscode-request-changes: | `:vscode-request-changes:` | `vscode/request-changes` |
| :vscode-robot: | `:vscode-robot:` | `vscode/robot` |
| :vscode-rocket: | `:vscode-rocket:` | `vscode/rocket` |
| :vscode-root-folder: | `:vscode-root-folder:` | `vscode/root-folder` |
| :vscode-root-folder-opened: | `:vscode-root-folder-opened:` | `vscode/root-folder-opened` |
| :vscode-rss: | `:vscode-rss:` | `vscode/rss` |
| :vscode-ruby: | `:vscode-ruby:` | `vscode/ruby` |
| :vscode-run-above: | `:vscode-run-above:` | `vscode/run-above` |
| :vscode-run-all: | `:vscode-run-all:` | `vscode/run-all` |
| :vscode-run-all-coverage: | `:vscode-run-all-coverage:` | `vscode/run-all-coverage` |
| :vscode-run-below: | `:vscode-run-below:` | `vscode/run-below` |
| :vscode-run-coverage: | `:vscode-run-coverage:` | `vscode/run-coverage` |
| :vscode-run-errors: | `:vscode-run-errors:` | `vscode/run-errors` |
| :vscode-run-with-deps: | `:vscode-run-with-deps:` | `vscode/run-with-deps` |
| :vscode-save: | `:vscode-save:` | `vscode/save` |
| :vscode-save-all: | `:vscode-save-all:` | `vscode/save-all` |
| :vscode-save-as: | `:vscode-save-as:` | `vscode/save-as` |
| :vscode-screen-cut: | `:vscode-screen-cut:` | `vscode/screen-cut` |
| :vscode-screen-full: | `:vscode-screen-full:` | `vscode/screen-full` |
| :vscode-screen-normal: | `:vscode-screen-normal:` | `vscode/screen-normal` |
| :vscode-search: | `:vscode-search:` | `vscode/search` |
| :vscode-search-fuzzy: | `:vscode-search-fuzzy:` | `vscode/search-fuzzy` |
| :vscode-search-large: | `:vscode-search-large:` | `vscode/search-large` |
| :vscode-search-sparkle: | `:vscode-search-sparkle:` | `vscode/search-sparkle` |
| :vscode-search-stop: | `:vscode-search-stop:` | `vscode/search-stop` |
| :vscode-send: | `:vscode-send:` | `vscode/send` |
| :vscode-send-to-remote-agent: | `:vscode-send-to-remote-agent:` | `vscode/send-to-remote-agent` |
| :vscode-server: | `:vscode-server:` | `vscode/server` |
| :vscode-server-environment: | `:vscode-server-environment:` | `vscode/server-environment` |
| :vscode-server-process: | `:vscode-server-process:` | `vscode/server-process` |
| :vscode-session-in-progress: | `:vscode-session-in-progress:` | `vscode/session-in-progress` |
| :vscode-settings: | `:vscode-settings:` | `vscode/settings` |
| :vscode-settings-gear: | `:vscode-settings-gear:` | `vscode/settings-gear` |
| :vscode-share: | `:vscode-share:` | `vscode/share` |
| :vscode-share-window: | `:vscode-share-window:` | `vscode/share-window` |
| :vscode-shield: | `:vscode-shield:` | `vscode/shield` |
| :vscode-sign-in: | `:vscode-sign-in:` | `vscode/sign-in` |
| :vscode-sign-out: | `:vscode-sign-out:` | `vscode/sign-out` |
| :vscode-skip: | `:vscode-skip:` | `vscode/skip` |
| :vscode-smiley: | `:vscode-smiley:` | `vscode/smiley` |
| :vscode-snake: | `:vscode-snake:` | `vscode/snake` |
| :vscode-sort-precedence: | `:vscode-sort-precedence:` | `vscode/sort-precedence` |
| :vscode-source-control: | `:vscode-source-control:` | `vscode/source-control` |
| :vscode-sparkle: | `:vscode-sparkle:` | `vscode/sparkle` |
| :vscode-sparkle-filled: | `:vscode-sparkle-filled:` | `vscode/sparkle-filled` |
| :vscode-split-horizontal: | `:vscode-split-horizontal:` | `vscode/split-horizontal` |
| :vscode-split-vertical: | `:vscode-split-vertical:` | `vscode/split-vertical` |
| :vscode-squirrel: | `:vscode-squirrel:` | `vscode/squirrel` |
| :vscode-star-empty: | `:vscode-star-empty:` | `vscode/star-empty` |
| :vscode-star-full: | `:vscode-star-full:` | `vscode/star-full` |
| :vscode-star-half: | `:vscode-star-half:` | `vscode/star-half` |
| :vscode-stop-circle: | `:vscode-stop-circle:` | `vscode/stop-circle` |
| :vscode-strikethrough: | `:vscode-strikethrough:` | `vscode/strikethrough` |
| :vscode-surround-with: | `:vscode-surround-with:` | `vscode/surround-with` |
| :vscode-symbol-array: | `:vscode-symbol-array:` | `vscode/symbol-array` |
| :vscode-symbol-boolean: | `:vscode-symbol-boolean:` | `vscode/symbol-boolean` |
| :vscode-symbol-class: | `:vscode-symbol-class:` | `vscode/symbol-class` |
| :vscode-symbol-color: | `:vscode-symbol-color:` | `vscode/symbol-color` |
| :vscode-symbol-constant: | `:vscode-symbol-constant:` | `vscode/symbol-constant` |
| :vscode-symbol-enum: | `:vscode-symbol-enum:` | `vscode/symbol-enum` |
| :vscode-symbol-enum-member: | `:vscode-symbol-enum-member:` | `vscode/symbol-enum-member` |
| :vscode-symbol-event: | `:vscode-symbol-event:` | `vscode/symbol-event` |
| :vscode-symbol-field: | `:vscode-symbol-field:` | `vscode/symbol-field` |
| :vscode-symbol-file: | `:vscode-symbol-file:` | `vscode/symbol-file` |
| :vscode-symbol-interface: | `:vscode-symbol-interface:` | `vscode/symbol-interface` |
| :vscode-symbol-key: | `:vscode-symbol-key:` | `vscode/symbol-key` |
| :vscode-symbol-keyword: | `:vscode-symbol-keyword:` | `vscode/symbol-keyword` |
| :vscode-symbol-method: | `:vscode-symbol-method:` | `vscode/symbol-method` |
| :vscode-symbol-method-arrow: | `:vscode-symbol-method-arrow:` | `vscode/symbol-method-arrow` |
| :vscode-symbol-misc: | `:vscode-symbol-misc:` | `vscode/symbol-misc` |
| :vscode-symbol-namespace: | `:vscode-symbol-namespace:` | `vscode/symbol-namespace` |
| :vscode-symbol-numeric: | `:vscode-symbol-numeric:` | `vscode/symbol-numeric` |
| :vscode-symbol-operator: | `:vscode-symbol-operator:` | `vscode/symbol-operator` |
| :vscode-symbol-parameter: | `:vscode-symbol-parameter:` | `vscode/symbol-parameter` |
| :vscode-symbol-property: | `:vscode-symbol-property:` | `vscode/symbol-property` |
| :vscode-symbol-ruler: | `:vscode-symbol-ruler:` | `vscode/symbol-ruler` |
| :vscode-symbol-snippet: | `:vscode-symbol-snippet:` | `vscode/symbol-snippet` |
| :vscode-symbol-string: | `:vscode-symbol-string:` | `vscode/symbol-string` |
| :vscode-symbol-structure: | `:vscode-symbol-structure:` | `vscode/symbol-structure` |
| :vscode-symbol-variable: | `:vscode-symbol-variable:` | `vscode/symbol-variable` |
| :vscode-sync: | `:vscode-sync:` | `vscode/sync` |
| :vscode-sync-ignored: | `:vscode-sync-ignored:` | `vscode/sync-ignored` |
| :vscode-table: | `:vscode-table:` | `vscode/table` |
| :vscode-tag: | `:vscode-tag:` | `vscode/tag` |
| :vscode-target: | `:vscode-target:` | `vscode/target` |
| :vscode-tasklist: | `:vscode-tasklist:` | `vscode/tasklist` |
| :vscode-telescope: | `:vscode-telescope:` | `vscode/telescope` |
| :vscode-terminal: | `:vscode-terminal:` | `vscode/terminal` |
| :vscode-terminal-bash: | `:vscode-terminal-bash:` | `vscode/terminal-bash` |
| :vscode-terminal-cmd: | `:vscode-terminal-cmd:` | `vscode/terminal-cmd` |
| :vscode-terminal-debian: | `:vscode-terminal-debian:` | `vscode/terminal-debian` |
| :vscode-terminal-git-bash: | `:vscode-terminal-git-bash:` | `vscode/terminal-git-bash` |
| :vscode-terminal-linux: | `:vscode-terminal-linux:` | `vscode/terminal-linux` |
| :vscode-terminal-powershell: | `:vscode-terminal-powershell:` | `vscode/terminal-powershell` |
| :vscode-terminal-secure: | `:vscode-terminal-secure:` | `vscode/terminal-secure` |
| :vscode-terminal-tmux: | `:vscode-terminal-tmux:` | `vscode/terminal-tmux` |
| :vscode-terminal-ubuntu: | `:vscode-terminal-ubuntu:` | `vscode/terminal-ubuntu` |
| :vscode-text-size: | `:vscode-text-size:` | `vscode/text-size` |
| :vscode-thinking: | `:vscode-thinking:` | `vscode/thinking` |
| :vscode-three-bars: | `:vscode-three-bars:` | `vscode/three-bars` |
| :vscode-thumbsdown: | `:vscode-thumbsdown:` | `vscode/thumbsdown` |
| :vscode-thumbsdown-filled: | `:vscode-thumbsdown-filled:` | `vscode/thumbsdown-filled` |
| :vscode-thumbsup: | `:vscode-thumbsup:` | `vscode/thumbsup` |
| :vscode-thumbsup-filled: | `:vscode-thumbsup-filled:` | `vscode/thumbsup-filled` |
| :vscode-tools: | `:vscode-tools:` | `vscode/tools` |
| :vscode-trash: | `:vscode-trash:` | `vscode/trash` |
| :vscode-triangle-down: | `:vscode-triangle-down:` | `vscode/triangle-down` |
| :vscode-triangle-left: | `:vscode-triangle-left:` | `vscode/triangle-left` |
| :vscode-triangle-right: | `:vscode-triangle-right:` | `vscode/triangle-right` |
| :vscode-triangle-up: | `:vscode-triangle-up:` | `vscode/triangle-up` |
| :vscode-twitter: | `:vscode-twitter:` | `vscode/twitter` |
| :vscode-type-hierarchy: | `:vscode-type-hierarchy:` | `vscode/type-hierarchy` |
| :vscode-type-hierarchy-sub: | `:vscode-type-hierarchy-sub:` | `vscode/type-hierarchy-sub` |
| :vscode-type-hierarchy-super: | `:vscode-type-hierarchy-super:` | `vscode/type-hierarchy-super` |
| :vscode-unarchive: | `:vscode-unarchive:` | `vscode/unarchive` |
| :vscode-unfold: | `:vscode-unfold:` | `vscode/unfold` |
| :vscode-ungroup-by-ref-type: | `:vscode-ungroup-by-ref-type:` | `vscode/ungroup-by-ref-type` |
| :vscode-unlock: | `:vscode-unlock:` | `vscode/unlock` |
| :vscode-unmute: | `:vscode-unmute:` | `vscode/unmute` |
| :vscode-unverified: | `:vscode-unverified:` | `vscode/unverified` |
| :vscode-variable-group: | `:vscode-variable-group:` | `vscode/variable-group` |
| :vscode-verified: | `:vscode-verified:` | `vscode/verified` |
| :vscode-verified-filled: | `:vscode-verified-filled:` | `vscode/verified-filled` |
| :vscode-versions: | `:vscode-versions:` | `vscode/versions` |
| :vscode-vm: | `:vscode-vm:` | `vscode/vm` |
| :vscode-vm-active: | `:vscode-vm-active:` | `vscode/vm-active` |
| :vscode-vm-connect: | `:vscode-vm-connect:` | `vscode/vm-connect` |
| :vscode-vm-outline: | `:vscode-vm-outline:` | `vscode/vm-outline` |
| :vscode-vm-pending: | `:vscode-vm-pending:` | `vscode/vm-pending` |
| :vscode-vm-running: | `:vscode-vm-running:` | `vscode/vm-running` |
| :vscode-vm-small: | `:vscode-vm-small:` | `vscode/vm-small` |
| :vscode-vr: | `:vscode-vr:` | `vscode/vr` |
| :vscode-vscode: | `:vscode-vscode:` | `vscode/vscode` |
| :vscode-vscode-insiders: | `:vscode-vscode-insiders:` | `vscode/vscode-insiders` |
| :vscode-wand: | `:vscode-wand:` | `vscode/wand` |
| :vscode-warning: | `:vscode-warning:` | `vscode/warning` |
| :vscode-watch: | `:vscode-watch:` | `vscode/watch` |
| :vscode-whitespace: | `:vscode-whitespace:` | `vscode/whitespace` |
| :vscode-whole-word: | `:vscode-whole-word:` | `vscode/whole-word` |
| :vscode-window: | `:vscode-window:` | `vscode/window` |
| :vscode-window-active: | `:vscode-window-active:` | `vscode/window-active` |
| :vscode-word-wrap: | `:vscode-word-wrap:` | `vscode/word-wrap` |
| :vscode-workspace-trusted: | `:vscode-workspace-trusted:` | `vscode/workspace-trusted` |
| :vscode-workspace-unknown: | `:vscode-workspace-unknown:` | `vscode/workspace-unknown` |
| :vscode-workspace-untrusted: | `:vscode-workspace-untrusted:` | `vscode/workspace-untrusted` |
| :vscode-worktree: | `:vscode-worktree:` | `vscode/worktree` |
| :vscode-worktree-small: | `:vscode-worktree-small:` | `vscode/worktree-small` |
| :vscode-zoom-in: | `:vscode-zoom-in:` | `vscode/zoom-in` |
| :vscode-zoom-out: | `:vscode-zoom-out:` | `vscode/zoom-out` |
+306
View File
@@ -0,0 +1,306 @@
# Компоненты
Здесь показаны примеры компонентов, которые можно внедрять в документацию.
Для синтаксиса обращайся в [документацию Zensical][1] и репозиторий [этой документации][2].
[1]: https://zensical.org/docs
[2]: https://git.axenov.dev/IPTV/docs
## Мелочёвка
=== "Бейджи"
Это кастомный функционал, в основе которого лежит `inline_badges.py` в корне репозитория.
<!-- md:env SOME_VAR -->
<!-- md:arg --arg -->
<!-- md:config server.host -->
<!-- md:version 1.2.3 -->
<!-- md:default default_value -->
<!-- md:beta -->
=== "Frontmatter"
```yaml
---
title: My Page
description: Some page description
icon: material/star
tags: [tag1, tag2]
hide: [navigation, toc, path]
status: new
#status: deprecated
#status: beta
---
# My Super-Duper Page
# Markdown content goes here
```
=== "Тултипы"
:material-information-outline:{ title="текст подсказки" }
[Hover me 1](https://example.com "I'm first tooltip!")
[Hover me 2][example]
[example]: https://example.com "I'm second tooltip!"
=== "Кнопки"
[Серая кнопка](https://example.com/){ .md-button }
[Синяя кнопка](https://example.com/){ .md-button .md-button--primary }
[:fontawesome-solid-paper-plane: Кнопка серая с иконкой и подсказкой](https://example.com/){ .md-button title="текст подсказки 2" }
[:fontawesome-solid-paper-plane: Кнопка синяя с иконкой](https://example.com/){ .md-button .md-button--primary }
---
=== "Простой блок"
```python
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "С аннотациями"
```python
def _render_icon(shortcode: str, md) -> str: #(1)!
emoji_pattern = md.inlinePatterns.get("emoji") #(2)!
if emoji_pattern is None:
return escape(shortcode) #(3)!
```
1. сигнатура функции
2. инициализация переменной
3. возврат результата
=== "С заголовком"
```python title="example.py"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "Нумерация с 5"
```python linenums="5"
def _render_icon(shortcode: str, md) -> str:
emoji_pattern = md.inlinePatterns.get("emoji")
if emoji_pattern is None:
return escape(shortcode)
```
=== "Выделение"
```python linenums="1" hl_lines="2-5 8 9 22"
def _render_icon(shortcode: str, md) -> str:
try:
emoji_pattern = md.inlinePatterns["emoji"]
except KeyError:
return escape(shortcode)
icon = emoji_pattern.emoji_index["emoji"].get(shortcode)
if icon is None:
return escape(shortcode)
element = emoji_pattern.generator(
emoji_pattern.emoji_index["name"],
shortcode,
None,
None,
shortcode,
"",
icon.get("category", ""),
emoji_pattern.options,
md,
)
return tostring(element, encoding="unicode", method="html")
```
---
## Врезки
=== "Полные"
!!! note "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! abstract "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! info "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! tip "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! success "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! question "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! warning "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! failure "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! danger "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! bug "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! example "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
!!! quote "Заголовок статичной врезки"
Содержимое, которое может
быть многострочным
=== "Свёрнутые"
??? note "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? abstract "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? info "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? tip "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? success "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? question "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? warning "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? failure "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? danger "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? bug "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? example "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
??? quote "Заголовок свёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Сворачиваемые"
???+ note "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ abstract "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ info "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ tip "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ success "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ question "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ warning "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ failure "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ danger "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ bug "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ example "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
???+ quote "Заголовок развёрнутой врезки"
Содержимое, которое может
быть многострочным
=== "Короткие"
!!! note
Дефолтный заголовок
!!! abstract ""
Пустой заголовок
!!! info "Пустое содержимое"
=== "Кастомные"
??? image "Врезка для скриншота"
Тут должна быть картинка
!!! boosty "Врезка Boosty"
Тут должна быть релевантная информация
!!! yoomoney "Врезка Yoomoney"
Тут должна быть релевантная информация
---
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
---
icon: material/application-brackets-outline
tags: ["iptvc", "разработка"]
---
# :material-application-brackets-outline: Среда разработки
## Быстрый старт
Для локальной разработки `iptvc` достаточно установить [Go 1.23.6+][1] и клонировать репозиторий:
```bash
git clone https://git.axenov.dev/IPTV/iptvc.git
cd iptvc
go run . serve -i playlists.ini -p 8800 --check
```
Веб-интерфейс будет доступен по адресу <http://localhost:8800>.
## Требования
| ПО | Версия | Назначение |
| ----------- | --------------------- | ------------------------------------- |
| [Go][1] | 1.23.6+ | Компиляция и запуск |
| [Make][2] | любая | Сборка через `Makefile` (опционально) |
| [Docker][3] | с `docker compose` v2 | Полное окружение (опционально) |
[1]: https://go.dev/dl/
[2]: https://www.gnu.org/software/make
[3]: https://docs.docker.com/engine/install
## Команды для разработки
```bash
# Запуск веб-сервера с фоновой проверкой
go run . serve -i playlists.ini -p 8800 --check
# Запуск проверки без веб-сервера
go run . check -i playlists.ini --repeat 1
# Сборка бинарника
go build -o iptvc .
# Проверка кода
go vet ./...
# Сборка под все платформы
make release
```
## Файлы конфигурации
Для работы `iptvc` нужны два файла рядом с бинарником:
- `playlists.ini` — список плейлистов. Формат описан в [справочнике форматов](../../common/formats/playlists.md).
- `channels.json` — правила тегов каналов. Формат описан в [справочнике форматов](../../common/formats/channels.md).
- `config.yml` — конфигурация программы. Описание — в [разделе config.yml](../../common/config/config.md).
## Полное Docker-окружение
Полная инфраструктура проекта (nginx, KeyDB, checker, docs) развёртывается через Docker.
Подробнее — в разделе [Развёртывание](../site/deploy.md).
## Дополнительные материалы
- [Компиляция из исходного кода](compile.md)
- [Построение Docker-образа](docker.md)
- [Архитектура iptvc](arch.md)
- [Развёртывание и доставка обновлений](../site/deploy.md)
+80
View File
@@ -0,0 +1,80 @@
---
icon: material/robot-outline
---
# :material-robot-outline: Telegram-бот
!!! info "Обрати внимание"
Локальная среда разработки должна быть [настроена и запущена](local-dev.md).
## Подготовка бота на стороне Telegram
1. Написать [@botfather](https://t.me/botfather), создать бота
2. Полученный токен установить значением переменной `TG_BOT_TOKEN` в файле `.env` репозитория `web`
3. Командой `/mybots` в [@botfather](https://t.me/botfather) выбрать свежесозданного бота, далее `Edit Bot` > `Edit Commands` и отправить текст:
```
list - Список плейлистов
info - Подробности о плейлисте по его коду
help - Помощь по командам бота
links - Ссылки на все страницы проекта
stats - Статистика по плейлистам и каналам
```
## Проброс внешних запросов на локальную машину
1. Установить [telebit](https://telebit.cloud) и пройти примитивную регистрацию.
В результате будет выдан уникальный адрес в формате `https://foo-bar-99.telebit.io`.
На email, указанный при регистрации, будет оформлен бесплатный SSL-серификат Let's Encrypt для этого домена.
Если адрес не используется месяц+, то сертификат протухнет, но он автоматически восстановится, если адрес начнёт использоваться вновь.
2. В терминале выполнить:
```
telebit http 8080
```
где `8080` — порт локальной машины, на который проброшен порт 80 из контейнера `iptv-nginx`.
Для выключения выполнить:
```
telebit http
```
3. Проверить работу адреса, перейдя по нему браузером.
Должен открыться твой локальный проект.
4. Полученный адрес установить значением переменной `APP_URL` в файле `.env` репозитория `web`
5. Установить веб-хук, отправив запрос браузером или любым HTTP-клиентом на адрес:
```
https://api.telegram.org/bot$BOT_TOKEN/setWebhook?url=$TELEBIT_URL/bot/webhook&secret_token=$SECRET_TOKEN
```
где:
* `$BOT_TOKEN` - авторизационный токен, который @botfather выдал твоему боту;
* `$TELEBIT_URL` - адрес, который telebit выдал тебе;
* `$SECRET_TOKEN` - секретный токен, опционален, см. ниже.
6. Проверить веб-хук, отправив запрос браузером или любым HTTP-клиентом на адрес:
```
https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo
```
где:
* `$BOT_TOKEN` - авторизационный токен, который @botfather выдал твоему боту.
7. После разработки нужно установить "боевой" адрес веб-хука аналогично п4.
## Что за секретный токен?
Telegram авторизует твоего бота по токену, который выдал ему сам.
Ты тоже можешь (не) авторизовать Telegram по токену, который ты выдашь ему.
Для этого нужно в значением переменной `TG_BOT_SECRET` в файле `.env` репозитория `web` установить любую строку.
Если ты это сделаешь, тогда ту же строку ты должен передать в параметре `secret_token` метода `setWebhook`.
В этом случае, все HTTP-запросы, которые приходят от Telegram, будут содержать заголовок `X-Telegram-Bot-Api-Secret-Token` со этой строкой в качестве значения.
Эта строка сверяется с той, что указана в `.env` проекта.
Если такого заголовка нет или его значение некорректно, входящий запрос отклоняется.
Если переменная `TG_BOT_SECRET` не задана, то заголовок проверяться не будет.
См. подробности в документации: [setWebhook](https://core.telegram.org/bots/api#setwebhook)