From 2b2608bfaac57d46460336656fb5057cba82bfd3 Mon Sep 17 00:00:00 2001 From: deexsed Date: Thu, 30 Apr 2026 15:41:41 +0300 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B5=D0=BD=20triage=20=D0=B4=D0=BB=D1=8F=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CODEOWNERS | 11 + .github/ISSUE_TEMPLATE/bug_report.yml | 53 +++- .github/ISSUE_TEMPLATE/config.yml | 6 + .github/ISSUE_TEMPLATE/feature_request.yml | 59 +++++ .github/ISSUE_TEMPLATE/question.yml | 57 ++++ .github/labels.md | 21 ++ .github/workflows/issue-sla-reminder.yml | 127 +++++++++ .github/workflows/issue-triage.yml | 295 +++++++++++++++++++++ CONTRIBUTING.md | 59 +++++ docs/BuildFromSource.md | 75 ++++++ docs/CfProxy.md | 39 +-- docs/FakeTlsNginx.md | 52 ++++ docs/Funding.md | 8 +- docs/README.linux.md | 51 ++++ docs/README.macos.md | 30 +++ docs/README.md | 254 ++---------------- docs/README.windows.md | 52 ++++ docs/TrayConfig.md | 31 +++ 18 files changed, 1021 insertions(+), 259 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/labels.md create mode 100644 .github/workflows/issue-sla-reminder.yml create mode 100644 .github/workflows/issue-triage.yml create mode 100644 CONTRIBUTING.md create mode 100644 docs/BuildFromSource.md create mode 100644 docs/FakeTlsNginx.md create mode 100644 docs/README.linux.md create mode 100644 docs/README.macos.md create mode 100644 docs/README.windows.md create mode 100644 docs/TrayConfig.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..54c14122 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,11 @@ +# Default owners +* @Flowseal + +# Automation and repository settings +.github/** @Flowseal + +# Documentation +docs/** @Flowseal + +# Core proxy implementation +proxy/** @Flowseal diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bef67fb4..4fb80702 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,12 +4,55 @@ description: Сообщить о проблеме labels: ['type: проблема', 'status: нуждается в сортировке'] body: + - type: input + id: app_version + attributes: + label: Версия TG WS Proxy + description: Укажите версию приложения (например, v1.2.3) + placeholder: vX.Y.Z + validations: + required: true + + - type: dropdown + id: os + attributes: + label: Операционная система + description: На какой ОС воспроизводится проблема? + options: + - Windows + - macOS + - Linux + - Другая + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Шаги для воспроизведения + description: Опишите шаги, после которых возникает проблема + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Ожидаемое поведение + description: Что должно было произойти? + placeholder: Кратко опишите ожидаемый результат + validations: + required: true + - type: textarea - id: description + id: actual attributes: - label: Опишите вашу проблему - description: Чётко опишите проблему с которой вы столкнулись - placeholder: Описание проблемы + label: Фактическое поведение + description: Что произошло на самом деле? + placeholder: Кратко опишите фактический результат validations: required: true @@ -17,4 +60,4 @@ body: id: additions attributes: label: Дополнительные детали - description: Если у вас проблемы с работой прокси, то приложите файл логов в момент возникновения проблемы. \ No newline at end of file + description: Приложите дополнительные детали (логи, скриншоты, текст ошибок). Для проблем с прокси добавьте лог-файл на момент возникновения проблемы. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..1f825ceb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false + +contact_links: + - name: 📚 Документация + url: https://github.com/Flowseal/tg-ws-proxy/tree/main/docs + about: Ознакомьтесь с документацией перед созданием issue diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..8b1d7dba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,59 @@ +name: 🚀 Предложение +title: '[Предложение] ' +description: Предложить улучшение или новую функциональность +labels: ['type: предложение', 'status: нуждается в сортировке'] + +body: + - type: textarea + id: problem + attributes: + label: Какую проблему решает предложение? + description: Опишите текущую боль или ограничение, с которым вы столкнулись + placeholder: | + Сейчас ... + Это неудобно, потому что ... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Предлагаемое решение + description: Опишите, как именно вы предлагаете улучшить проект + placeholder: | + Предлагаю добавить ... + Это позволит ... + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Рассмотренные альтернативы + description: Опишите альтернативные варианты, если вы их рассматривали + placeholder: | + Вариант 1 ... + Вариант 2 ... + validations: + required: false + + - type: dropdown + id: platform + attributes: + label: Для какой платформы актуально? + description: Выберите платформу, если предложение связано с конкретной ОС + options: + - Все платформы + - Windows + - macOS + - Linux + - Другое + validations: + required: true + + - type: textarea + id: context + attributes: + label: Дополнительный контекст + description: Добавьте примеры, ссылки, скриншоты или другие детали + placeholder: Любые дополнительные материалы по предложению diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 00000000..243b8826 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,57 @@ +name: ❓ Вопрос / Поддержка +title: '[Вопрос] ' +description: Задать вопрос по настройке или работе TG WS Proxy +labels: ['type: вопрос', 'status: нуждается в сортировке'] + +body: + - type: dropdown + id: os + attributes: + label: Операционная система + description: На какой ОС возник вопрос? + options: + - Windows + - macOS + - Linux + - Другая + validations: + required: true + + - type: input + id: app_version + attributes: + label: Версия TG WS Proxy + description: Укажите версию приложения (если знаете) + placeholder: vX.Y.Z + validations: + required: false + + - type: textarea + id: question + attributes: + label: Ваш вопрос + description: Кратко опишите, что именно не получается или непонятно + placeholder: | + Что вы хотите сделать? + На каком шаге возник вопрос? + validations: + required: true + + - type: textarea + id: tried + attributes: + label: Что уже пробовали + description: Перечислите, какие действия уже выполнили + placeholder: | + 1. ... + 2. ... + 3. ... + validations: + required: false + + - type: textarea + id: additions + attributes: + label: Дополнительные детали + description: Прикрепите логи, скриншоты или текст ошибок, если это применимо + placeholder: Любая дополнительная информация diff --git a/.github/labels.md b/.github/labels.md new file mode 100644 index 00000000..1f6a644f --- /dev/null +++ b/.github/labels.md @@ -0,0 +1,21 @@ +# Labels Reference + +Этот список фиксирует лейблы, которые используются в issue-шаблонах и workflow triage. + +## Тип issue + +- `type: проблема` — баг-репорты. +- `type: предложение` — предложения по улучшениям. +- `type: вопрос` — вопросы по настройке и использованию. + +## Статус issue + +- `status: нуждается в сортировке` — новый issue, ожидает первичного triage. +- `status: в работе` — issue взят в работу мейнтейнером. +- `status: нужен ответ` — требуется ответ мейнтейнера по SLA. + +## Важно + +- Используйте лейблы строго в таком написании. +- Не создавайте дубликаты с похожими названиями. +- Если нужно изменить словарь лейблов, сначала обновите этот файл и связанные workflow. diff --git a/.github/workflows/issue-sla-reminder.yml b/.github/workflows/issue-sla-reminder.yml new file mode 100644 index 00000000..d9d483bf --- /dev/null +++ b/.github/workflows/issue-sla-reminder.yml @@ -0,0 +1,127 @@ +name: Issue SLA Reminder + +on: + schedule: + - cron: "0 */12 * * *" + workflow_dispatch: + inputs: + sla_days: + description: "SLA threshold in days for unanswered bug issues" + required: false + default: "5" + type: string + +permissions: + issues: write + +env: + DEFAULT_SLA_DAYS: "5" + +jobs: + remind-unanswered-bugs: + runs-on: ubuntu-latest + steps: + - name: Mark and remind unanswered bug issues + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const dispatchSla = context.payload.inputs?.sla_days; + const configuredSla = dispatchSla ?? process.env.DEFAULT_SLA_DAYS ?? "5"; + const slaDays = Number(configuredSla); + + if (!Number.isFinite(slaDays) || slaDays <= 0) { + core.setFailed(`Invalid SLA value: "${configuredSla}". Use a positive number.`); + return; + } + + const now = Date.now(); + const marker = ""; + const waitingLabel = "status: нужен ответ"; + + async function ensureLabel(name, color, description) { + try { + await github.rest.issues.getLabel({ owner, repo, name }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner, + repo, + name, + color, + description, + }); + } else { + throw error; + } + } + } + + await ensureLabel(waitingLabel, "D93F0B", "Issue ожидает ответа мейнтейнера"); + + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner, + repo, + state: "open", + labels: "type: проблема", + per_page: 100, + }); + + for (const issue of issues) { + if (issue.pull_request) continue; + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issue.number, + per_page: 100, + }); + + const hasMaintainerReply = comments.some((c) => + ["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association), + ); + if (hasMaintainerReply) continue; + + const nonMaintainerTimestamps = [ + new Date(issue.created_at).getTime(), + ...comments + .filter((c) => !["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association)) + .map((c) => new Date(c.created_at).getTime()), + ]; + const lastNonMaintainerActivity = Math.max(...nonMaintainerTimestamps); + const inactivityDays = (now - lastNonMaintainerActivity) / (1000 * 60 * 60 * 24); + if (inactivityDays < slaDays) continue; + + const labelNames = (issue.labels || []).map((l) => l.name); + if (!labelNames.includes(waitingLabel)) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: issue.number, + labels: [waitingLabel], + }); + } + + const reminderExists = comments.some( + (c) => + c.user?.type === "Bot" && + typeof c.body === "string" && + c.body.includes(marker), + ); + if (reminderExists) continue; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issue.number, + body: `${marker} + Напоминание по SLA: в issue пока нет ответа мейнтейнера. + + Спасибо за терпение. Мы вернемся с ответом при первой возможности. + Чтобы ускорить triage, можно дополнить issue: + - версией TG WS Proxy; + - логами/текстом ошибки; + - шагами воспроизведения.`, + }); + } diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml new file mode 100644 index 00000000..15315161 --- /dev/null +++ b/.github/workflows/issue-triage.yml @@ -0,0 +1,295 @@ +name: Issue Triage + +on: + issues: + types: + - opened + - labeled + issue_comment: + types: + - created + +permissions: + issues: write + +jobs: + question-auto-reply: + if: | + github.event_name == 'issues' && + contains(github.event.issue.labels.*.name, 'type: вопрос') && + github.event.issue.user.type != 'Bot' + runs-on: ubuntu-latest + steps: + - name: Add support comment + uses: actions/github-script@v8 + with: + script: | + const issueNumber = context.payload.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + const marker = ""; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + per_page: 100, + }); + + const alreadyPosted = comments.some( + (c) => + c.user?.type === "Bot" && + typeof c.body === "string" && + c.body.includes(marker), + ); + + if (alreadyPosted) { + core.info("Auto-reply already exists, skipping."); + return; + } + + const body = `${marker} + Спасибо за вопрос! Мы посмотрим его как можно скорее. + + Чтобы ускорить ответ, пожалуйста, убедитесь, что в issue есть: + - версия TG WS Proxy; + - операционная система; + - что уже было проверено; + - лог/скриншот (если есть ошибка). + + Полезные разделы документации: + - https://github.com/${owner}/${repo}/blob/main/docs/README.md + - https://github.com/${owner}/${repo}/blob/main/docs/CfProxy.md`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); + + bug-auto-reply: + if: | + github.event_name == 'issues' && + contains(github.event.issue.labels.*.name, 'type: проблема') && + github.event.issue.user.type != 'Bot' + runs-on: ubuntu-latest + steps: + - name: Add bug triage comment + uses: actions/github-script@v8 + with: + script: | + const issueNumber = context.payload.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + const issueBody = context.payload.issue.body || ""; + + const marker = ""; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + per_page: 100, + }); + + const alreadyPosted = comments.some( + (c) => + c.user?.type === "Bot" && + typeof c.body === "string" && + c.body.includes(marker), + ); + + if (alreadyPosted) { + core.info("Auto-reply already exists, skipping."); + return; + } + + function extractSection(title) { + const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const re = new RegExp(`### ${escaped}\\r?\\n\\r?\\n([\\s\\S]*?)(?=\\r?\\n### |$)`, "m"); + const match = issueBody.match(re); + if (!match) return ""; + return match[1].trim(); + } + + const appVersion = extractSection("Версия TG WS Proxy"); + const additions = extractSection("Дополнительные детали"); + + const noResponseValues = new Set(["", "_No response_", "No response"]); + const missingVersion = noResponseValues.has(appVersion); + const missingLogs = noResponseValues.has(additions); + + const missingParts = []; + if (missingVersion) missingParts.push("- версия TG WS Proxy"); + if (missingLogs) missingParts.push("- лог/скриншот или текст ошибки"); + + const extraRequest = missingParts.length + ? `\nЧтобы ускорить диагностику, пожалуйста, дополните issue:\n${missingParts.join("\n")}\n` + : ""; + + const body = `${marker} + Спасибо за баг-репорт! Мы посмотрим его как можно скорее. + + ${extraRequest} + + Полезные разделы документации: + - https://github.com/${owner}/${repo}/blob/main/docs/README.md + - https://github.com/${owner}/${repo}/blob/main/docs/CfProxy.md`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); + + feature-auto-reply: + if: | + github.event_name == 'issues' && + contains(github.event.issue.labels.*.name, 'type: предложение') && + github.event.issue.user.type != 'Bot' + runs-on: ubuntu-latest + steps: + - name: Add feature triage comment + uses: actions/github-script@v8 + with: + script: | + const issueNumber = context.payload.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + const marker = ""; + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + per_page: 100, + }); + + const alreadyPosted = comments.some( + (c) => + c.user?.type === "Bot" && + typeof c.body === "string" && + c.body.includes(marker), + ); + + if (alreadyPosted) { + core.info("Auto-reply already exists, skipping."); + return; + } + + const body = `${marker} + Спасибо за предложение! Мы рассмотрим его в рамках triage. + + Чтобы ускорить принятие решения, полезно добавить: + - сценарий использования; + - ожидаемую пользу для пользователей; + - возможные альтернативы. + + Полезный раздел: + - https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md`; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); + + status-in-progress-on-maintainer-comment: + if: | + github.event_name == 'issue_comment' && + github.event.issue.pull_request == null && + contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + steps: + - name: Ensure status label exists and apply + uses: actions/github-script@v8 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const issueNumber = context.payload.issue.number; + const statusLabel = "status: в работе"; + const needsTriageLabel = "status: нуждается в сортировке"; + const currentCommentId = context.payload.comment.id; + + const comments = await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + per_page: 100, + }); + + const maintainerCommentsBeforeCurrent = comments.filter( + (c) => + c.id !== currentCommentId && + ["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association), + ); + + if (maintainerCommentsBeforeCurrent.length > 0) { + core.info("Not first maintainer comment, skipping status transition."); + return; + } + + try { + await github.rest.issues.getLabel({ + owner, + repo, + name: statusLabel, + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner, + repo, + name: statusLabel, + color: "1D76DB", + description: "Issue в активной работе", + }); + } else { + throw error; + } + } + + try { + await github.rest.issues.getLabel({ + owner, + repo, + name: needsTriageLabel, + }); + } catch (error) { + if (error.status === 404) { + await github.rest.issues.createLabel({ + owner, + repo, + name: needsTriageLabel, + color: "FBCA04", + description: "Issue ожидает первичного triage", + }); + } else { + throw error; + } + } + + const currentLabels = (context.payload.issue.labels || []).map((l) => l.name); + if (currentLabels.includes(statusLabel)) { + core.info("Status label already set, skipping."); + return; + } + + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: issueNumber, + labels: [statusLabel], + }); + + if (currentLabels.includes(needsTriageLabel)) { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: issueNumber, + name: needsTriageLabel, + }); + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..23e85f14 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# CONTRIBUTING + +Спасибо за желание помочь проекту `tg-ws-proxy`. + +## Перед созданием issue + +1. Проверьте документацию в `docs/README.md`. +2. Убедитесь, что похожий issue еще не открыт. +3. Выберите подходящий шаблон: + - `Проблема` — для багов, + - `Предложение` — для новых возможностей, + - `Вопрос / Поддержка` — для вопросов по настройке и использованию. +4. Для корректной работы triage используйте стандартные лейблы из `.github/labels.md`. + +## Как сообщать о проблемах + +- Используйте шаблон `Проблема`. +- По возможности укажите: + - версию приложения, + - ОС, + - шаги воспроизведения, + - ожидаемое и фактическое поведение, + - лог-файл или текст ошибки. + +Чем точнее описание, тем быстрее можно помочь. + +## Локальный запуск из исходников + +Требуется Python `>=3.8`. + +```bash +pip install -e . +``` + +Запуск: + +- консольный режим: `tg-ws-proxy` +- Windows tray: `tg-ws-proxy-tray-win` +- macOS tray: `tg-ws-proxy-tray-macos` +- Linux tray: `tg-ws-proxy-tray-linux` + +Подробности: `docs/BuildFromSource.md`. + +## Pull Request + +Перед открытием PR: + +1. Убедитесь, что изменение решает конкретную проблему. +2. Проверьте, что не сломаны существующие сценарии. +3. Обновите документацию, если меняется поведение или настройка. + +В описании PR укажите: + +- цель изменения; +- что именно изменено; +- как это проверить; +- для какой ОС (если применимо). + +Небольшие и сфокусированные PR проверяются и принимаются быстрее. diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md new file mode 100644 index 00000000..4ec0387c --- /dev/null +++ b/docs/BuildFromSource.md @@ -0,0 +1,75 @@ +# Установка из исходников + +## Консольный прокси + +Для запуска только прокси без интерфейса системного трея достаточно базовой установки: + +```bash +pip install -e . +tg-ws-proxy +``` + +## Tray-приложение по ОС + +### Windows 7/10+ + +```bash +pip install -e . +tg-ws-proxy-tray-win +``` + +### macOS + +```bash +pip install -e . +tg-ws-proxy-tray-macos +``` + +### Linux + +```bash +pip install -e . +tg-ws-proxy-tray-linux +``` + +## Консольный режим из исходников + +```bash +tg-ws-proxy [--port PORT] [--host HOST] [--dc-ip DC:IP ...] [-v] +``` + +**Аргументы:** + +| Аргумент | По умолчанию | Описание | +|---|---|---| +| `--port` | `1443` | Порт прокси | +| `--host` | `127.0.0.1` | Хост прокси | +| `--secret` | `random` | 32-значный hex-ключ для авторизации клиентов | +| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) | +| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) | +| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее](./CfProxy.md) | +| `--cfproxy-priority` | `true` | Пробовать проксировать через Cloudflare перед прямым TCP подключением | +| `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом | +| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) | +| `--buf-kb` | `256` | Размер буфера в КБ | +| `--pool-size` | `4` | Количество заготовленных соединений на каждый DC | +| `--log-file` | выкл. | Путь к файлу, в который будут сохраняться логи | +| `--log-max-mb` | `5` | Максимальный размер файла логов в МБ (после этого начинается перезапись) | +| `--log-backups` | `0` | Количество сохранений логов после перезаписи | +| `-v`, `--verbose` | выкл. | Подробное логирование (DEBUG) | + +**Примеры:** + +```bash +# Стандартный запуск +tg-ws-proxy + +# Другой порт и дополнительные DC +tg-ws-proxy --port 9050 --dc-ip 1:149.154.175.205 --dc-ip 2:149.154.167.220 + +# С подробным логированием +tg-ws-proxy -v + +# Fake TLS маскировка (ee-secret) +tg-ws-proxy --fake-tls-domain example.com +``` diff --git a/docs/CfProxy.md b/docs/CfProxy.md index 1b2fe200..458ed58c 100644 --- a/docs/CfProxy.md +++ b/docs/CfProxy.md @@ -1,29 +1,32 @@ -# Cloudflare Proxy +# Cloudflare-прокси -Для недоступных датацентров можно использовать альтернативный бесплатный метод подключения - проксирование через Cloudflare. **Для работы нужен только домен**. В приложении есть домен по умолчанию, но его можно (и лучше) заменить на свой. +Для недоступных дата-центров можно использовать альтернативный бесплатный способ подключения — проксирование через Cloudflare. **Для работы нужен только домен**. В приложении есть домен по умолчанию, но его можно (и желательно) заменить на свой. -Прокси возвращает доступ к тому, что до этого не грузило (реакциям, некоторым стикерам). Если у вас до этого не грузило видео/фото на аккаунте без премиума, то уберите всё кроме `4:149.154.167.220` из `DC->IP` блока в настройках. Если CF-прокси у вас работает - медиа снова начнёт грузиться. +Прокси возвращает доступ к тому, что раньше не загружалось (реакции, некоторые стикеры). Если на аккаунте без Premium не загружаются фото/видео, оставьте в блоке `DC → IP` только `4:149.154.167.220`. Если CF-прокси работает, медиа снова начнет загружаться. ## Зачем мне настраивать свой домен? -Cloudflare имеет лимиты на одновременное количество подключений WS. Домен по умолчанию может перестать работать в любой момент. + +Cloudflare имеет лимиты на одновременное количество WS-подключений. Домен по умолчанию может перестать работать в любой момент. ## Настройка своего домена -1. Добавьте свой домен в Cloudflare (либо купив у них напрямую, либо поменяв NS сервера: https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/). Домены стоят +- 150 рублей на год, подойдёт любой. -2. В `SSL/TLS` -> `Overview` выставьте режим **Flexible** +1. Добавьте свой домен в Cloudflare (либо купив его напрямую у Cloudflare, либо изменив NS-серверы: https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/). Домены стоят примерно 150 рублей в год, подойдёт любой. + +2. В `SSL/TLS` → `Overview` выставьте режим **Flexible**. + +3. В `DNS` → `Records` добавьте следующие `A`-записи через `+ Add Record`: + - `kws1` → `149.154.175.50` + - `kws2` → `149.154.167.51` + - `kws3` → `149.154.175.100` + - `kws4` → `149.154.167.91` + - `kws5` → `149.154.171.5` + - `kws203` → `91.105.192.100` -3. В `DNS` -> `Records` добавьте следующие `A` записи через `+ Add Record`: -- Name=`kws1` IPv4=`149.154.175.50` -- Name=`kws2` IPv4=`149.154.167.51` -- Name=`kws3` IPv4=`149.154.175.100` -- Name=`kws4` IPv4=`149.154.167.91` -- Name=`kws5` IPv4=`149.154.171.5` -- Name=`kws203` IPv4=`91.105.192.100` +4. **Добавьте домен в [zapret](https://github.com/Flowseal/zapret-discord-youtube/) или в любое другое ПО, так как подсеть Cloudflare может быть заблокирована (например, в России).** -4. **Добавьте домен в [zapret](https://github.com/Flowseal/zapret-discord-youtube/) или в любое другое ПО, так как подсеть Cloudflare забанена (по крайней мере, если вы из России)** +5. В настройках `TgWsProxy` замените домен на свой. -5. В настройках TgWsProxy поменяйте домен на свой +## Благодарности -## Mentions -Idea - https://github.com/Nekogram/WSProxy -Thanks to [@UjuiUjuMandan](https://github.com/UjuiUjuMandan) for the information \ No newline at end of file +- Идея: https://github.com/Nekogram/WSProxy +- Спасибо [@UjuiUjuMandan](https://github.com/UjuiUjuMandan) за информацию. \ No newline at end of file diff --git a/docs/FakeTlsNginx.md b/docs/FakeTlsNginx.md new file mode 100644 index 00000000..f85b0bc2 --- /dev/null +++ b/docs/FakeTlsNginx.md @@ -0,0 +1,52 @@ +# Fake TLS + upstream в nginx + +Домен в параметре `--fake-tls-domain` должен указывать на тот же IP, на котором запущен прокси. + +## Пример `nginx.conf` для stream-модуля + +```nginx +upstream mtproto { + server 127.0.0.1:8446; +} + +map $ssl_preread_server_name $sni_name { + hostnames; + example.com mtproto; + # if you have xray with selfsni running: + # sub.example.com www; + # default xray; +} + +# upstream xray { +# server 127.0.0.1:8443; +# } +# +# upstream www { +# server 127.0.0.1:7443; +# } + +server { + proxy_protocol on; + set_real_ip_from unix:; + listen 443; + proxy_pass $sni_name; + ssl_preread on; +} +``` + +## Запуск прокси за Nginx + +```bash +python3 proxy/tg_ws_proxy.py \ + --port 8446 \ + --host 127.0.0.1 \ + --fake-tls-domain example.com \ + --proxy-protocol \ + --secret <32-hex-chars> +``` + +Ссылка для подключения будет в формате `ee`-секрета: + +```text +tg://proxy?server=your.domain.com&port=443&secret=ee +``` diff --git a/docs/Funding.md b/docs/Funding.md index 7611b633..9d99ff06 100644 --- a/docs/Funding.md +++ b/docs/Funding.md @@ -7,8 +7,6 @@ > **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1` > **Другие монеты**: https://nowpayments.io/donation/flowseal -## - -### Проект полностью бесплатен для использования всеми. -### Однако его развитие и стабильная работа при росте числа пользователей требуют от меня определённых вложений. -### Буду благодарен за любую форму поддержки! Спасибо ❤️ \ No newline at end of file +Проект полностью бесплатен для всех. +Однако его развитие и стабильная работа при росте числа пользователей требуют вложений. +Буду благодарен за любую форму поддержки! Спасибо ❤️ \ No newline at end of file diff --git a/docs/README.linux.md b/docs/README.linux.md new file mode 100644 index 00000000..19d7894e --- /dev/null +++ b/docs/README.linux.md @@ -0,0 +1,51 @@ +# TG WS Proxy для Linux + +## Готовые сборки + +Для Debian/Ubuntu скачайте со [страницы релизов](https://github.com/Flowseal/tg-ws-proxy/releases) пакет `TgWsProxy_linux_amd64.deb`. + +Для Arch и основанных на Arch дистрибутивов подготовлены пакеты в AUR: + +- [tg-ws-proxy-bin](https://aur.archlinux.org/packages/tg-ws-proxy-bin) +- [tg-ws-proxy-git](https://aur.archlinux.org/packages/tg-ws-proxy-git) +- [tg-ws-proxy-cli](https://aur.archlinux.org/packages/tg-ws-proxy-cli) + +```shell +# Установка без AUR-helper +git clone https://aur.archlinux.org/tg-ws-proxy-bin.git +cd tg-ws-proxy-bin +makepkg -si + +# При помощи AUR-helper +paru -S tg-ws-proxy-bin + +# Для пакета -cli запуск через systemd (8888 — номер порта; secret можно сгенерировать командой openssl rand -hex 16) +sudo systemctl start tg-ws-proxy@8888:3075abe65830f0325116bb0416cadf9f +``` + +Для остальных дистрибутивов можно использовать `TgWsProxy_linux_amd64` (бинарный файл для x86_64). + +```bash +chmod +x TgWsProxy_linux_amd64 +./TgWsProxy_linux_amd64 +``` + +При первом запуске откроется окно с инструкцией. Приложение работает в системном трее (требуется AppIndicator). + +## Настройка Telegram Desktop + +1. Telegram → **Настройки** → **Продвинутые настройки** → **Тип подключения** → **Прокси** +2. Добавьте прокси: + - **Тип:** MTProto + - **Сервер:** `127.0.0.1` (или переопределенный вами) + - **Порт:** `1443` (или переопределенный вами) + - **Secret:** из настроек или логов + +## Установка из исходников + +Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md) + +```bash +pip install -e . +tg-ws-proxy-tray-linux +``` diff --git a/docs/README.macos.md b/docs/README.macos.md new file mode 100644 index 00000000..cb701cfd --- /dev/null +++ b/docs/README.macos.md @@ -0,0 +1,30 @@ +# TG WS Proxy для macOS + +Перейдите на [страницу релизов](https://github.com/Flowseal/tg-ws-proxy/releases) и скачайте `TgWsProxy_macos_universal.dmg` (универсальная сборка для Apple Silicon и Intel). + +1. Откройте образ +2. Перенесите `TG WS Proxy.app` в папку `Applications` +3. При первом запуске macOS может попросить подтвердить открытие: **Системные настройки → Конфиденциальность и безопасность → Всё равно открыть** + +Минимально поддерживаемые версии: + +- Intel macOS 10.15+ +- Apple Silicon macOS 11.0+ + +## Настройка Telegram Desktop + +1. Telegram → **Настройки** → **Продвинутые настройки** → **Тип подключения** → **Прокси** +2. Добавьте прокси: + - **Тип:** MTProto + - **Сервер:** `127.0.0.1` (или переопределенный вами) + - **Порт:** `1443` (или переопределенный вами) + - **Secret:** из настроек или логов + +## Установка из исходников + +Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md) + +```bash +pip install -e . +tg-ws-proxy-tray-macos +``` diff --git a/docs/README.md b/docs/README.md index 9eb85973..51569765 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ > [!TIP] > -> ### [🎉 Поддержать меня](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md) +> ### [🎉 Поддержать меня](./Funding.md) > > **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu` > **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w` @@ -14,7 +14,7 @@ > Антивирусы часто ошибочно помечают приложение как вирус из-за упаковщика. > Если вы не можете скачать из-за блокировки антивирусом, то: > -> 1) **Попробуйте скачать версию win7 (она ничем не отличается в плане функционала)** +> 1) **Попробуйте скачать версию для Windows 7 (по функциональности она не отличается)** > 2) Отключите антивирус на время скачивания, добавьте файл в исключения и включите обратно > > Всегда проверяйте, что скачиваете из интернета, тем более из непроверенных источников. Всегда лучше смотреть на детекты широко известных антивирусов на VirusTotal @@ -25,6 +25,15 @@ image +## Навигация + +- [Быстрый старт по ОС](#-быстрый-старт): [Windows](./README.windows.md), [macOS](./README.macos.md), [Linux](./README.linux.md) +- [Настройка Cloudflare-домена (CfProxy)](./CfProxy.md) +- [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) +- [Файлы конфигурации Tray-приложения](./TrayConfig.md) +- [Установка из исходников](./BuildFromSource.md) +- [Руководство для контрибьюторов](../CONTRIBUTING.md) + ## Как это работает ``` @@ -39,244 +48,27 @@ Telegram Desktop → MTProto Proxy (127.0.0.1:1443) → WebSocket → Telegram D > [!IMPORTANT] > ### Не грузит фото/видео? -> **Удалите в настройках прокси в DC->IP всё, кроме `4:149.154.167.220`** -> **Если не помогло, то полностью очистите это поле** -> #### +> **Удалите в настройках прокси в DC → IP всё, кроме `4:149.154.167.220`** +> **Если это не помогло, полностью очистите это поле** > Подобная проблема встречается на аккаунтах без Premium -> Если вам не помогло, то настраивайте свой домен по гайду отсюда: https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/CfProxy.md +> Если это не помогло, настройте собственный домен по инструкции: [CfProxy.md](./CfProxy.md) ## 🚀 Быстрый старт -### Windows - -Перейдите на [страницу релизов](https://github.com/Flowseal/tg-ws-proxy/releases) и скачайте **`TgWsProxy_windows.exe`**. Он собирается автоматически через [GitHub Actions](https://github.com/Flowseal/tg-ws-proxy/actions) из открытого исходного кода. - -При первом запуске откроется окно с инструкцией по подключению Telegram Desktop. Приложение сворачивается в системный трей. - -**Меню трея:** - -- **Открыть в Telegram** — автоматически настроить прокси через ссылку `tg://proxy` -- **Скопировать ссылку** — скопировать ссылку для подключения -- **Перезапустить прокси** — перезапуск без выхода из приложения -- **Настройки...** — GUI-редактор конфигурации (версия приложения, опциональная проверка обновлений с GitHub) -- **Открыть логи** — открыть файл логов -- **Выход** — остановить прокси и закрыть приложение - -При первом запуске после старта может появиться запрос об открытии страницы релиза, если на GitHub вышла новая версия (эту проверку можно отключить в настройках). - -### Настройка Telegram Desktop - -### Автоматически: - -ПКМ по иконке в трее → **«Открыть в Telegram»** -Если не сработало (не открылся Telegram с подключением), то: -1. ПКМ по иконке в трее → **«Скопировать ссылку»** -2. Отправьте ссылку себе в избранное в Telegram-клиенте и нажмите по ней ЛКМ -3. Подключитесь - -### Вручную: - -1. Telegram → **Настройки** → **Продвинутые настройки** → **Тип подключения** → **Прокси** -2. Добавить прокси: - - **Тип:** MTProto - - **Сервер:** `127.0.0.1` (или переопределенный вами) - - **Порт:** `1443` (или переопределенный вами) - - **Secret:** из настроек или логов - -## -### macOS - -Перейдите на [страницу релизов](https://github.com/Flowseal/tg-ws-proxy/releases) и скачайте **`TgWsProxy_macos_universal.dmg`** — универсальная сборка для Apple Silicon и Intel. - -1. Открыть образ -2. Перенести **TG WS Proxy.app** в папку **Applications** -3. При первом запуске macOS может попросить подтвердить открытие: **Системные настройки → Конфиденциальность и безопасность → Всё равно открыть** - -### Linux - -Для Debian/Ubuntu скачайте со [страницы релизов](https://github.com/Flowseal/tg-ws-proxy/releases) пакет **`TgWsProxy_linux_amd64.deb`**. +Выберите свою ОС: -Для Arch и Arch-based дистрибутивов подготовлены пакеты в AUR: [tg-ws-proxy-bin](https://aur.archlinux.org/packages/tg-ws-proxy-bin), [tg-ws-proxy-git](https://aur.archlinux.org/packages/tg-ws-proxy-git), [tg-ws-proxy-cli](https://aur.archlinux.org/packages/tg-ws-proxy-cli) - -```shell -# Установка без AUR-helper -git clone https://aur.archlinux.org/tg-ws-proxy-bin.git -cd tg-ws-proxy-bin -makepkg -si - -# При помощи AUR-helper -paru -S tg-ws-proxy-bin - -# Если вы установили -cli пакет, то запуск осуществляется через systemctl, где 8888 - это номер порта, -# разделитель ":" и secret, который можно сгенерировать командой: openssl rand -hex 16 -sudo systemctl start tg-ws-proxy@8888:3075abe65830f0325116bb0416cadf9f -``` - -Для остальных дистрибутивов можно использовать **`TgWsProxy_linux_amd64`** (бинарный файл для x86_64). - -```bash -chmod +x TgWsProxy_linux_amd64 -./TgWsProxy_linux_amd64 -``` - -При первом запуске откроется окно с инструкцией. Приложение работает в системном трее (требуется AppIndicator). +- [Windows](./README.windows.md) +- [macOS](./README.macos.md) +- [Linux](./README.linux.md) ## Установка из исходников -### Консольный proxy - -Для запуска только proxy без tray-интерфейса достаточно базовой установки: - -```bash -pip install -e . -tg-ws-proxy -``` - -### Windows 7/10+ - -```bash -pip install -e . -tg-ws-proxy-tray-win -``` - -### macOS - -```bash -pip install -e . -tg-ws-proxy-tray-macos -``` - -### Linux - -```bash -pip install -e . -tg-ws-proxy-tray-linux -``` - -### Консольный режим из исходников - -```bash -tg-ws-proxy [--port PORT] [--host HOST] [--dc-ip DC:IP ...] [-v] -``` - -**Аргументы:** - -| Аргумент | По умолчанию | Описание | -|---|---|---| -| `--port` | `1443` | Порт прокси | -| `--host` | `127.0.0.1` | Хост прокси | -| `--secret` | `random` | 32-значный hex-ключ для авторизации клиентов | -| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (можно указать несколько раз) | -| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/CfProxy.md) | -| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее тут](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/CfProxy.md) | -| `--cfproxy-priority` | `true` | Пробовать проксировать через Cloudflare перед прямым TCP подключением | -| `--fake-tls-domain` | | Включить Fake TLS (ee-secret) маскировку с указанным SNI-доменом | -| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) | -| `--buf-kb` | `256` | Размер буфера в КБ | -| `--pool-size` | `4` | Количество заготовленных соединений на каждый DC | -| `--log-file` | выкл. | Путь к файлу, в который будут сохраняться логи | -| `--log-max-mb` | `5` | Максимальный размер файла логов в МБ (после идёт перезапись) | -| `--log-backups` | `0` | Количество сохранений логов после перезаписи | -| `-v`, `--verbose` | выкл. | Подробное логирование (DEBUG) | - -**Примеры:** - -```bash -# Стандартный запуск -tg-ws-proxy - -# Другой порт и дополнительные DC -tg-ws-proxy --port 9050 --dc-ip 1:149.154.175.205 --dc-ip 2:149.154.167.220 - -# С подробным логированием -tg-ws-proxy -v - -# Fake TLS маскировка (ee-secret) -tg-ws-proxy --fake-tls-domain example.com -``` - -## Fake TLS + nginx upstream -### Домен (`--fake-tls-domain`) должен указывать на тот же IP, на котором стоит прокси - -**Пример `nginx.conf` (stream):** +- [Инструкция по установке из исходников](./BuildFromSource.md) -```nginx -upstream mtproto { - server 127.0.0.1:8446; -} - -map $ssl_preread_server_name $sni_name { - hostnames; - example.com mtproto; - # if you have xray with selfsni running: - # sub.example.com www; - # default xray; -} - -# upstream xray { -# server 127.0.0.1:8443; -# } -# -# upstream www { -# server 127.0.0.1:7443; -# } - -server { - proxy_protocol on; - set_real_ip_from unix:; - listen 443; - proxy_pass $sni_name; - ssl_preread on; -} -``` - -**Запуск прокси за nginx:** - -```bash -python3 proxy/tg_ws_proxy.py \ - --port 8446 \ - --host 127.0.0.1 \ - --fake-tls-domain example.com \ - --proxy-protocol \ - --secret <32-hex-chars> -``` - -Ссылка для подключения будет в формате `ee`-секрета: - -``` -tg://proxy?server=your.domain.com&port=443&secret=ee -``` - -## Файлы конфигурации Tray-приложения - -Tray-приложение хранит данные в: - -- **Windows:** `%APPDATA%/TgWsProxy` -- **macOS:** `~/Library/Application Support/TgWsProxy` -- **Linux:** `~/.config/TgWsProxy` (или `$XDG_CONFIG_HOME/TgWsProxy`) - -```json -{ - "host": "127.0.0.1", - "port": 1443, - "secret": "...", - "dc_ip": [ - "2:149.154.167.220", - "4:149.154.167.220" - ], - "verbose": false, - "buf_kb": 256, - "pool_size": 4, - "log_max_mb": 5.0, - "check_updates": true, - "cfproxy": true, - "cfproxy_priority": true, - "cfproxy_user_domain": "", - "appearance": "auto" -} -``` +## Дополнительные инструкции -Ключ **`check_updates`** — при `true` при запросе к GitHub сравнивается версия с последним релизом (только уведомление и ссылка на страницу загрузки). На Windows в конфиге может быть **`autostart`** (автозапуск при входе в систему). +- [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) +- [Файлы конфигурации Tray-приложения](./TrayConfig.md) ## Автоматическая сборка @@ -293,4 +85,4 @@ Tray-приложение хранит данные в: ## Лицензия -[MIT License](https://github.com/Flowseal/tg-ws-proxy/blob/main/LICENSE) +[MIT License](../LICENSE) diff --git a/docs/README.windows.md b/docs/README.windows.md new file mode 100644 index 00000000..2e4020a4 --- /dev/null +++ b/docs/README.windows.md @@ -0,0 +1,52 @@ +# TG WS Proxy для Windows + +Перейдите на [страницу релизов](https://github.com/Flowseal/tg-ws-proxy/releases) и скачайте: + +- `TgWsProxy_windows.exe` (Windows 10+) +- `TgWsProxy_windows_7_64bit.exe` (Windows 7 x64) +- `TgWsProxy_windows_7_32bit.exe` (Windows 7 x32) + +Сборки публикуются автоматически через [GitHub Actions](https://github.com/Flowseal/tg-ws-proxy/actions) из открытого исходного кода. + +При первом запуске откроется окно с инструкцией по подключению Telegram Desktop. Приложение сворачивается в системный трей. + +## Меню трея + +- **Открыть в Telegram** — автоматически настроить прокси через ссылку `tg://proxy` +- **Скопировать ссылку** — скопировать ссылку для подключения +- **Перезапустить прокси** — перезапуск без выхода из приложения +- **Настройки...** — GUI-редактор конфигурации (версия приложения, опциональная проверка обновлений с GitHub) +- **Открыть логи** — открыть файл логов +- **Выход** — остановить прокси и закрыть приложение + +При первом запуске после старта может появиться запрос об открытии страницы релиза, если на GitHub вышла новая версия (эту проверку можно отключить в настройках). + +## Настройка Telegram Desktop + +### Автоматическая настройка + +Щелкните правой кнопкой мыши по значку в трее и выберите **«Открыть в Telegram»**. + +Если не сработало (Telegram не открылся с подключением), выполните шаги ниже: + +1. Щелкните правой кнопкой мыши по значку в трее и выберите **«Скопировать ссылку»** +2. Отправьте ссылку в «Избранное» в Telegram и нажмите по ней левой кнопкой мыши +3. Подключитесь + +### Ручная настройка + +1. Telegram → **Настройки** → **Продвинутые настройки** → **Тип подключения** → **Прокси** +2. Добавьте прокси: + - **Тип:** MTProto + - **Сервер:** `127.0.0.1` (или переопределенный вами) + - **Порт:** `1443` (или переопределенный вами) + - **Secret:** из настроек или логов + +## Установка из исходников + +Подробная инструкция: [BuildFromSource.md](./BuildFromSource.md) + +```bash +pip install -e . +tg-ws-proxy-tray-win +``` diff --git a/docs/TrayConfig.md b/docs/TrayConfig.md new file mode 100644 index 00000000..b8c194f7 --- /dev/null +++ b/docs/TrayConfig.md @@ -0,0 +1,31 @@ +# Файлы конфигурации Tray-приложения + +Tray-приложение хранит данные в: + +- **Windows:** `%APPDATA%/TgWsProxy` +- **macOS:** `~/Library/Application Support/TgWsProxy` +- **Linux:** `~/.config/TgWsProxy` (или `$XDG_CONFIG_HOME/TgWsProxy`) + +```json +{ + "host": "127.0.0.1", + "port": 1443, + "secret": "...", + "dc_ip": [ + "2:149.154.167.220", + "4:149.154.167.220" + ], + "verbose": false, + "buf_kb": 256, + "pool_size": 4, + "log_max_mb": 5.0, + "check_updates": true, + "cfproxy": true, + "cfproxy_priority": true, + "cfproxy_user_domain": "", + "appearance": "auto" +} +``` + +Ключ `check_updates`: при `true` выполняется запрос к GitHub и сравнение текущей версии с последним релизом (только уведомление и ссылка на страницу загрузки). +На Windows в конфиге может быть `autostart` (автозапуск при входе в систему). From 296064ae1aeab338389e9a7c59f8f309c1028d1d Mon Sep 17 00:00:00 2001 From: deexsed Date: Thu, 30 Apr 2026 20:26:47 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A3=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=20issue-=D1=88=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D1=8B=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=BF=D0=BE=20=D1=84=D0=B8=D0=B4=D0=B1?= =?UTF-8?q?=D0=B5=D0=BA=D1=83=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/ISSUE_TEMPLATE/bug_report.yml | 50 +--- .github/ISSUE_TEMPLATE/feature_request.yml | 22 -- .github/ISSUE_TEMPLATE/question.yml | 57 ---- .github/labels.md | 2 - .github/workflows/issue-sla-reminder.yml | 127 --------- .github/workflows/issue-triage.yml | 299 ++------------------- CONTRIBUTING.md | 13 +- docs/CfProxy.md | 12 +- docs/README.md | 32 +-- 9 files changed, 44 insertions(+), 570 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/question.yml delete mode 100644 .github/workflows/issue-sla-reminder.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4fb80702..c85ebf2d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -13,51 +13,11 @@ body: validations: required: true - - type: dropdown - id: os - attributes: - label: Операционная система - description: На какой ОС воспроизводится проблема? - options: - - Windows - - macOS - - Linux - - Другая - validations: - required: true - - - type: textarea - id: steps - attributes: - label: Шаги для воспроизведения - description: Опишите шаги, после которых возникает проблема - placeholder: | - 1. ... - 2. ... - 3. ... - validations: - required: true - - type: textarea - id: expected + id: description attributes: - label: Ожидаемое поведение - description: Что должно было произойти? - placeholder: Кратко опишите ожидаемый результат + label: Опишите вашу проблему + description: Чётко опишите проблему, с которой вы столкнулись + placeholder: Описание проблемы validations: - required: true - - - type: textarea - id: actual - attributes: - label: Фактическое поведение - description: Что произошло на самом деле? - placeholder: Кратко опишите фактический результат - validations: - required: true - - - type: textarea - id: additions - attributes: - label: Дополнительные детали - description: Приложите дополнительные детали (логи, скриншоты, текст ошибок). Для проблем с прокси добавьте лог-файл на момент возникновения проблемы. \ No newline at end of file + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 8b1d7dba..88444f15 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,17 +4,6 @@ description: Предложить улучшение или новую функ labels: ['type: предложение', 'status: нуждается в сортировке'] body: - - type: textarea - id: problem - attributes: - label: Какую проблему решает предложение? - description: Опишите текущую боль или ограничение, с которым вы столкнулись - placeholder: | - Сейчас ... - Это неудобно, потому что ... - validations: - required: true - - type: textarea id: solution attributes: @@ -26,17 +15,6 @@ body: validations: required: true - - type: textarea - id: alternatives - attributes: - label: Рассмотренные альтернативы - description: Опишите альтернативные варианты, если вы их рассматривали - placeholder: | - Вариант 1 ... - Вариант 2 ... - validations: - required: false - - type: dropdown id: platform attributes: diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml deleted file mode 100644 index 243b8826..00000000 --- a/.github/ISSUE_TEMPLATE/question.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: ❓ Вопрос / Поддержка -title: '[Вопрос] ' -description: Задать вопрос по настройке или работе TG WS Proxy -labels: ['type: вопрос', 'status: нуждается в сортировке'] - -body: - - type: dropdown - id: os - attributes: - label: Операционная система - description: На какой ОС возник вопрос? - options: - - Windows - - macOS - - Linux - - Другая - validations: - required: true - - - type: input - id: app_version - attributes: - label: Версия TG WS Proxy - description: Укажите версию приложения (если знаете) - placeholder: vX.Y.Z - validations: - required: false - - - type: textarea - id: question - attributes: - label: Ваш вопрос - description: Кратко опишите, что именно не получается или непонятно - placeholder: | - Что вы хотите сделать? - На каком шаге возник вопрос? - validations: - required: true - - - type: textarea - id: tried - attributes: - label: Что уже пробовали - description: Перечислите, какие действия уже выполнили - placeholder: | - 1. ... - 2. ... - 3. ... - validations: - required: false - - - type: textarea - id: additions - attributes: - label: Дополнительные детали - description: Прикрепите логи, скриншоты или текст ошибок, если это применимо - placeholder: Любая дополнительная информация diff --git a/.github/labels.md b/.github/labels.md index 1f6a644f..04f50173 100644 --- a/.github/labels.md +++ b/.github/labels.md @@ -6,13 +6,11 @@ - `type: проблема` — баг-репорты. - `type: предложение` — предложения по улучшениям. -- `type: вопрос` — вопросы по настройке и использованию. ## Статус issue - `status: нуждается в сортировке` — новый issue, ожидает первичного triage. - `status: в работе` — issue взят в работу мейнтейнером. -- `status: нужен ответ` — требуется ответ мейнтейнера по SLA. ## Важно diff --git a/.github/workflows/issue-sla-reminder.yml b/.github/workflows/issue-sla-reminder.yml deleted file mode 100644 index d9d483bf..00000000 --- a/.github/workflows/issue-sla-reminder.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Issue SLA Reminder - -on: - schedule: - - cron: "0 */12 * * *" - workflow_dispatch: - inputs: - sla_days: - description: "SLA threshold in days for unanswered bug issues" - required: false - default: "5" - type: string - -permissions: - issues: write - -env: - DEFAULT_SLA_DAYS: "5" - -jobs: - remind-unanswered-bugs: - runs-on: ubuntu-latest - steps: - - name: Mark and remind unanswered bug issues - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const dispatchSla = context.payload.inputs?.sla_days; - const configuredSla = dispatchSla ?? process.env.DEFAULT_SLA_DAYS ?? "5"; - const slaDays = Number(configuredSla); - - if (!Number.isFinite(slaDays) || slaDays <= 0) { - core.setFailed(`Invalid SLA value: "${configuredSla}". Use a positive number.`); - return; - } - - const now = Date.now(); - const marker = ""; - const waitingLabel = "status: нужен ответ"; - - async function ensureLabel(name, color, description) { - try { - await github.rest.issues.getLabel({ owner, repo, name }); - } catch (error) { - if (error.status === 404) { - await github.rest.issues.createLabel({ - owner, - repo, - name, - color, - description, - }); - } else { - throw error; - } - } - } - - await ensureLabel(waitingLabel, "D93F0B", "Issue ожидает ответа мейнтейнера"); - - const issues = await github.paginate(github.rest.issues.listForRepo, { - owner, - repo, - state: "open", - labels: "type: проблема", - per_page: 100, - }); - - for (const issue of issues) { - if (issue.pull_request) continue; - - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: issue.number, - per_page: 100, - }); - - const hasMaintainerReply = comments.some((c) => - ["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association), - ); - if (hasMaintainerReply) continue; - - const nonMaintainerTimestamps = [ - new Date(issue.created_at).getTime(), - ...comments - .filter((c) => !["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association)) - .map((c) => new Date(c.created_at).getTime()), - ]; - const lastNonMaintainerActivity = Math.max(...nonMaintainerTimestamps); - const inactivityDays = (now - lastNonMaintainerActivity) / (1000 * 60 * 60 * 24); - if (inactivityDays < slaDays) continue; - - const labelNames = (issue.labels || []).map((l) => l.name); - if (!labelNames.includes(waitingLabel)) { - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: issue.number, - labels: [waitingLabel], - }); - } - - const reminderExists = comments.some( - (c) => - c.user?.type === "Bot" && - typeof c.body === "string" && - c.body.includes(marker), - ); - if (reminderExists) continue; - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issue.number, - body: `${marker} - Напоминание по SLA: в issue пока нет ответа мейнтейнера. - - Спасибо за терпение. Мы вернемся с ответом при первой возможности. - Чтобы ускорить triage, можно дополнить issue: - - версией TG WS Proxy; - - логами/текстом ошибки; - - шагами воспроизведения.`, - }); - } diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 15315161..50854ea3 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -1,295 +1,34 @@ -name: Issue Triage +name: Auto comment on new issues on: issues: - types: - - opened - - labeled - issue_comment: - types: - - created + types: [opened] permissions: issues: write jobs: - question-auto-reply: - if: | - github.event_name == 'issues' && - contains(github.event.issue.labels.*.name, 'type: вопрос') && - github.event.issue.user.type != 'Bot' + comment: runs-on: ubuntu-latest steps: - - name: Add support comment - uses: actions/github-script@v8 + - name: Comment on new issue + uses: peter-evans/create-or-update-comment@v5 with: - script: | - const issueNumber = context.payload.issue.number; - const owner = context.repo.owner; - const repo = context.repo.repo; + issue-number: ${{ github.event.issue.number }} + body: | + Спасибо за issue и за помощь проекту ❤️ - const marker = ""; - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }); + Чтобы не терять ваше время, сначала проверьте две вещи: + - вы на последней версии: [Releases](https://github.com/${{ github.repository }}/releases) + - запускали по инструкции для своей ОС: [Quick start](https://github.com/${{ github.repository }}/blob/main/docs/README.md#-быстрый-старт) - const alreadyPosted = comments.some( - (c) => - c.user?.type === "Bot" && - typeof c.body === "string" && - c.body.includes(marker), - ); + Если проблема осталась, пожалуйста, допишите в этот issue: + - версию `tg-ws-proxy`; + - вашу ОС и способ запуска (релизный бинарник / из исходников); + - короткое описание, как воспроизвести проблему (если возможно); + - лог/текст ошибки или скриншот. - if (alreadyPosted) { - core.info("Auto-reply already exists, skipping."); - return; - } + Частый кейс: если не грузит медиа в Telegram, проверьте раздел в документации + «Не грузит фото/видео?» — [README](https://github.com/${{ github.repository }}/blob/main/docs/README.md#не-грузит-фотовидео). - const body = `${marker} - Спасибо за вопрос! Мы посмотрим его как можно скорее. - - Чтобы ускорить ответ, пожалуйста, убедитесь, что в issue есть: - - версия TG WS Proxy; - - операционная система; - - что уже было проверено; - - лог/скриншот (если есть ошибка). - - Полезные разделы документации: - - https://github.com/${owner}/${repo}/blob/main/docs/README.md - - https://github.com/${owner}/${repo}/blob/main/docs/CfProxy.md`; - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body, - }); - - bug-auto-reply: - if: | - github.event_name == 'issues' && - contains(github.event.issue.labels.*.name, 'type: проблема') && - github.event.issue.user.type != 'Bot' - runs-on: ubuntu-latest - steps: - - name: Add bug triage comment - uses: actions/github-script@v8 - with: - script: | - const issueNumber = context.payload.issue.number; - const owner = context.repo.owner; - const repo = context.repo.repo; - const issueBody = context.payload.issue.body || ""; - - const marker = ""; - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }); - - const alreadyPosted = comments.some( - (c) => - c.user?.type === "Bot" && - typeof c.body === "string" && - c.body.includes(marker), - ); - - if (alreadyPosted) { - core.info("Auto-reply already exists, skipping."); - return; - } - - function extractSection(title) { - const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const re = new RegExp(`### ${escaped}\\r?\\n\\r?\\n([\\s\\S]*?)(?=\\r?\\n### |$)`, "m"); - const match = issueBody.match(re); - if (!match) return ""; - return match[1].trim(); - } - - const appVersion = extractSection("Версия TG WS Proxy"); - const additions = extractSection("Дополнительные детали"); - - const noResponseValues = new Set(["", "_No response_", "No response"]); - const missingVersion = noResponseValues.has(appVersion); - const missingLogs = noResponseValues.has(additions); - - const missingParts = []; - if (missingVersion) missingParts.push("- версия TG WS Proxy"); - if (missingLogs) missingParts.push("- лог/скриншот или текст ошибки"); - - const extraRequest = missingParts.length - ? `\nЧтобы ускорить диагностику, пожалуйста, дополните issue:\n${missingParts.join("\n")}\n` - : ""; - - const body = `${marker} - Спасибо за баг-репорт! Мы посмотрим его как можно скорее. - - ${extraRequest} - - Полезные разделы документации: - - https://github.com/${owner}/${repo}/blob/main/docs/README.md - - https://github.com/${owner}/${repo}/blob/main/docs/CfProxy.md`; - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body, - }); - - feature-auto-reply: - if: | - github.event_name == 'issues' && - contains(github.event.issue.labels.*.name, 'type: предложение') && - github.event.issue.user.type != 'Bot' - runs-on: ubuntu-latest - steps: - - name: Add feature triage comment - uses: actions/github-script@v8 - with: - script: | - const issueNumber = context.payload.issue.number; - const owner = context.repo.owner; - const repo = context.repo.repo; - - const marker = ""; - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }); - - const alreadyPosted = comments.some( - (c) => - c.user?.type === "Bot" && - typeof c.body === "string" && - c.body.includes(marker), - ); - - if (alreadyPosted) { - core.info("Auto-reply already exists, skipping."); - return; - } - - const body = `${marker} - Спасибо за предложение! Мы рассмотрим его в рамках triage. - - Чтобы ускорить принятие решения, полезно добавить: - - сценарий использования; - - ожидаемую пользу для пользователей; - - возможные альтернативы. - - Полезный раздел: - - https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md`; - - await github.rest.issues.createComment({ - owner, - repo, - issue_number: issueNumber, - body, - }); - - status-in-progress-on-maintainer-comment: - if: | - github.event_name == 'issue_comment' && - github.event.issue.pull_request == null && - contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) - runs-on: ubuntu-latest - steps: - - name: Ensure status label exists and apply - uses: actions/github-script@v8 - with: - script: | - const owner = context.repo.owner; - const repo = context.repo.repo; - const issueNumber = context.payload.issue.number; - const statusLabel = "status: в работе"; - const needsTriageLabel = "status: нуждается в сортировке"; - const currentCommentId = context.payload.comment.id; - - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: issueNumber, - per_page: 100, - }); - - const maintainerCommentsBeforeCurrent = comments.filter( - (c) => - c.id !== currentCommentId && - ["OWNER", "MEMBER", "COLLABORATOR"].includes(c.author_association), - ); - - if (maintainerCommentsBeforeCurrent.length > 0) { - core.info("Not first maintainer comment, skipping status transition."); - return; - } - - try { - await github.rest.issues.getLabel({ - owner, - repo, - name: statusLabel, - }); - } catch (error) { - if (error.status === 404) { - await github.rest.issues.createLabel({ - owner, - repo, - name: statusLabel, - color: "1D76DB", - description: "Issue в активной работе", - }); - } else { - throw error; - } - } - - try { - await github.rest.issues.getLabel({ - owner, - repo, - name: needsTriageLabel, - }); - } catch (error) { - if (error.status === 404) { - await github.rest.issues.createLabel({ - owner, - repo, - name: needsTriageLabel, - color: "FBCA04", - description: "Issue ожидает первичного triage", - }); - } else { - throw error; - } - } - - const currentLabels = (context.payload.issue.labels || []).map((l) => l.name); - if (currentLabels.includes(statusLabel)) { - core.info("Status label already set, skipping."); - return; - } - - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: issueNumber, - labels: [statusLabel], - }); - - if (currentLabels.includes(needsTriageLabel)) { - await github.rest.issues.removeLabel({ - owner, - repo, - issue_number: issueNumber, - name: needsTriageLabel, - }); - } + После этого мы сможем быстрее воспроизвести проблему и дать точный ответ. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23e85f14..59889d22 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,11 +6,7 @@ 1. Проверьте документацию в `docs/README.md`. 2. Убедитесь, что похожий issue еще не открыт. -3. Выберите подходящий шаблон: - - `Проблема` — для багов, - - `Предложение` — для новых возможностей, - - `Вопрос / Поддержка` — для вопросов по настройке и использованию. -4. Для корректной работы triage используйте стандартные лейблы из `.github/labels.md`. +3. Для корректной работы triage используйте стандартные лейблы из `.github/labels.md`. ## Как сообщать о проблемах @@ -49,11 +45,4 @@ pip install -e . 2. Проверьте, что не сломаны существующие сценарии. 3. Обновите документацию, если меняется поведение или настройка. -В описании PR укажите: - -- цель изменения; -- что именно изменено; -- как это проверить; -- для какой ОС (если применимо). - Небольшие и сфокусированные PR проверяются и принимаются быстрее. diff --git a/docs/CfProxy.md b/docs/CfProxy.md index 458ed58c..59830a1e 100644 --- a/docs/CfProxy.md +++ b/docs/CfProxy.md @@ -15,12 +15,12 @@ Cloudflare имеет лимиты на одновременное количе 2. В `SSL/TLS` → `Overview` выставьте режим **Flexible**. 3. В `DNS` → `Records` добавьте следующие `A`-записи через `+ Add Record`: - - `kws1` → `149.154.175.50` - - `kws2` → `149.154.167.51` - - `kws3` → `149.154.175.100` - - `kws4` → `149.154.167.91` - - `kws5` → `149.154.171.5` - - `kws203` → `91.105.192.100` +- Name=`kws1` IPv4=`149.154.175.50` +- Name=`kws2` IPv4=`149.154.167.51` +- Name=`kws3` IPv4=`149.154.175.100` +- Name=`kws4` IPv4=`149.154.167.91` +- Name=`kws5` IPv4=`149.154.171.5` +- Name=`kws203` IPv4=`91.105.192.100` 4. **Добавьте домен в [zapret](https://github.com/Flowseal/zapret-discord-youtube/) или в любое другое ПО, так как подсеть Cloudflare может быть заблокирована (например, в России).** diff --git a/docs/README.md b/docs/README.md index 51569765..60694447 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,8 +27,11 @@ ## Навигация -- [Быстрый старт по ОС](#-быстрый-старт): [Windows](./README.windows.md), [macOS](./README.macos.md), [Linux](./README.linux.md) -- [Настройка Cloudflare-домена (CfProxy)](./CfProxy.md) +### **🚀 Быстрый старт** + - **[Windows](./README.windows.md)** + - **[macOS](./README.macos.md)** + - **[Linux](./README.linux.md)** +- [Настройка Cloudflare-домена (CF-прокси)](./CfProxy.md) - [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) - [Файлы конфигурации Tray-приложения](./TrayConfig.md) - [Установка из исходников](./BuildFromSource.md) @@ -53,23 +56,6 @@ Telegram Desktop → MTProto Proxy (127.0.0.1:1443) → WebSocket → Telegram D > Подобная проблема встречается на аккаунтах без Premium > Если это не помогло, настройте собственный домен по инструкции: [CfProxy.md](./CfProxy.md) -## 🚀 Быстрый старт - -Выберите свою ОС: - -- [Windows](./README.windows.md) -- [macOS](./README.macos.md) -- [Linux](./README.linux.md) - -## Установка из исходников - -- [Инструкция по установке из исходников](./BuildFromSource.md) - -## Дополнительные инструкции - -- [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) -- [Файлы конфигурации Tray-приложения](./TrayConfig.md) - ## Автоматическая сборка Проект содержит спецификации PyInstaller ([`packaging/windows.spec`](../packaging/windows.spec), [`packaging/macos.spec`](../packaging/macos.spec), [`packaging/linux.spec`](../packaging/linux.spec)) и GitHub Actions workflow ([`.github/workflows/build.yml`](../.github/workflows/build.yml)) для автоматической сборки. @@ -83,6 +69,14 @@ Telegram Desktop → MTProto Proxy (127.0.0.1:1443) → WebSocket → Telegram D - Apple Silicon macOS 11.0+ - Linux x86_64 (требуется AppIndicator для системного трея) +## Контрибьюторы + +Спасибо всем, кто помогает развивать проект ❤️ + + + + + ## Лицензия [MIT License](../LICENSE) From 0e7b31d010151afaa1320cde72bb3b2ec14101d6 Mon Sep 17 00:00:00 2001 From: deexsed Date: Thu, 30 Apr 2026 20:37:20 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=D1=84=D0=B0=D0=BA=D0=B0=D0=BF...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/issue-triage.yml | 2 +- docs/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 50854ea3..1f9c4457 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -20,7 +20,7 @@ jobs: Чтобы не терять ваше время, сначала проверьте две вещи: - вы на последней версии: [Releases](https://github.com/${{ github.repository }}/releases) - - запускали по инструкции для своей ОС: [Quick start](https://github.com/${{ github.repository }}/blob/main/docs/README.md#-быстрый-старт) + - запускали по инструкции для своей ОС: [Быстрый старт](https://github.com/${{ github.repository }}/blob/main/docs/README.md#-быстрый-старт) Если проблема осталась, пожалуйста, допишите в этот issue: - версию `tg-ws-proxy`; diff --git a/docs/README.md b/docs/README.md index 60694447..6d635c75 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ ## Навигация -### **🚀 Быстрый старт** +- **🚀 Быстрый старт** - **[Windows](./README.windows.md)** - **[macOS](./README.macos.md)** - **[Linux](./README.linux.md)**