Documentation
¶
Overview ¶
The templates in this file are MarkdownV2 (bold is *single asterisks*, italic is _underscores_). Render them exclusively with RenderMarkdownV2 so every data value is escaped; the fullwidth punctuation (!:。,) in the static text needs no escaping because MarkdownV2 only reserves ASCII syntax characters.
Index ¶
- Constants
- func EscapeMarkdownV2(s string) string
- func RenderMarkdownV2(tpl string, data map[string]string) (string, error)
- func TicketTopicTitle(t *ticket.Ticket) string
- type Command
- type TelegramAdmin
- type TelegramAdminActionStore
- type TelegramAdminDependencies
- type TelegramAdminHandler
- type TelegramCommandRegistrar
- type TelegramLogic
- type TelegramLogicDependencies
- type TelegramMessenger
- type TelegramRedisStore
- type TelegramRelayLimiter
- type TelegramSessionStore
- type TelegramTopicClient
- type TopicService
- func (s *TopicService) Close(topic *telegramtopic.Topic) error
- func (s *TopicService) Ensure(kind uint8, refID int64, title string) (topic *telegramtopic.Topic, created bool, err error)
- func (s *TopicService) PostMarkdown(m TelegramMessenger, topic *telegramtopic.Topic, text string) (*telegramtopic.Topic, error)
- func (s *TopicService) PostText(m TelegramMessenger, topic *telegramtopic.Topic, text string) (*telegramtopic.Topic, error)
- func (s *TopicService) Recreate(topic *telegramtopic.Topic) (*telegramtopic.Topic, error)
- func (s *TopicService) Relay(topic *telegramtopic.Topic, op func(threadID int64) error) (*telegramtopic.Topic, error)
- func (s *TopicService) Reopen(topic *telegramtopic.Topic) (*telegramtopic.Topic, error)
- func (s *TopicService) TicketCreated(m TelegramMessenger, t *ticket.Ticket, userLabel string) error
- func (s *TopicService) TicketReplied(m TelegramMessenger, ticketID int64, from, content string) error
- func (s *TopicService) TicketStatusChanged(ticketID int64, status uint8) error
Constants ¶
const AdminOrderDaily = `` /* 309-byte string literal not displayed */
AdminOrderDaily 管理员每日订单统计
const AdminOrderNotify = `` /* 331-byte string literal not displayed */
AdminOrderNotify 管理员订单通知
const BindNotify = `` /* 238-byte string literal not displayed */
const NotifyTopicTitle = "📣 运营通知"
NotifyTopicTitle names the operations feed topic the bot creates in the administrators' group.
const PurchaseNotify = `` /* 370-byte string literal not displayed */
const RechargeNotify = `` /* 349-byte string literal not displayed */
RechargeNotify 充值通知
const RenewalNotify = `` /* 370-byte string literal not displayed */
const ResetTrafficNotify = `` /* 333-byte string literal not displayed */
ResetTrafficNotify 重置流量通知
const SubscribeExpireNotify = `` /* 283-byte string literal not displayed */
SubscribeExpireNotify 订阅到期通知
const UnbindNotify = `` /* 353-byte string literal not displayed */
UnbindNotify 解绑通知
Variables ¶
This section is empty.
Functions ¶
func EscapeMarkdownV2 ¶ added in v1.15.18
EscapeMarkdownV2 returns s with all MarkdownV2 syntax characters escaped, so arbitrary data renders as literal text.
func RenderMarkdownV2 ¶ added in v1.15.18
RenderMarkdownV2 renders a message template after escaping every data value. Templates own their formatting; data is always literal text. This is the only correct way to build MarkdownV2 messages from dynamic data: one unescaped '.' or '-' in an order number makes Telegram reject the whole message.
func TicketTopicTitle ¶ added in v1.15.18
TicketTopicTitle names a ticket topic; the leading number keeps the topic list scannable next to /tk.
Types ¶
type Command ¶ added in v1.15.16
Command describes one entry of the bot's command menu. It mirrors Telegram's BotCommand without binding this package to the bot library.
func AdminCommands ¶ added in v1.15.16
func AdminCommands() []Command
AdminCommands is the menu published to the administrators of the admin group (chat_administrators scope), where the administrator commands are the only place they work. It deliberately excludes /confirm_* and /cancel_* because those carry a generated action id and are only ever offered inline.
func PublicCommands ¶ added in v1.15.16
func PublicCommands() []Command
PublicCommands is the menu every user gets: account binding, plus the help entry that makes the administrator surface discoverable to the people who have access to it. Handlers still authenticate, so a non-administrator only learns that the command exists.
type TelegramAdmin ¶
TelegramAdmin handles administrative Telegram commands independently from the general Telegram bot flow.
func NewTelegramAdmin ¶
func NewTelegramAdmin(ctx context.Context, deps TelegramAdminDependencies) *TelegramAdmin
func (*TelegramAdmin) Handle ¶
func (a *TelegramAdmin) Handle(msg *models.Message)
Handle runs an administrator command.
type TelegramAdminActionStore ¶
type TelegramAdminActionStore interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key, value string, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
TelegramAdminActionStore persists short-lived confirmations for destructive administrator commands.
type TelegramAdminDependencies ¶
type TelegramAdminDependencies struct {
Messenger TelegramMessenger
Actions TelegramAdminActionStore
// MirrorTicketStatus and MirrorTicketReply keep the ticket's forum
// topic in step with ticket mutations done through bot commands
// (/close, /reopen, /rp), which bypass the support module's notifier.
// Optional and best-effort.
MirrorTicketStatus func(ticketID int64, status uint8)
MirrorTicketReply func(ticketID int64, content string)
Tickets repository.TicketRepo
Orders repository.OrderRepo
Users repository.UserRepo
UserAuth repository.UserAuthRepo
Subscriptions repository.UserSubscriptionRepo
UserCache repository.UserCacheRepo
Plans repository.SubscribeRepo
Logs repository.LogRepo
// Wallet is the billing-domain read port for balance display.
Wallet repository.WalletRepo
}
TelegramAdminDependencies contains only the collaborators used by Telegram administrator commands. It intentionally does not accept ServiceContext.
type TelegramAdminHandler ¶
TelegramAdminHandler handles administrator Telegram commands.
type TelegramCommandRegistrar ¶ added in v1.15.16
type TelegramCommandRegistrar interface {
SetCommands(chatID int64, commands []Command) error
SetGroupAdminCommands(chatID int64, commands []Command) error
}
TelegramCommandRegistrar publishes the command menu Telegram shows in the message composer. A zero chatID addresses the default scope, which every user sees; a non-zero chatID scopes the menu to that chat. SetGroupAdminCommands scopes the menu to a group's administrators, which is how the administrator commands stay invisible to ordinary members.
func NewTelegramBotCommandRegistrar ¶ added in v1.15.16
func NewTelegramBotCommandRegistrar(bot *tgbot.Bot) TelegramCommandRegistrar
NewTelegramBotCommandRegistrar adapts a Telegram Bot API client to the command-menu port.
type TelegramLogic ¶
func NewTelegramLogic ¶
func NewTelegramLogic(ctx context.Context, deps TelegramLogicDependencies) *TelegramLogic
func (*TelegramLogic) TelegramLogic ¶
func (l *TelegramLogic) TelegramLogic(req *models.Update)
TelegramLogic routes one update. User commands live in the private chat; everything administrative — commands, support topics, ticket topics — lives in the configured admin group. Messages from other groups are ignored entirely.
type TelegramLogicDependencies ¶
type TelegramLogicDependencies struct {
Messenger TelegramMessenger
Sessions TelegramSessionStore
UserAuth repository.UserAuthRepo
UserCache repository.UserCacheRepo
Admin TelegramAdminHandler
// GroupChatID returns the validated admin group; zero disables group
// routing. Read per call because re-initialisation may change it.
GroupChatID func() int64
Topics repository.TelegramTopicRepo
TopicClient TelegramTopicClient
Tickets repository.TicketRepo
Users repository.UserRepo
Limiter TelegramRelayLimiter
}
TelegramLogicDependencies explicitly declares the collaborators used by update routing: user command dispatch and account binding in the private chat, and the group-side relays. The group fields may be left zero, which turns every group feature off.
type TelegramMessenger ¶
type TelegramMessenger interface {
Send(chatID, threadID int64, message string) error
SendMarkdown(chatID, threadID int64, message string) error
}
TelegramMessenger sends a response to a Telegram chat. A non-zero threadID addresses one forum topic inside a group; zero addresses the chat itself (private chats, or a group's General topic). Send delivers plain text; SendMarkdown delivers MarkdownV2 produced by RenderMarkdownV2, and must never receive unescaped dynamic data — Telegram rejects the whole message over one stray reserved character.
func NewTelegramBotMessenger ¶
func NewTelegramBotMessenger(bot *tgbot.Bot) TelegramMessenger
NewTelegramBotMessenger adapts a Telegram Bot API client to the command messenger port.
type TelegramRedisStore ¶
type TelegramRedisStore interface {
TelegramSessionStore
TelegramAdminActionStore
TelegramRelayLimiter
}
TelegramRedisStore supports account-binding sessions, administrator command confirmations, and the support relay rate limit.
func NewTelegramRedisStore ¶
func NewTelegramRedisStore(client *redis.Client) TelegramRedisStore
NewTelegramRedisStore adapts Redis to the binding-session and administrator confirmation ports.
type TelegramRelayLimiter ¶ added in v1.15.18
type TelegramRelayLimiter interface {
// Allow reports whether the chat may relay another message right now.
// shouldNotify is true only for the first rejection of a window, so a
// flood is answered with one notice instead of one per message.
Allow(ctx context.Context, chatID int64) (allowed, shouldNotify bool)
}
TelegramRelayLimiter caps how fast one chat may relay support messages into the admin group.
type TelegramSessionStore ¶
type TelegramSessionStore interface {
Get(ctx context.Context, key string) (string, error)
Delete(ctx context.Context, key string) error
}
TelegramSessionStore reads and consumes short-lived account-binding tokens. Deleting the token on a successful bind is what makes the deep link single-use.
type TelegramTopicClient ¶ added in v1.15.18
type TelegramTopicClient interface {
// ValidateAdminGroup confirms the chat is a forum-enabled supergroup
// where the bot may manage topics.
ValidateAdminGroup(ctx context.Context, chatID int64) error
CreateTopic(ctx context.Context, chatID int64, name string) (int64, error)
DeleteTopic(ctx context.Context, chatID, threadID int64) error
CloseTopic(ctx context.Context, chatID, threadID int64) error
ReopenTopic(ctx context.Context, chatID, threadID int64) error
// ForwardToThread relays a user's message into a group topic keeping
// the sender identity visible to the support staff.
ForwardToThread(ctx context.Context, chatID, threadID, fromChatID int64, messageID int) error
// CopyTo relays a group message to a private chat without exposing
// which administrator wrote it.
CopyTo(ctx context.Context, toChatID, fromChatID int64, messageID int) error
}
TelegramTopicClient is the group-side Bot API surface the topic layer drives. telegramBotMessenger implements it; tests fake it.
func NewTelegramTopicClient ¶ added in v1.15.18
func NewTelegramTopicClient(bot *tgbot.Bot) TelegramTopicClient
NewTelegramTopicClient adapts the bot client to the topic-management port.
type TopicService ¶ added in v1.15.18
TopicService owns the mapping between forum topics and the conversation each carries.
func NewTopicService ¶ added in v1.15.18
func NewTopicService(ctx context.Context, client TelegramTopicClient, topics repository.TelegramTopicRepo, group int64) *TopicService
func (*TopicService) Close ¶ added in v1.15.18
func (s *TopicService) Close(topic *telegramtopic.Topic) error
Close closes the forum topic and marks the mapping; a topic already gone from Telegram still gets its mapping closed.
func (*TopicService) Ensure ¶ added in v1.15.18
func (s *TopicService) Ensure(kind uint8, refID int64, title string) (topic *telegramtopic.Topic, created bool, err error)
Ensure returns the mapping for (kind, ref), creating the forum topic on first use; created reports whether this call made it. Concurrent creators race on the unique key; the loser adopts the winner's row.
func (*TopicService) PostMarkdown ¶ added in v1.15.18
func (s *TopicService) PostMarkdown(m TelegramMessenger, topic *telegramtopic.Topic, text string) (*telegramtopic.Topic, error)
PostMarkdown sends MarkdownV2 into a topic through Relay's self-healing.
func (*TopicService) PostText ¶ added in v1.15.18
func (s *TopicService) PostText(m TelegramMessenger, topic *telegramtopic.Topic, text string) (*telegramtopic.Topic, error)
PostText is PostMarkdown for plain text.
func (*TopicService) Recreate ¶ added in v1.15.18
func (s *TopicService) Recreate(topic *telegramtopic.Topic) (*telegramtopic.Topic, error)
Recreate repoints a mapping whose topic was deleted inside Telegram.
func (*TopicService) Relay ¶ added in v1.15.18
func (s *TopicService) Relay(topic *telegramtopic.Topic, op func(threadID int64) error) (*telegramtopic.Topic, error)
Relay runs op against the topic's thread, transparently recreating a deleted topic or reopening a closed one, then retrying once. It returns the mapping actually used, which may have been repointed.
func (*TopicService) Reopen ¶ added in v1.15.18
func (s *TopicService) Reopen(topic *telegramtopic.Topic) (*telegramtopic.Topic, error)
Reopen reopens a closed topic and marks the mapping active. A missing topic is recreated instead.
func (*TopicService) TicketCreated ¶ added in v1.15.18
func (s *TopicService) TicketCreated(m TelegramMessenger, t *ticket.Ticket, userLabel string) error
TicketCreated opens the ticket's topic and posts its opening message. Only tickets created after the group went live get a topic; older tickets have no mapping and their events are skipped upstream.
func (*TopicService) TicketReplied ¶ added in v1.15.18
func (s *TopicService) TicketReplied(m TelegramMessenger, ticketID int64, from, content string) error
TicketReplied posts a website-side reply into the ticket's topic. A ticket without a mapping predates the group and is silently skipped.
func (*TopicService) TicketStatusChanged ¶ added in v1.15.18
func (s *TopicService) TicketStatusChanged(ticketID int64, status uint8) error
TicketStatusChanged mirrors a website-side status change onto the topic: closing the ticket closes the topic, any other status reopens it.