frist
This commit is contained in:
197
internal/telegram/bot.go
Normal file
197
internal/telegram/bot.go
Normal file
@@ -0,0 +1,197 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user