1"""Corrected operator-initiated Telegram setup flow.
5- oc_connect.connectors.telegram.destinations (DestinationStore, TelegramDestination - records the pinned operator destination)
6- oc_connect.connectors.telegram.transport (TelegramTransport - the default transport for update discovery and the test send)
7- oc_connect.credentials (CredentialBroker - stores the bot token inside a rollback transaction)
8- oc_connect.egress (require_safe - the default egress check on the test message)
9- oc_diagnostics (_dbg structured logger)
11from __future__
import annotations
14from collections.abc
import Callable
15from typing
import Any, Protocol
17from oc_diagnostics
import _dbg
19from ...credentials
import CredentialBroker
20from ...egress
import require_safe
21from .destinations
import DestinationStore, TelegramDestination
22from .transport
import TelegramTransport
24TELEGRAM_CREDENTIAL_NAME =
"telegram-operator-bot"
25TEST_MESSAGE =
"OC Connect is live."
29 """The operator cancelled setup; all local writes were rolled back."""
33 """Setup could not identify a valid operator-initiated update."""
37 """The two Bot API methods needed during setup."""
41 def send_message(self, chat_id: str, text: str) -> dict[str, Any]: ...
51 """Capture, pin, test, and commit one operator destination atomically."""
56 broker: CredentialBroker,
57 destinations: DestinationStore,
58 transport_factory: Callable[[str], SetupTransport] = _default_transport,
59 token_reader: Callable[[str], str] = getpass.getpass,
60 input_reader: Callable[[str], str] = input,
61 output: Callable[[str],
None] =
print,
62 egress_check: Callable[[str],
None] = require_safe,
75 "Telegram bot token (input hidden, Enter to cancel): "
77 except (EOFError, KeyboardInterrupt):
78 raise SetupCancelled(
"Telegram setup cancelled at credential capture")
from None
79 if raw_value
is None or not raw_value.strip():
80 raise SetupCancelled(
"Telegram setup cancelled at credential capture")
81 return raw_value.strip()
86 except (EOFError, KeyboardInterrupt):
91 for update
in reversed(updates):
92 message = update.get(
"message")
93 if not isinstance(message, dict):
95 account = message.get(
"from")
96 chat = message.get(
"chat")
97 if not isinstance(account, dict)
or not isinstance(chat, dict):
99 if account.get(
"is_bot")
is True:
101 account_id = account.get(
"id")
102 chat_id = chat.get(
"id")
103 if account_id
is not None and chat_id
is not None:
104 return str(account_id), str(chat_id)
106 "no operator-authored Telegram message was found; message the bot and retry"
109 def run(self) -> TelegramDestination:
110 """Run setup, committing local state only after the test send succeeds."""
112 "Create the bot with BotFather, then provide its token. The bot cannot "
113 "start a conversation or create a group."
116 with self.
broker.transaction():
117 self.
broker.store(TELEGRAM_CREDENTIAL_NAME, raw_value)
121 "Now message the bot from your operator account. A private direct "
122 "chat is the Phase 1 default."
125 "Press Enter after sending the message, or type cancel: "
127 if answer.lower() ==
"cancel":
131 f
"Found operator account {account_id} in chat {chat_id}."
133 confirmed = self.
_answer(
"Pin this account and chat? [y/N]: ")
134 if confirmed.lower()
not in (
"y",
"yes"):
137 transport.send_message(chat_id, TEST_MESSAGE)
141 operator_account_id=account_id,
142 credential_name=TELEGRAM_CREDENTIAL_NAME,
145 "OC_CONNECT.TELEGRAM",
147 f
"setup complete destination={destination.name} "
148 f
"account={account_id} chat={chat_id}",
158 "TELEGRAM_CREDENTIAL_NAME",
161 "TelegramSetupError",
dict[str, Any] send_message(self, str chat_id, str text)
list[dict[str, Any]] get_updates(self)
None __init__(self, *, CredentialBroker broker, DestinationStore destinations, Callable[[str], SetupTransport] transport_factory=_default_transport, Callable[[str], str] token_reader=getpass.getpass, Callable[[str], str] input_reader=input, Callable[[str], None] output=print, Callable[[str], None] egress_check=require_safe)
str _answer(self, str prompt)
TelegramDestination run(self)
tuple[str, str] _operator_update(list[dict[str, Any]] updates)
TelegramTransport _default_transport(str raw_value)