198 lines
4.1 KiB
Go
198 lines
4.1 KiB
Go
package telegram
|
||
|
||
import (
|
||
"context"
|
||
"log/slog"
|
||
"sync"
|
||
"time"
|
||
|
||
"proxyrotator/internal/config"
|
||
"proxyrotator/internal/store"
|
||
|
||
tele "gopkg.in/telebot.v3"
|
||
)
|
||
|
||
// Bot Telegram Bot 管理器
|
||
type Bot struct {
|
||
mu sync.RWMutex
|
||
bot *tele.Bot
|
||
cfg *config.Config
|
||
store store.ProxyStore
|
||
|
||
scheduler *Scheduler
|
||
notifier *Notifier
|
||
|
||
running bool
|
||
stopChan chan struct{}
|
||
}
|
||
|
||
// Status Bot 状态
|
||
type Status struct {
|
||
Running bool `json:"running"`
|
||
Connected bool `json:"connected"`
|
||
Username string `json:"username,omitempty"`
|
||
}
|
||
|
||
// NewBot 创建 Bot 实例
|
||
func NewBot(cfg *config.Config, proxyStore store.ProxyStore) *Bot {
|
||
return &Bot{
|
||
cfg: cfg,
|
||
store: proxyStore,
|
||
stopChan: make(chan struct{}),
|
||
}
|
||
}
|
||
|
||
// Start 启动 Bot
|
||
func (b *Bot) Start(ctx context.Context) error {
|
||
b.mu.Lock()
|
||
defer b.mu.Unlock()
|
||
|
||
if b.running {
|
||
return nil
|
||
}
|
||
|
||
if b.cfg.TelegramBotToken == "" {
|
||
slog.Info("telegram bot token not configured, skipping")
|
||
return nil
|
||
}
|
||
|
||
return b.startInternal()
|
||
}
|
||
|
||
// startInternal 内部启动(需要持有锁)
|
||
func (b *Bot) startInternal() error {
|
||
pref := tele.Settings{
|
||
Token: b.cfg.TelegramBotToken,
|
||
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
|
||
}
|
||
|
||
bot, err := tele.NewBot(pref)
|
||
if err != nil {
|
||
slog.Error("failed to create telegram bot", "error", err)
|
||
return err
|
||
}
|
||
|
||
b.bot = bot
|
||
b.notifier = NewNotifier(bot, b.cfg.TelegramNotifyChatID)
|
||
b.scheduler = NewScheduler(b.store, b.notifier, b.cfg)
|
||
|
||
// 注册命令处理器
|
||
b.registerCommands(b.cfg.TelegramAdminIDs)
|
||
|
||
// 启动调度器
|
||
b.scheduler.Start()
|
||
|
||
// 注册命令菜单
|
||
commands := []tele.Command{
|
||
{Text: "stats", Description: "查看代理池统计"},
|
||
{Text: "groups", Description: "查看分组统计"},
|
||
{Text: "get", Description: "获取可用代理 (默认1个,如 /get 5)"},
|
||
{Text: "import", Description: "导入代理 (如 /import groupname)"},
|
||
{Text: "test", Description: "触发测活 (如 /test groupname)"},
|
||
{Text: "purge", Description: "清理死代理"},
|
||
{Text: "help", Description: "显示帮助信息"},
|
||
}
|
||
if err := bot.SetCommands(commands); err != nil {
|
||
slog.Warn("failed to set bot commands", "error", err)
|
||
}
|
||
|
||
// 启动 Bot
|
||
b.stopChan = make(chan struct{})
|
||
go func() {
|
||
slog.Info("telegram bot started", "username", bot.Me.Username)
|
||
bot.Start()
|
||
}()
|
||
|
||
b.running = true
|
||
return nil
|
||
}
|
||
|
||
// Stop 停止 Bot
|
||
func (b *Bot) Stop() {
|
||
b.mu.Lock()
|
||
defer b.mu.Unlock()
|
||
|
||
b.stopInternal()
|
||
}
|
||
|
||
// stopInternal 内部停止(需要持有锁)
|
||
func (b *Bot) stopInternal() {
|
||
if !b.running {
|
||
return
|
||
}
|
||
|
||
if b.scheduler != nil {
|
||
b.scheduler.Stop()
|
||
}
|
||
|
||
if b.bot != nil {
|
||
b.bot.Stop()
|
||
slog.Info("telegram bot stopped")
|
||
}
|
||
|
||
close(b.stopChan)
|
||
b.running = false
|
||
}
|
||
|
||
// Status 获取 Bot 状态
|
||
func (b *Bot) Status() Status {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
|
||
status := Status{
|
||
Running: b.running,
|
||
}
|
||
|
||
if b.bot != nil && b.running {
|
||
status.Connected = true
|
||
status.Username = b.bot.Me.Username
|
||
}
|
||
|
||
return status
|
||
}
|
||
|
||
// TriggerTest 手动触发测活
|
||
func (b *Bot) TriggerTest(ctx context.Context) error {
|
||
b.mu.RLock()
|
||
defer b.mu.RUnlock()
|
||
|
||
if b.scheduler == nil {
|
||
return nil
|
||
}
|
||
|
||
return b.scheduler.RunTest(ctx)
|
||
}
|
||
|
||
// registerCommands 注册命令
|
||
func (b *Bot) registerCommands(adminIDs []int64) {
|
||
// 管理员权限中间件
|
||
adminOnly := func(next tele.HandlerFunc) tele.HandlerFunc {
|
||
return func(c tele.Context) error {
|
||
if len(adminIDs) == 0 {
|
||
return next(c)
|
||
}
|
||
userID := c.Sender().ID
|
||
for _, id := range adminIDs {
|
||
if id == userID {
|
||
return next(c)
|
||
}
|
||
}
|
||
return c.Send("⛔ 无权限访问")
|
||
}
|
||
}
|
||
|
||
// 创建命令处理器
|
||
cmds := NewCommands(b.store, b.scheduler)
|
||
|
||
b.bot.Handle("/start", adminOnly(cmds.HandleStart))
|
||
b.bot.Handle("/help", adminOnly(cmds.HandleHelp))
|
||
b.bot.Handle("/stats", adminOnly(cmds.HandleStats))
|
||
b.bot.Handle("/groups", adminOnly(cmds.HandleGroups))
|
||
b.bot.Handle("/get", adminOnly(cmds.HandleGet))
|
||
b.bot.Handle("/test", adminOnly(cmds.HandleTest))
|
||
b.bot.Handle("/purge", adminOnly(cmds.HandlePurge))
|
||
b.bot.Handle("/import", adminOnly(cmds.HandleImport))
|
||
b.bot.Handle(tele.OnDocument, adminOnly(cmds.HandleDocument))
|
||
b.bot.Handle(tele.OnText, adminOnly(cmds.HandleText))
|
||
}
|