✨ feat(core): 初始化 Bot 命令处理服务器
- 基于 Gin 框架搭建 HTTP 服务,接收并处理 Bot 命令请求 - 实现插件化命令系统,支持通过 Plugin 接口扩展新命令 - 内置菜单、启用/禁用、时间查询等基础命令 - 新增图片生成插件,对接 OpenAI Images API - 支持管理员权限控制、命令动态启禁用 - 提供完整配置管理(.env)与 Docker 部署方案
This commit is contained in:
20
command/admin.go
Normal file
20
command/admin.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"ncatbot-command-server/config"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsAdmin 检查用户是否为管理员
|
||||
func IsAdmin(userID string) bool {
|
||||
admins := config.Cfg.AdminUsers
|
||||
if admins == "" {
|
||||
return false
|
||||
}
|
||||
for _, id := range strings.Split(admins, ",") {
|
||||
if strings.TrimSpace(id) == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
92
command/builtin.go
Normal file
92
command/builtin.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RegisterBuiltinCommands 注册内置命令
|
||||
func RegisterBuiltinCommands() {
|
||||
// 菜单
|
||||
Register(Command{
|
||||
Name: "菜单",
|
||||
Description: "显示所有可用命令",
|
||||
Usage: "菜单",
|
||||
Handler: handleMenu,
|
||||
})
|
||||
|
||||
// 启用(管理员)
|
||||
Register(Command{
|
||||
Name: "启用",
|
||||
Description: "启用指定命令",
|
||||
Usage: "启用 <命令名>",
|
||||
Handler: handleEnable,
|
||||
AdminOnly: true,
|
||||
})
|
||||
|
||||
// 禁用(管理员)
|
||||
Register(Command{
|
||||
Name: "禁用",
|
||||
Description: "禁用指定命令",
|
||||
Usage: "禁用 <命令名>",
|
||||
Handler: handleDisable,
|
||||
AdminOnly: true,
|
||||
})
|
||||
|
||||
// 时间
|
||||
Register(Command{
|
||||
Name: "时间",
|
||||
Description: "查询当前时间",
|
||||
Usage: "时间",
|
||||
Handler: func(req *Req) Resp {
|
||||
return Resp{Reply: "当前时间是: " + time.Now().Format("2006-01-02 15:04:05")}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func handleMenu(req *Req) Resp {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("可用命令列表:\n\n")
|
||||
|
||||
for _, cmd := range AllCommands() {
|
||||
status := "✅"
|
||||
if !cmd.Enabled {
|
||||
status = "❌"
|
||||
}
|
||||
adminTag := ""
|
||||
if cmd.AdminOnly {
|
||||
adminTag = " [管理员]"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s %s%s — %s\n", status, cmd.Name, adminTag, cmd.Description))
|
||||
sb.WriteString(fmt.Sprintf(" 用法:%s\n\n", cmd.Usage))
|
||||
}
|
||||
|
||||
return Resp{Reply: strings.TrimSpace(sb.String())}
|
||||
}
|
||||
|
||||
func handleEnable(req *Req) Resp {
|
||||
name := strings.TrimSpace(req.Content)
|
||||
if name == "" {
|
||||
return Resp{Reply: "请指定要启用的命令,例如:启用 生图"}
|
||||
}
|
||||
|
||||
if err := Toggle(name, true); err != nil {
|
||||
return Resp{Reply: err.Error()}
|
||||
}
|
||||
|
||||
return Resp{Reply: fmt.Sprintf("已启用命令\"%s\"", name)}
|
||||
}
|
||||
|
||||
func handleDisable(req *Req) Resp {
|
||||
name := strings.TrimSpace(req.Content)
|
||||
if name == "" {
|
||||
return Resp{Reply: "请指定要禁用的命令,例如:禁用 问候"}
|
||||
}
|
||||
|
||||
if err := Toggle(name, false); err != nil {
|
||||
return Resp{Reply: err.Error()}
|
||||
}
|
||||
|
||||
return Resp{Reply: fmt.Sprintf("已禁用命令\"%s\"", name)}
|
||||
}
|
||||
11
command/init.go
Normal file
11
command/init.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Init 初始化命令系统(仅注册内置命令,插件由 main.go 调用)
|
||||
func Init() {
|
||||
RegisterBuiltinCommands()
|
||||
log.Printf("registered %d commands", len(AllCommands()))
|
||||
}
|
||||
20
command/plugin.go
Normal file
20
command/plugin.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package command
|
||||
|
||||
// Plugin 命令插件接口
|
||||
type Plugin interface {
|
||||
Name() string // 命令名
|
||||
Description() string // 命令说明
|
||||
Usage() string // 用法提示
|
||||
Run(req *Req) Resp // 执行命令
|
||||
}
|
||||
|
||||
// PluginWithInit 支持初始化的插件接口
|
||||
type PluginWithInit interface {
|
||||
Plugin
|
||||
Init() // 初始化函数
|
||||
}
|
||||
|
||||
// PluginAdmin 可选接口:标记是否仅管理员可用
|
||||
type PluginAdmin interface {
|
||||
IsAdminOnly() bool
|
||||
}
|
||||
167
command/registry.go
Normal file
167
command/registry.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
plugins []Plugin
|
||||
commands map[string]*commandInfo
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
type commandInfo struct {
|
||||
name string
|
||||
description string
|
||||
usage string
|
||||
handler Handler
|
||||
initFunc func()
|
||||
enabled bool
|
||||
adminOnly bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
plugins = make([]Plugin, 0)
|
||||
commands = make(map[string]*commandInfo)
|
||||
}
|
||||
|
||||
// RegisterPlugin 注册一个插件(推荐方式)
|
||||
func RegisterPlugin(p Plugin) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
plugins = append(plugins, p)
|
||||
|
||||
info := &commandInfo{
|
||||
name: p.Name(),
|
||||
description: p.Description(),
|
||||
usage: p.Usage(),
|
||||
handler: p.Run,
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
// 检查是否实现了 PluginWithInit
|
||||
if initable, ok := p.(PluginWithInit); ok {
|
||||
info.initFunc = initable.Init
|
||||
}
|
||||
|
||||
// 检查是否实现了 PluginAdmin
|
||||
if admin, ok := p.(PluginAdmin); ok {
|
||||
info.adminOnly = admin.IsAdminOnly()
|
||||
}
|
||||
|
||||
commands[info.name] = info
|
||||
}
|
||||
|
||||
// Register 兼容旧方式:直接注册 Command
|
||||
func Register(cmd Command) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
commands[cmd.Name] = &commandInfo{
|
||||
name: cmd.Name,
|
||||
description: cmd.Description,
|
||||
usage: cmd.Usage,
|
||||
handler: cmd.Handler,
|
||||
initFunc: cmd.Init,
|
||||
enabled: true,
|
||||
adminOnly: cmd.AdminOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// InitAll 初始化所有需要初始化的插件
|
||||
func InitAll() {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
for _, info := range commands {
|
||||
if info.initFunc != nil {
|
||||
info.initFunc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 查找并执行命令
|
||||
func Handle(req *Req) Resp {
|
||||
mu.RLock()
|
||||
info, ok := commands[req.Command]
|
||||
mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return Resp{Reply: "无法识别的命令。发送\"菜单\"查看可用命令。"}
|
||||
}
|
||||
|
||||
if !info.enabled {
|
||||
return Resp{Reply: fmt.Sprintf("命令\"%s\"已被禁用。", info.name)}
|
||||
}
|
||||
|
||||
if info.adminOnly && !IsAdmin(req.UserID) {
|
||||
return Resp{Reply: "权限不足,该命令仅管理员可用。"}
|
||||
}
|
||||
|
||||
return info.handler(req)
|
||||
}
|
||||
|
||||
// AllPlugins 返回所有插件
|
||||
func AllPlugins() []Plugin {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return append([]Plugin(nil), plugins...)
|
||||
}
|
||||
|
||||
// AllCommands 返回所有命令信息(用于菜单)
|
||||
func AllCommands() []CommandInfo {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
|
||||
result := make([]CommandInfo, 0, len(commands))
|
||||
for _, info := range commands {
|
||||
result = append(result, CommandInfo{
|
||||
Name: info.name,
|
||||
Description: info.description,
|
||||
Usage: info.usage,
|
||||
Enabled: info.enabled,
|
||||
AdminOnly: info.adminOnly,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CommandInfo 命令信息(用于菜单显示)
|
||||
type CommandInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
Usage string
|
||||
Enabled bool
|
||||
AdminOnly bool
|
||||
}
|
||||
|
||||
// Toggle 启用或禁用命令
|
||||
func Toggle(name string, enable bool) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
info, ok := commands[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("命令\"%s\"不存在", name)
|
||||
}
|
||||
|
||||
if info.adminOnly {
|
||||
return fmt.Errorf("管理命令\"%s\"不可被禁用", name)
|
||||
}
|
||||
|
||||
info.enabled = enable
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handler 命令处理函数(兼容旧方式)
|
||||
type Handler func(req *Req) Resp
|
||||
|
||||
// Command 命令定义(兼容旧方式)
|
||||
type Command struct {
|
||||
Name string
|
||||
Description string
|
||||
Usage string
|
||||
Handler Handler
|
||||
Init func()
|
||||
AdminOnly bool
|
||||
}
|
||||
35
command/types.go
Normal file
35
command/types.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package command
|
||||
|
||||
// Req 请求结构体
|
||||
type Req struct {
|
||||
Command string `json:"command"`
|
||||
Content string `json:"content"`
|
||||
RawMessage string `json:"raw_message"`
|
||||
UserID string `json:"user_id"`
|
||||
GroupID string `json:"group_id"`
|
||||
MessageID string `json:"message_id"`
|
||||
}
|
||||
|
||||
// Resp 响应结构体
|
||||
type Resp struct {
|
||||
Reply string `json:"reply,omitempty"`
|
||||
Messages []Message `json:"messages,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
// Message 消息结构体
|
||||
type Message struct {
|
||||
Type MsgType `json:"type"`
|
||||
Msg string `json:"msg,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// MsgType 消息类型
|
||||
type MsgType string
|
||||
|
||||
const (
|
||||
MsgTypeText MsgType = "text"
|
||||
MsgTypeImage MsgType = "image"
|
||||
MsgTypeFile MsgType = "file"
|
||||
MsgTypeVideo MsgType = "video"
|
||||
)
|
||||
Reference in New Issue
Block a user