Documentation
¶
Overview ¶
Package telegram implements a Telegram bot webhook handler that provides read-only commands (/price, /portfolio, /positions, /orders, /pnl, /alerts, /prices, /mywatchlist, /status, /help) and trading commands (/buy, /sell, /quick, /setalert) with inline-keyboard confirmation for registered users.
Index ¶
- Constants
- type AlertLookup
- type BotHandler
- func (h *BotHandler) CleanupNow()
- func (h *BotHandler) RegisterCommand(name string, fn PluginCommandHandler) error
- func (h *BotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (h *BotHandler) SetTradingEnabled(enabled bool)
- func (h *BotHandler) Shutdown()
- func (h *BotHandler) TradingEnabled() bool
- type InstrumentLookup
- type KiteClientFactory
- type KiteManager
- type PaperEngineLookup
- type PluginCommandContext
- type PluginCommandHandler
- type TelegramLookup
- type TickerLookup
- type WatchlistLookup
Constants ¶
const DisclaimerFullText = `` /* 699-byte string literal not displayed */
DisclaimerFullText is returned by the /disclaimer command. Mirrors the classification-statement excerpt from TERMS.md §3 so users always see the same wording whether they read the website or the bot.
const DisclaimerPrefix = "\u26A0\uFE0F <i>Not investment advice. Kite MCP is a tool, not an advisor.</i>\n\n"
DisclaimerPrefix is the short banner prepended to every financial outbound Telegram message. Keep it short (one visible line) so it doesn't dominate the chat surface, but unambiguous.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AlertLookup ¶
type AlertLookup interface {
Add(email, tradingsymbol, exchange string, instrumentToken uint32, targetPrice float64, direction alerts.Direction) (string, error)
AddWithReferencePrice(email, tradingsymbol, exchange string, instrumentToken uint32, targetPrice float64, direction alerts.Direction, referencePrice float64) (string, error)
List(email string) []*alerts.Alert
ActiveCount(email string) int
}
AlertLookup is the narrow port over the alert store's CRUD surface used by /alerts, /setalert, /status. Locally-declared (rather than imported from kc) so the package stays free of an upward dependency on the parent kc package; *alerts.Store satisfies it structurally, as does kc.AlertStoreInterface.
type BotHandler ¶
type BotHandler struct {
// contains filtered or unexported fields
}
BotHandler handles incoming Telegram webhook updates and routes them to the appropriate command handler. It enforces that only private chats from registered users are served, with per-chat rate limiting.
func NewBotHandler ¶
func NewBotHandler(bot alerts.BotAPI, webhookSecret string, manager KiteManager, logger *slog.Logger, factory KiteClientFactory) *BotHandler
NewBotHandler creates a new BotHandler and starts a background goroutine that periodically prunes stale rate-limit and pending-order entries. A nil factory disables trading commands (getClient returns an error).
tradingEnabled defaults to *true* for back-compat with local/single-user builds and the existing test suite. The hosted multi-user server constructs this and then calls SetTradingEnabled(false) from app.Config.EnableTrading so /buy, /sell, /quick are gated consistently with the MCP tool gating.
func (*BotHandler) CleanupNow ¶
func (h *BotHandler) CleanupNow()
CleanupNow triggers an immediate cleanup of stale rate-limit and pending-order entries. Exposed for testing the cleanup logic without waiting for the background ticker.
func (*BotHandler) RegisterCommand ¶
func (h *BotHandler) RegisterCommand(name string, fn PluginCommandHandler) error
RegisterCommand installs a plugin handler for the given /command. Returns an error when:
- name is empty or doesn't start with "/";
- name contains whitespace (commands are single tokens);
- name collides with a built-in (reservedCommands).
Registering the same name twice is allowed — last-wins semantics — which matches a plugin's likely lifecycle (re-registration on a scheduler restart or a config-reload).
Rationale for "built-in wins" precedence:
- Built-in commands carry rate-limiting, tier gating, and confirmation flows that plugins can't replicate correctly. Letting a plugin shadow /buy would bypass riskguard. Hard no.
- Plugins that want to CO-OPERATE with a built-in (e.g. "run a metric after /buy") use OnAfterToolExecution-equivalent hooks at the MCP layer, not the Telegram bot layer.
func (*BotHandler) ServeHTTP ¶
func (h *BotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler. It validates the request, parses the Telegram update, and dispatches to the appropriate command handler.
func (*BotHandler) SetTradingEnabled ¶
func (h *BotHandler) SetTradingEnabled(enabled bool)
SetTradingEnabled toggles the /buy, /sell, /quick Telegram commands on or off. Called once at wire-up from app.Config.EnableTrading so the Telegram surface matches the MCP tool gating (Path 2 compliance — NSE/INVG/69255 Annexure I Para 2.8 Algo Provider classification).
func (*BotHandler) Shutdown ¶
func (h *BotHandler) Shutdown()
Shutdown stops the background cleanup goroutine. It is safe to call multiple times.
func (*BotHandler) TradingEnabled ¶
func (h *BotHandler) TradingEnabled() bool
TradingEnabled reports the current trading-gate state. Exported so tests and admin tools can read it without reaching into unexported fields.
type InstrumentLookup ¶
InstrumentLookup is the narrow port over the instruments manager used by /setalert (resolves "NSE:SYMBOL" → instrument token). Returns the upstream concrete instruments.Instrument value type.
type KiteClientFactory ¶
type KiteClientFactory = zerodha.KiteClientFactory
KiteClientFactory creates Kite API clients. Type alias to the canonical declaration in broker/zerodha — kc/telegram cannot import kc directly (would cycle through kc → kc/telegram), but broker/zerodha is a leaf package safely importable from anywhere. Returns the hexagonal zerodha.KiteSDK port rather than the concrete SDK client so trading commands can be exercised off-HTTP with zerodha.MockKiteSDK.
F4 consolidation (Phase B/D close-out): was a duplicate 2-method interface declaration; aliased to broker/zerodha.KiteClientFactory to eliminate drift risk while preserving the package-local name + existing callsites unchanged. See broker/zerodha/sdk_adapter.go for the canonical declaration.
type KiteManager ¶
type KiteManager interface {
TelegramStore() TelegramLookup
AlertStore() AlertLookup
WatchlistStore() WatchlistLookup
GetAPIKeyForEmail(email string) string
GetAccessTokenForEmail(email string) string
TelegramNotifier() *alerts.TelegramNotifier
InstrumentsManager() InstrumentLookup
IsTokenValid(email string) bool
// Trading support — riskguard, paper trading, ticker.
RiskGuard() *riskguard.Guard
PaperEngine() PaperEngineLookup
TickerService() TickerLookup
}
KiteManager abstracts the kc.Manager methods needed by the bot handler. Phase 3a kc/-side migration: store/service returns are now port-typed (AlertLookup, WatchlistLookup, InstrumentLookup, PaperEngineLookup, TickerLookup) instead of concrete pointer types — the prior `*alerts.Store`, `*watchlist.Store`, `*instruments.Manager`, `*papertrading.PaperEngine`, `*ticker.Service` returns leaked Concrete-pattern abstraction breaks across the package boundary. TelegramNotifier and RiskGuard remain on concrete types per their leaf-domain ownership (alerts.TelegramNotifier is the alert subsystem's exposed type; riskguard.Guard is the canonical Guard whose full method surface trading consumers depend on).
type PaperEngineLookup ¶
type PaperEngineLookup interface {
IsEnabled(email string) bool
PlaceOrder(email string, params map[string]any) (map[string]any, error)
}
PaperEngineLookup is the narrow port over the paper engine used by /buy, /sell, /quick to detect paper-trading mode and route confirmed orders through the paper-trading simulator. *papertrading.PaperEngine satisfies it structurally.
type PluginCommandContext ¶
type PluginCommandContext struct {
// ChatID is the Telegram chat the command came from. Private chats
// only; group/channel messages are filtered upstream by ServeHTTP.
ChatID int64
// Email is the registered user's email, resolved via TelegramLookup.
// Guaranteed non-empty by the time the plugin handler runs.
Email string
// Args is the raw argument text after the command token, stripped
// of surrounding whitespace. Empty when the user types just the
// command (e.g. "/echo" with no args).
Args string
}
PluginCommandContext is what a plugin-registered command handler receives when a user sends its command. It is intentionally narrow: chat ID, caller email, and the raw argument string (everything after the command token). Plugins that need to send rich responses can use the BotHandler reference on the caller side — PluginCommandContext only carries inputs.
type PluginCommandHandler ¶
type PluginCommandHandler func(ctx PluginCommandContext) string
PluginCommandHandler is the function signature a plugin implements to handle a /command. It returns the reply text (HTML-formatted; the bot will send it via sendHTML). An empty return string means "nothing to say" — useful when the plugin sends its own response out-of-band.
Safety contract: the bot's ServeHTTP recovers panics in plugin handlers and converts them into an error message to the user. Plugins SHOULD return errors via a formatted string rather than panicking, but the recovery net keeps one buggy plugin from crashing the bot for every other user.
type TelegramLookup ¶
TelegramLookup abstracts the Telegram chat-ID-to-email mapping needed by the bot. Separated from alerts per Single Responsibility Principle.
type TickerLookup ¶
type TickerLookup interface {
IsRunning(email string) bool
Subscribe(email string, tokens []uint32, mode brokerticker.Mode) error
}
TickerLookup is the narrow port over the ticker service used by /setalert to auto-subscribe instruments for real-time monitoring.
type WatchlistLookup ¶
type WatchlistLookup interface {
ListWatchlists(email string) []*watchlist.Watchlist
GetItems(watchlistID string) []*watchlist.WatchlistItem
}
WatchlistLookup is the narrow port over the watchlist store's read surface used by /mywatchlist. Returns are exactly the upstream concrete pointer slices so the call site needs no projection layer; *watchlist.Store satisfies it structurally.