91 lines
2.9 KiB
Go
91 lines
2.9 KiB
Go
package telegram
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
|
)
|
|
|
|
// Defaults and knob values for the Telegram Bot API integration.
|
|
const (
|
|
// defaultBaseURL is the Telegram API root for unconfigured adapters.
|
|
defaultBaseURL = "https://api.telegram.org"
|
|
// httpTimeout bounds a single Bot API request.
|
|
httpTimeout = 180 * time.Second
|
|
// longPollTimeoutSec is the getUpdates long-poll timeout in seconds.
|
|
longPollTimeoutSec = 50
|
|
// pollInterval is how long to sleep between getUpdates batches.
|
|
pollInterval = 1 * time.Second
|
|
// maxUpdatesPerPoll caps the number of updates fetched in one getUpdates call.
|
|
maxUpdatesPerPoll = 100
|
|
// maxMessageLen is Telegram's per-message text limit.
|
|
maxMessageLen = 4096
|
|
)
|
|
|
|
// Config holds the settings required to reach the Telegram Bot API.
|
|
type Config struct {
|
|
// BotToken is the secret token issued by BotFather. It is stored and
|
|
// surfaced through Config() but never written to logs.
|
|
BotToken string `json:"bot_token"`
|
|
// WebhookURL is the public HTTPS endpoint Telegram delivers updates to.
|
|
// When empty the adapter uses getUpdates long polling instead.
|
|
WebhookURL string `json:"webhook_url"`
|
|
}
|
|
|
|
// Option customizes an Adapter at construction time.
|
|
type Option func(*Adapter)
|
|
|
|
// WithHTTPClient injects the *http.Client used for Bot API requests. Tests pass
|
|
// the client of an httptest.Server so the mock endpoint answers calls.
|
|
func WithHTTPClient(c *http.Client) Option {
|
|
return func(a *Adapter) { a.client = c }
|
|
}
|
|
|
|
// WithBaseURL overrides the Telegram API root (default https://api.telegram.org).
|
|
// Tests point it at an httptest.Server URL.
|
|
func WithBaseURL(base string) Option {
|
|
return func(a *Adapter) {
|
|
if base != "" {
|
|
a.baseURL = base
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithLogger injects the logger used by the adapter. When nil the adapter logs
|
|
// through a discard logger so a bare adapter stays silent; callers wire their
|
|
// production logger via this option.
|
|
func WithLogger(l *slog.Logger) Option {
|
|
return func(a *Adapter) {
|
|
if l == nil {
|
|
l = logger.New(logger.Options{Out: io.Discard})
|
|
}
|
|
a.log = logger.WithSource(l, "plugins/channels/telegram")
|
|
}
|
|
}
|
|
|
|
// New builds a Telegram adapter. It performs no network I/O; call Connect to
|
|
// establish the session.
|
|
func New(cfg Config, opts ...Option) *Adapter {
|
|
a := &Adapter{
|
|
cfg: cfg,
|
|
client: &http.Client{Timeout: httpTimeout},
|
|
baseURL: defaultBaseURL,
|
|
log: logger.WithSource(logger.New(logger.Options{Out: io.Discard}), "plugins/channels/telegram"),
|
|
chats: map[string]adapter.ChannelInfo{},
|
|
}
|
|
for _, opt := range opts {
|
|
opt(a)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// endpoint returns the absolute Bot API URL for the given method, bundling the
|
|
// token into the path (never into query parameters or logs).
|
|
func (a *Adapter) endpoint(method string) string {
|
|
return a.baseURL + "/bot" + a.cfg.BotToken + "/" + method
|
|
}
|