refactor: replace config command with perm and move permissions to db

This commit is contained in:
2025-12-15 23:49:09 +08:00
parent 175193c606
commit 3f46c7fc9d
6 changed files with 450 additions and 559 deletions

View File

@@ -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 {

View File

@@ -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
View 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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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 {