Files
MeBox/internal/service/telegram_commands.go
T
2026-06-10 20:43:42 +08:00

166 lines
11 KiB
Go

// Package service — Telegram command registry and dispatch.
package service
import (
"context"
"fmt"
"strings"
"time"
"github.com/ShukeBta/MediaStationGo/internal/model"
)
type telegramCommandHandler func(args []string) (telegramCommandReply, error)
type telegramCommandDefinition struct {
Aliases []string
AdminOnly bool
AdminOnlyText string
Handle telegramCommandHandler
}
func (s *TelegramBotService) telegramCommandDefinitions(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage) []telegramCommandDefinition {
adminOnly := "此命令仅管理员可用。"
return []telegramCommandDefinition{
{Aliases: []string{"/start"}, Handle: func(args []string) (telegramCommandReply, error) {
if len(args) == 0 {
return s.mainMenu(ctx, channel, msg), nil
}
return s.cmdStart(ctx, msg, args), nil
}},
{Aliases: []string{"/menu"}, Handle: func(args []string) (telegramCommandReply, error) { return s.mainMenu(ctx, channel, msg), nil }},
{Aliases: []string{"/cancel"}, Handle: func(args []string) (telegramCommandReply, error) {
s.takePending(int64(msg.From.ID))
return telegramCommandReply{Text: "已取消当前操作。"}, nil
}},
{Aliases: []string{"/help"}, Handle: func(args []string) (telegramCommandReply, error) {
return telegramCommandReply{Text: s.cmdHelp(ctx, msg)}, nil
}},
{Aliases: []string{"/hideadult", "/hide_adult", "/adult"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdHideAdult(ctx, msg, args), nil }},
{Aliases: []string{"/account", "/me"}, Handle: func(args []string) (telegramCommandReply, error) { return s.replyAccount(ctx, msg), nil }},
{Aliases: []string{"/signin", "/checkin"}, Handle: func(args []string) (telegramCommandReply, error) { return s.replySignIn(ctx, msg), nil }},
{Aliases: []string{"/devices"}, Handle: func(args []string) (telegramCommandReply, error) { return s.replyDevices(ctx, msg), nil }},
{Aliases: []string{"/kick"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdKick(ctx, msg, args), nil }},
{Aliases: []string{"/setname", "/rename"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdSetName(ctx, msg, args), nil }},
{Aliases: []string{"/setpass", "/passwd", "/password"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdSetPass(ctx, msg, args), nil }},
{Aliases: []string{"/redeem"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdRedeem(ctx, channel, msg, args), nil }},
{Aliases: []string{"/redeem_register"}, Handle: func(args []string) (telegramCommandReply, error) {
return s.cmdRedeemRegister(ctx, channel, msg, args), nil
}},
{Aliases: []string{"/redeem_renew"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdRedeemRenew(ctx, msg, args), nil }},
{Aliases: []string{"/register", "/reg", "/signup"}, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdRegister(ctx, channel, msg, args), nil }},
{Aliases: []string{"/registration", "/reg_switch", "/openreg"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdRegistrationToggle(ctx, args), nil }},
{Aliases: []string{"/capacity"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.replyCapacity(ctx), nil }},
{Aliases: []string{"/users"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.replyUserList(ctx), nil }},
{Aliases: []string{"/gencode"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdGenCode(ctx, msg, args), nil }},
{Aliases: []string{"/renew_user"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUserRenew(ctx, args), nil }},
{Aliases: []string{"/delete_user"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUserDelete(ctx, args), nil }},
{Aliases: []string{"/unbind"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUnbind(ctx, args), nil }},
{Aliases: []string{"/unbind_duplicates"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUnbindDuplicates(ctx), nil }},
{Aliases: []string{"/unbind_inactive"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUnbindInactive(ctx, args), nil }},
{Aliases: []string{"/devicepolicy", "/policy"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdDevicePolicy(ctx, args), nil }},
{Aliases: []string{"/antishare"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdAntiShare(ctx, args), nil }},
{Aliases: []string{"/cleanup"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdCleanup(ctx, args), nil }},
{Aliases: []string{"/cleanup_mode"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdCleanupMode(ctx, args), nil }},
{Aliases: []string{"/cleanup_rule"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdCleanupRule(ctx, args), nil }},
{Aliases: []string{"/ban"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUserBan(ctx, args, false), nil }},
{Aliases: []string{"/unban"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdUserBan(ctx, args, true), nil }},
{Aliases: []string{"/status"}, AdminOnly: true, AdminOnlyText: "此命令仅管理员可用。普通用户只能使用 /start 绑定账号,并通过按钮隐藏成人目录。", Handle: func(args []string) (telegramCommandReply, error) { return s.cmdStatus(ctx) }},
{Aliases: []string{"/search"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdSearch(ctx, args) }},
{Aliases: []string{"/downloads"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdDownloads(ctx) }},
{Aliases: []string{"/stats"}, AdminOnly: true, AdminOnlyText: adminOnly, Handle: func(args []string) (telegramCommandReply, error) { return s.cmdStats(ctx) }},
}
}
func (s *TelegramBotService) telegramCommandRegistry(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage) map[string]telegramCommandDefinition {
defs := s.telegramCommandDefinitions(ctx, channel, msg)
registry := make(map[string]telegramCommandDefinition, len(defs)*2)
for _, def := range defs {
for _, alias := range def.Aliases {
registry[alias] = def
}
}
return registry
}
// executeCommand parses and dispatches Telegram commands through a registry so
// adding a command does not grow a monolithic switch.
func (s *TelegramBotService) executeCommand(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage, text string) (telegramCommandReply, error) {
parts := strings.Fields(text)
if len(parts) == 0 {
return telegramCommandReply{}, nil
}
cmd := telegramCommandName(parts[0])
args := parts[1:]
if msg.Chat.Type != "" && msg.Chat.Type != "private" && !s.telegramChatAllowed(channel, msg.Chat.ID) {
return telegramCommandReply{Text: "此群组/频道未绑定到 Bot 管理入口,请在通知渠道里填写「绑定群组 ID」或「绑定频道 ID」。"}, nil
}
def, ok := s.telegramCommandRegistry(ctx, channel, msg)[cmd]
if !ok {
return telegramCommandReply{Text: fmt.Sprintf("未知命令: %s\n\n输入 /help 查看可用命令列表。", cmd)}, nil
}
if def.AdminOnly && !s.telegramUserIsAdmin(ctx, channel, msg.From.ID) {
return telegramCommandReply{Text: def.AdminOnlyText}, nil
}
return def.Handle(args)
}
func telegramSupportedCommand(cmd string) bool {
cmd = telegramCommandName(cmd)
if cmd == "" {
return false
}
_, ok := telegramSupportedCommandSet[cmd]
return ok
}
var telegramSupportedCommandSet = map[string]struct{}{
"/start": {}, "/menu": {}, "/cancel": {}, "/help": {}, "/hideadult": {}, "/hide_adult": {}, "/adult": {},
"/account": {}, "/me": {}, "/signin": {}, "/checkin": {}, "/devices": {}, "/kick": {}, "/setname": {}, "/rename": {}, "/setpass": {}, "/passwd": {}, "/password": {},
"/redeem": {}, "/redeem_register": {}, "/redeem_renew": {},
"/register": {}, "/reg": {}, "/signup": {}, "/registration": {}, "/reg_switch": {}, "/openreg": {},
"/capacity": {}, "/users": {}, "/gencode": {}, "/renew_user": {}, "/delete_user": {}, "/unbind": {}, "/unbind_duplicates": {}, "/unbind_inactive": {},
"/devicepolicy": {}, "/policy": {}, "/antishare": {}, "/cleanup": {}, "/cleanup_mode": {}, "/cleanup_rule": {},
"/ban": {}, "/unban": {}, "/status": {}, "/search": {}, "/downloads": {}, "/stats": {},
}
type telegramBotCommand struct {
Command string `json:"command"`
Description string `json:"description"`
}
func telegramBotCommandMenu() []telegramBotCommand {
return []telegramBotCommand{
{Command: "start", Description: "绑定账号或打开主菜单"},
{Command: "menu", Description: "打开功能菜单"},
{Command: "help", Description: "查看命令帮助"},
{Command: "account", Description: "查看账号状态"},
{Command: "signin", Description: "签到"},
{Command: "devices", Description: "查看登录设备"},
{Command: "kick", Description: "踢下线设备"},
{Command: "hideadult", Description: "隐藏/显示成人媒体库"},
{Command: "redeem", Description: "兑换注册码或续期码"},
{Command: "register", Description: "注册新账号"},
{Command: "status", Description: "系统运行状态(管理员)"},
{Command: "search", Description: "搜索媒体库(管理员)"},
{Command: "downloads", Description: "下载列表(管理员)"},
{Command: "stats", Description: "媒体库统计(管理员)"},
{Command: "users", Description: "用户管理(管理员)"},
{Command: "cleanup", Description: "删号规则巡检(管理员)"},
{Command: "cleanup_rule", Description: "保号规则管理(管理员)"},
}
}
func registerTelegramBotCommands(ctx context.Context, cfg map[string]string) error {
if strings.TrimSpace(cfg["bot_token"]) == "" {
return nil
}
payload := map[string]interface{}{
"commands": telegramBotCommandMenu(),
}
return telegramPostJSON(ctx, cfg, "setMyCommands", payload, 15*time.Second)
}