mirror of
https://github.com/awfufu/go-hurobot.git
synced 2026-03-01 05:29:43 +08:00
refactor: replace config command with perm and move permissions to db
This commit is contained in:
84
cmds/cmds.go
84
cmds/cmds.go
@@ -1,12 +1,14 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/shlex"
|
||||
|
||||
"github.com/awfufu/go-hurobot/config"
|
||||
"github.com/awfufu/go-hurobot/db"
|
||||
"github.com/awfufu/qbot"
|
||||
)
|
||||
|
||||
@@ -37,6 +39,7 @@ func init() {
|
||||
"essence": NewEssenceCommand(),
|
||||
"fx": NewErCommand(),
|
||||
"group": NewGroupCommand(),
|
||||
"perm": NewPermCommand(),
|
||||
"psql": NewPsqlCommand(),
|
||||
"sh": NewShCommand(),
|
||||
"specialtitle": NewSpecialtitleCommand(),
|
||||
@@ -59,7 +62,7 @@ func HandleCommand(b *qbot.Bot, msg *qbot.Message) {
|
||||
cmdBase := cmd.Self()
|
||||
|
||||
// check permission
|
||||
if !checkCmdPermission(cmdBase.Name, msg.UserID) {
|
||||
if !checkCmdPermission(cmdBase.Name, msg.UserID, msg.GroupID) {
|
||||
b.SendGroupMsg(msg.GroupID, cmdBase.Name+": Permission denied")
|
||||
return
|
||||
}
|
||||
@@ -210,44 +213,75 @@ func isHelpRequest(args []qbot.MsgItem) bool {
|
||||
}
|
||||
|
||||
func getCmdPermLevel(cmdName string) config.Permission {
|
||||
cmdCfg := config.Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return config.Master
|
||||
}
|
||||
return cmdCfg.GetPermissionLevel()
|
||||
// Deprecated or can read from DB for default if permission struct changes
|
||||
// For now, logic handled in checkCmdPermission
|
||||
return config.Master
|
||||
}
|
||||
|
||||
func checkCmdPermission(cmdName string, userID uint64) bool {
|
||||
cmdCfg := config.Cfg.GetCmdConfig(cmdName)
|
||||
|
||||
// If not configured, use default permission check
|
||||
if cmdCfg == nil {
|
||||
func checkCmdPermission(cmdName string, userID, groupID uint64) bool {
|
||||
// 1. Master Bypass
|
||||
if userID == config.Cfg.Permissions.MasterID {
|
||||
return true
|
||||
}
|
||||
|
||||
userPerm := config.GetUserPermission(userID)
|
||||
requiredPerm := cmdCfg.GetPermissionLevel()
|
||||
// 2. Load Permissions from DB
|
||||
perm := db.GetCommandPermission(cmdName)
|
||||
|
||||
// Master users are not restricted by reject_users
|
||||
if userPerm == config.Master {
|
||||
var userDefault string = "master"
|
||||
var groupDefault string = "disable"
|
||||
var allowUsers []uint64
|
||||
var rejectUsers []uint64
|
||||
var allowGroups []uint64
|
||||
var rejectGroups []uint64
|
||||
|
||||
if perm != nil {
|
||||
userDefault = perm.UserDefault
|
||||
groupDefault = perm.GroupDefault
|
||||
allowUsers = perm.ParseAllowUsers()
|
||||
rejectUsers = perm.ParseRejectUsers()
|
||||
allowGroups = perm.ParseAllowGroups()
|
||||
rejectGroups = perm.ParseRejectGroups()
|
||||
}
|
||||
|
||||
// 3. User Allow/Reject Lists
|
||||
if slices.Contains(rejectUsers, userID) {
|
||||
return false
|
||||
}
|
||||
if slices.Contains(allowUsers, userID) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check reject_users (effective when command permission < master)
|
||||
if requiredPerm < config.Master && cmdCfg.IsInRejectList(userID) {
|
||||
// 4. User Role Check
|
||||
userRole := config.GetUserPermission(userID)
|
||||
var requiredPerm config.Permission
|
||||
switch userDefault {
|
||||
case "guest":
|
||||
requiredPerm = config.Guest
|
||||
case "admin":
|
||||
requiredPerm = config.Admin
|
||||
case "master":
|
||||
requiredPerm = config.Master
|
||||
default:
|
||||
requiredPerm = config.Master
|
||||
}
|
||||
if userRole < requiredPerm {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check allow_users (effective when command permission > guest)
|
||||
if requiredPerm > config.Guest {
|
||||
// If there is an allow_users list, only allow users in the list
|
||||
if len(cmdCfg.AllowUsers) > 0 {
|
||||
return cmdCfg.IsInAllowList(userID)
|
||||
}
|
||||
// 5. Group Allow/Reject Lists
|
||||
if slices.Contains(rejectGroups, groupID) {
|
||||
return false
|
||||
}
|
||||
if slices.Contains(allowGroups, groupID) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check basic permission
|
||||
return userPerm >= requiredPerm
|
||||
// 6. Group Default Check
|
||||
if groupDefault == "disable" {
|
||||
return false
|
||||
}
|
||||
// enable
|
||||
return true
|
||||
}
|
||||
|
||||
func decodeSpecialChars(raw string) string {
|
||||
|
||||
357
cmds/config.go
357
cmds/config.go
@@ -1,357 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/awfufu/go-hurobot/config"
|
||||
"github.com/awfufu/qbot"
|
||||
)
|
||||
|
||||
const configHelpMsg string = `Manage bot configuration.
|
||||
Usage: config <subcommand> [args...]
|
||||
Subcommands:
|
||||
admin add @user... Add user(s) as admin
|
||||
admin rm @user... Remove user(s) from admin
|
||||
admin list List all admins
|
||||
allow <cmd> add @user... Add user(s) to command allow list
|
||||
allow <cmd> rm @user... Remove user(s) from command allow list
|
||||
allow <cmd> list List users in command allow list
|
||||
reject <cmd> add @user... Add user(s) to command reject list
|
||||
reject <cmd> rm @user... Remove user(s) from command reject list
|
||||
reject <cmd> list List users in command reject list
|
||||
reload Reload configuration from file
|
||||
Examples:
|
||||
/config admin add @user1 @user2
|
||||
/config admin list
|
||||
/config allow sh add @user
|
||||
/config reject llm list
|
||||
/config reload`
|
||||
|
||||
type ConfigCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewConfigCommand() *ConfigCommand {
|
||||
return &ConfigCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "config",
|
||||
HelpMsg: configHelpMsg,
|
||||
Permission: config.Admin,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) Exec(b *qbot.Bot, msg *qbot.Message) {
|
||||
if len(msg.Array) < 2 {
|
||||
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
subCmd := getText(1)
|
||||
switch subCmd {
|
||||
case "admin":
|
||||
cmd.handleAdmin(b, msg)
|
||||
case "allow":
|
||||
cmd.handleAllow(b, msg)
|
||||
case "reject":
|
||||
cmd.handleReject(b, msg)
|
||||
case "reload":
|
||||
cmd.handleReload(b, msg)
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown subcommand: "+subCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleAdmin(b *qbot.Bot, msg *qbot.Message) {
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(msg.Array) < 3 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config admin [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := getText(2)
|
||||
switch action {
|
||||
case "add":
|
||||
if len(msg.Array) < 4 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config admin add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.AddAdmin(userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: set as admin", userID))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(msg.Array) < 4 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config admin rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.RemoveAdmin(userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: removed from admin", userID))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
admins := config.GetAdmins()
|
||||
if len(admins) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Admin list is empty")
|
||||
} else {
|
||||
adminStrs := make([]string, len(admins))
|
||||
for i, u := range admins {
|
||||
adminStrs[i] = strconv.FormatUint(u, 10)
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Admins: %s", strings.Join(adminStrs, ", ")))
|
||||
}
|
||||
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func extractUserIDs(args []qbot.MsgItem) []uint64 {
|
||||
userIDs := make([]uint64, 0, len(args))
|
||||
seen := make(map[uint64]bool)
|
||||
|
||||
for _, arg := range args {
|
||||
if atItem := arg.GetAtItem(); atItem != nil {
|
||||
userID := atItem.TargetID
|
||||
if userID != 0 && !seen[userID] {
|
||||
seen[userID] = true
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return userIDs
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleAllow(b *qbot.Bot, msg *qbot.Message) {
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(msg.Array) < 3 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config allow <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := getText(2)
|
||||
if len(msg.Array) < 4 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config allow <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := getText(3)
|
||||
switch action {
|
||||
case "add":
|
||||
if len(msg.Array) < 5 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config allow <cmd> add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.AddAllowUser(cmdName, userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: added to %s allow list", userID, cmdName))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(msg.Array) < 5 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config allow <cmd> rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.RemoveAllowUser(cmdName, userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: removed from %s allow list", userID, cmdName))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
users, err := config.GetAllowUsers(cmdName)
|
||||
if err != nil {
|
||||
b.SendGroupMsg(msg.GroupID, "Failed to get allow list: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Allow list for %s is empty", cmdName))
|
||||
} else {
|
||||
userStrs := make([]string, len(users))
|
||||
for i, u := range users {
|
||||
userStrs[i] = strconv.FormatUint(u, 10)
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Allow list for %s: %s", cmdName, strings.Join(userStrs, ", ")))
|
||||
}
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleReject(b *qbot.Bot, msg *qbot.Message) {
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(msg.Array) < 3 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config reject <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := getText(2)
|
||||
if len(msg.Array) < 4 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config reject <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := getText(3)
|
||||
switch action {
|
||||
case "add":
|
||||
if len(msg.Array) < 5 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config reject <cmd> add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.AddRejectUser(cmdName, userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: added to %s reject list", userID, cmdName))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(msg.Array) < 5 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: config reject <cmd> rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(msg.Array)
|
||||
if len(userIDs) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "Please mention at least one user with @")
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
err := config.RemoveRejectUser(cmdName, userID)
|
||||
if err != nil {
|
||||
results = append(results, fmt.Sprintf("❌ %d: %s", userID, err.Error()))
|
||||
} else {
|
||||
results = append(results, fmt.Sprintf("✅ %d: removed from %s reject list", userID, cmdName))
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
users, err := config.GetRejectUsers(cmdName)
|
||||
if err != nil {
|
||||
b.SendGroupMsg(msg.GroupID, "Failed to get reject list: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Reject list for %s is empty", cmdName))
|
||||
} else {
|
||||
userStrs := make([]string, len(users))
|
||||
for i, u := range users {
|
||||
userStrs[i] = strconv.FormatUint(u, 10)
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Reject list for %s: %s", cmdName, strings.Join(userStrs, ", ")))
|
||||
}
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleReload(b *qbot.Bot, msg *qbot.Message) {
|
||||
err := config.ReloadConfig()
|
||||
if err != nil {
|
||||
b.SendGroupMsg(msg.GroupID, "Failed to reload config: "+err.Error())
|
||||
} else {
|
||||
b.SendGroupMsg(msg.GroupID, "Configuration reloaded successfully")
|
||||
}
|
||||
}
|
||||
299
cmds/perm.go
Normal file
299
cmds/perm.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/awfufu/go-hurobot/config"
|
||||
"github.com/awfufu/go-hurobot/db"
|
||||
"github.com/awfufu/qbot"
|
||||
)
|
||||
|
||||
const permHelpMsg string = `Manage command permissions.
|
||||
Usage: perm <subcommand> [args...]
|
||||
Subcommands:
|
||||
set <cmd> user_default <guest|admin|master>
|
||||
set <cmd> group_default <enable|disable>
|
||||
allow <cmd> user <add|rm|list> [targets...]
|
||||
allow <cmd> group <add|rm|list> [targets...]
|
||||
reject <cmd> user <add|rm|list> [targets...]
|
||||
reject <cmd> group <add|rm|list> [targets...]
|
||||
Examples:
|
||||
/perm set draw user_default guest
|
||||
/perm allow sh user add @user
|
||||
/perm reject llm group add 12345`
|
||||
|
||||
type PermCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
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.Bot, msg *qbot.Message) {
|
||||
if len(msg.Array) < 2 {
|
||||
b.SendGroupMsg(msg.GroupID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
subCmd := getText(1)
|
||||
switch subCmd {
|
||||
case "set":
|
||||
cmd.handleSet(b, msg)
|
||||
case "allow":
|
||||
cmd.handleListModify(b, msg, true)
|
||||
case "reject":
|
||||
cmd.handleListModify(b, msg, false)
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown subcommand: "+subCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *PermCommand) handleSet(b *qbot.Bot, msg *qbot.Message) {
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(msg.Array) < 5 {
|
||||
b.SendGroupMsg(msg.GroupID, "Usage: perm set <cmd> <user_default|group_default> <value>")
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := getText(2)
|
||||
settingType := getText(3)
|
||||
value := getText(4)
|
||||
|
||||
perm := db.GetCommandPermission(cmdName)
|
||||
if perm == nil {
|
||||
// Initialize if not exists
|
||||
perm = &db.DbPermissions{
|
||||
Command: cmdName,
|
||||
UserDefault: "master",
|
||||
GroupDefault: "disable",
|
||||
AllowUsers: "[]",
|
||||
RejectUsers: "[]",
|
||||
AllowGroups: "[]",
|
||||
RejectGroups: "[]",
|
||||
}
|
||||
}
|
||||
|
||||
switch settingType {
|
||||
case "user_default":
|
||||
if value != "guest" && value != "admin" && value != "master" {
|
||||
b.SendGroupMsg(msg.GroupID, "Invalid value for user_default. Must be guest, admin, or master.")
|
||||
return
|
||||
}
|
||||
perm.UserDefault = value
|
||||
case "group_default":
|
||||
if value != "enable" && value != "disable" {
|
||||
b.SendGroupMsg(msg.GroupID, "Invalid value for group_default. Must be enable or disable.")
|
||||
return
|
||||
}
|
||||
perm.GroupDefault = value
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown setting type: "+settingType)
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.SaveCommandPermission(perm); err != nil {
|
||||
b.SendGroupMsg(msg.GroupID, "Failed to save permission: "+err.Error())
|
||||
} else {
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Updated %s %s to %s", cmdName, settingType, value))
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *PermCommand) handleListModify(b *qbot.Bot, msg *qbot.Message, isAllow bool) {
|
||||
getText := func(i int) string {
|
||||
if i < len(msg.Array) {
|
||||
if txt := msg.Array[i].GetTextItem(); txt != nil {
|
||||
return txt.Content
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// allow <cmd> <user|group> <add|rm|list> [targets...]
|
||||
// 0 1 2 3 4
|
||||
if len(msg.Array) < 4 {
|
||||
actionType := "allow"
|
||||
if !isAllow {
|
||||
actionType = "reject"
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Usage: perm %s <cmd> <user|group> <add|rm|list> [targets...]", actionType))
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := getText(1)
|
||||
targetType := getText(2)
|
||||
action := getText(3)
|
||||
|
||||
if targetType != "user" && targetType != "group" {
|
||||
b.SendGroupMsg(msg.GroupID, "Invalid target type. Must be user or group.")
|
||||
return
|
||||
}
|
||||
|
||||
var currentList []uint64
|
||||
var rawStr string
|
||||
|
||||
perm := db.GetCommandPermission(cmdName)
|
||||
if perm == nil {
|
||||
// Initialize
|
||||
perm = &db.DbPermissions{
|
||||
Command: cmdName,
|
||||
UserDefault: "master",
|
||||
GroupDefault: "disable",
|
||||
AllowUsers: "",
|
||||
RejectUsers: "",
|
||||
AllowGroups: "",
|
||||
RejectGroups: "",
|
||||
}
|
||||
} else {
|
||||
// Ensure we are working with correct struct fields
|
||||
if isAllow {
|
||||
if targetType == "user" {
|
||||
rawStr = perm.AllowUsers
|
||||
} else {
|
||||
rawStr = perm.AllowGroups
|
||||
}
|
||||
} else {
|
||||
if targetType == "user" {
|
||||
rawStr = perm.RejectUsers
|
||||
} else {
|
||||
rawStr = perm.RejectGroups
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentList = db.ParseIDList(rawStr)
|
||||
|
||||
switch action {
|
||||
case "add":
|
||||
targets := extractTargets(msg.Array, targetType)
|
||||
if len(targets) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("No %ss specified.", targetType))
|
||||
return
|
||||
}
|
||||
count := 0
|
||||
for _, t := range targets {
|
||||
if !slices.Contains(currentList, t) {
|
||||
currentList = append(currentList, t)
|
||||
count++
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Added %d %ss.", count, targetType))
|
||||
|
||||
case "rm":
|
||||
targets := extractTargets(msg.Array, targetType)
|
||||
if len(targets) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("No %ss specified.", targetType))
|
||||
return
|
||||
}
|
||||
count := 0
|
||||
for _, t := range targets {
|
||||
idx := slices.Index(currentList, t)
|
||||
if idx != -1 {
|
||||
currentList = append(currentList[:idx], currentList[idx+1:]...)
|
||||
count++
|
||||
}
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, fmt.Sprintf("Removed %d %ss.", count, targetType))
|
||||
|
||||
case "list":
|
||||
if len(currentList) == 0 {
|
||||
b.SendGroupMsg(msg.GroupID, "List is empty.")
|
||||
} else {
|
||||
strs := make([]string, len(currentList))
|
||||
for i, v := range currentList {
|
||||
strs[i] = strconv.FormatUint(v, 10)
|
||||
}
|
||||
b.SendGroupMsg(msg.GroupID, strings.Join(strs, ", "))
|
||||
}
|
||||
return // No save needed for list
|
||||
|
||||
default:
|
||||
b.SendGroupMsg(msg.GroupID, "Unknown action: "+action)
|
||||
return
|
||||
}
|
||||
|
||||
// Update struct
|
||||
newStr := db.JoinIDList(currentList)
|
||||
|
||||
if isAllow {
|
||||
if targetType == "user" {
|
||||
perm.AllowUsers = newStr
|
||||
} else {
|
||||
perm.AllowGroups = newStr
|
||||
}
|
||||
} else {
|
||||
if targetType == "user" {
|
||||
perm.RejectUsers = newStr
|
||||
} else {
|
||||
perm.RejectGroups = newStr
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.SaveCommandPermission(perm); err != nil {
|
||||
b.SendGroupMsg(msg.GroupID, "Failed to save: "+err.Error())
|
||||
} else {
|
||||
b.SendGroupMsg(msg.GroupID, "Permission updated.")
|
||||
}
|
||||
}
|
||||
|
||||
func extractTargets(args []qbot.MsgItem, targetType string) []uint64 {
|
||||
var targets []uint64
|
||||
for _, arg := range args {
|
||||
if targetType == "user" {
|
||||
if at := arg.GetAtItem(); at != nil {
|
||||
targets = append(targets, at.TargetID)
|
||||
} else if txt := arg.GetTextItem(); txt != nil {
|
||||
// Try to parse raw ID from text if provided as argument
|
||||
parts := strings.Fields(txt.Content)
|
||||
for _, part := range parts {
|
||||
if id, err := strconv.ParseUint(part, 10, 64); err == nil {
|
||||
targets = append(targets, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Groups are usually just numbers in text
|
||||
if txt := arg.GetTextItem(); txt != nil {
|
||||
parts := strings.Fields(txt.Content)
|
||||
for _, part := range parts {
|
||||
if id, err := strconv.ParseUint(part, 10, 64); err == nil {
|
||||
targets = append(targets, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
187
config/config.go
187
config/config.go
@@ -13,8 +13,8 @@ import (
|
||||
// Config 配置结构体
|
||||
type yamlConfig struct {
|
||||
// NapCat 配置
|
||||
NapcatHttpServer string `yaml:"napcat_http_server"` // 正向 HTTP 地址
|
||||
ReverseHttpListen string `yaml:"reverse_http_listen"` // 反向 HTTP 监听端口
|
||||
HttpRemote string `yaml:"http_remote"` // 正向 HTTP 地址
|
||||
HttpListen string `yaml:"http_listen"` // 反向 HTTP 监听端口
|
||||
|
||||
// API Keys 配置
|
||||
ApiKeys struct {
|
||||
@@ -22,33 +22,19 @@ type yamlConfig struct {
|
||||
DrawApiKey string `yaml:"draw_api_key"`
|
||||
ExchangeRateAPIKey string `yaml:"exchange_rate_api_key"`
|
||||
OkxMirrorAPIKey string `yaml:"okx_mirror_api_key"`
|
||||
Longport struct {
|
||||
AppKey string `yaml:"app_key"`
|
||||
AppSecret string `yaml:"app_secret"`
|
||||
AccessToken string `yaml:"access_token"`
|
||||
Region string `yaml:"region"`
|
||||
EnableOvernight bool `yaml:"enable_overnight"`
|
||||
} `yaml:"longport"`
|
||||
} `yaml:"api_keys"`
|
||||
|
||||
// PostgreSQL 配置
|
||||
// SQLite 配置
|
||||
SQLite struct {
|
||||
Path string `yaml:"path"`
|
||||
} `yaml:"sqlite"`
|
||||
|
||||
// Python 配置
|
||||
Python struct {
|
||||
Interpreter string `yaml:"interpreter"`
|
||||
} `yaml:"python"`
|
||||
|
||||
// 权限配置
|
||||
Permissions struct {
|
||||
MasterID uint64 `yaml:"master_id"`
|
||||
BotID uint64 `yaml:"bot_id"`
|
||||
AdminIDs []uint64 `yaml:"admin_ids,omitempty"`
|
||||
BotOwnerGroupIDs []uint64 `yaml:"bot_owner_group_ids,omitempty"`
|
||||
Cmds []CmdConfig `yaml:"cmds,omitempty"`
|
||||
MasterID uint64 `yaml:"master_id"`
|
||||
BotID uint64 `yaml:"bot_id"`
|
||||
AdminIDs []uint64 `yaml:"admin_ids,omitempty"`
|
||||
BotOwnerGroupIDs []uint64 `yaml:"bot_owner_group_ids,omitempty"`
|
||||
} `yaml:"permissions"`
|
||||
|
||||
// 其他配置
|
||||
@@ -63,14 +49,6 @@ type SupplierConfig struct {
|
||||
Proxy string `yaml:"proxy,omitempty"`
|
||||
}
|
||||
|
||||
// CmdConfig 命令配置结构
|
||||
type CmdConfig struct {
|
||||
Name string `yaml:"name"` // 命令名称
|
||||
Permission string `yaml:"permission,omitempty"` // 权限级别: guest, admin, master,默认为 master
|
||||
AllowUsers []uint64 `yaml:"allow_users,omitempty"` // 允许执行的用户列表
|
||||
RejectUsers []uint64 `yaml:"reject_users,omitempty"` // 拒绝执行的用户列表
|
||||
}
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
@@ -92,11 +70,11 @@ func LoadConfig(configPath string) error {
|
||||
}
|
||||
|
||||
// NapCat 默认值
|
||||
if Cfg.NapcatHttpServer == "" {
|
||||
Cfg.NapcatHttpServer = "http://127.0.0.1:3000"
|
||||
if Cfg.HttpRemote == "" {
|
||||
Cfg.HttpRemote = "http://127.0.0.1:3000"
|
||||
}
|
||||
if Cfg.ReverseHttpListen == "" {
|
||||
Cfg.ReverseHttpListen = "0.0.0.0:3001"
|
||||
if Cfg.HttpListen == "" {
|
||||
Cfg.HttpListen = "0.0.0.0:3001"
|
||||
}
|
||||
|
||||
// 权限默认值
|
||||
@@ -112,16 +90,6 @@ func LoadConfig(configPath string) error {
|
||||
Cfg.SQLite.Path = "db/bot.db"
|
||||
}
|
||||
|
||||
// Longport 默认值
|
||||
if Cfg.ApiKeys.Longport.Region == "" {
|
||||
Cfg.ApiKeys.Longport.Region = "cn"
|
||||
}
|
||||
|
||||
// Python 默认值
|
||||
if Cfg.Python.Interpreter == "" {
|
||||
Cfg.Python.Interpreter = "python3"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -133,18 +101,11 @@ func LoadConfigFile() {
|
||||
if err := LoadConfig(configPath); err != nil {
|
||||
log.Fatalf("加载配置失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("配置加载成功: %s", configPath)
|
||||
}
|
||||
|
||||
// GetCmdConfig 获取指定命令的配置
|
||||
func (cfg *yamlConfig) GetCmdConfig(cmdName string) *CmdConfig {
|
||||
for i := range cfg.Permissions.Cmds {
|
||||
if cfg.Permissions.Cmds[i].Name == cmdName {
|
||||
return &cfg.Permissions.Cmds[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// GetConfigPath returns the config path
|
||||
func GetConfigPath() string {
|
||||
return configPath
|
||||
}
|
||||
|
||||
func GetUserPermission(userID uint64) Permission {
|
||||
@@ -165,46 +126,6 @@ func (cfg *yamlConfig) IsAdmin(userID uint64) bool {
|
||||
return slices.Contains(cfg.Permissions.AdminIDs, userID)
|
||||
}
|
||||
|
||||
// IsInAllowList 检查用户是否在命令的允许列表中
|
||||
func (c *CmdConfig) IsInAllowList(userID uint64) bool {
|
||||
if len(c.AllowUsers) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, id := range c.AllowUsers {
|
||||
if id == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsInRejectList 检查用户是否在命令的拒绝列表中
|
||||
func (c *CmdConfig) IsInRejectList(userID uint64) bool {
|
||||
if len(c.RejectUsers) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, id := range c.RejectUsers {
|
||||
if id == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetPermissionLevel 获取权限级别(返回数值便于比较)
|
||||
func (c *CmdConfig) GetPermissionLevel() Permission {
|
||||
switch c.Permission {
|
||||
case "guest":
|
||||
return 0
|
||||
case "admin":
|
||||
return 3
|
||||
case "master":
|
||||
return 4
|
||||
default:
|
||||
return 4 // 默认为 master
|
||||
}
|
||||
}
|
||||
|
||||
func AddAdmin(userID uint64) error {
|
||||
if userID == Cfg.Permissions.MasterID {
|
||||
return fmt.Errorf("user is already master")
|
||||
@@ -238,88 +159,6 @@ func GetAdmins() []uint64 {
|
||||
return Cfg.Permissions.AdminIDs
|
||||
}
|
||||
|
||||
func AddAllowUser(cmdName string, userID uint64) error {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
cmdCfg = &CmdConfig{Name: cmdName}
|
||||
Cfg.Permissions.Cmds = append(Cfg.Permissions.Cmds, *cmdCfg)
|
||||
cmdCfg = &Cfg.Permissions.Cmds[len(Cfg.Permissions.Cmds)-1]
|
||||
}
|
||||
if slices.Contains(cmdCfg.AllowUsers, userID) {
|
||||
return fmt.Errorf("user already in allow list")
|
||||
}
|
||||
cmdCfg.AllowUsers = append(cmdCfg.AllowUsers, userID)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func RemoveAllowUser(cmdName string, userID uint64) error {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return fmt.Errorf("command config not found")
|
||||
}
|
||||
idx := -1
|
||||
for i, id := range cmdCfg.AllowUsers {
|
||||
if id == userID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("user not in allow list")
|
||||
}
|
||||
cmdCfg.AllowUsers = append(cmdCfg.AllowUsers[:idx], cmdCfg.AllowUsers[idx+1:]...)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func GetAllowUsers(cmdName string) ([]uint64, error) {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return []uint64{}, nil
|
||||
}
|
||||
return cmdCfg.AllowUsers, nil
|
||||
}
|
||||
|
||||
func AddRejectUser(cmdName string, userID uint64) error {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
cmdCfg = &CmdConfig{Name: cmdName}
|
||||
Cfg.Permissions.Cmds = append(Cfg.Permissions.Cmds, *cmdCfg)
|
||||
cmdCfg = &Cfg.Permissions.Cmds[len(Cfg.Permissions.Cmds)-1]
|
||||
}
|
||||
if slices.Contains(cmdCfg.RejectUsers, userID) {
|
||||
return fmt.Errorf("user already in reject list")
|
||||
}
|
||||
cmdCfg.RejectUsers = append(cmdCfg.RejectUsers, userID)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func RemoveRejectUser(cmdName string, userID uint64) error {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return fmt.Errorf("command config not found")
|
||||
}
|
||||
idx := -1
|
||||
for i, id := range cmdCfg.RejectUsers {
|
||||
if id == userID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("user not in reject list")
|
||||
}
|
||||
cmdCfg.RejectUsers = append(cmdCfg.RejectUsers[:idx], cmdCfg.RejectUsers[idx+1:]...)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func GetRejectUsers(cmdName string) ([]uint64, error) {
|
||||
cmdCfg := Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return []uint64{}, nil
|
||||
}
|
||||
return cmdCfg.RejectUsers, nil
|
||||
}
|
||||
|
||||
var configPath string
|
||||
|
||||
func SetConfigPath(path string) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/awfufu/go-hurobot/config"
|
||||
@@ -38,6 +40,68 @@ func (dbMessages) TableName() string {
|
||||
return "messages"
|
||||
}
|
||||
|
||||
type DbPermissions struct {
|
||||
Command string `gorm:"primaryKey;column:command"`
|
||||
UserDefault string `gorm:"not null;column:user_default;default:master"` // guest, admin, master
|
||||
GroupDefault string `gorm:"not null;column:group_default;default:disable"` // enable, disable
|
||||
AllowUsers string `gorm:"column:allow_users"` // CSV string: "123,456"
|
||||
RejectUsers string `gorm:"column:reject_users"` // CSV string: "123,456"
|
||||
AllowGroups string `gorm:"column:allow_groups"` // CSV string: "123,456"
|
||||
RejectGroups string `gorm:"column:reject_groups"` // CSV string: "123,456"
|
||||
}
|
||||
|
||||
func (DbPermissions) TableName() string {
|
||||
return "permissions"
|
||||
}
|
||||
|
||||
func (p *DbPermissions) ParseAllowUsers() []uint64 {
|
||||
return ParseIDList(p.AllowUsers)
|
||||
}
|
||||
|
||||
func (p *DbPermissions) ParseRejectUsers() []uint64 {
|
||||
return ParseIDList(p.RejectUsers)
|
||||
}
|
||||
|
||||
func (p *DbPermissions) ParseAllowGroups() []uint64 {
|
||||
return ParseIDList(p.AllowGroups)
|
||||
}
|
||||
|
||||
func (p *DbPermissions) ParseRejectGroups() []uint64 {
|
||||
return ParseIDList(p.RejectGroups)
|
||||
}
|
||||
|
||||
func ParseIDList(s string) []uint64 {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
// Simple split by comma
|
||||
// Assuming valid string like "123,456"
|
||||
// To be safe against empty parts like "123,,456", we can filter
|
||||
// But user said "only digits and commas".
|
||||
parts := strings.Split(s, ",")
|
||||
res := make([]uint64, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if val, err := strconv.ParseUint(part, 10, 64); err == nil {
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func JoinIDList(ids []uint64) string {
|
||||
if len(ids) == 0 {
|
||||
return ""
|
||||
}
|
||||
strs := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
strs[i] = strconv.FormatUint(id, 10)
|
||||
}
|
||||
return strings.Join(strs, ",")
|
||||
}
|
||||
|
||||
func InitDB() {
|
||||
var err error
|
||||
// Ensure directory exists
|
||||
@@ -50,7 +114,7 @@ func InitDB() {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
PsqlConnected = true
|
||||
PsqlDB.AutoMigrate(&dbUsers{}, &dbMessages{})
|
||||
PsqlDB.AutoMigrate(&dbUsers{}, &dbMessages{}, &DbPermissions{})
|
||||
}
|
||||
|
||||
func SaveDatabase(msg *qbot.Message) error {
|
||||
@@ -83,3 +147,15 @@ func SaveDatabase(msg *qbot.Message) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetCommandPermission(cmd string) *DbPermissions {
|
||||
var perm DbPermissions
|
||||
if err := PsqlDB.Where("command = ?", cmd).First(&perm).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
return &perm
|
||||
}
|
||||
|
||||
func SaveCommandPermission(perm *DbPermissions) error {
|
||||
return PsqlDB.Save(perm).Error
|
||||
}
|
||||
|
||||
4
main.go
4
main.go
@@ -13,8 +13,8 @@ func main() {
|
||||
config.LoadConfigFile()
|
||||
db.InitDB()
|
||||
|
||||
bot := qbot.NewBot(config.Cfg.ReverseHttpListen)
|
||||
bot.ConnectNapcat(config.Cfg.NapcatHttpServer)
|
||||
bot := qbot.NewBot(config.Cfg.HttpListen)
|
||||
bot.ConnectNapcat(config.Cfg.HttpRemote)
|
||||
|
||||
bot.OnMessage(func(b *qbot.Bot, msg *qbot.Message) {
|
||||
if msg.ChatType != qbot.Group {
|
||||
|
||||
Reference in New Issue
Block a user