diff --git a/docs/chat-apps.md b/docs/chat-apps.md index afade5ce..84522f53 100644 --- a/docs/chat-apps.md +++ b/docs/chat-apps.md @@ -109,7 +109,24 @@ If `nanobot channels status` does not show the channel as enabled, the config sn
Telegram -**Install the optional channel dependency** +**Recommended WebUI setup** + +1. Create a bot with `@BotFather` and copy its token. +2. Run `nanobot webui`, then open **Settings → Channels → Telegram**. +3. Paste the token. If the gateway cannot reach Telegram directly, expand + **Advanced** and add an HTTP or SOCKS proxy. +4. Save and enable Telegram, then send the bot a direct message. + +The configuration badge means nanobot found a saved token. The live connection +check is separate, so a temporary Telegram or proxy outage does not make an +existing configuration disappear. Saved tokens and proxy URLs remain masked. + +See the [step-by-step Telegram guide](./guides/telegram-ai-agent.md) for pairing +and troubleshooting. + +**Manual setup** + +Install the optional channel dependency: ```bash nanobot plugins enable telegram @@ -134,6 +151,21 @@ nanobot plugins enable telegram } ``` +If the gateway cannot reach Telegram directly, add a proxy to the same section: + +```json +{ + "channels": { + "telegram": { + "proxy": "http://127.0.0.1:7890" + } + } +} +``` + +HTTP, HTTPS, SOCKS5, and SOCKS5H proxy URLs are accepted. Treat a proxy URL +containing a username or password as a secret. + > You can find your **User ID** in Telegram settings. It is shown as `@yourUserId`. Copy this value **without the `@` symbol** and paste it into the config file. > > `richMessages` defaults to `false`. Set it to `true` only if your Telegram client supports Bot API 10.1 rich messages and you want richer markdown rendering; keep it disabled for Telegram Web, which may show unsupported-message errors for rich messages. diff --git a/docs/guides/telegram-ai-agent.md b/docs/guides/telegram-ai-agent.md index 5c9c43f5..b19be8ca 100644 --- a/docs/guides/telegram-ai-agent.md +++ b/docs/guides/telegram-ai-agent.md @@ -1,8 +1,7 @@ -# Build a Telegram AI Agent with nanobot +# Connect Telegram to nanobot -This guide connects nanobot to Telegram so a paired Telegram user can message a -self-hosted AI agent backed by your normal nanobot config, tools, memory, and -workspace. +This guide connects one Telegram bot to nanobot. Messages sent to that bot use +your normal nanobot model, tools, memory, and workspace. ## What this guide builds @@ -29,27 +28,55 @@ python -m pip install nanobot-ai nanobot onboard --wizard ``` -## Enable the Telegram channel +## Connect Telegram in the WebUI -Install the optional channel dependency: +Start the WebUI: + +```bash +nanobot webui +``` + +Open **Settings → Channels → Telegram**: + +1. If Telegram support is not installed, turn on its switch and confirm the + installation. +2. Paste the token from BotFather. +3. If the gateway cannot reach Telegram directly, expand **Advanced** and enter + an HTTP or SOCKS proxy such as `http://127.0.0.1:7890`. +4. Save and enable Telegram. + +The configuration badge appears as soon as a bot token is saved. A connection +check is separate: if Telegram is temporarily unreachable, the saved +configuration remains valid and the bot can continue working in environments +where the gateway has network access. + +Saved tokens and proxy URLs are masked. A proxy entered here is used both for +the connection check and for normal Telegram traffic. + +## Manual setup + +For a headless installation, install Telegram support: ```bash nanobot plugins enable telegram ``` -Merge this snippet into `~/.nanobot/config.json`: +Then merge this snippet into `~/.nanobot/config.json`: ```json { "channels": { "telegram": { "enabled": true, - "token": "YOUR_BOT_TOKEN" + "token": "YOUR_BOT_TOKEN", + "proxy": "http://127.0.0.1:7890" } } } ``` +Omit `proxy` when the gateway can reach Telegram directly. + Omitting `allowFrom` enables pairing-only mode. The first DM from a new user gets a pairing code instead of agent access. @@ -95,8 +122,13 @@ workspace as your local CLI check. - If the channel is not listed, run `nanobot plugins enable telegram` again in the same Python environment. -- If messages do not arrive, run `nanobot gateway --verbose` and check the bot - token. +- If the WebUI shows a saved configuration but the live check cannot reach Telegram, + the token is still saved. Confirm the gateway can reach `api.telegram.org`, + or open **Advanced → Network proxy** and enter a proxy. +- If Telegram rejects the token, copy the current token from BotFather or + regenerate it. +- If messages do not arrive, run `nanobot gateway --verbose` and confirm the + Telegram channel is enabled. - If a first DM returns a pairing code, that is expected. Approve the code before testing normal agent replies. - If Telegram Web shows unsupported rich messages, keep `richMessages` disabled. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b7e05ee0..e2b46a06 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -275,7 +275,9 @@ Then check: |---|---| | Bot never replies | Gateway is not running, the channel is not enabled, or the bot/app token is wrong. | | Unknown sender ignored | Configure `allowFrom`, pairing, or the channel-specific allow list. | -| Telegram fails | Confirm the BotFather token and `allowFrom` user ID. | +| Telegram shows a saved configuration but cannot complete a live check | The token is saved. Confirm the gateway can reach `api.telegram.org`, or open **Settings → Channels → Telegram → Advanced → Network proxy** and enter an HTTP or SOCKS proxy. | +| Telegram rejects the token | Copy the current token from BotFather or regenerate it. | +| Telegram receives no messages | Confirm the channel is enabled, the gateway is running, and the sender is paired or listed in `allowFrom`. | | Discord replies missing | Enable Message Content intent and invite the bot with the required permissions. | | WhatsApp or WeChat login expired | Re-run `nanobot channels login whatsapp` or `nanobot channels login weixin`. | | Chat app works but WebUI does not | The provider and gateway are likely fine; debug the WebSocket channel separately. | diff --git a/nanobot/channels/telegram/manifest.py b/nanobot/channels/telegram/manifest.py index ee20f715..f02a7a40 100644 --- a/nanobot/channels/telegram/manifest.py +++ b/nanobot/channels/telegram/manifest.py @@ -8,6 +8,7 @@ from nanobot.channels.telegram.validation import validate SETUP_SPEC = ChannelSetupSpec( fields={ "token": field("secret"), + "proxy": field("secret"), "allowFrom": field("list"), "groupPolicy": field("enum", choices=GROUP_POLICIES, default="mention"), }, diff --git a/nanobot/channels/telegram/tests/test_validation.py b/nanobot/channels/telegram/tests/test_validation.py index 5d822b61..95c8278c 100644 --- a/nanobot/channels/telegram/tests/test_validation.py +++ b/nanobot/channels/telegram/tests/test_validation.py @@ -4,11 +4,60 @@ import httpx import pytest from nanobot.channels.telegram import validation as telegram_validation +from nanobot.channels.telegram.manifest import SETUP_SPEC from nanobot.channels.validation import validate_channel_config from nanobot.config.loader import save_config from nanobot.config.schema import Config +def test_telegram_setup_exposes_proxy_as_an_optional_secret() -> None: + proxy = SETUP_SPEC.fields["proxy"] + + assert proxy.kind == "secret" + assert "proxy" not in SETUP_SPEC.simple_required_fields + + +def test_get_me_builds_http_client_with_explicit_proxy( + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + proxy = "socks5://proxy-user:proxy-pass@127.0.0.1:1080" + captured: dict[str, object] = {} + + class FakeResponse: + def raise_for_status(self) -> None: + return None + + def json(self) -> dict: + return {"ok": True, "result": {"id": 42}} + + class FakeClient: + def __init__(self, **kwargs) -> None: + captured["kwargs"] = kwargs + + def __enter__(self): + return self + + def __exit__(self, *_args) -> None: + return None + + def get(self, url: str) -> FakeResponse: + captured["url"] = url + return FakeResponse() + + monkeypatch.setattr(telegram_validation.httpx, "Client", FakeClient) + + result = telegram_validation._get_me(token, proxy) + + assert result["ok"] is True + assert captured["kwargs"] == { + "timeout": 4.0, + "proxy": proxy, + "trust_env": False, + } + assert captured["url"] == f"https://api.telegram.org/bot{token}/getMe" + + def test_validate_telegram_bad_token_is_invalid(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: config_path = tmp_path / "config.json" save_config(Config(), config_path) @@ -21,7 +70,38 @@ def test_validate_telegram_bad_token_is_invalid(tmp_path, monkeypatch: pytest.Mo assert result["missing_fields"] == [] -def test_validate_telegram_does_not_expose_saved_token_in_http_errors( +@pytest.mark.parametrize("status_code", [401, 404]) +def test_validate_telegram_rejects_denied_tokens_without_exposing_them( + tmp_path, + monkeypatch: pytest.MonkeyPatch, + status_code: int, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + config_path = tmp_path / "config.json" + save_config( + Config.model_validate({"channels": {"telegram": {"token": token}}}), + config_path, + ) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + + def raise_http_error(token_value: str, _proxy: str | None) -> dict: + request = httpx.Request("GET", f"https://api.telegram.org/bot{token_value}/getMe") + response = httpx.Response(status_code, request=request) + raise httpx.HTTPStatusError("rejected", request=request, response=response) + + monkeypatch.setattr(telegram_validation, "_get_me", raise_http_error) + + result = validate_channel_config("telegram", {"channels.telegram.token": ""}) + + assert result["status"] == "invalid" + assert result["can_enable"] is False + assert token not in str(result) + assert any( + f"HTTP {status_code}" in check.get("message", "") for check in result["checks"] + ) + + +def test_validate_telegram_keeps_transient_http_failures_retryable( tmp_path, monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -33,14 +113,197 @@ def test_validate_telegram_does_not_expose_saved_token_in_http_errors( ) monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) - def raise_http_error(url: str, **_kwargs) -> dict: - request = httpx.Request("GET", url) - response = httpx.Response(401, request=request) - raise httpx.HTTPStatusError("unauthorized", request=request, response=response) + def raise_http_error(token_value: str, _proxy: str | None) -> dict: + request = httpx.Request("GET", f"https://api.telegram.org/bot{token_value}/getMe") + response = httpx.Response(503, request=request) + raise httpx.HTTPStatusError("unavailable", request=request, response=response) - monkeypatch.setattr(telegram_validation, "http_get", raise_http_error) + monkeypatch.setattr(telegram_validation, "_get_me", raise_http_error) result = validate_channel_config("telegram", {"channels.telegram.token": ""}) + assert result["status"] == "configured" + assert result["can_enable"] is True assert token not in str(result) - assert any("HTTP 401" in check.get("message", "") for check in result["checks"]) + assert any("HTTP 503" in check.get("message", "") for check in result["checks"]) + + +def test_validate_telegram_marks_proxy_transport_failures_without_exposing_proxy( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + proxy = "http://proxy-user:proxy-pass@127.0.0.1:7890" + config_path = tmp_path / "config.json" + save_config( + Config.model_validate( + {"channels": {"telegram": {"token": token, "proxy": proxy}}} + ), + config_path, + ) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + + def raise_proxy_error(_token: str, _proxy: str | None) -> dict: + raise httpx.ProxyError("proxy credentials rejected") + + monkeypatch.setattr(telegram_validation, "_get_me", raise_proxy_error) + + result = validate_channel_config("telegram") + + assert result["status"] == "configured" + assert result["can_enable"] is True + assert proxy not in str(result) + assert any(check["id"] == "proxy_connection" for check in result["checks"]) + + +def test_validate_telegram_uses_saved_proxy_without_exposing_it( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + proxy = "socks5://proxy-user:proxy-pass@127.0.0.1:1080" + config_path = tmp_path / "config.json" + save_config( + Config.model_validate( + {"channels": {"telegram": {"token": token, "proxy": proxy}}} + ), + config_path, + ) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + captured: dict[str, str | None] = {} + + def fake_get_me(token_value: str, proxy_value: str | None) -> dict: + captured.update(token=token_value, proxy=proxy_value) + return {"ok": True, "result": {"id": 42, "username": "working_bot"}} + + monkeypatch.setattr(telegram_validation, "_get_me", fake_get_me) + + result = validate_channel_config("telegram") + + assert result["status"] == "connected" + assert captured == {"token": token, "proxy": proxy} + assert proxy not in str(result) + + +def test_validate_telegram_resolves_saved_secret_env_refs_without_exposing_them( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + token_ref = "${TELEGRAM_TOKEN_TEST}" + proxy_ref = "${TELEGRAM_PROXY_TEST}" + proxy = "http://proxy-user:proxy-pass@127.0.0.1:7890" + monkeypatch.setenv("TELEGRAM_TOKEN_TEST", token) + monkeypatch.setenv("TELEGRAM_PROXY_TEST", proxy) + config_path = tmp_path / "config.json" + save_config( + Config.model_validate( + {"channels": {"telegram": {"token": token_ref, "proxy": proxy_ref}}} + ), + config_path, + ) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + captured: dict[str, str | None] = {} + + def fake_get_me(token_value: str, proxy_value: str | None) -> dict: + captured.update(token=token_value, proxy=proxy_value) + return {"ok": True, "result": {"id": 42, "username": "working_bot"}} + + monkeypatch.setattr(telegram_validation, "_get_me", fake_get_me) + + result = validate_channel_config("telegram") + + assert result["status"] == "connected" + assert captured == {"token": token, "proxy": proxy} + assert token_ref not in str(result) + assert token not in str(result) + assert proxy_ref not in str(result) + assert proxy not in str(result) + + +def test_validate_telegram_rejects_unset_proxy_env_ref_without_connecting( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + proxy_ref = "${TELEGRAM_MISSING_PROXY_TEST}" + monkeypatch.delenv("TELEGRAM_MISSING_PROXY_TEST", raising=False) + config_path = tmp_path / "config.json" + save_config( + Config.model_validate( + {"channels": {"telegram": {"token": token, "proxy": proxy_ref}}} + ), + config_path, + ) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + + def fail_get_me(*_args) -> dict: + pytest.fail("an unresolved proxy reference must not fall back to direct access") + + monkeypatch.setattr(telegram_validation, "_get_me", fail_get_me) + + result = validate_channel_config("telegram") + + assert result["status"] == "invalid" + assert result["can_enable"] is False + assert proxy_ref not in str(result) + assert any(check["id"] == "proxy_env" for check in result["checks"]) + + +def test_validate_telegram_uses_proxy_submitted_with_new_token( + tmp_path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + proxy = "http://127.0.0.1:7890" + config_path = tmp_path / "config.json" + save_config(Config(), config_path) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + captured: dict[str, str | None] = {} + + def fake_get_me(token_value: str, proxy_value: str | None) -> dict: + captured.update(token=token_value, proxy=proxy_value) + return {"ok": True, "result": {"id": 42, "username": "new_bot"}} + + monkeypatch.setattr(telegram_validation, "_get_me", fake_get_me) + + result = validate_channel_config( + "telegram", + { + "channels.telegram.token": token, + "channels.telegram.proxy": proxy, + }, + ) + + assert result["status"] == "connected" + assert captured == {"token": token, "proxy": proxy} + + +@pytest.mark.parametrize("proxy", ["127.0.0.1:7890", "http://[", "http://localhost:not-a-port"]) +def test_validate_telegram_rejects_invalid_proxy_without_trying_token( + tmp_path, + monkeypatch: pytest.MonkeyPatch, + proxy: str, +) -> None: + token = "123456:abcdefghijklmnopqrstuvwxyz" + config_path = tmp_path / "config.json" + save_config(Config(), config_path) + monkeypatch.setattr("nanobot.config.loader._current_config_path", config_path) + + def fail_get_me(*_args) -> dict: + pytest.fail("invalid proxy must stop before getMe") + + monkeypatch.setattr(telegram_validation, "_get_me", fail_get_me) + + result = validate_channel_config( + "telegram", + { + "channels.telegram.token": token, + "channels.telegram.proxy": proxy, + }, + ) + + assert result["status"] == "invalid" + assert result["can_enable"] is False + assert proxy not in str(result) + assert any(check["id"] == "proxy_format" for check in result["checks"]) diff --git a/nanobot/channels/telegram/validation.py b/nanobot/channels/telegram/validation.py index f6995b7c..7244b1c6 100644 --- a/nanobot/channels/telegram/validation.py +++ b/nanobot/channels/telegram/validation.py @@ -2,24 +2,82 @@ import re from typing import Any +from urllib.parse import urlparse import httpx from nanobot.channels.contracts import ChannelValidationContext from nanobot.channels.validation import ( check, - http_get, message_from_response, payload, required_checks, status_from_checks, string_value, ) +from nanobot.config.loader import resolve_env_refs + +_TIMEOUT_SECONDS = 4.0 +_SUPPORTED_PROXY_SCHEMES = {"http", "https", "socks5", "socks5h"} + + +def _proxy_url_is_valid(proxy: str) -> bool: + try: + parsed = urlparse(proxy) + hostname = parsed.hostname + parsed.port + except ValueError: + return False + return parsed.scheme.lower() in _SUPPORTED_PROXY_SCHEMES and bool(hostname) + + +def _get_me(token: str, proxy: str | None) -> dict[str, Any]: + client_kwargs: dict[str, Any] = {"timeout": _TIMEOUT_SECONDS} + if proxy: + client_kwargs.update(proxy=proxy, trust_env=False) + with httpx.Client(**client_kwargs) as client: + response = client.get(f"https://api.telegram.org/bot{token}/getMe") + response.raise_for_status() + data = response.json() + return data if isinstance(data, dict) else {} def validate(values: dict[str, Any], _context: ChannelValidationContext) -> dict[str, Any]: checks, missing = required_checks("telegram", values) - token = string_value(values.get("token")) + raw_token = string_value(values.get("token")) + raw_proxy = string_value(values.get("proxy")) + token = string_value(resolve_env_refs(raw_token)) + proxy = string_value(resolve_env_refs(raw_proxy)) + if raw_token and not token: + checks.append( + check( + "token_env", + "Token environment variable", + "fail", + "Set every environment variable referenced by the bot token.", + ) + ) + if raw_proxy and not proxy: + checks.append( + check( + "proxy_env", + "Proxy environment variable", + "fail", + "Set every environment variable referenced by the network proxy.", + ) + ) + if (raw_token and not token) or (raw_proxy and not proxy): + return status_from_checks("telegram", checks, missing) + if proxy and not _proxy_url_is_valid(proxy): + checks.append( + check( + "proxy_format", + "Network proxy", + "fail", + "Enter a full HTTP or SOCKS proxy URL.", + ) + ) + return status_from_checks("telegram", checks, missing) if token: if not re.match(r"^\d+:[A-Za-z0-9_-]{20,}$", token): checks.append( @@ -35,7 +93,7 @@ def validate(values: dict[str, Any], _context: ChannelValidationContext) -> dict check("token_format", "Token format", "pass", "Looks like a BotFather token.") ) try: - data = http_get(f"https://api.telegram.org/bot{token}/getMe") + data = _get_me(token, proxy or None) if data.get("ok") and isinstance(data.get("result"), dict): bot = data["result"] identity = { @@ -61,12 +119,31 @@ def validate(values: dict[str, Any], _context: ChannelValidationContext) -> dict ) ) except httpx.HTTPStatusError as exc: + status_code = exc.response.status_code + rejected = status_code in {400, 401, 403, 404} checks.append( check( "get_me", "Bot identity", + "fail" if rejected else "warn", + ( + f"Telegram rejected the token: HTTP {status_code}." + if rejected + else f"Telegram could not verify the token: HTTP {status_code}." + ), + ) + ) + except httpx.TransportError: + checks.append( + check( + "proxy_connection" if proxy else "get_me", + "Network proxy" if proxy else "Bot identity", "warn", - f"Telegram could not verify the token: HTTP {exc.response.status_code}.", + ( + "Could not reach Telegram through the network proxy." + if proxy + else "Could not reach Telegram now. Try again later." + ), ) ) except Exception: @@ -75,7 +152,7 @@ def validate(values: dict[str, Any], _context: ChannelValidationContext) -> dict "get_me", "Bot identity", "warn", - "Could not reach Telegram now. Try again later.", + "Could not verify Telegram now. Try again later.", ) ) return status_from_checks("telegram", checks, missing) diff --git a/nanobot/channels/telegram/webui/index.ts b/nanobot/channels/telegram/webui/index.ts index 5a4302ad..57d4cdc8 100644 --- a/nanobot/channels/telegram/webui/index.ts +++ b/nanobot/channels/telegram/webui/index.ts @@ -12,6 +12,7 @@ export default { docsUrl: chatAppGuideUrl("telegram"), fields: [ { key: "channels.telegram.token" }, + { key: "channels.telegram.proxy" }, { key: "channels.telegram.allowFrom" }, { key: "channels.telegram.groupPolicy" }, ], diff --git a/nanobot/channels/telegram/webui/locales/en.json b/nanobot/channels/telegram/webui/locales/en.json index e968efce..c06342b9 100644 --- a/nanobot/channels/telegram/webui/locales/en.json +++ b/nanobot/channels/telegram/webui/locales/en.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Create it with BotFather." }, + "proxy": { + "label": "Network proxy", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Allowed users", "placeholder": "* or Telegram user IDs", diff --git a/nanobot/channels/telegram/webui/locales/es.json b/nanobot/channels/telegram/webui/locales/es.json index 394ceefd..2909b4cf 100644 --- a/nanobot/channels/telegram/webui/locales/es.json +++ b/nanobot/channels/telegram/webui/locales/es.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Créalo con BotFather." }, + "proxy": { + "label": "Proxy de red", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Usuarios permitidos", "placeholder": "* o ID de usuario de Telegram", diff --git a/nanobot/channels/telegram/webui/locales/fr.json b/nanobot/channels/telegram/webui/locales/fr.json index 9913ff4c..f8a136e6 100644 --- a/nanobot/channels/telegram/webui/locales/fr.json +++ b/nanobot/channels/telegram/webui/locales/fr.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Créez-le avec BotFather." }, + "proxy": { + "label": "Proxy réseau", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Utilisateurs autorisés", "placeholder": "* ou ID utilisateur Telegram", diff --git a/nanobot/channels/telegram/webui/locales/id.json b/nanobot/channels/telegram/webui/locales/id.json index 1dc37a25..9ef7a0aa 100644 --- a/nanobot/channels/telegram/webui/locales/id.json +++ b/nanobot/channels/telegram/webui/locales/id.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Buat dengan BotFather." }, + "proxy": { + "label": "Proxy jaringan", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Pengguna yang diizinkan", "placeholder": "* atau ID pengguna Telegram", diff --git a/nanobot/channels/telegram/webui/locales/ja.json b/nanobot/channels/telegram/webui/locales/ja.json index fe56e052..9bd3d054 100644 --- a/nanobot/channels/telegram/webui/locales/ja.json +++ b/nanobot/channels/telegram/webui/locales/ja.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "BotFather で作成します。" }, + "proxy": { + "label": "ネットワークプロキシ", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "許可するユーザー", "placeholder": "* または Telegram ユーザー ID", diff --git a/nanobot/channels/telegram/webui/locales/ko.json b/nanobot/channels/telegram/webui/locales/ko.json index 709d17ff..3404f32a 100644 --- a/nanobot/channels/telegram/webui/locales/ko.json +++ b/nanobot/channels/telegram/webui/locales/ko.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "BotFather에서 생성하세요." }, + "proxy": { + "label": "네트워크 프록시", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "허용된 사용자", "placeholder": "* 또는 Telegram 사용자 ID", diff --git a/nanobot/channels/telegram/webui/locales/pt-BR.json b/nanobot/channels/telegram/webui/locales/pt-BR.json index ef1537bc..9e806fc8 100644 --- a/nanobot/channels/telegram/webui/locales/pt-BR.json +++ b/nanobot/channels/telegram/webui/locales/pt-BR.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Crie-o com o BotFather." }, + "proxy": { + "label": "Proxy de rede", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Usuários permitidos", "placeholder": "* ou IDs de usuário do Telegram", diff --git a/nanobot/channels/telegram/webui/locales/vi.json b/nanobot/channels/telegram/webui/locales/vi.json index bb0a16bd..3e07a08a 100644 --- a/nanobot/channels/telegram/webui/locales/vi.json +++ b/nanobot/channels/telegram/webui/locales/vi.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "Tạo bằng BotFather." }, + "proxy": { + "label": "Proxy mạng", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "Người dùng được phép", "placeholder": "* hoặc ID người dùng Telegram", diff --git a/nanobot/channels/telegram/webui/locales/zh-CN.json b/nanobot/channels/telegram/webui/locales/zh-CN.json index 8498cac0..e75425b8 100644 --- a/nanobot/channels/telegram/webui/locales/zh-CN.json +++ b/nanobot/channels/telegram/webui/locales/zh-CN.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "使用 BotFather 创建。" }, + "proxy": { + "label": "网络代理", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "允许的用户", "placeholder": "* 或 Telegram 用户 ID", diff --git a/nanobot/channels/telegram/webui/locales/zh-TW.json b/nanobot/channels/telegram/webui/locales/zh-TW.json index bc4a1620..22a46a66 100644 --- a/nanobot/channels/telegram/webui/locales/zh-TW.json +++ b/nanobot/channels/telegram/webui/locales/zh-TW.json @@ -17,6 +17,10 @@ "placeholder": "123456:ABC...", "help": "使用 BotFather 建立。" }, + "proxy": { + "label": "網路代理", + "placeholder": "http://127.0.0.1:7890" + }, "allowFrom": { "label": "允許的使用者", "placeholder": "* 或 Telegram 使用者 ID",