telegram

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 15, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package telegram is a self-contained Telegram Bot API client plus the panel's admin bot: it long-polls for commands (view/add/remove users) and pushes scheduled backups to the linked admin chat. It deliberately depends only on the standard library — the Bot API is plain HTTP+JSON — so the panel keeps its minimal dependency surface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SendBackup

func SendBackup(ctx context.Context, client *Client, chats []int64, dataDir string,
	manifest backup.Manifest, checkpoint func() error, caption string) error

SendBackup checkpoints the DB, builds a fresh tar.gz of the data directory, and uploads it to each chat as a document. It's shared by the scheduler and the panel's "send test backup" action. A per-chat send failure doesn't abort the rest; the first error is returned.

func UserBotLink(botUsername string) string

UserBotLink is the public bot URL (open /start, no payload).

func UserDeepLink(botUsername, linkCode string) string

UserDeepLink builds a t.me link that binds an existing panel user via a one-time, short-lived bind code (see model.TelegramLinkCodeTTL).

Types

type CallbackQuery

type CallbackQuery struct {
	ID      string   `json:"id"`
	From    *User    `json:"from"`
	Message *Message `json:"message"`
	Data    string   `json:"data"`
}

CallbackQuery is an inline-button tap (used for the delete confirmation).

type Chat

type Chat struct {
	ID int64 `json:"id"`
}

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to one bot (identified by its token). It's cheap to construct and safe for use from the single bot loop plus the occasional handler-driven send.

func NewClient

func NewClient(token string) *Client

NewClient builds a client for the given bot token. The HTTP timeout comfortably exceeds the long-poll window so GetUpdates isn't cut off mid-wait.

func (*Client) AnswerCallback

func (c *Client) AnswerCallback(ctx context.Context, id, text string) error

AnswerCallback acknowledges a button tap so Telegram stops the spinner.

func (*Client) EditMenu

func (c *Client) EditMenu(ctx context.Context, chatID, messageID int64, html string, rows [][]InlineButton) error

EditMenu replaces the text + inline keyboard of an existing message, so the bot's menus navigate in place instead of stacking new messages.

func (*Client) GetMe

func (c *Client) GetMe(ctx context.Context) (*User, error)

GetMe returns the bot's own account (used to show its @username in the panel).

func (*Client) GetUpdates

func (c *Client) GetUpdates(ctx context.Context, offset int64, timeout int) ([]Update, error)

GetUpdates long-polls for new updates past offset, blocking up to timeout seconds. Only messages and callback queries are requested.

func (*Client) SendDocument

func (c *Client) SendDocument(ctx context.Context, chatID int64, filename, caption string, r io.Reader) error

SendDocument uploads a file to a chat as a document, with an optional caption.

func (*Client) SendMenu

func (c *Client) SendMenu(ctx context.Context, chatID int64, html string, rows [][]InlineButton) error

SendMenu sends an HTML message with an inline keyboard (rows of buttons). Pass a nil/empty rows for a plain message.

func (*Client) SendMessage

func (c *Client) SendMessage(ctx context.Context, chatID int64, html string) error

SendMessage sends a plain HTML-formatted message (no buttons). HTML parse mode lets the bot bold headers; all dynamic text must be escaped by the caller (esc).

func (*Client) SendPhoto

func (c *Client) SendPhoto(ctx context.Context, chatID int64, filename, caption string, r io.Reader) error

SendPhoto uploads an image to a chat (shown inline), with an optional HTML caption. Used to deliver the subscription QR code.

type InlineButton

type InlineButton struct {
	Text         string      `json:"text"`
	CallbackData string      `json:"callback_data,omitempty"`
	URL          string      `json:"url,omitempty"`     // URL button (mutually exclusive with callback_data)
	WebApp       *WebAppInfo `json:"web_app,omitempty"` // opens a Mini App in-place (private chats only)
}

InlineButton is one inline-keyboard button (label + callback payload).

type Message

type Message struct {
	MessageID int64  `json:"message_id"`
	From      *User  `json:"from"`
	Chat      Chat   `json:"chat"`
	Text      string `json:"text"`
}

Message is the subset of a Telegram message the bot acts on.

type Panel

type Panel interface {
	ListUsers() ([]model.User, error)
	CreateUser(ctx context.Context, name string, dataLimit, expireAt int64) (*model.User, error)
	DeleteUser(ctx context.Context, id int64) error
	SetUserEnabled(ctx context.Context, id int64, enabled bool) error
	ResetTraffic(ctx context.Context, id int64) error
	BackupManifest() backup.Manifest
	Location() *time.Location

	// Billing (no-op surface unless tariffs are enabled).
	ListTariffPlans(includeDisabled bool) ([]model.TariffPlan, error)
	ApplyPlanToUser(ctx context.Context, userID, planID int64, extendFromCurrent bool) error
	PlanName(planID int64) string
	RequestPlanPayment(ctx context.Context, userID, planID int64) (*model.PaymentOrder, string, error)
	// CreateRegisteredUser signs a new user up (active, for the open/invite modes).
	CreateRegisteredUser(ctx context.Context, name string) (*model.User, error)
	// RequestRegistration records a moderated signup (no user yet); ApproveRegistration
	// Request creates the user, RejectRegistrationRequest drops it.
	RequestRegistration(ctx context.Context, chatID int64, name string) (bool, error)
	RegistrationPending(chatID int64) bool
	ApproveRegistrationRequest(ctx context.Context, reqID int64) error
	RejectRegistrationRequest(ctx context.Context, reqID int64) error
	// ActivePaidPlan reports the user's active paid plan (nil = none), and
	// CancelUserPlan drops it to the free plan. Together they gate plan switching:
	// while a paid plan is active only renewal or cancellation is allowed.
	ActivePaidPlan(u model.User) *model.TariffPlan
	CancelUserPlan(ctx context.Context, userID int64) error

	// Automatic payment providers (no-op surface unless configured).
	PaymentMethods() []string
	ProviderLabel(key string) string
	StartPlanPayment(ctx context.Context, userID, planID int64, provider string) (*model.PaymentOrder, error)
	SetUserNotifier(fn func(chatID int64, html string))
	SetAdminNotifier(fn func(html string))
	SetAdminModerationNotifier(fn func(reqID int64, name, plan string))

	// Audit hooks for the actions the bots perform directly on the store.
	UnlinkUserTelegram(ctx context.Context, id int64) error
	AuditTelegramLinked(ctx context.Context, id int64, username string)
}

Panel is the slice of the core Manager the bot drives. Defining it here (rather than importing core) keeps the dependency one-way — core never imports telegram — and makes the bot trivially testable with a fake. Every mutating method takes a context: it carries the actor for the audit log, so a change made from a bot is attributed to the Telegram admin (or the VPN user) who made it rather than to "system". See the actor package.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service is the running bot: it long-polls for updates and pushes scheduled backups. The whole UI is inline buttons — the only text a user ever types is "/start <code>" to link, and (when prompted) a new user's name. It re-reads settings every cycle, so enabling/disabling, rotating the token, or changing the schedule all take effect without a restart.

func New

func New(panel Panel, st *store.Store, dataDir string) *Service

New builds the bot service. It does not start polling — call Run.

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run drives the bot until ctx is cancelled: it long-polls for updates and, in a sibling goroutine, fires scheduled backups. When the bot is disabled or has no token it idles, re-checking periodically.

type Update

type Update struct {
	UpdateID int64          `json:"update_id"`
	Message  *Message       `json:"message"`
	Callback *CallbackQuery `json:"callback_query"`
}

Update is one polled event: a message or a callback-query (inline button tap).

type User

type User struct {
	ID        int64  `json:"id"`
	Username  string `json:"username"`
	FirstName string `json:"first_name"`
}

User / Chat carry only the identifiers the bot needs.

type UserService added in v0.3.0

type UserService struct {
	// contains filtered or unexported fields
}

UserService is the public VPN user bot: open registration, personal subscription menu, and optional deep-link binding for accounts created in the panel.

func NewUser added in v0.3.0

func NewUser(panel Panel, st *store.Store) *UserService

NewUser builds the public user bot. Call Run to start polling.

func (*UserService) Run added in v0.3.0

func (s *UserService) Run(ctx context.Context)

Run long-polls the user bot until ctx is cancelled.

type WebAppInfo added in v0.11.0

type WebAppInfo struct {
	URL string `json:"url"`
}

WebAppInfo points a web_app button at an HTTPS page opened inside Telegram as a Mini App. Inline web_app buttons need no @BotFather domain setup, but work only in private chats and require an https:// URL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL