refactor: convert commands to global variables; use log.Fatal in main

This commit is contained in:
2025-12-18 11:07:46 +08:00
parent 4f8268213f
commit b1c19872fe
13 changed files with 139 additions and 273 deletions

View File

@@ -1,6 +1,8 @@
package main
import (
"log"
"github.com/awfufu/go-hurobot/internal/cmds"
"github.com/awfufu/go-hurobot/internal/config"
"github.com/awfufu/go-hurobot/internal/db"
@@ -28,6 +30,8 @@ func main() {
cmds.HandleCommand(sender, msg)
}
}()
case err := <-receiver.Error():
log.Fatal(err)
}
}
}

View File

@@ -12,43 +12,39 @@ import (
"github.com/google/shlex"
)
type command interface {
Self() *cmdBase
Exec(b *qbot.Sender, msg *qbot.Message)
}
type cmdBase struct {
type Command struct {
Name string // Command name
HelpMsg string // Help message
Permission config.Permission // Permission requirement
NeedRawMsg bool
MaxArgs int // Maximum number of arguments
MinArgs int // Minimum number of arguments
MaxArgs int // Maximum number of arguments
MinArgs int // Minimum number of arguments
Exec func(b *qbot.Sender, msg *qbot.Message) // Execute function
}
const commandPrefix = '/'
var cmdMap map[string]command
var cmdMap map[string]*Command
func init() {
cmdMap = map[string]command{
"crypto": NewCryptoCommand(),
"delete": NewDeleteCommand(),
"draw": NewDrawCommand(),
"echo": NewEchoCommand(),
"essence": NewEssenceCommand(),
"fx": NewErCommand(),
"group": NewGroupCommand(),
"perm": NewPermCommand(),
"sh": NewShCommand(),
"specialtitle": NewSpecialtitleCommand(),
"which": NewWhichCommand(),
cmdMap = map[string]*Command{
"crypto": cryptoCommand,
"delete": deleteCommand,
"draw": drawCommand,
"echo": echoCommand,
"essence": essenceCommand,
"fx": erCommand,
"group": groupCommand,
"perm": permCommand,
"sh": shCommand,
"specialtitle": specialtitleCommand,
"which": whichCommand,
}
}
func InitCommandPermissions() {
for name, cmd := range cmdMap {
base := cmd.Self()
base := cmd
perm := db.GetCommandPermission(name)
if perm != nil {
@@ -103,7 +99,7 @@ func HandleCommand(b *qbot.Sender, msg *qbot.Message) {
return
}
cmdBase := cmd.Self()
cmdBase := cmd
// check permission
if !checkCmdPermission(cmdBase.Name, msg.UserID, msg.GroupID) {

View File

@@ -75,29 +75,17 @@ Examples:
/crypto BTC
/crypto BTC USD`
type CryptoCommand struct {
cmdBase
var cryptoCommand *Command = &Command{
Name: "crypto",
HelpMsg: cryptoHelpMsg,
Permission: getCmdPermLevel("crypto"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 2,
Exec: execCrypto,
}
func NewCryptoCommand() *CryptoCommand {
return &CryptoCommand{
cmdBase: cmdBase{
Name: "crypto",
HelpMsg: cryptoHelpMsg,
Permission: getCmdPermLevel("crypto"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 2,
},
}
}
func (cmd *CryptoCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *CryptoCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execCrypto(b *qbot.Sender, msg *qbot.Message) {
if len(msg.Array) == 2 {
if msg.Array[1].Type() == qbot.TextType {
coin := strings.ToUpper(msg.Array[1].Text())
@@ -116,7 +104,7 @@ func (cmd *CryptoCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
handleCryptoCurrencyPair(b, msg, coin, currency)
}
} else {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, cryptoHelpMsg)
}
}

View File

@@ -9,32 +9,19 @@ import (
const deleteHelpMsg = `Delete a message by replying to it.
Usage: [Reply to a message] /delete`
type DeleteCommand struct {
cmdBase
}
func NewDeleteCommand() *DeleteCommand {
return &DeleteCommand{
cmdBase: cmdBase{
Name: "delete",
HelpMsg: deleteHelpMsg,
Permission: getCmdPermLevel("delete"),
NeedRawMsg: false,
MaxArgs: 1,
MinArgs: 1,
},
}
}
func (cmd *DeleteCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *DeleteCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
if msg.ReplyID != 0 {
b.DeleteMsg(msg.ReplyID)
log.Printf("delete message %d", msg.ReplyID)
} else {
b.SendGroupMsg(msg.GroupID, "Please reply to a message to delete it, and ensure the bot has permission to delete it")
}
var deleteCommand *Command = &Command{
Name: "delete",
HelpMsg: deleteHelpMsg,
Permission: getCmdPermLevel("delete"),
NeedRawMsg: false,
MaxArgs: 1,
MinArgs: 1,
Exec: func(b *qbot.Sender, msg *qbot.Message) {
if msg.ReplyID != 0 {
b.DeleteMsg(msg.ReplyID)
log.Printf("delete message %d", msg.ReplyID)
} else {
b.SendGroupMsg(msg.GroupID, "Please reply to a message to delete it, and ensure the bot has permission to delete it")
}
},
}

View File

@@ -18,6 +18,15 @@ Usage: /draw <prompt> [--size <size>]
Supported sizes: 1328x1328, 1584x1056, 1140x1472, 1664x928, 928x1664
Example: /draw a cat --size 1328x1328`
var drawCommand *Command = &Command{
Name: "draw",
HelpMsg: drawHelpMsg,
Permission: getCmdPermLevel("draw"),
NeedRawMsg: false,
MinArgs: 2,
Exec: execDraw,
}
type ImageGenerationRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
@@ -36,28 +45,7 @@ type ImageGenerationResponse struct {
Seed int64 `json:"seed"`
}
type DrawCommand struct {
cmdBase
}
func NewDrawCommand() *DrawCommand {
return &DrawCommand{
cmdBase: cmdBase{
Name: "draw",
HelpMsg: drawHelpMsg,
Permission: getCmdPermLevel("draw"),
NeedRawMsg: false,
MinArgs: 2,
},
}
}
func (cmd *DrawCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *DrawCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execDraw(b *qbot.Sender, msg *qbot.Message) {
if config.Cfg.ApiKeys.DrawApiKey == "" {
b.SendGroupMsg(msg.GroupID, "No API key")
return

View File

@@ -8,27 +8,13 @@ const echoHelpMsg string = `Echoes messages to a target destination.
Usage: /echo <any>
Example: /echo helloworld`
type EchoCommand struct {
cmdBase
}
func NewEchoCommand() *EchoCommand {
return &EchoCommand{
cmdBase: cmdBase{
Name: "echo",
HelpMsg: echoHelpMsg,
Permission: getCmdPermLevel("echo"),
NeedRawMsg: false,
MinArgs: 2,
},
}
}
func (cmd *EchoCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *EchoCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
b.SendGroupMsg(msg.GroupID, msg.Array[1:])
var echoCommand *Command = &Command{
Name: "echo",
HelpMsg: echoHelpMsg,
Permission: getCmdPermLevel("echo"),
NeedRawMsg: false,
MinArgs: 2,
Exec: func(b *qbot.Sender, msg *qbot.Message) {
b.SendGroupMsg(msg.GroupID, msg.Array[1:])
},
}

View File

@@ -7,29 +7,17 @@ import (
const essenceHelpMsg string = `Manage essence messages.
Usage: [Reply to a message] /essence [add|rm]`
type EssenceCommand struct {
cmdBase
var essenceCommand *Command = &Command{
Name: "essence",
HelpMsg: essenceHelpMsg,
Permission: getCmdPermLevel("essence"),
NeedRawMsg: false,
Exec: execEssence,
}
func NewEssenceCommand() *EssenceCommand {
return &EssenceCommand{
cmdBase: cmdBase{
Name: "essence",
HelpMsg: essenceHelpMsg,
Permission: getCmdPermLevel("essence"),
NeedRawMsg: false,
},
}
}
func (cmd *EssenceCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *EssenceCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execEssence(b *qbot.Sender, msg *qbot.Message) {
if msg.ReplyID == 0 {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, essenceHelpMsg)
return
}
@@ -41,10 +29,10 @@ func (cmd *EssenceCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
case "add":
b.SetGroupEssence(msg.ReplyID)
default:
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, essenceHelpMsg)
}
} else {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, essenceHelpMsg)
}
} else {
b.SetGroupEssence(msg.ReplyID)

View File

@@ -22,35 +22,23 @@ const erHelpMsg string = `Query foreign exchange rates.
Usage: fx <from_currency> <to_currency>
Example: fx CNY HKD`
type ErCommand struct {
cmdBase
var erCommand *Command = &Command{
Name: "fx",
HelpMsg: erHelpMsg,
Permission: getCmdPermLevel("fx"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 3,
Exec: execEr,
}
func NewErCommand() *ErCommand {
return &ErCommand{
cmdBase: cmdBase{
Name: "fx",
HelpMsg: erHelpMsg,
Permission: getCmdPermLevel("fx"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 3,
},
}
}
func (cmd *ErCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *ErCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execEr(b *qbot.Sender, msg *qbot.Message) {
if config.Cfg.ApiKeys.ExchangeRateAPIKey == "" {
return
}
if len(msg.Array) < 3 {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, erHelpMsg)
return
}

View File

@@ -15,28 +15,16 @@ Examples:
/group rename awa
/group op @user1 @user2 ...`
type GroupCommand struct {
cmdBase
var groupCommand *Command = &Command{
Name: "group",
HelpMsg: groupHelpMsg,
Permission: getCmdPermLevel("group"),
NeedRawMsg: false,
MinArgs: 2,
Exec: execGroup,
}
func NewGroupCommand() *GroupCommand {
return &GroupCommand{
cmdBase: cmdBase{
Name: "group",
HelpMsg: groupHelpMsg,
Permission: getCmdPermLevel("group"),
NeedRawMsg: false,
MinArgs: 2,
},
}
}
func (cmd *GroupCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *GroupCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execGroup(b *qbot.Sender, msg *qbot.Message) {
getText := func(i int) string {
if i < len(msg.Array) {
if msg.Array[i].Type() == qbot.TextType {
@@ -47,7 +35,7 @@ func (cmd *GroupCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
}
if len(msg.Array) < 2 {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, groupHelpMsg)
return
}

View File

@@ -25,29 +25,18 @@ Examples:
/perm special draw user add @user
/perm user @user admin`
type PermCommand struct {
cmdBase
var permCommand *Command = &Command{
Name: "perm",
HelpMsg: permHelpMsg,
Permission: config.Master,
NeedRawMsg: false,
MinArgs: 2,
Exec: execPerm,
}
func NewPermCommand() *PermCommand {
return &PermCommand{
cmdBase: cmdBase{
Name: "perm",
HelpMsg: permHelpMsg,
Permission: config.Master, // Only master can manage permissions
NeedRawMsg: false,
MinArgs: 2,
},
}
}
func (cmd *PermCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *PermCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execPerm(b *qbot.Sender, msg *qbot.Message) {
if len(msg.Array) < 2 {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, permHelpMsg)
return
}
@@ -63,17 +52,17 @@ func (cmd *PermCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
subCmd := getText(1)
switch subCmd {
case "set":
cmd.handleSet(b, msg)
handleSet(b, msg)
case "special":
cmd.handleSpecial(b, msg)
handleSpecial(b, msg)
case "user":
cmd.handleUserRole(b, msg)
handleUserRole(b, msg)
default:
b.SendGroupMsg(msg.GroupID, "Unknown subcommand: "+subCmd)
}
}
func (cmd *PermCommand) handleSet(b *qbot.Sender, msg *qbot.Message) {
func handleSet(b *qbot.Sender, msg *qbot.Message) {
getText := func(i int) string {
if i < len(msg.Array) {
if msg.Array[i].Type() == qbot.TextType {
@@ -144,7 +133,7 @@ func (cmd *PermCommand) handleSet(b *qbot.Sender, msg *qbot.Message) {
}
}
func (cmd *PermCommand) handleSpecial(b *qbot.Sender, msg *qbot.Message) {
func handleSpecial(b *qbot.Sender, msg *qbot.Message) {
getText := func(i int) string {
if i < len(msg.Array) {
if msg.Array[i].Type() == qbot.TextType {
@@ -288,7 +277,7 @@ func extractTargets(args []qbot.MsgItem, targetType string) []uint64 {
return targets
}
func (cmd *PermCommand) handleUserRole(b *qbot.Sender, msg *qbot.Message) {
func handleUserRole(b *qbot.Sender, msg *qbot.Message) {
getText := func(i int) string {
if i < len(msg.Array) {
if msg.Array[i].Type() == qbot.TextType {

View File

@@ -57,28 +57,16 @@ func truncateString(s string) string {
return s
}
type ShCommand struct {
cmdBase
var shCommand *Command = &Command{
Name: "sh",
HelpMsg: shHelpMsg,
Permission: getCmdPermLevel("sh"),
NeedRawMsg: true,
MinArgs: 2,
Exec: execSh,
}
func NewShCommand() *ShCommand {
return &ShCommand{
cmdBase: cmdBase{
Name: "sh",
HelpMsg: shHelpMsg,
Permission: getCmdPermLevel("sh"),
NeedRawMsg: true,
MinArgs: 2,
},
}
}
func (cmd *ShCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *ShCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execSh(b *qbot.Sender, msg *qbot.Message) {
// For NeedRawMsg, msg.Array[1] contains the raw arguments string.
// But let's verify if we need parsing for --reset
// The original code checked args[1] == "--reset".

View File

@@ -11,29 +11,17 @@ const specialtitleHelpMsg string = `Set special title for group members.
Usage: /specialtitle [@user] <title>
Example: /specialtitle @user qwq`
type SpecialtitleCommand struct {
cmdBase
var specialtitleCommand *Command = &Command{
Name: "specialtitle",
HelpMsg: specialtitleHelpMsg,
Permission: getCmdPermLevel("specialtitle"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 2,
Exec: execSpecialTitle,
}
func NewSpecialtitleCommand() *SpecialtitleCommand {
return &SpecialtitleCommand{
cmdBase: cmdBase{
Name: "specialtitle",
HelpMsg: specialtitleHelpMsg,
Permission: getCmdPermLevel("specialtitle"),
NeedRawMsg: false,
MaxArgs: 3,
MinArgs: 2,
},
}
}
func (cmd *SpecialtitleCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *SpecialtitleCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execSpecialTitle(b *qbot.Sender, msg *qbot.Message) {
var targetUserID qbot.UserID
var title string
@@ -81,7 +69,7 @@ func (cmd *SpecialtitleCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
}
} else {
// Should be handled by MaxArgs/MinArgs, but safe fallback
b.SendGroupReplyMsg(msg.GroupID, msg.MsgID, cmd.HelpMsg)
b.SendGroupReplyMsg(msg.GroupID, msg.MsgID, specialtitleHelpMsg)
return
}

View File

@@ -26,29 +26,17 @@ const whichHelpMsg string = `Query abbreviation meanings.
Usage: /which <text>
Example: /which yyds`
type WhichCommand struct {
cmdBase
var whichCommand *Command = &Command{
Name: "which",
HelpMsg: whichHelpMsg,
Permission: getCmdPermLevel("which"),
NeedRawMsg: false,
MaxArgs: 2,
MinArgs: 2,
Exec: execWhich,
}
func NewWhichCommand() *WhichCommand {
return &WhichCommand{
cmdBase: cmdBase{
Name: "which",
HelpMsg: whichHelpMsg,
Permission: getCmdPermLevel("which"),
NeedRawMsg: false,
MaxArgs: 2,
MinArgs: 2,
},
}
}
func (cmd *WhichCommand) Self() *cmdBase {
return &cmd.cmdBase
}
func (cmd *WhichCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
func execWhich(b *qbot.Sender, msg *qbot.Message) {
// Check for non-text type parameters
for i := 1; i < len(msg.Array); i++ {
str := ""
@@ -70,7 +58,7 @@ func (cmd *WhichCommand) Exec(b *qbot.Sender, msg *qbot.Message) {
text := strings.Join(parts, " ")
if text == "" {
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
b.SendGroupMsg(msg.GroupID, whichHelpMsg)
return
}