mirror of
https://github.com/awfufu/go-hurobot.git
synced 2026-03-01 05:29:43 +08:00
refactor: replace WebSocket with bidirectional HTTP for NapCat communication
refactor: migrate command handling from function pointers to object-oriented interface feat: replace run.sh with YAML-based configuration file
This commit is contained in:
@@ -2,46 +2,66 @@ package cmds
|
||||
|
||||
import (
|
||||
"go-hurobot/qbot"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_callme(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
var callmeHelpMsg string = `Set or query your nickname.
|
||||
Usage:
|
||||
/callme
|
||||
/callme <nickname>
|
||||
/callme <@user>
|
||||
/callme <@user> <nickname>`
|
||||
|
||||
type CallmeCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewCallmeCommand() *CallmeCommand {
|
||||
return &CallmeCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "callme",
|
||||
HelpMsg: callmeHelpMsg,
|
||||
Permission: getCmdPermLevel("callme"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 3, // callme <nickname> <@id>
|
||||
MinArgs: 1, // callme
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *CallmeCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *CallmeCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
var targetID uint64
|
||||
var nickname string
|
||||
var isQuery bool = true
|
||||
|
||||
switch args.Size {
|
||||
switch len(args) {
|
||||
case 1: // callme
|
||||
targetID = msg.UserID
|
||||
targetID = src.UserID
|
||||
case 2: // callme <nickname> / callme <@id>
|
||||
if args.Types[1] == qbot.At {
|
||||
if strings.HasPrefix(args[1], "--at=") {
|
||||
// callme <@id>
|
||||
targetID = str2uin64(args.Contents[1])
|
||||
targetID = str2uin64(strings.TrimPrefix(args[1], "--at="))
|
||||
} else {
|
||||
// callme <nickname>
|
||||
targetID = msg.UserID
|
||||
nickname = args.Contents[1]
|
||||
targetID = src.UserID
|
||||
nickname = args[1]
|
||||
isQuery = false
|
||||
}
|
||||
case 3: // callme <@id> <nickname> 或 callme <nickname> <@id>
|
||||
case 3: // callme <nickname> <@id>
|
||||
isQuery = false
|
||||
if args.Types[1] == qbot.At {
|
||||
// callme <@id> <nickname>
|
||||
targetID = str2uin64(args.Contents[1])
|
||||
nickname = args.Contents[2]
|
||||
} else if args.Types[2] == qbot.At {
|
||||
// callme <nickname> <@id>
|
||||
targetID = str2uin64(args.Contents[2])
|
||||
nickname = args.Contents[1]
|
||||
if userid, ok := strings.CutPrefix(args[2], "--at="); ok {
|
||||
targetID = str2uin64(userid)
|
||||
nickname = args[1]
|
||||
} else {
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.SendMsg(msg, `Usage:
|
||||
- callme
|
||||
- callme <nickname>
|
||||
- callme <@id>
|
||||
- callme <@id> <nickname>`)
|
||||
c.SendMsg(src.GroupID, src.UserID, callmeHelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,14 +69,14 @@ func cmd_callme(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
var user qbot.Users
|
||||
result := qbot.PsqlDB.Where("user_id = ?", targetID).First(&user)
|
||||
if result.Error != nil || user.Nickname == "" {
|
||||
c.SendMsg(msg, "")
|
||||
c.SendMsg(src.GroupID, src.UserID, "")
|
||||
return
|
||||
}
|
||||
c.SendMsg(msg, user.Nickname)
|
||||
c.SendMsg(src.GroupID, src.UserID, user.Nickname)
|
||||
} else {
|
||||
user := qbot.Users{
|
||||
UserID: targetID,
|
||||
Name: msg.Nickname,
|
||||
Name: nickname,
|
||||
Nickname: nickname,
|
||||
}
|
||||
|
||||
@@ -65,13 +85,13 @@ func cmd_callme(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
).FirstOrCreate(&user)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "failed")
|
||||
c.SendMsg(src.GroupID, src.UserID, "failed")
|
||||
return
|
||||
}
|
||||
if targetID == msg.UserID {
|
||||
c.SendMsg(msg, "Update nickname: "+nickname)
|
||||
if targetID == src.UserID {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Update nickname: "+nickname)
|
||||
} else {
|
||||
c.SendMsg(msg, "Update nickname for "+strconv.FormatUint(targetID, 10)+": "+nickname)
|
||||
c.SendMsg(src.GroupID, src.UserID, "Update nickname for ["+qbot.CQAt(targetID)+"]: "+nickname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
378
cmds/cmds.go
378
cmds/cmds.go
@@ -6,158 +6,297 @@ import (
|
||||
|
||||
"github.com/google/shlex"
|
||||
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
var maxCommandLength int = 0
|
||||
|
||||
type ArgsList struct {
|
||||
Contents []string
|
||||
Types []qbot.MsgType
|
||||
Size int
|
||||
type srcMsg struct {
|
||||
MsgID uint64
|
||||
UserID uint64
|
||||
GroupID uint64
|
||||
Card string
|
||||
Role string
|
||||
Time uint64
|
||||
Raw string
|
||||
}
|
||||
|
||||
type CmdHandler func(*qbot.Client, *qbot.Message, *ArgsList)
|
||||
const (
|
||||
atPrefix string = "--at="
|
||||
replyPrefix string = "--reply="
|
||||
facePrefix string = "--face="
|
||||
imagePrefix string = "--image="
|
||||
recordPrefix string = "--record="
|
||||
filePrefix string = "--file="
|
||||
forwardPrefix string = "--forward="
|
||||
jsonPrefix string = "--json="
|
||||
)
|
||||
|
||||
var cmdMap map[string]CmdHandler
|
||||
type command interface {
|
||||
Self() *cmdBase
|
||||
Exec(c *qbot.Client, args []string, src *srcMsg, begin int)
|
||||
}
|
||||
|
||||
type cmdBase struct {
|
||||
Name string // Command name
|
||||
HelpMsg string // Help message
|
||||
Permission config.Permission // Permission requirement
|
||||
AllowPrefix bool // Whether to allow prefixes (like @)
|
||||
NeedRawMsg bool // Whether raw message is needed
|
||||
MaxArgs int // Maximum number of arguments
|
||||
MinArgs int // Minimum number of arguments
|
||||
}
|
||||
|
||||
const commandPrefix = '/'
|
||||
|
||||
var cmdMap map[string]command
|
||||
|
||||
func init() {
|
||||
cmdMap = map[string]CmdHandler{
|
||||
"echo": cmd_echo,
|
||||
"specialtitle": cmd_specialtitle,
|
||||
"psql": cmd_psql,
|
||||
"group": cmd_group,
|
||||
"delete": cmd_delete,
|
||||
"llm": cmd_llm,
|
||||
"callme": cmd_callme,
|
||||
"debug": cmd_debug,
|
||||
"essence": cmd_essence,
|
||||
"draw": cmd_draw,
|
||||
"which": cmd_which,
|
||||
"fx": cmd_er,
|
||||
"crypto": cmd_crypto,
|
||||
"event": cmd_event,
|
||||
"sh": cmd_sh,
|
||||
"rps": cmd_rps,
|
||||
"dice": cmd_dice,
|
||||
"memberinfo": cmd_memberinfo,
|
||||
"info": cmd_info,
|
||||
"rcon": cmd_rcon,
|
||||
"mc": cmd_mc,
|
||||
}
|
||||
|
||||
for key := range cmdMap {
|
||||
if len(key) > maxCommandLength {
|
||||
maxCommandLength = len(key)
|
||||
}
|
||||
cmdMap = map[string]command{
|
||||
"callme": NewCallmeCommand(),
|
||||
"config": NewConfigCommand(),
|
||||
"crypto": NewCryptoCommand(),
|
||||
"delete": NewDeleteCommand(),
|
||||
"dice": NewDiceCommand(),
|
||||
"draw": NewDrawCommand(),
|
||||
"echo": NewEchoCommand(),
|
||||
"essence": NewEssenceCommand(),
|
||||
"fx": NewErCommand(),
|
||||
"group": NewGroupCommand(),
|
||||
"llm": NewLlmCommand(),
|
||||
"mc": NewMcCommand(),
|
||||
"memberinfo": NewMemberinfoCommand(),
|
||||
"psql": NewPsqlCommand(),
|
||||
"py": NewPyCommand(),
|
||||
"rcon": NewRconCommand(),
|
||||
"rps": NewRpsCommand(),
|
||||
"sh": NewShCommand(),
|
||||
"specialtitle": NewSpecialtitleCommand(),
|
||||
"testapi": NewTestapiCommand(),
|
||||
"which": NewWhichCommand(),
|
||||
}
|
||||
}
|
||||
|
||||
func HandleCommand(c *qbot.Client, msg *qbot.Message) bool {
|
||||
skip := 0
|
||||
if len(msg.Array) == 0 {
|
||||
func HandleCommand(c *qbot.Client, msg *qbot.Message) bool /* is command */ {
|
||||
cmdName, raw, skip := parseCmd(msg)
|
||||
if skip == -1 {
|
||||
return false
|
||||
}
|
||||
if msg.Array[0].Type == qbot.Reply {
|
||||
skip++
|
||||
if len(msg.Array) > 1 && msg.Array[1].Type == qbot.At {
|
||||
skip++
|
||||
}
|
||||
} else if msg.Array[0].Type == qbot.At {
|
||||
skip++
|
||||
|
||||
if cmdName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
var raw string
|
||||
if skip != 0 {
|
||||
if p := findNthClosingBracket(msg.Raw, skip); p != len(msg.Raw) {
|
||||
raw = msg.Raw
|
||||
msg.Raw = msg.Raw[p:]
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
cmd, exists := cmdMap[cmdName]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
handler := findCommand(getCommandName(msg.Raw))
|
||||
if handler != nil {
|
||||
if args := splitArguments(msg, skip); args != nil {
|
||||
handler(c, msg, args)
|
||||
}
|
||||
|
||||
src := &srcMsg{
|
||||
MsgID: msg.MsgID,
|
||||
UserID: msg.UserID,
|
||||
GroupID: msg.GroupID,
|
||||
Card: msg.Card,
|
||||
Role: msg.Role,
|
||||
Time: msg.Time,
|
||||
Raw: raw,
|
||||
}
|
||||
if raw != "" {
|
||||
msg.Raw = raw
|
||||
cmdBase := cmd.Self()
|
||||
|
||||
// check permission
|
||||
if !checkCmdPermission(cmdBase.Name, src.UserID) {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmdBase.Name+": Permission denied")
|
||||
return true
|
||||
}
|
||||
return handler != nil
|
||||
|
||||
// check if allow prefix
|
||||
if skip != 0 && !cmdBase.AllowPrefix {
|
||||
return true
|
||||
}
|
||||
|
||||
// parse arguments
|
||||
var args []string
|
||||
if cmdBase.NeedRawMsg {
|
||||
args = []string{cmdName, raw}
|
||||
} else {
|
||||
args = splitArguments(msg)
|
||||
}
|
||||
if args == nil {
|
||||
return false
|
||||
}
|
||||
argCount := len(args) - skip
|
||||
if (cmdBase.MinArgs > 0 && argCount < cmdBase.MinArgs) || (cmdBase.MaxArgs > 0 && argCount > cmdBase.MaxArgs) {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmdBase.HelpMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
// check if is help command
|
||||
if isHelpRequest(args, skip) {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmdBase.HelpMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
// execute command
|
||||
cmd.Exec(c, args, src, skip)
|
||||
return true
|
||||
}
|
||||
|
||||
func splitArguments(msg *qbot.Message, skip int) *ArgsList {
|
||||
result := &ArgsList{
|
||||
Contents: make([]string, 0, 20),
|
||||
Types: make([]qbot.MsgType, 0, 20),
|
||||
Size: 0,
|
||||
// calculate the number of prefixes to skip
|
||||
func parseCmd(msg *qbot.Message) (string, string, int) {
|
||||
if len(msg.Array) == 0 {
|
||||
return "", "", -1
|
||||
}
|
||||
|
||||
if skip < 0 {
|
||||
skip = 0
|
||||
for skip := 0; skip < len(msg.Array); skip++ {
|
||||
switch msg.Array[skip].Type {
|
||||
case qbot.Reply, qbot.At:
|
||||
continue
|
||||
case qbot.Text:
|
||||
content := msg.Array[skip].Content
|
||||
|
||||
// Skip leading spaces
|
||||
offset := 0
|
||||
for offset < len(content) && content[offset] == ' ' {
|
||||
offset++
|
||||
}
|
||||
|
||||
// Check if starts with command prefix
|
||||
if offset >= len(content) || content[offset] != commandPrefix {
|
||||
return "", "", -1
|
||||
}
|
||||
offset++ // skip the '/' character
|
||||
|
||||
// Find the skip-th ']' character in msg.Raw and extract content after it
|
||||
rawStart := 0
|
||||
for i := 0; i < skip; i++ {
|
||||
idx := strings.Index(msg.Raw[rawStart:], "]")
|
||||
if idx == -1 {
|
||||
break
|
||||
}
|
||||
rawStart += idx + 1
|
||||
}
|
||||
|
||||
// Skip to the command prefix in raw
|
||||
for rawStart < len(msg.Raw) && msg.Raw[rawStart] != commandPrefix {
|
||||
rawStart++
|
||||
}
|
||||
if rawStart >= len(msg.Raw) {
|
||||
return "", "", -1
|
||||
}
|
||||
rawStart++ // skip the '/' character
|
||||
|
||||
raw := msg.Raw[rawStart:]
|
||||
|
||||
// Find command name (up to first space)
|
||||
cmdIndex := strings.Index(raw, " ")
|
||||
if cmdIndex == -1 {
|
||||
return raw, "", skip
|
||||
}
|
||||
return raw[:cmdIndex], raw[cmdIndex+1:], skip
|
||||
default:
|
||||
return "", "", -1
|
||||
}
|
||||
}
|
||||
return "", "", -1
|
||||
}
|
||||
|
||||
// isHelpRequest checks if it is a help request
|
||||
func isHelpRequest(args []string, skip int) bool {
|
||||
if len(args) <= skip {
|
||||
return false
|
||||
}
|
||||
firstArg := args[skip]
|
||||
return firstArg == "-h" || firstArg == "-?" || firstArg == "--help"
|
||||
}
|
||||
|
||||
func getCmdPermLevel(cmdName string) config.Permission {
|
||||
cmdCfg := config.Cfg.GetCmdConfig(cmdName)
|
||||
if cmdCfg == nil {
|
||||
return config.Master
|
||||
}
|
||||
return cmdCfg.GetPermissionLevel()
|
||||
}
|
||||
|
||||
// checkCmdPermission checks if user has permission to execute specified command
|
||||
// considering permission, allow_users, reject_users in command configuration
|
||||
func checkCmdPermission(cmdName string, userID uint64) bool {
|
||||
cmdCfg := config.Cfg.GetCmdConfig(cmdName)
|
||||
|
||||
// If not configured, use default permission check
|
||||
if cmdCfg == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if skip >= len(msg.Array) {
|
||||
return result
|
||||
userPerm := config.GetUserPermission(userID)
|
||||
requiredPerm := cmdCfg.GetPermissionLevel()
|
||||
|
||||
// Master users are not restricted by reject_users
|
||||
if userPerm == config.Master {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, item := range msg.Array[skip:] {
|
||||
// Check reject_users (effective when command permission < master)
|
||||
if requiredPerm < config.Master && cmdCfg.IsInRejectList(userID) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Check basic permission
|
||||
return userPerm >= requiredPerm
|
||||
}
|
||||
|
||||
func splitArguments(msg *qbot.Message) []string {
|
||||
result := make([]string, 0, 20)
|
||||
|
||||
firstText := true
|
||||
for _, item := range msg.Array {
|
||||
if item.Type == qbot.Text {
|
||||
texts, err := shlex.Split(item.Content)
|
||||
content := item.Content
|
||||
if firstText {
|
||||
content = item.Content[1:]
|
||||
firstText = false
|
||||
}
|
||||
texts, err := shlex.Split(content)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
result.Contents = append(result.Contents, texts...)
|
||||
result.Types = appendRepeatedValues(result.Types, qbot.Text, len(texts))
|
||||
result.Size += len(texts)
|
||||
result = append(result, texts...)
|
||||
} else {
|
||||
result.Contents = append(result.Contents, item.Content)
|
||||
result.Types = append(result.Types, item.Type)
|
||||
result.Size++
|
||||
result = append(result, msgItemToArg(item))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func findNthClosingBracket(s string, n int) int {
|
||||
count := 0
|
||||
for i, char := range s {
|
||||
if char == ']' {
|
||||
count++
|
||||
if count == n {
|
||||
i++
|
||||
for i < len(s) && s[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
func msgItemToArg(item qbot.MsgItem) string {
|
||||
var prefix string
|
||||
switch item.Type {
|
||||
case qbot.At:
|
||||
prefix = atPrefix
|
||||
case qbot.Face:
|
||||
prefix = facePrefix
|
||||
case qbot.Image:
|
||||
prefix = imagePrefix
|
||||
case qbot.Record:
|
||||
prefix = recordPrefix
|
||||
case qbot.Reply:
|
||||
prefix = replyPrefix
|
||||
case qbot.File:
|
||||
prefix = filePrefix
|
||||
case qbot.Forward:
|
||||
prefix = forwardPrefix
|
||||
case qbot.Json:
|
||||
prefix = jsonPrefix
|
||||
default:
|
||||
return item.Content
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func findCommand(cmd string) CmdHandler {
|
||||
if cmd == "" {
|
||||
return nil
|
||||
}
|
||||
return cmdMap[cmd]
|
||||
}
|
||||
|
||||
func getCommandName(s string) string {
|
||||
sliced := false
|
||||
if len(s) > maxCommandLength+1 {
|
||||
s = s[:maxCommandLength+1]
|
||||
sliced = true
|
||||
}
|
||||
if i := strings.IndexAny(s, " \n"); i != -1 {
|
||||
return s[:i]
|
||||
}
|
||||
if sliced {
|
||||
return ""
|
||||
}
|
||||
return s
|
||||
return prefix + item.Content
|
||||
}
|
||||
|
||||
func decodeSpecialChars(raw string) string {
|
||||
@@ -189,12 +328,3 @@ func str2uin64(s string) uint64 {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func appendRepeatedValues[T any](slice []T, value T, count int) []T {
|
||||
newSlice := make([]T, len(slice)+count)
|
||||
copy(newSlice, slice)
|
||||
for i := len(slice); i < len(newSlice); i++ {
|
||||
newSlice[i] = value
|
||||
}
|
||||
return newSlice
|
||||
}
|
||||
|
||||
317
cmds/config.go
Normal file
317
cmds/config.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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,
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
subCmd := args[1]
|
||||
switch subCmd {
|
||||
case "admin":
|
||||
cmd.handleAdmin(c, args, src)
|
||||
case "allow":
|
||||
cmd.handleAllow(c, args, src)
|
||||
case "reject":
|
||||
cmd.handleReject(c, args, src)
|
||||
case "reload":
|
||||
cmd.handleReload(c, src)
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, "Unknown subcommand: "+subCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleAdmin(c *qbot.Client, args []string, src *srcMsg) {
|
||||
if len(args) < 3 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config admin [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := args[2]
|
||||
switch action {
|
||||
case "add":
|
||||
if len(args) < 4 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config admin add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[3:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(args) < 4 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config admin rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[3:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
admins := config.GetAdmins()
|
||||
if len(admins) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Admin list is empty")
|
||||
} else {
|
||||
adminStrs := make([]string, len(admins))
|
||||
for i, u := range admins {
|
||||
adminStrs[i] = strconv.FormatUint(u, 10)
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Admins: %s", strings.Join(adminStrs, ", ")))
|
||||
}
|
||||
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func extractUserIDs(args []string) []uint64 {
|
||||
userIDs := make([]uint64, 0, len(args))
|
||||
seen := make(map[uint64]bool)
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.HasPrefix(arg, atPrefix) {
|
||||
continue
|
||||
}
|
||||
userID := str2uin64(strings.TrimPrefix(arg, atPrefix))
|
||||
if userID != 0 && !seen[userID] {
|
||||
seen[userID] = true
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
}
|
||||
return userIDs
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleAllow(c *qbot.Client, args []string, src *srcMsg) {
|
||||
if len(args) < 3 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config allow <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := args[2]
|
||||
if len(args) < 4 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config allow <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := args[3]
|
||||
switch action {
|
||||
case "add":
|
||||
if len(args) < 5 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config allow <cmd> add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[4:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(args) < 5 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config allow <cmd> rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[4:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
users, err := config.GetAllowUsers(cmdName)
|
||||
if err != nil {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed to get allow list: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, 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)
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Allow list for %s: %s", cmdName, strings.Join(userStrs, ", ")))
|
||||
}
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleReject(c *qbot.Client, args []string, src *srcMsg) {
|
||||
if len(args) < 3 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config reject <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
cmdName := args[2]
|
||||
if len(args) < 4 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config reject <cmd> [add|rm|list] [@user...]")
|
||||
return
|
||||
}
|
||||
|
||||
action := args[3]
|
||||
switch action {
|
||||
case "add":
|
||||
if len(args) < 5 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config reject <cmd> add @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[4:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "rm":
|
||||
if len(args) < 5 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage: config reject <cmd> rm @user...")
|
||||
return
|
||||
}
|
||||
userIDs := extractUserIDs(args[4:])
|
||||
if len(userIDs) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "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))
|
||||
}
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(results, "\n"))
|
||||
|
||||
case "list":
|
||||
users, err := config.GetRejectUsers(cmdName)
|
||||
if err != nil {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed to get reject list: "+err.Error())
|
||||
return
|
||||
}
|
||||
if len(users) == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, 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)
|
||||
}
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Reject list for %s: %s", cmdName, strings.Join(userStrs, ", ")))
|
||||
}
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, "Unknown action: "+action)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ConfigCommand) handleReload(c *qbot.Client, src *srcMsg) {
|
||||
err := config.ReloadConfig()
|
||||
if err != nil {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed to reload config: "+err.Error())
|
||||
} else {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Configuration reloaded successfully")
|
||||
}
|
||||
}
|
||||
131
cmds/crypto.go
131
cmds/crypto.go
@@ -67,157 +67,176 @@ type TickerResp struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func cmd_crypto(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if args.Size < 2 {
|
||||
c.SendMsg(msg, "用法:\n1.crypto <币种> - 查询币种对USDT价格\n2.crypto <源币种> <目标币种> - 查询币种对目标货币价格\n例如: crypto BTC 或 crypto BTC USD")
|
||||
return
|
||||
}
|
||||
const cryptoHelpMsg string = `Query cryptocurrency prices.
|
||||
Usage:
|
||||
/crypto <coin> - Query coin price in USDT
|
||||
/crypto <from_coin> <to_coin> - Query coin price in target currency
|
||||
Examples:
|
||||
/crypto BTC
|
||||
/crypto BTC USD`
|
||||
|
||||
if args.Size == 2 {
|
||||
coin := strings.ToUpper(args.Contents[1])
|
||||
handleSingleCrypto(c, msg, coin)
|
||||
return
|
||||
}
|
||||
|
||||
if args.Size == 3 {
|
||||
fromCoin := strings.ToUpper(args.Contents[1])
|
||||
toCurrency := strings.ToUpper(args.Contents[2])
|
||||
handleCryptoCurrencyPair(c, msg, fromCoin, toCurrency)
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, "参数数量错误")
|
||||
type CryptoCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func handleSingleCrypto(c *qbot.Client, msg *qbot.Message, coin string) {
|
||||
log.Printf("查询单个加密货币: %s", coin)
|
||||
func NewCryptoCommand() *CryptoCommand {
|
||||
return &CryptoCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "crypto",
|
||||
HelpMsg: cryptoHelpMsg,
|
||||
Permission: getCmdPermLevel("crypto"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 3,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *CryptoCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *CryptoCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if len(args) == 2 {
|
||||
coin := strings.ToUpper(args[1])
|
||||
handleSingleCrypto(c, src, coin)
|
||||
} else if len(args) == 3 {
|
||||
fromCoin := strings.ToUpper(args[1])
|
||||
toCurrency := strings.ToUpper(args[2])
|
||||
handleCryptoCurrencyPair(c, src, fromCoin, toCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSingleCrypto(c *qbot.Client, src *srcMsg, coin string) {
|
||||
log.Printf("Query single cryptocurrency: %s", coin)
|
||||
price, err := getCryptoPrice(coin, "USDT")
|
||||
if err != nil {
|
||||
log.Printf("查询%s价格失败: %v", coin, err)
|
||||
c.SendMsg(msg, fmt.Sprintf("查询失败: %s", err.Error()))
|
||||
log.Printf("Failed to query %s price: %v", coin, err)
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Query failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
c.SendMsg(msg, fmt.Sprintf("1 %s = %s USDT", coin, price))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("1 %s = %s USDT", coin, price))
|
||||
}
|
||||
|
||||
func handleCryptoCurrencyPair(c *qbot.Client, msg *qbot.Message, fromCoin string, toCurrency string) {
|
||||
log.Printf("查询加密货币对: %s -> %s", fromCoin, toCurrency)
|
||||
func handleCryptoCurrencyPair(c *qbot.Client, src *srcMsg, fromCoin string, toCurrency string) {
|
||||
log.Printf("Query cryptocurrency pair: %s -> %s", fromCoin, toCurrency)
|
||||
|
||||
usdPrice, err := getCryptoPrice(fromCoin, "USD")
|
||||
if err != nil {
|
||||
log.Printf("查询%s USD价格失败: %v", fromCoin, err)
|
||||
c.SendMsg(msg, fmt.Sprintf("查询%s价格失败: %s", fromCoin, err.Error()))
|
||||
log.Printf("Failed to query %s USD price: %v", fromCoin, err)
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Failed to query %s price: %s", fromCoin, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
usdPriceFloat, err := strconv.ParseFloat(usdPrice, 64)
|
||||
if err != nil {
|
||||
log.Printf("价格解析失败: %v", err)
|
||||
c.SendMsg(msg, fmt.Sprintf("价格解析失败: %s", err.Error()))
|
||||
log.Printf("Price parsing failed: %v", err)
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Price parsing failed: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if toCurrency == "USD" {
|
||||
c.SendMsg(msg, fmt.Sprintf("%s 最新USD价格: %.4f", fromCoin, usdPriceFloat))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%s latest USD price: %.4f", fromCoin, usdPriceFloat))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("需要汇率换算: USD -> %s", toCurrency)
|
||||
log.Printf("Need exchange rate conversion: USD -> %s", toCurrency)
|
||||
exchangeRate, err := getExchangeRate("USD", toCurrency)
|
||||
if err != nil {
|
||||
log.Printf("获取汇率失败: %v", err)
|
||||
c.SendMsg(msg, fmt.Sprintf("获取汇率失败: %s", err.Error()))
|
||||
log.Printf("Failed to get exchange rate: %v", err)
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Failed to get exchange rate: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
finalPrice := usdPriceFloat * exchangeRate
|
||||
log.Printf("换算完成: %s USD价格 %.4f, 汇率 %.4f, 最终价格 %.4f %s", fromCoin, usdPriceFloat, exchangeRate, finalPrice, toCurrency)
|
||||
c.SendMsg(msg, fmt.Sprintf("1 %s=%.4f %s", fromCoin, finalPrice, toCurrency))
|
||||
log.Printf("Conversion complete: %s USD price %.4f, exchange rate %.4f, final price %.4f %s", fromCoin, usdPriceFloat, exchangeRate, finalPrice, toCurrency)
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("1 %s=%.4f %s", fromCoin, finalPrice, toCurrency))
|
||||
}
|
||||
|
||||
func getCryptoPrice(coin string, quoteCurrency string) (string, error) {
|
||||
instId := coin + "-" + quoteCurrency + "-SWAP"
|
||||
url := "https://bot-forward.lavacreeper.net/api/v5/market/ticker?instId=" + instId
|
||||
|
||||
log.Printf("请求加密货币价格: %s", url)
|
||||
log.Printf("Request cryptocurrency price: %s", url)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求创建失败: %v", err)
|
||||
return "", fmt.Errorf("request creation failed: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "Okx-Python-Client")
|
||||
req.Header.Set("X-API-Key", config.OkxMirrorAPIKey)
|
||||
req.Header.Set("X-API-Key", config.Cfg.ApiKeys.OkxMirrorAPIKey)
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求失败: %v", err)
|
||||
return "", fmt.Errorf("request failed: %v", err)
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("关闭响应体失败: %v", err)
|
||||
log.Printf("Failed to close response body: %v", err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("HTTP错误: %d", resp.StatusCode)
|
||||
return "", fmt.Errorf("HTTP error: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var ticker TickerResp
|
||||
if err := json.NewDecoder(resp.Body).Decode(&ticker); err != nil {
|
||||
return "", fmt.Errorf("解析失败: %v", err)
|
||||
return "", fmt.Errorf("parsing failed: %v", err)
|
||||
}
|
||||
|
||||
if ticker.Code != "0" || len(ticker.Data) == 0 {
|
||||
return "", fmt.Errorf("API返回错误: %s", ticker.Msg)
|
||||
return "", fmt.Errorf("API returned error: %s", ticker.Msg)
|
||||
}
|
||||
|
||||
log.Printf("获取到价格: %s = %s", instId, ticker.Data[0].Last)
|
||||
log.Printf("Got price: %s = %s", instId, ticker.Data[0].Last)
|
||||
return ticker.Data[0].Last, nil
|
||||
}
|
||||
|
||||
func getExchangeRate(baseCode string, targetCode string) (float64, error) {
|
||||
if config.ExchangeRateAPIKey == "" {
|
||||
return 0, fmt.Errorf("汇率API密钥未配置")
|
||||
if config.Cfg.ApiKeys.ExchangeRateAPIKey == "" {
|
||||
return 0, fmt.Errorf("exchange rate API key not configured")
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://v6.exchangerate-api.com/v6/%s/latest/%s", config.ExchangeRateAPIKey, baseCode)
|
||||
url := fmt.Sprintf("https://v6.exchangerate-api.com/v6/%s/latest/%s", config.Cfg.ApiKeys.ExchangeRateAPIKey, baseCode)
|
||||
|
||||
log.Printf("请求汇率: %s", url)
|
||||
log.Printf("Request exchange rate: %s", url)
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("汇率请求失败: %v", err)
|
||||
return 0, fmt.Errorf("exchange rate request failed: %v", err)
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("关闭响应体失败: %v", err)
|
||||
log.Printf("Failed to close response body: %v", err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, fmt.Errorf("汇率API HTTP错误: %d", resp.StatusCode)
|
||||
return 0, fmt.Errorf("exchange rate API HTTP error: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var exchangeData ExchangeRateResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&exchangeData); err != nil {
|
||||
return 0, fmt.Errorf("汇率数据解析失败: %v", err)
|
||||
return 0, fmt.Errorf("exchange rate data parsing failed: %v", err)
|
||||
}
|
||||
|
||||
if exchangeData.Result != "success" {
|
||||
return 0, fmt.Errorf("汇率API返回错误: %s", exchangeData.Result)
|
||||
return 0, fmt.Errorf("exchange rate API returned error: %s", exchangeData.Result)
|
||||
}
|
||||
|
||||
rate, exists := exchangeData.ConversionRates[targetCode]
|
||||
if !exists {
|
||||
return 0, fmt.Errorf("不支持的货币: %s", targetCode)
|
||||
return 0, fmt.Errorf("unsupported currency: %s", targetCode)
|
||||
}
|
||||
|
||||
log.Printf("获取到汇率: 1 %s = %f %s", baseCode, rate, targetCode)
|
||||
log.Printf("Got exchange rate: 1 %s = %f %s", baseCode, rate, targetCode)
|
||||
return rate, nil
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_debug(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if msg.UserID == config.MasterID {
|
||||
c.SendMsg(msg, decodeSpecialChars(strings.Trim(msg.Raw[6:], " \n")))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,25 +3,48 @@ package cmds
|
||||
import (
|
||||
"go-hurobot/qbot"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_delete(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
// 禁止某个无锡人滥用删除功能,只有0.6%概率允许
|
||||
if msg.UserID == 3112813730 {
|
||||
if rand.Float64() > 0.006 {
|
||||
c.SendMsg(msg, "无锡人本次运气不佳,删除失败!建议明天再试,或者考虑搬家")
|
||||
return
|
||||
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"),
|
||||
AllowPrefix: true, // Allow prefix
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 1,
|
||||
MinArgs: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *DeleteCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *DeleteCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
// Check for --reply= parameter
|
||||
var replyMsgID uint64
|
||||
if after, ok := strings.CutPrefix(args[0], "--reply="); ok {
|
||||
if msgid, err := strconv.ParseUint(after, 10, 64); err == nil {
|
||||
replyMsgID = msgid
|
||||
}
|
||||
}
|
||||
|
||||
if msg.Array[0].Type == qbot.Reply {
|
||||
if msgid, err := strconv.ParseUint(msg.Array[0].Content, 10, 64); err == nil {
|
||||
c.DeleteMsg(msgid)
|
||||
log.Printf("delete message %d", msgid)
|
||||
}
|
||||
if replyMsgID != 0 {
|
||||
c.DeleteMsg(replyMsgID)
|
||||
log.Printf("delete message %d", replyMsgID)
|
||||
} else {
|
||||
c.SendMsg(msg, "请回复一条需要删除的消息,并确保 bot 有权限删除它")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Please reply to a message to delete it, and ensure the bot has permission to delete it")
|
||||
}
|
||||
}
|
||||
|
||||
28
cmds/dice.go
28
cmds/dice.go
@@ -4,6 +4,30 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_dice(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
c.SendMsg(msg, qbot.CQDice())
|
||||
const diceHelpMsg = "Roll a dice.\nUsage: /dice"
|
||||
|
||||
type DiceCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewDiceCommand() *DiceCommand {
|
||||
return &DiceCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "dice",
|
||||
HelpMsg: diceHelpMsg,
|
||||
Permission: getCmdPermLevel("dice"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 1,
|
||||
MinArgs: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *DiceCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *DiceCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQDice())
|
||||
}
|
||||
|
||||
70
cmds/draw.go
70
cmds/draw.go
@@ -13,6 +13,11 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
const drawHelpMsg string = `Generate images from text prompts.
|
||||
Usage: /draw <prompt> [--size <size>]
|
||||
Supported sizes: 1328x1328, 1584x1056, 1140x1472, 1664x928, 928x1664
|
||||
Example: /draw a cat --size 1328x1328`
|
||||
|
||||
type ImageGenerationRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
@@ -31,30 +36,45 @@ type ImageGenerationResponse struct {
|
||||
Seed int64 `json:"seed"`
|
||||
}
|
||||
|
||||
func cmd_draw(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if args.Size < 2 {
|
||||
helpMsg := `Usage: draw <prompt> [--size <1328x1328|1584x1056|1140x1472|1664x928|928x1664>]`
|
||||
c.SendMsg(msg, helpMsg)
|
||||
type DrawCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewDrawCommand() *DrawCommand {
|
||||
return &DrawCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "draw",
|
||||
HelpMsg: drawHelpMsg,
|
||||
Permission: getCmdPermLevel("draw"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *DrawCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *DrawCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if config.Cfg.ApiKeys.DrawApiKey == "" {
|
||||
c.SendMsg(src.GroupID, src.UserID, "No API key")
|
||||
return
|
||||
}
|
||||
|
||||
if config.ApiKey == "" {
|
||||
c.SendMsg(msg, "No API key")
|
||||
return
|
||||
}
|
||||
|
||||
prompt, imageSize, err := parseDrawArgs(args.Contents[1:])
|
||||
prompt, imageSize, err := parseDrawArgs(args[1:])
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if prompt == "" {
|
||||
c.SendMsg(msg, "Please provide a prompt")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Please provide a prompt")
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, "Image generating...")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Image generating...")
|
||||
|
||||
reqData := ImageGenerationRequest{
|
||||
Model: "Qwen/Qwen-Image",
|
||||
@@ -66,17 +86,17 @@ func cmd_draw(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
|
||||
jsonData, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.siliconflow.cn/v1/images/generations", bytes.NewBuffer(jsonData))
|
||||
req, err := http.NewRequest("POST", config.Cfg.ApiKeys.DrawUrlBase, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+config.ApiKey)
|
||||
req.Header.Set("Authorization", "Bearer "+config.Cfg.ApiKeys.DrawApiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
@@ -85,35 +105,35 @@ func cmd_draw(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
c.SendMsg(msg, fmt.Sprintf("%d\n%s", resp.StatusCode, string(body)))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%d\n%s", resp.StatusCode, string(body)))
|
||||
return
|
||||
}
|
||||
|
||||
var imgResp ImageGenerationResponse
|
||||
if err := json.Unmarshal(body, &imgResp); err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if len(imgResp.Images) == 0 {
|
||||
c.SendMsg(msg, "error: 未生成任何图片")
|
||||
c.SendMsg(src.GroupID, src.UserID, "error: no images generated")
|
||||
return
|
||||
}
|
||||
|
||||
imageURL := imgResp.Images[0].URL
|
||||
c.SendMsg(msg, qbot.CQReply(msg.MsgID)+qbot.CQImage(imageURL))
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+qbot.CQImage(imageURL))
|
||||
}
|
||||
|
||||
func parseDrawArgs(args []string) (prompt, imageSize string, err error) {
|
||||
@@ -130,12 +150,12 @@ func parseDrawArgs(args []string) (prompt, imageSize string, err error) {
|
||||
if i+1 < len(args) {
|
||||
size := args[i+1]
|
||||
if !isValidSize(size) {
|
||||
return "", "", fmt.Errorf("不支持的图片尺寸: %s\n支持的尺寸: 1328x1328, 1584x1056, 1140x1472, 1664x928, 928x1664", size)
|
||||
return "", "", fmt.Errorf("unsupported image size: %s\nSupported sizes: 1328x1328, 1584x1056, 1140x1472, 1664x928, 928x1664", size)
|
||||
}
|
||||
imageSize = size
|
||||
i += 2
|
||||
} else {
|
||||
return "", "", fmt.Errorf("--size: 需要指定尺寸值")
|
||||
return "", "", fmt.Errorf("--size: size value required")
|
||||
}
|
||||
default:
|
||||
promptParts = append(promptParts, arg)
|
||||
|
||||
41
cmds/echo.go
41
cmds/echo.go
@@ -5,6 +5,43 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_echo(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
c.SendMsg(msg, strings.Trim(msg.Raw[4:], " \n"))
|
||||
const echoHelpMsg string = `Echoes messages to a target destination.
|
||||
Usage: /echo [options] <content>
|
||||
Options:
|
||||
-d, -r Decode special characters
|
||||
-e Encode special characters
|
||||
Example: /echo "Hello, world!"`
|
||||
|
||||
type EchoCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewEchoCommand() *EchoCommand {
|
||||
return &EchoCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "echo",
|
||||
HelpMsg: echoHelpMsg,
|
||||
Permission: getCmdPermLevel("echo"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *EchoCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *EchoCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
if len(args) >= 3 && args[1][0] == '-' {
|
||||
switch args[1] {
|
||||
case "-r":
|
||||
c.SendMsg(src.GroupID, src.UserID, encodeSpecialChars(src.Raw[3:]))
|
||||
case "-d":
|
||||
c.SendMsg(src.GroupID, src.UserID, decodeSpecialChars(args[2]))
|
||||
}
|
||||
} else {
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(args[1:], " "))
|
||||
}
|
||||
}
|
||||
|
||||
85
cmds/er.go
85
cmds/er.go
@@ -1,85 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
type ExchangeRateResponse struct {
|
||||
Result string `json:"result"`
|
||||
BaseCode string `json:"base_code"`
|
||||
ConversionRates map[string]float64 `json:"conversion_rates"`
|
||||
}
|
||||
|
||||
func cmd_er(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if args.Size < 3 {
|
||||
c.SendMsg(msg, "用法: fx <源币种> <目标币种>\n例如: fx CNY HKD")
|
||||
return
|
||||
}
|
||||
|
||||
if config.ExchangeRateAPIKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
fromCurrency := strings.ToUpper(args.Contents[1])
|
||||
toCurrency := strings.ToUpper(args.Contents[2])
|
||||
|
||||
url := fmt.Sprintf("https://v6.exchangerate-api.com/v6/%s/latest/%s", config.ExchangeRateAPIKey, fromCurrency)
|
||||
|
||||
log.Println(url)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.SendMsg(msg, fmt.Sprintf("%d", resp.StatusCode))
|
||||
return
|
||||
}
|
||||
|
||||
var exchangeData ExchangeRateResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&exchangeData); err != nil {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if exchangeData.Result != "success" {
|
||||
c.SendMsg(msg, fmt.Sprintf("%v", exchangeData.Result))
|
||||
return
|
||||
}
|
||||
|
||||
toRate, exists := exchangeData.ConversionRates[toCurrency]
|
||||
if !exists {
|
||||
c.SendMsg(msg, fmt.Sprintf("Unsupported %s", toCurrency))
|
||||
return
|
||||
}
|
||||
|
||||
fromRate, exists := exchangeData.ConversionRates[fromCurrency]
|
||||
if !exists {
|
||||
c.SendMsg(msg, fmt.Sprintf("Unsupported %s", fromCurrency))
|
||||
return
|
||||
}
|
||||
|
||||
rate1to2 := toRate / fromRate
|
||||
rate2to1 := fromRate / toRate
|
||||
|
||||
result := fmt.Sprintf("1 %s = %.4f %s\n1 %s = %.4f %s",
|
||||
fromCurrency, rate1to2, toCurrency,
|
||||
toCurrency, rate2to1, fromCurrency)
|
||||
|
||||
c.SendMsg(msg, result)
|
||||
}
|
||||
@@ -5,32 +5,65 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_essence(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if !slices.Contains(config.BotOwnerGroupIDs, raw.GroupID) {
|
||||
return
|
||||
}
|
||||
help := "请回复一条消息,再使用 essence [set|delete]"
|
||||
if raw.Array[0].Type != qbot.Reply {
|
||||
c.SendMsg(raw, help)
|
||||
return
|
||||
}
|
||||
msgID, err := strconv.ParseUint(raw.Array[0].Content, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if args.Size == 2 {
|
||||
if args.Contents[1] == "delete" {
|
||||
c.DeleteGroupEssence(msgID)
|
||||
} else if args.Contents[1] == "set" {
|
||||
c.SetGroupEssence(msgID)
|
||||
} else {
|
||||
c.SendMsg(raw, help)
|
||||
}
|
||||
} else if args.Size == 1 {
|
||||
c.SetGroupEssence(msgID)
|
||||
} else {
|
||||
c.SendMsg(raw, help)
|
||||
const essenceHelpMsg string = `Manage essence messages.
|
||||
Usage: [Reply to a message] /essence [add|rm]`
|
||||
|
||||
type EssenceCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewEssenceCommand() *EssenceCommand {
|
||||
return &EssenceCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "essence",
|
||||
HelpMsg: essenceHelpMsg,
|
||||
Permission: getCmdPermLevel("essence"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *EssenceCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *EssenceCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
if !slices.Contains(config.Cfg.Permissions.BotOwnerGroupIDs, src.GroupID) {
|
||||
return
|
||||
}
|
||||
|
||||
// 查找 --reply= 参数
|
||||
var msgID uint64
|
||||
for _, arg := range args {
|
||||
if strings.HasPrefix(arg, "--reply=") {
|
||||
if id, err := strconv.ParseUint(strings.TrimPrefix(arg, "--reply="), 10, 64); err == nil {
|
||||
msgID = id
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if msgID == 0 {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) == 2 {
|
||||
switch args[1] {
|
||||
case "rm":
|
||||
c.DeleteGroupEssence(msgID)
|
||||
case "add":
|
||||
c.SetGroupEssence(msgID)
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
}
|
||||
} else if len(args) == 1 {
|
||||
c.SetGroupEssence(msgID)
|
||||
} else {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
}
|
||||
}
|
||||
|
||||
282
cmds/event.go
282
cmds/event.go
@@ -1,282 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_event(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
const help = `Usage: event [list | del <idx> | clear | msg=<regex> reply=<text> [user=<user_id>] [rand=<0.0-1.0>]]
|
||||
|
||||
Examples:
|
||||
event list - 查看所有事件
|
||||
event del 0 - 删除第 0 个事件
|
||||
event clear - 删除所有事件
|
||||
event msg=hello reply=world - 添加事件:当消息包含 "hello" 时,回复 "world"
|
||||
event msg=".*test.*" reply="matched" rand=0.5 - 添加事件:当消息包含 "test" 时,回复 "matched",触发概率 50%`
|
||||
|
||||
if args.Size == 1 {
|
||||
c.SendMsg(raw, help)
|
||||
return
|
||||
}
|
||||
|
||||
switch args.Contents[1] {
|
||||
case "list":
|
||||
listUserEvents(c, raw)
|
||||
case "del", "delete":
|
||||
if args.Size != 3 {
|
||||
c.SendMsg(raw, "Usage: event del <idx>")
|
||||
return
|
||||
}
|
||||
deleteUserEvent(c, raw, args.Contents[2])
|
||||
case "clear":
|
||||
clearUserEvents(c, raw)
|
||||
default:
|
||||
// Parse parameters for adding new event
|
||||
addUserEvent(c, raw, args)
|
||||
}
|
||||
}
|
||||
|
||||
func listUserEvents(c *qbot.Client, msg *qbot.Message) {
|
||||
var events []qbot.UserEvents
|
||||
result := qbot.PsqlDB.Where("user_id = ?", msg.UserID).
|
||||
Order("event_idx").Find(&events)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "Database error: "+result.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(events) == 0 {
|
||||
c.SendMsg(msg, "No events found")
|
||||
return
|
||||
}
|
||||
|
||||
var output strings.Builder
|
||||
output.WriteString("Your events:\n")
|
||||
for _, event := range events {
|
||||
output.WriteString(fmt.Sprintf("[%d] msg=%q reply=%q rand=%.2f\n",
|
||||
event.EventIdx, event.MsgRegex, event.ReplyText, event.RandProb))
|
||||
}
|
||||
c.SendMsg(msg, output.String())
|
||||
}
|
||||
|
||||
func deleteUserEvent(c *qbot.Client, msg *qbot.Message, idxStr string) {
|
||||
idx, err := strconv.Atoi(idxStr)
|
||||
if err != nil || idx < 0 || idx > 9 {
|
||||
c.SendMsg(msg, "Invalid index. Must be 0-9")
|
||||
return
|
||||
}
|
||||
|
||||
result := qbot.PsqlDB.Where("user_id = ? AND event_idx = ?", msg.UserID, idx).
|
||||
Delete(&qbot.UserEvents{})
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "Database error: "+result.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
c.SendMsg(msg, "Event not found")
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, fmt.Sprintf("Deleted event %d", idx))
|
||||
}
|
||||
|
||||
func clearUserEvents(c *qbot.Client, msg *qbot.Message) {
|
||||
result := qbot.PsqlDB.Where("user_id = ?", msg.UserID).Delete(&qbot.UserEvents{})
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "Database error: "+result.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, fmt.Sprintf("Cleared %d events", result.RowsAffected))
|
||||
}
|
||||
|
||||
func addUserEvent(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
// Parse key=value parameters
|
||||
params := parseEventParams(args)
|
||||
|
||||
msgRegex, ok := params["msg"]
|
||||
if !ok {
|
||||
c.SendMsg(msg, "Missing required parameter: msg")
|
||||
return
|
||||
}
|
||||
|
||||
replyText, ok := params["reply"]
|
||||
if !ok {
|
||||
c.SendMsg(msg, "Missing required parameter: reply")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate regex
|
||||
if _, err := regexp.Compile(msgRegex); err != nil {
|
||||
c.SendMsg(msg, "Invalid regex: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Parse optional parameters
|
||||
targetUserID := msg.UserID
|
||||
if userIDStr, ok := params["user"]; ok {
|
||||
if uid := str2uin64(userIDStr); uid != 0 {
|
||||
targetUserID = uid
|
||||
} else {
|
||||
c.SendMsg(msg, "Invalid user ID")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
randProb := float32(1.0)
|
||||
if randStr, ok := params["rand"]; ok {
|
||||
if prob, err := strconv.ParseFloat(randStr, 32); err != nil || prob < 0 || prob > 1 {
|
||||
c.SendMsg(msg, "Invalid rand value. Must be 0.0-1.0")
|
||||
return
|
||||
} else {
|
||||
randProb = float32(prob)
|
||||
}
|
||||
}
|
||||
|
||||
// Count existing events for this user
|
||||
var count int64
|
||||
qbot.PsqlDB.Model(&qbot.UserEvents{}).Where("user_id = ?", targetUserID).Count(&count)
|
||||
|
||||
if count >= 10 {
|
||||
c.SendMsg(msg, "Maximum 10 events per user")
|
||||
return
|
||||
}
|
||||
|
||||
// Find next available index
|
||||
var existingIndexes []int
|
||||
qbot.PsqlDB.Model(&qbot.UserEvents{}).Where("user_id = ?", targetUserID).
|
||||
Pluck("event_idx", &existingIndexes)
|
||||
|
||||
nextIdx := 0
|
||||
for nextIdx < 10 {
|
||||
found := false
|
||||
for _, idx := range existingIndexes {
|
||||
if idx == nextIdx {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
nextIdx++
|
||||
}
|
||||
|
||||
// Create new event
|
||||
event := qbot.UserEvents{
|
||||
UserID: targetUserID,
|
||||
EventIdx: nextIdx,
|
||||
MsgRegex: msgRegex,
|
||||
ReplyText: decodeSpecialChars(replyText),
|
||||
RandProb: randProb,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := qbot.PsqlDB.Create(&event).Error; err != nil {
|
||||
c.SendMsg(msg, "Database error: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, fmt.Sprintf("Added event %d: msg=%q reply=%q rand=%.2f",
|
||||
nextIdx, msgRegex, replyText, randProb))
|
||||
}
|
||||
|
||||
func parseEventParams(args *ArgsList) map[string]string {
|
||||
params := make(map[string]string)
|
||||
|
||||
// Join all arguments starting from index 1
|
||||
fullArgs := strings.Join(args.Contents[1:], " ")
|
||||
|
||||
// Parse key=value pairs, handling quoted values
|
||||
i := 0
|
||||
for i < len(fullArgs) {
|
||||
// Skip whitespace
|
||||
for i < len(fullArgs) && fullArgs[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
if i >= len(fullArgs) {
|
||||
break
|
||||
}
|
||||
|
||||
// Find key
|
||||
keyStart := i
|
||||
for i < len(fullArgs) && fullArgs[i] != '=' && fullArgs[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
if i >= len(fullArgs) || fullArgs[i] != '=' {
|
||||
// Not a key=value pair, skip to next space
|
||||
for i < len(fullArgs) && fullArgs[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
key := fullArgs[keyStart:i]
|
||||
i++ // skip '='
|
||||
|
||||
// Find value
|
||||
valueStart := i
|
||||
var value string
|
||||
|
||||
if i < len(fullArgs) && (fullArgs[i] == '"' || fullArgs[i] == '\'') {
|
||||
// Quoted value
|
||||
quote := fullArgs[i]
|
||||
i++ // skip opening quote
|
||||
valueStart = i
|
||||
for i < len(fullArgs) && fullArgs[i] != quote {
|
||||
i++
|
||||
}
|
||||
value = fullArgs[valueStart:i]
|
||||
if i < len(fullArgs) {
|
||||
i++ // skip closing quote
|
||||
}
|
||||
} else {
|
||||
// Unquoted value
|
||||
for i < len(fullArgs) && fullArgs[i] != ' ' {
|
||||
i++
|
||||
}
|
||||
value = fullArgs[valueStart:i]
|
||||
}
|
||||
|
||||
params[key] = value
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
// CheckUserEvents checks if any user events should trigger for the given message
|
||||
func CheckUserEvents(c *qbot.Client, msg *qbot.Message) bool {
|
||||
var events []qbot.UserEvents
|
||||
result := qbot.PsqlDB.Where("user_id = ?", msg.UserID).Find(&events)
|
||||
|
||||
if result.Error != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
triggered := false
|
||||
for _, event := range events {
|
||||
// Check if regex matches
|
||||
if regex, err := regexp.Compile(event.MsgRegex); err == nil {
|
||||
if regex.MatchString(msg.Content) || regex.MatchString(msg.Raw) {
|
||||
// Check probability
|
||||
if event.RandProb >= 1.0 || rand.Float32() <= event.RandProb {
|
||||
c.SendMsg(msg, event.ReplyText)
|
||||
triggered = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return triggered
|
||||
}
|
||||
106
cmds/fx.go
Normal file
106
cmds/fx.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
type ExchangeRateResponse struct {
|
||||
Result string `json:"result"`
|
||||
BaseCode string `json:"base_code"`
|
||||
ConversionRates map[string]float64 `json:"conversion_rates"`
|
||||
}
|
||||
|
||||
const erHelpMsg string = `Query foreign exchange rates.
|
||||
Usage: fx <from_currency> <to_currency>
|
||||
Example: fx CNY HKD`
|
||||
|
||||
type ErCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewErCommand() *ErCommand {
|
||||
return &ErCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "fx",
|
||||
HelpMsg: erHelpMsg,
|
||||
Permission: getCmdPermLevel("fx"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 3,
|
||||
MinArgs: 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ErCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *ErCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if config.Cfg.ApiKeys.ExchangeRateAPIKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
fromCurrency := strings.ToUpper(args[1])
|
||||
toCurrency := strings.ToUpper(args[2])
|
||||
|
||||
url := fmt.Sprintf("https://v6.exchangerate-api.com/v6/%s/latest/%s", config.Cfg.ApiKeys.ExchangeRateAPIKey, fromCurrency)
|
||||
|
||||
log.Println(url)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%d", resp.StatusCode))
|
||||
return
|
||||
}
|
||||
|
||||
var exchangeData ExchangeRateResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&exchangeData); err != nil {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if exchangeData.Result != "success" {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%v", exchangeData.Result))
|
||||
return
|
||||
}
|
||||
|
||||
toRate, exists := exchangeData.ConversionRates[toCurrency]
|
||||
if !exists {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Unsupported %s", toCurrency))
|
||||
return
|
||||
}
|
||||
|
||||
fromRate, exists := exchangeData.ConversionRates[fromCurrency]
|
||||
if !exists {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Unsupported %s", fromCurrency))
|
||||
return
|
||||
}
|
||||
|
||||
rate1to2 := toRate / fromRate
|
||||
rate2to1 := fromRate / toRate
|
||||
|
||||
result := fmt.Sprintf("1 %s = %.4f %s\n1 %s = %.4f %s",
|
||||
fromCurrency, rate1to2, toCurrency,
|
||||
toCurrency, rate2to1, fromCurrency)
|
||||
|
||||
c.SendMsg(src.GroupID, src.UserID, result)
|
||||
}
|
||||
126
cmds/group.go
126
cmds/group.go
@@ -9,63 +9,65 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_group(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if !slices.Contains(config.BotOwnerGroupIDs, raw.GroupID) {
|
||||
return
|
||||
}
|
||||
const help = "Usage: group [rename <group name> | op [@user1 @user2 ...] | deop [@user1 @user2 ...] | banme <time> | ban @user <time>]"
|
||||
if args.Size == 1 {
|
||||
c.SendMsg(raw, help)
|
||||
return
|
||||
}
|
||||
switch args.Contents[1] {
|
||||
case "rename":
|
||||
if args.Size < 3 {
|
||||
c.SendMsg(raw, help)
|
||||
} else {
|
||||
newName := decodeSpecialChars(strings.Join(args.Contents[2:], " "))
|
||||
c.SendMsg(raw, fmt.Sprintf("rename: %q", newName))
|
||||
c.SetGroupName(raw.GroupID, newName)
|
||||
}
|
||||
case "op":
|
||||
setGroupAdmin(c, raw, args, true)
|
||||
case "deop":
|
||||
setGroupAdmin(c, raw, args, false)
|
||||
case "banme":
|
||||
if args.Size != 3 {
|
||||
c.SendMsg(raw, help)
|
||||
} else {
|
||||
time, err := strconv.Atoi(args.Contents[2])
|
||||
if err != nil || time < 1 || time > 24*60*30 {
|
||||
c.SendMsg(raw, "Invalid time duration")
|
||||
return
|
||||
}
|
||||
c.SetGroupBan(raw.GroupID, raw.UserID, time*60)
|
||||
}
|
||||
case "ban":
|
||||
if args.Size != 4 {
|
||||
c.SendMsg(raw, help)
|
||||
} else if args.Types[2] == qbot.At {
|
||||
time, err := strconv.Atoi(args.Contents[3])
|
||||
if err != nil || time < 1 || time > 24*60*30 {
|
||||
c.SendMsg(raw, "Invalid time duration")
|
||||
return
|
||||
}
|
||||
if raw.UserID == 3112813730 {
|
||||
c.SetGroupBan(raw.GroupID, raw.UserID, time*60)
|
||||
} else {
|
||||
c.SetGroupBan(raw.GroupID, str2uin64(args.Contents[2]), time*60)
|
||||
}
|
||||
} else {
|
||||
c.SendMsg(raw, "Invalid user")
|
||||
}
|
||||
const groupHelpMsg string = `Manage group settings.
|
||||
Usage: group [rename <name> | op [@users...] | deop [@users...] | banme <minutes> | ban @user <minutes>]
|
||||
Examples:
|
||||
/group rename awa
|
||||
/group op @user1 @user2`
|
||||
|
||||
type GroupCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewGroupCommand() *GroupCommand {
|
||||
return &GroupCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "group",
|
||||
HelpMsg: groupHelpMsg,
|
||||
Permission: getCmdPermLevel("group"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 4,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setGroupAdmin(c *qbot.Client, raw *qbot.Message, args *ArgsList, isOp bool) {
|
||||
targetUserIDs, err := extractTargetUsers(args, 2, raw.UserID)
|
||||
func (cmd *GroupCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *GroupCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if !slices.Contains(config.Cfg.Permissions.BotOwnerGroupIDs, src.GroupID) {
|
||||
return
|
||||
}
|
||||
if len(args) == 1 {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
switch args[1] {
|
||||
case "rename":
|
||||
newName := decodeSpecialChars(strings.Join(args[2:], " "))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("rename: %q", newName))
|
||||
c.SetGroupName(src.GroupID, newName)
|
||||
case "op":
|
||||
setGroupAdmin(c, src, args, true)
|
||||
case "deop":
|
||||
setGroupAdmin(c, src, args, false)
|
||||
case "ban":
|
||||
time, err := strconv.Atoi(args[3])
|
||||
if err != nil || time < 1 || time > 24*60*30 {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Invalid time duration")
|
||||
return
|
||||
}
|
||||
c.SetGroupBan(src.GroupID, str2uin64(strings.TrimPrefix(args[2], "--at=")), time*60)
|
||||
}
|
||||
}
|
||||
|
||||
func setGroupAdmin(c *qbot.Client, src *srcMsg, args []string, isOp bool) {
|
||||
targetUserIDs, err := extractTargetUsers(args, 2, src.UserID)
|
||||
if err != nil {
|
||||
c.SendMsg(raw, "Invalid argument: "+err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, "Invalid argument: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,8 +80,8 @@ func setGroupAdmin(c *qbot.Client, raw *qbot.Message, args *ArgsList, isOp bool)
|
||||
}
|
||||
|
||||
for _, userID := range targetUserIDs {
|
||||
if userID == config.BotID {
|
||||
c.SendMsg(raw, fmt.Sprintf("Cannot %s bot", action))
|
||||
if userID == config.Cfg.Permissions.BotID {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Cannot %s bot", action))
|
||||
continue
|
||||
}
|
||||
if !userIDSet[userID] {
|
||||
@@ -96,28 +98,28 @@ func setGroupAdmin(c *qbot.Client, raw *qbot.Message, args *ArgsList, isOp bool)
|
||||
}
|
||||
|
||||
for _, userID := range validUserIDs {
|
||||
c.SetGroupAdmin(raw.GroupID, userID, isOp)
|
||||
c.SetGroupAdmin(src.GroupID, userID, isOp)
|
||||
}
|
||||
|
||||
if len(validUserIDs) == 1 {
|
||||
c.SendMsg(raw, fmt.Sprintf("%s: %d", action, validUserIDs[0]))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%s: %d", action, validUserIDs[0]))
|
||||
} else {
|
||||
userIDStrings := make([]string, len(validUserIDs))
|
||||
for i, id := range validUserIDs {
|
||||
userIDStrings[i] = strconv.FormatUint(id, 10)
|
||||
}
|
||||
c.SendMsg(raw, fmt.Sprintf("%s: %s", action, strings.Join(userIDStrings, ", ")))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("%s: %s", action, strings.Join(userIDStrings, ", ")))
|
||||
}
|
||||
}
|
||||
|
||||
func extractTargetUsers(args *ArgsList, startIndex int, defaultUserID uint64) ([]uint64, error) {
|
||||
func extractTargetUsers(args []string, startIndex int, defaultUserID uint64) ([]uint64, error) {
|
||||
var targetUserIDs []uint64
|
||||
hasAtUsers := false
|
||||
|
||||
for i := startIndex; i < args.Size; i++ {
|
||||
if args.Types[i] == qbot.At {
|
||||
for i := startIndex; i < len(args); i++ {
|
||||
if strings.HasPrefix(args[i], "--at=") {
|
||||
hasAtUsers = true
|
||||
targetUserIDs = append(targetUserIDs, str2uin64(args.Contents[i]))
|
||||
targetUserIDs = append(targetUserIDs, str2uin64(strings.TrimPrefix(args[i], "--at=")))
|
||||
} else {
|
||||
return nil, fmt.Errorf("use @ to mention users")
|
||||
}
|
||||
|
||||
19
cmds/info.go
19
cmds/info.go
@@ -1,19 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/qbot"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_info(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
cmd := exec.Command("top", "-l", "1", "-n", "0")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
c.SendReplyMsg(msg, fmt.Sprintf("Failed to get system info: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
c.SendReplyMsg(msg, strings.TrimSpace(string(output)))
|
||||
}
|
||||
148
cmds/llm.go
148
cmds/llm.go
@@ -8,9 +8,39 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if args.Size < 2 {
|
||||
c.SendMsg(msg, "Usage:\nllm prompt [新提示词]\nllm max-history [能看见的历史消息数]\nllm enable/disable\nllm status\nllm model [模型]\nllm supplier [API供应商]")
|
||||
const llmHelpMsg string = `Configure LLM settings.
|
||||
Usage:
|
||||
/llm prompt [new_prompt] - Set or view system prompt
|
||||
/llm max-history [number] - Set or view max history messages
|
||||
/llm enable|disable - Enable or disable LLM
|
||||
/llm status - View current settings
|
||||
/llm model [model_name] - Set or view model
|
||||
/llm supplier [supplier_name] - Set or view API supplier`
|
||||
|
||||
type LlmCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewLlmCommand() *LlmCommand {
|
||||
return &LlmCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "llm",
|
||||
HelpMsg: llmHelpMsg,
|
||||
Permission: getCmdPermLevel("llm"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *LlmCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *LlmCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if len(args) < 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,7 +54,7 @@ func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
}
|
||||
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
First(&llmConfig).Error
|
||||
|
||||
if err != nil {
|
||||
@@ -44,7 +74,7 @@ func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
Model: "deepseek-ai/DeepSeek-V3",
|
||||
}
|
||||
qbot.PsqlDB.Table("group_llm_configs").Create(map[string]any{
|
||||
"group_id": msg.GroupID,
|
||||
"group_id": src.GroupID,
|
||||
"prompt": llmConfig.Prompt,
|
||||
"max_history": llmConfig.MaxHistory,
|
||||
"enabled": llmConfig.Enabled,
|
||||
@@ -54,67 +84,67 @@ func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
})
|
||||
}
|
||||
|
||||
switch args.Contents[1] {
|
||||
switch args[1] {
|
||||
case "prompt":
|
||||
if args.Size == 2 {
|
||||
c.SendMsg(msg, fmt.Sprintf("prompt: %s", llmConfig.Prompt))
|
||||
if len(args) == 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("prompt: %s", llmConfig.Prompt))
|
||||
} else {
|
||||
newPrompt := strings.Join(args.Contents[2:], " ")
|
||||
newPrompt := strings.Join(args[2:], " ")
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("prompt", newPrompt).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, "prompt updated")
|
||||
c.SendMsg(src.GroupID, src.UserID, "prompt updated")
|
||||
}
|
||||
}
|
||||
|
||||
case "max-history":
|
||||
if args.Size == 2 {
|
||||
c.SendMsg(msg, fmt.Sprintf("max-history: %d", llmConfig.MaxHistory))
|
||||
if len(args) == 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("max-history: %d", llmConfig.MaxHistory))
|
||||
} else {
|
||||
maxHistory, err := strconv.Atoi(args.Contents[2])
|
||||
maxHistory, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
c.SendMsg(msg, "Enter a valid number")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Enter a valid number")
|
||||
return
|
||||
}
|
||||
if maxHistory < 0 {
|
||||
c.SendMsg(msg, "max-history cannot be negative")
|
||||
c.SendMsg(src.GroupID, src.UserID, "max-history cannot be negative")
|
||||
return
|
||||
}
|
||||
if maxHistory > 300 {
|
||||
c.SendMsg(msg, "max-history cannot exceed 300")
|
||||
c.SendMsg(src.GroupID, src.UserID, "max-history cannot exceed 300")
|
||||
return
|
||||
}
|
||||
err = qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("max_history", maxHistory).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, "Failed: "+err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed: "+err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, "max-history updated")
|
||||
c.SendMsg(src.GroupID, src.UserID, "max-history updated")
|
||||
}
|
||||
}
|
||||
|
||||
case "enable":
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("enabled", true).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, "Enabled LLM")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Enabled LLM")
|
||||
}
|
||||
|
||||
case "disable":
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("enabled", false).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, "Disabled LLM")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Disabled LLM")
|
||||
}
|
||||
|
||||
case "status":
|
||||
@@ -125,75 +155,75 @@ func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
llmConfig.Model,
|
||||
llmConfig.Prompt,
|
||||
)
|
||||
c.SendMsg(msg, status)
|
||||
c.SendMsg(src.GroupID, src.UserID, status)
|
||||
|
||||
case "tokens":
|
||||
var user qbot.Users
|
||||
if args.Size == 2 {
|
||||
err := qbot.PsqlDB.Where("user_id = ?", msg.UserID).First(&user).Error
|
||||
if len(args) == 2 {
|
||||
err := qbot.PsqlDB.Where("user_id = ?", src.UserID).First(&user).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, "Failed to get token usage")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed to get token usage")
|
||||
return
|
||||
}
|
||||
c.SendMsg(msg, fmt.Sprintf("Token usage: %d", user.TokenUsage))
|
||||
} else if args.Size == 3 && args.Types[2] == qbot.At {
|
||||
targetID := str2uin64(args.Contents[2])
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Token usage: %d", user.TokenUsage))
|
||||
} else if len(args) == 3 && strings.HasPrefix(args[2], "--at=") {
|
||||
targetID := str2uin64(strings.TrimPrefix(args[2], "--at="))
|
||||
err := qbot.PsqlDB.Where("user_id = ?", targetID).First(&user).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, "Failed to get token usage")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Failed to get token usage")
|
||||
return
|
||||
}
|
||||
c.SendMsg(msg, fmt.Sprintf("Token usage for %s: %d", args.Contents[2], user.TokenUsage))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Token usage for %s: %d", args[2], user.TokenUsage))
|
||||
} else {
|
||||
c.SendMsg(msg, "Usage:\nllm tokens\nllm tokens @user")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Usage:\nllm tokens\nllm tokens @user")
|
||||
}
|
||||
|
||||
case "debug":
|
||||
if args.Size == 2 {
|
||||
c.SendMsg(msg, fmt.Sprintf("debug: %v", llmConfig.Debug))
|
||||
if len(args) == 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("debug: %v", llmConfig.Debug))
|
||||
} else {
|
||||
debugValue := strings.ToLower(args.Contents[2])
|
||||
debugValue := strings.ToLower(args[2])
|
||||
if debugValue != "on" && debugValue != "off" {
|
||||
return
|
||||
}
|
||||
newDebug := debugValue == "on"
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("debug", newDebug).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, fmt.Sprintf("debug = %v", newDebug))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("debug = %v", newDebug))
|
||||
}
|
||||
}
|
||||
|
||||
case "model":
|
||||
if args.Size == 2 {
|
||||
c.SendMsg(msg, fmt.Sprintf("model: %s", llmConfig.Model))
|
||||
if len(args) == 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("model: %s", llmConfig.Model))
|
||||
} else {
|
||||
newModel := args.Contents[2]
|
||||
newModel := args[2]
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("model", newModel).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else {
|
||||
c.SendMsg(msg, fmt.Sprintf("model updated to %s", newModel))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("model updated to %s", newModel))
|
||||
}
|
||||
}
|
||||
|
||||
case "supplier":
|
||||
if args.Size == 2 {
|
||||
c.SendMsg(msg, fmt.Sprintf("supplier: %s", llmConfig.Supplier))
|
||||
if len(args) == 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("supplier: %s", llmConfig.Supplier))
|
||||
} else {
|
||||
newSupplier := args.Contents[2]
|
||||
newSupplier := args[2]
|
||||
|
||||
var exists int64
|
||||
qbot.PsqlDB.Table("suppliers").
|
||||
Where("name = ?", newSupplier).
|
||||
Count(&exists)
|
||||
if exists == 0 {
|
||||
c.SendMsg(msg, fmt.Sprintf("unknown supplier: %s", newSupplier))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("unknown supplier: %s", newSupplier))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,25 +237,25 @@ func cmd_llm(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
|
||||
// Update supplier
|
||||
err := qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("supplier", newSupplier).Error
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-switch model to supplier default if provided
|
||||
if strings.TrimSpace(sup.DefaultModel) != "" {
|
||||
_ = qbot.PsqlDB.Table("group_llm_configs").
|
||||
Where("group_id = ?", msg.GroupID).
|
||||
Where("group_id = ?", src.GroupID).
|
||||
Update("model", sup.DefaultModel).Error
|
||||
c.SendMsg(msg, fmt.Sprintf("supplier updated to %s, model -> %s", newSupplier, sup.DefaultModel))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("supplier updated to %s, model -> %s", newSupplier, sup.DefaultModel))
|
||||
} else {
|
||||
c.SendMsg(msg, fmt.Sprintf("supplier updated to %s", newSupplier))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("supplier updated to %s", newSupplier))
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
c.SendMsg(msg, fmt.Sprintf("Unrecognized parameter >>%s<<", args.Contents[1]))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("Unrecognized parameter >>%s<<", args[1]))
|
||||
}
|
||||
}
|
||||
|
||||
48
cmds/mc.go
48
cmds/mc.go
@@ -12,15 +12,35 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func cmd_mc(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if args.Size < 2 {
|
||||
c.SendMsg(raw, "Usage: mc <command>")
|
||||
return
|
||||
}
|
||||
const mcHelpMsg string = `Execute Minecraft RCON commands.
|
||||
Usage: /mc <command>
|
||||
Example: /mc list`
|
||||
|
||||
type McCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewMcCommand() *McCommand {
|
||||
return &McCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "mc",
|
||||
HelpMsg: mcHelpMsg,
|
||||
Permission: getCmdPermLevel("mc"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *McCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *McCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
// Get RCON configuration for this group
|
||||
var rconConfig qbot.GroupRconConfigs
|
||||
result := qbot.PsqlDB.Where("group_id = ?", raw.GroupID).First(&rconConfig)
|
||||
result := qbot.PsqlDB.Where("group_id = ?", src.GroupID).First(&rconConfig)
|
||||
|
||||
if result.Error != nil {
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
@@ -32,36 +52,36 @@ func cmd_mc(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
}
|
||||
|
||||
if !rconConfig.Enabled {
|
||||
c.SendMsg(raw, "RCON is disabled for this group")
|
||||
c.SendMsg(src.GroupID, src.UserID, "RCON is disabled for this group")
|
||||
return
|
||||
}
|
||||
|
||||
// Join all arguments after 'mc' as the command
|
||||
command := strings.Join(args.Contents[1:], " ")
|
||||
command := strings.Join(args[1:], " ")
|
||||
|
||||
// Check permissions for non-master users
|
||||
if raw.UserID != config.MasterID && !isAllowedCommand(command) {
|
||||
c.SendMsg(raw, "Permission denied. You can only use query commands.")
|
||||
if src.UserID != config.Cfg.Permissions.MasterID && !isAllowedCommand(command) {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Permission denied. You can only use query commands.")
|
||||
return
|
||||
}
|
||||
|
||||
// Execute RCON command
|
||||
response, err := executeRconCommand(rconConfig.Address, rconConfig.Password, command)
|
||||
if err != nil {
|
||||
c.SendMsg(raw, fmt.Sprintf("RCON error: %s", err.Error()))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("RCON error: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// Send response back (limit to avoid spam)
|
||||
if len(response) > 1000 {
|
||||
response = response[:1000] + "... (truncated)"
|
||||
if len(response) > 2048 {
|
||||
response = response[:2048] + "... (truncated)"
|
||||
}
|
||||
|
||||
if response == "" {
|
||||
response = "No output"
|
||||
}
|
||||
|
||||
c.SendMsg(raw, qbot.CQReply(raw.UserID)+response)
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+response)
|
||||
}
|
||||
|
||||
func executeRconCommand(address, password, command string) (string, error) {
|
||||
|
||||
@@ -3,42 +3,69 @@ package cmds
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/qbot"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func cmd_memberinfo(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
// 只能在群聊中使用
|
||||
if msg.GroupID == 0 {
|
||||
const memberinfoHelpMsg string = `Query group member information.
|
||||
Usage: /memberinfo [@user]
|
||||
Example: /memberinfo @user`
|
||||
|
||||
type MemberinfoCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewMemberinfoCommand() *MemberinfoCommand {
|
||||
return &MemberinfoCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "memberinfo",
|
||||
HelpMsg: memberinfoHelpMsg,
|
||||
Permission: getCmdPermLevel("memberinfo"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 2,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *MemberinfoCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *MemberinfoCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
// Only available in group chats
|
||||
if src.GroupID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var targetUserID uint64
|
||||
|
||||
if args.Size >= 2 && args.Types[1] == qbot.At {
|
||||
targetUserID = str2uin64(args.Contents[1])
|
||||
if len(args) >= 2 && strings.HasPrefix(args[1], "--at=") {
|
||||
targetUserID = str2uin64(strings.TrimPrefix(args[1], "--at="))
|
||||
} else {
|
||||
targetUserID = msg.UserID
|
||||
targetUserID = src.UserID
|
||||
}
|
||||
|
||||
if targetUserID == 0 {
|
||||
c.SendReplyMsg(msg, "Invalid user ID")
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+"Invalid user ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取群成员信息
|
||||
memberInfo, err := c.GetGroupMemberInfo(msg.GroupID, targetUserID, false)
|
||||
// Get group member information
|
||||
memberInfo, err := c.GetGroupMemberInfo(src.GroupID, targetUserID, false)
|
||||
if err != nil {
|
||||
c.SendReplyMsg(msg, fmt.Sprintf("Failed to get member info: %v", err))
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+fmt.Sprintf("Failed to get member info: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
response := fmt.Sprintf(
|
||||
"QQ号: %d\n"+
|
||||
"昵称: %s\n"+
|
||||
"名片: %s\n"+
|
||||
"性别: %s\n"+
|
||||
"权限: %s\n"+
|
||||
"等级: Lv %s",
|
||||
"QQ: %d\n"+
|
||||
"Nickname: %s\n"+
|
||||
"Card: %s\n"+
|
||||
"Gender: %s\n"+
|
||||
"Role: %s\n"+
|
||||
"Level: Lv %s",
|
||||
memberInfo.UserID,
|
||||
memberInfo.Nickname,
|
||||
getCardOrNickname(memberInfo.Card, memberInfo.Nickname),
|
||||
@@ -47,30 +74,30 @@ func cmd_memberinfo(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
memberInfo.Level)
|
||||
|
||||
if memberInfo.Age > 0 {
|
||||
response += fmt.Sprintf("\n年龄: %d", memberInfo.Age)
|
||||
response += fmt.Sprintf("\nAge: %d", memberInfo.Age)
|
||||
}
|
||||
|
||||
if memberInfo.Area != "" {
|
||||
response += fmt.Sprintf("\n地区: %s", memberInfo.Area)
|
||||
response += fmt.Sprintf("\nArea: %s", memberInfo.Area)
|
||||
}
|
||||
|
||||
if memberInfo.Title != "" {
|
||||
response += fmt.Sprintf("\n头衔: %s", memberInfo.Title)
|
||||
response += fmt.Sprintf("\nTitle: %s", memberInfo.Title)
|
||||
}
|
||||
|
||||
if memberInfo.ShutUpTimestamp > 0 {
|
||||
shutUpTime := time.Unix(memberInfo.ShutUpTimestamp, 0)
|
||||
if shutUpTime.After(time.Now()) {
|
||||
response += fmt.Sprintf("\n禁言到期: %s", shutUpTime.Format("2006-01-02 15:04:05"))
|
||||
response += fmt.Sprintf("\nMuted until: %s", shutUpTime.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
||||
|
||||
if memberInfo.JoinTime > 0 {
|
||||
joinTime := time.Unix(int64(memberInfo.JoinTime), 0)
|
||||
response += fmt.Sprintf("\n加群时间: %s", joinTime.Format("2006-01-02 15:04:05"))
|
||||
response += fmt.Sprintf("\nJoined: %s", joinTime.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
c.SendReplyMsg(msg, response)
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+response)
|
||||
}
|
||||
|
||||
func getCardOrNickname(card, nickname string) string {
|
||||
@@ -94,11 +121,11 @@ func getSexString(sex string) string {
|
||||
func getRoleString(role string) string {
|
||||
switch role {
|
||||
case "owner":
|
||||
return "👑群主"
|
||||
return "Owner"
|
||||
case "admin":
|
||||
return "管理员"
|
||||
return "Admin"
|
||||
case "member":
|
||||
return "🐱成员"
|
||||
return "Member"
|
||||
default:
|
||||
return role
|
||||
}
|
||||
|
||||
46
cmds/psql.go
46
cmds/psql.go
@@ -3,27 +3,47 @@ package cmds
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_psql(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if raw.UserID != config.MasterID {
|
||||
c.SendMsg(raw, fmt.Sprintf("%s: Permission denied", args.Contents[0]))
|
||||
return
|
||||
}
|
||||
const psqlHelpMsg = `Execute PostgreSQL queries.
|
||||
Usage: /psql <query>
|
||||
Example: /psql SELECT * FROM users LIMIT 10`
|
||||
|
||||
rows, err := qbot.PsqlDB.Raw(decodeSpecialChars(raw.Raw[5:])).Rows()
|
||||
type PsqlCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewPsqlCommand() *PsqlCommand {
|
||||
return &PsqlCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "psql",
|
||||
HelpMsg: psqlHelpMsg,
|
||||
Permission: getCmdPermLevel("psql"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: true, // uses raw message
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *PsqlCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *PsqlCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
query := args[len(args)-1]
|
||||
rows, err := qbot.PsqlDB.Raw(decodeSpecialChars(query)).Rows()
|
||||
if err != nil {
|
||||
c.SendMsg(raw, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
c.SendMsg(raw, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,7 +61,7 @@ func cmd_psql(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
}
|
||||
|
||||
if err := rows.Scan(values...); err != nil {
|
||||
c.SendMsg(raw, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,10 +78,10 @@ func cmd_psql(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
count++
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
c.SendMsg(raw, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
} else if result == "" {
|
||||
c.SendMsg(raw, "[]")
|
||||
c.SendMsg(src.GroupID, src.UserID, "[]")
|
||||
} else {
|
||||
c.SendMsg(raw, result)
|
||||
c.SendMsg(src.GroupID, src.UserID, encodeSpecialChars(result))
|
||||
}
|
||||
}
|
||||
|
||||
79
cmds/py.go
Normal file
79
cmds/py.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const pyHelpMsg string = `Execute Python code.
|
||||
Usage: /py <python_code>
|
||||
Example: /py print("Hello, World!")`
|
||||
|
||||
type PyCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewPyCommand() *PyCommand {
|
||||
return &PyCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "py",
|
||||
HelpMsg: pyHelpMsg,
|
||||
Permission: getCmdPermLevel("py"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: true,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *PyCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *PyCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if len(args) <= 1 {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// get interpreter path
|
||||
interpreter := config.Cfg.Python.Interpreter
|
||||
if interpreter == "" {
|
||||
interpreter = "python3"
|
||||
}
|
||||
|
||||
pythonCode := decodeSpecialChars(src.Raw)
|
||||
pythonCode = strings.TrimSpace(pythonCode)
|
||||
|
||||
pythonCmd := exec.Command(interpreter, "-c", pythonCode)
|
||||
|
||||
done := make(chan error, 1)
|
||||
var output []byte
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
output, err = pythonCmd.CombinedOutput()
|
||||
log.Printf("run python command: %s, output: %s, error: %v",
|
||||
pythonCode, string(output), err)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
if err == nil {
|
||||
// success
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+truncateString(string(output)))
|
||||
} else {
|
||||
// failed
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+fmt.Sprintf("%v\n%s", err, truncateString(string(output))))
|
||||
}
|
||||
case <-time.After(300 * time.Second):
|
||||
pythonCmd.Process.Kill()
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+fmt.Sprintf("Timeout: %q", pythonCode))
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_rawmsg(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if args.Size >= 2 && (args.Contents[1] == "-f" || args.Contents[1] == "--format") {
|
||||
if args.Size >= 3 {
|
||||
switch args.Contents[2] {
|
||||
case "json": // default
|
||||
case "%v":
|
||||
fallthrough
|
||||
case "%+v":
|
||||
fallthrough
|
||||
case "%#v":
|
||||
c.SendReplyMsg(raw, fmt.Sprintf(args.Contents[2], raw))
|
||||
return
|
||||
default:
|
||||
c.SendReplyMsg(raw, fmt.Sprintf("Unknown format %q", args.Contents[2]))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
c.SendReplyMsg(raw, fmt.Sprintf("Usage: %s [-f|--format format]", args.Contents[0]))
|
||||
return
|
||||
}
|
||||
}
|
||||
jsonStr, _ := json.Marshal(raw)
|
||||
jsonBytes := []byte(jsonStr)
|
||||
var out bytes.Buffer
|
||||
err := json.Indent(&out, jsonBytes, "", " ")
|
||||
if err != nil {
|
||||
c.SendReplyMsg(raw, string(jsonStr))
|
||||
} else {
|
||||
c.SendReplyMsg(raw, out.String())
|
||||
}
|
||||
}
|
||||
112
cmds/rcon.go
112
cmds/rcon.go
@@ -5,52 +5,64 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_rcon(c *qbot.Client, raw *qbot.Message, args *ArgsList) {
|
||||
if raw.UserID != config.MasterID {
|
||||
return
|
||||
}
|
||||
|
||||
const help = `Usage: rcon [status | set <address> <password> | enable | disable]
|
||||
|
||||
const rconHelpMsg string = `Manage RCON configuration.
|
||||
Usage: /rcon [status | set <address> <password> | enable | disable]
|
||||
Examples:
|
||||
rcon status
|
||||
rcon set '127.0.0.1:25575' 'password'
|
||||
rcon enable
|
||||
rcon disable`
|
||||
/rcon status
|
||||
/rcon set 127.0.0.1:25575 password
|
||||
/rcon enable
|
||||
/rcon disable`
|
||||
|
||||
if args.Size == 1 {
|
||||
c.SendMsg(raw, help)
|
||||
return
|
||||
}
|
||||
type RconCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
switch args.Contents[1] {
|
||||
case "status":
|
||||
showRconStatus(c, raw)
|
||||
case "set":
|
||||
if args.Size != 4 {
|
||||
c.SendMsg(raw, "Usage: rcon set <address> <password>")
|
||||
return
|
||||
}
|
||||
setRconConfig(c, raw, args.Contents[2], args.Contents[3])
|
||||
case "enable":
|
||||
toggleRcon(c, raw, true)
|
||||
case "disable":
|
||||
toggleRcon(c, raw, false)
|
||||
default:
|
||||
c.SendMsg(raw, help)
|
||||
func NewRconCommand() *RconCommand {
|
||||
return &RconCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "rcon",
|
||||
HelpMsg: rconHelpMsg,
|
||||
Permission: getCmdPermLevel("rcon"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 4,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func showRconStatus(c *qbot.Client, msg *qbot.Message) {
|
||||
func (cmd *RconCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *RconCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
switch args[1] {
|
||||
case "status":
|
||||
showRconStatus(c, src)
|
||||
case "set":
|
||||
if len(args) != 4 {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
setRconConfig(c, src, args[2], args[3])
|
||||
case "enable":
|
||||
toggleRcon(c, src, true)
|
||||
case "disable":
|
||||
toggleRcon(c, src, false)
|
||||
default:
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func showRconStatus(c *qbot.Client, src *srcMsg) {
|
||||
var config qbot.GroupRconConfigs
|
||||
result := qbot.PsqlDB.Where("group_id = ?", msg.GroupID).First(&config)
|
||||
result := qbot.PsqlDB.Where("group_id = ?", src.GroupID).First(&config)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "RCON not configured for this group")
|
||||
c.SendMsg(src.GroupID, src.UserID, "RCON not configured for this group")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,37 +76,37 @@ func showRconStatus(c *qbot.Client, msg *qbot.Message) {
|
||||
response := fmt.Sprintf("RCON Status: %s\nAddress: %s\nPassword: %s",
|
||||
status, config.Address, maskedPassword)
|
||||
|
||||
c.SendMsg(msg, response)
|
||||
c.SendMsg(src.GroupID, src.UserID, response)
|
||||
}
|
||||
|
||||
func setRconConfig(c *qbot.Client, msg *qbot.Message, address, password string) {
|
||||
func setRconConfig(c *qbot.Client, src *srcMsg, address, password string) {
|
||||
// Validate address format (should contain port)
|
||||
if !strings.Contains(address, ":") {
|
||||
c.SendMsg(msg, "Invalid address format. Use host:port (e.g., 127.0.0.1:25575)")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Invalid address format. Use host:port (e.g., 127.0.0.1:25575)")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate port
|
||||
parts := strings.Split(address, ":")
|
||||
if len(parts) != 2 {
|
||||
c.SendMsg(msg, "Invalid address format. Use host:port")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Invalid address format. Use host:port")
|
||||
return
|
||||
}
|
||||
|
||||
if port, err := strconv.Atoi(parts[1]); err != nil || port < 1 || port > 65535 {
|
||||
c.SendMsg(msg, "Invalid port number")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Invalid port number")
|
||||
return
|
||||
}
|
||||
|
||||
config := qbot.GroupRconConfigs{
|
||||
GroupID: msg.GroupID,
|
||||
GroupID: src.GroupID,
|
||||
Address: address,
|
||||
Password: password,
|
||||
Enabled: false, // Default to disabled for security
|
||||
Enabled: true, // Default to disabled for security
|
||||
}
|
||||
|
||||
// Use Upsert to create or update
|
||||
result := qbot.PsqlDB.Where("group_id = ?", msg.GroupID).Assign(
|
||||
result := qbot.PsqlDB.Where("group_id = ?", src.GroupID).Assign(
|
||||
qbot.GroupRconConfigs{
|
||||
Address: address,
|
||||
Password: password,
|
||||
@@ -102,28 +114,28 @@ func setRconConfig(c *qbot.Client, msg *qbot.Message, address, password string)
|
||||
).FirstOrCreate(&config)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "Database error: "+result.Error.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, "Database error: "+result.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, fmt.Sprintf("RCON configuration updated:\nAddress: %s\nStatus: disabled (use 'rcon enable' to enable)", address))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("RCON configuration updated: %s:%s", address, password))
|
||||
}
|
||||
|
||||
func toggleRcon(c *qbot.Client, msg *qbot.Message, enabled bool) {
|
||||
func toggleRcon(c *qbot.Client, src *srcMsg, enabled bool) {
|
||||
// Check if configuration exists
|
||||
var config qbot.GroupRconConfigs
|
||||
result := qbot.PsqlDB.Where("group_id = ?", msg.GroupID).First(&config)
|
||||
result := qbot.PsqlDB.Where("group_id = ?", src.GroupID).First(&config)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "RCON not configured for this group. Use 'rcon set' first.")
|
||||
c.SendMsg(src.GroupID, src.UserID, "RCON not configured for this group. Use 'rcon set' first.")
|
||||
return
|
||||
}
|
||||
|
||||
// Update enabled status
|
||||
result = qbot.PsqlDB.Model(&config).Where("group_id = ?", msg.GroupID).Update("enabled", enabled)
|
||||
result = qbot.PsqlDB.Model(&config).Where("group_id = ?", src.GroupID).Update("enabled", enabled)
|
||||
|
||||
if result.Error != nil {
|
||||
c.SendMsg(msg, "Database error: "+result.Error.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, "Database error: "+result.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,5 +143,5 @@ func toggleRcon(c *qbot.Client, msg *qbot.Message, enabled bool) {
|
||||
if enabled {
|
||||
status = "enabled"
|
||||
}
|
||||
c.SendMsg(msg, fmt.Sprintf("RCON %s for this group", status))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("RCON %s for this group", status))
|
||||
}
|
||||
|
||||
29
cmds/rps.go
29
cmds/rps.go
@@ -4,6 +4,31 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func cmd_rps(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
c.SendMsg(msg, qbot.CQRps())
|
||||
const rpsHelpMsg string = `Play rock-paper-scissors.
|
||||
Usage: /rps`
|
||||
|
||||
type RpsCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewRpsCommand() *RpsCommand {
|
||||
return &RpsCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "rps",
|
||||
HelpMsg: rpsHelpMsg,
|
||||
Permission: getCmdPermLevel("rps"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 1,
|
||||
MinArgs: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *RpsCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *RpsCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQRps())
|
||||
}
|
||||
|
||||
64
cmds/sh.go
64
cmds/sh.go
@@ -2,7 +2,6 @@ package cmds
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"log"
|
||||
"os/exec"
|
||||
@@ -10,14 +9,18 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const shHelpMsg string = `Execute shell commands.
|
||||
Usage: /sh <command>
|
||||
Example: /sh ls -la`
|
||||
|
||||
var workingDir string = "/tmp"
|
||||
|
||||
func truncateString(s string) string {
|
||||
s = encodeSpecialChars(s)
|
||||
const (
|
||||
maxLines = 10
|
||||
maxChars = 500
|
||||
truncateMsg = "\n输出过长,已自动截断"
|
||||
maxLines = 20
|
||||
maxChars = 1024
|
||||
truncateMsg = "... (truncated)"
|
||||
)
|
||||
|
||||
lineCount := strings.Count(s, "\n") + 1
|
||||
@@ -40,42 +43,59 @@ func truncateString(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func cmd_sh(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if msg.UserID != config.MasterID {
|
||||
type ShCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewShCommand() *ShCommand {
|
||||
return &ShCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "sh",
|
||||
HelpMsg: shHelpMsg,
|
||||
Permission: getCmdPermLevel("sh"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: true,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *ShCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *ShCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
if len(args) <= 1 {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if args.Size <= 1 {
|
||||
c.SendReplyMsg(msg, "Usage: sh <command>")
|
||||
return
|
||||
}
|
||||
rawcmd := decodeSpecialChars(src.Raw)
|
||||
|
||||
rawcmd := decodeSpecialChars(msg.Raw[3:])
|
||||
|
||||
if strings.HasPrefix(args.Contents[1], "cd") {
|
||||
if strings.HasPrefix(args[1], "cd") {
|
||||
absPath, err := exec.Command("bash", "-c",
|
||||
fmt.Sprintf("cd %s && %s && pwd", workingDir, rawcmd)).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
c.SendReplyMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
workingDir = strings.TrimSpace(string(absPath))
|
||||
c.SendReplyMsg(msg, workingDir)
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+workingDir)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("bash", "-c", fmt.Sprintf("cd %s && %s", workingDir, rawcmd))
|
||||
shellCmd := exec.Command("bash", "-c", fmt.Sprintf("cd %s && %s", workingDir, rawcmd))
|
||||
|
||||
done := make(chan error, 1)
|
||||
var output []byte
|
||||
|
||||
go func() {
|
||||
var err error
|
||||
output, err = cmd.CombinedOutput()
|
||||
output, err = shellCmd.CombinedOutput()
|
||||
log.Printf("run command: %s, output: %s, error: %v",
|
||||
strings.Join(args.Contents[1:], " "), string(output), err)
|
||||
strings.Join(args[1:], " "), string(output), err)
|
||||
done <- err
|
||||
}()
|
||||
|
||||
@@ -83,13 +103,13 @@ func cmd_sh(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
case err := <-done:
|
||||
if err == nil {
|
||||
// success
|
||||
c.SendReplyMsg(msg, truncateString(string(output)))
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+truncateString(string(output)))
|
||||
} else {
|
||||
// failed
|
||||
c.SendReplyMsg(msg, fmt.Sprintf("%v\n%s", err, truncateString(string(output))))
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+fmt.Sprintf("%v\n%s", err, truncateString(string(output))))
|
||||
}
|
||||
case <-time.After(300 * time.Second):
|
||||
cmd.Process.Kill()
|
||||
c.SendReplyMsg(msg, fmt.Sprintf("Timeout: %q", rawcmd))
|
||||
shellCmd.Process.Kill()
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+fmt.Sprintf("Timeout: %q", rawcmd))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,61 @@ import (
|
||||
"go-hurobot/qbot"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func cmd_specialtitle(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
if !slices.Contains(config.BotOwnerGroupIDs, msg.GroupID) {
|
||||
const specialtitleHelpMsg string = `Set special title for group members.
|
||||
Usage: /specialtitle [@user] <title>
|
||||
Example: /specialtitle @user VIP`
|
||||
|
||||
type SpecialtitleCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewSpecialtitleCommand() *SpecialtitleCommand {
|
||||
return &SpecialtitleCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "specialtitle",
|
||||
HelpMsg: specialtitleHelpMsg,
|
||||
Permission: getCmdPermLevel("specialtitle"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 3,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *SpecialtitleCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *SpecialtitleCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
if !slices.Contains(config.Cfg.Permissions.BotOwnerGroupIDs, src.GroupID) {
|
||||
return
|
||||
}
|
||||
|
||||
if args.Size == 1 {
|
||||
c.SendReplyMsg(msg, "Usage: specialtitle <specialtitle>")
|
||||
} else if len(msg.Array) > 1 && msg.Array[1].Type != qbot.At {
|
||||
c.SendReplyMsg(msg, "群头衔一定是一个文本!")
|
||||
} else if length := len([]byte(args.Contents[1])); length > 18 {
|
||||
c.SendReplyMsg(msg, "头衔长度不允许超过 18 字节,当前 "+strconv.FormatInt(int64(length), 10)+" 字节")
|
||||
// check if the second parameter is @
|
||||
var targetUserID uint64
|
||||
var titleIdx int
|
||||
if len(args) > 1 && strings.HasPrefix(args[1], "--at=") {
|
||||
targetUserID = str2uin64(strings.TrimPrefix(args[1], "--at="))
|
||||
titleIdx = 2
|
||||
} else {
|
||||
if len(msg.Array) > 1 {
|
||||
id := str2uin64(msg.Array[1].Content)
|
||||
c.SetGroupSpecialTitle(msg.GroupID, id, decodeSpecialChars(args.Contents[1]))
|
||||
return
|
||||
}
|
||||
c.SetGroupSpecialTitle(msg.GroupID, msg.UserID, args.Contents[1])
|
||||
targetUserID = src.UserID
|
||||
titleIdx = 1
|
||||
}
|
||||
|
||||
if titleIdx >= len(args) {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
title := args[titleIdx]
|
||||
if length := len([]byte(title)); length > 18 {
|
||||
c.SendMsg(src.GroupID, src.UserID, qbot.CQReply(src.MsgID)+"Title length not allowed to exceed 18 bytes, currently "+strconv.FormatInt(int64(length), 10)+" bytes")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetGroupSpecialTitle(src.GroupID, targetUserID, decodeSpecialChars(title))
|
||||
}
|
||||
|
||||
104
cmds/testapi.go
Normal file
104
cmds/testapi.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
const testapiHelpMsg string = `Test API functionality.
|
||||
Usage: /testapi <action> [arg1="value1" arg2=value2 ...]
|
||||
Example: /testapi get_login_info`
|
||||
|
||||
type TestapiCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewTestapiCommand() *TestapiCommand {
|
||||
return &TestapiCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "testapi",
|
||||
HelpMsg: testapiHelpMsg,
|
||||
Permission: getCmdPermLevel("testapi"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *TestapiCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *TestapiCommand) Exec(c *qbot.Client, args []string, src *srcMsg, _ int) {
|
||||
if len(args) < 2 {
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
action := args[1]
|
||||
|
||||
// parse parameters
|
||||
params := make(map[string]any)
|
||||
|
||||
for i := 2; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
|
||||
// find equal sign separator
|
||||
if idx := strings.Index(arg, "="); idx != -1 {
|
||||
key := arg[:idx]
|
||||
value := arg[idx+1:]
|
||||
|
||||
// handle $group variable replacement
|
||||
if value == "$group" {
|
||||
params[key] = src.GroupID
|
||||
continue
|
||||
}
|
||||
|
||||
// check if it is a string type (surrounded by quotes)
|
||||
if len(value) >= 2 && value[0] == '"' && value[len(value)-1] == '"' {
|
||||
// string type, remove quotes
|
||||
params[key] = value[1 : len(value)-1]
|
||||
} else {
|
||||
// try to parse as numeric type
|
||||
if intVal, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
params[key] = intVal
|
||||
} else if floatVal, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
params[key] = floatVal
|
||||
} else if boolVal, err := strconv.ParseBool(value); err == nil {
|
||||
params[key] = boolVal
|
||||
} else {
|
||||
// if all parsing fails, treat as string
|
||||
params[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call test API
|
||||
resp, err := c.SendTestAPIRequest(action, params)
|
||||
|
||||
var result string
|
||||
if err != nil {
|
||||
result = fmt.Sprintf("Error: %v", err)
|
||||
} else if resp == "" {
|
||||
result = "null"
|
||||
} else {
|
||||
// directly use the returned JSON string, and format it
|
||||
var jsonMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(resp), &jsonMap); err == nil {
|
||||
if jsonBytes, err := json.MarshalIndent(jsonMap, "", " "); err == nil {
|
||||
result = string(jsonBytes)
|
||||
} else {
|
||||
result = resp // if formatting fails, return the original string
|
||||
}
|
||||
} else {
|
||||
result = resp // if parsing fails, return the original string
|
||||
}
|
||||
}
|
||||
|
||||
c.SendMsg(src.GroupID, src.UserID, encodeSpecialChars(result))
|
||||
}
|
||||
@@ -22,28 +22,49 @@ type NbnhhshResponse []struct {
|
||||
Inputting []string `json:"inputting,omitempty"`
|
||||
}
|
||||
|
||||
func cmd_which(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
const helpMsg = `Usage: which <text>`
|
||||
if args.Size < 2 {
|
||||
c.SendMsg(msg, helpMsg)
|
||||
return
|
||||
}
|
||||
const whichHelpMsg string = `Query abbreviation meanings.
|
||||
Usage: /which <text>
|
||||
Example: /which yyds`
|
||||
|
||||
for i := 1; i < len(args.Types); i++ {
|
||||
if args.Types[i] != qbot.Text {
|
||||
c.SendMsg(msg, "Only plain text is allowed")
|
||||
type WhichCommand struct {
|
||||
cmdBase
|
||||
}
|
||||
|
||||
func NewWhichCommand() *WhichCommand {
|
||||
return &WhichCommand{
|
||||
cmdBase: cmdBase{
|
||||
Name: "which",
|
||||
HelpMsg: whichHelpMsg,
|
||||
Permission: getCmdPermLevel("which"),
|
||||
AllowPrefix: false,
|
||||
NeedRawMsg: false,
|
||||
MaxArgs: 2,
|
||||
MinArgs: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *WhichCommand) Self() *cmdBase {
|
||||
return &cmd.cmdBase
|
||||
}
|
||||
|
||||
func (cmd *WhichCommand) Exec(c *qbot.Client, args []string, src *srcMsg, begin int) {
|
||||
// Check for non-text type parameters
|
||||
for i := 1; i < len(args); i++ {
|
||||
if strings.HasPrefix(args[i], "--") {
|
||||
c.SendMsg(src.GroupID, src.UserID, "Only plain text is allowed")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
text := strings.Join(args.Contents[1:], " ")
|
||||
text := strings.Join(args[1:], " ")
|
||||
if text == "" {
|
||||
c.SendMsg(msg, helpMsg)
|
||||
c.SendMsg(src.GroupID, src.UserID, cmd.HelpMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(text, ";") {
|
||||
c.SendMsg(msg, "Multiple queries are not allowed")
|
||||
c.SendMsg(src.GroupID, src.UserID, "Multiple queries are not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,13 +74,13 @@ func cmd_which(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
|
||||
jsonData, err := json.Marshal(reqData)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://lab.magiconch.com/api/nbnhhsh/guess", bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,39 +92,39 @@ func cmd_which(c *qbot.Client, msg *qbot.Message, args *ArgsList) {
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
c.SendMsg(msg, fmt.Sprintf("http error %d", resp.StatusCode))
|
||||
c.SendMsg(src.GroupID, src.UserID, fmt.Sprintf("http error %d", resp.StatusCode))
|
||||
return
|
||||
}
|
||||
|
||||
var nbnhhshResp NbnhhshResponse
|
||||
if err := json.Unmarshal(body, &nbnhhshResp); err != nil {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(src.GroupID, src.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(nbnhhshResp) == 0 {
|
||||
c.SendMsg(msg, "null")
|
||||
c.SendMsg(src.GroupID, src.UserID, "null")
|
||||
return
|
||||
}
|
||||
|
||||
result := nbnhhshResp[0]
|
||||
|
||||
if len(result.Trans) > 0 {
|
||||
c.SendMsg(msg, strings.Join(result.Trans, ", "))
|
||||
c.SendMsg(src.GroupID, src.UserID, strings.Join(result.Trans, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
c.SendMsg(msg, "null")
|
||||
c.SendMsg(src.GroupID, src.UserID, "null")
|
||||
}
|
||||
|
||||
397
config/config.go
397
config/config.go
@@ -1,100 +1,347 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"slices"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
// export
|
||||
ApiKey string
|
||||
NapcatWSURL string
|
||||
MasterID uint64
|
||||
BotID uint64
|
||||
// Config 配置结构体
|
||||
type yamlConfig struct {
|
||||
// NapCat 配置
|
||||
NapcatHttpServer string `yaml:"napcat_http_server"` // 正向 HTTP 地址
|
||||
ReverseHttpServer string `yaml:"reverse_http_server"` // 反向 HTTP 监听端口
|
||||
|
||||
PsqlHost string
|
||||
PsqlPort uint16
|
||||
PsqlUser string
|
||||
PsqlPassword string
|
||||
PsqlDbName string
|
||||
// API Keys 配置
|
||||
ApiKeys struct {
|
||||
DrawUrlBase string `yaml:"draw_url_base"`
|
||||
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"`
|
||||
|
||||
ExchangeRateAPIKey string
|
||||
OkxMirrorAPIKey string
|
||||
)
|
||||
// PostgreSQL 配置
|
||||
PostgreSQL struct {
|
||||
Host string `yaml:"host"`
|
||||
Port uint16 `yaml:"port"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
DbName string `yaml:"dbname"`
|
||||
} `yaml:"postgresql"`
|
||||
|
||||
// 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"`
|
||||
} `yaml:"permissions"`
|
||||
|
||||
// 其他配置
|
||||
ProxyURL string `yaml:"proxy_url,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 (
|
||||
// environment values
|
||||
env_NAPCAT_HOST = "NAPCAT_HOST"
|
||||
env_ACCESS_TOKEN = "ACCESS_TOKEN"
|
||||
env_API_KEY = "API_KEY"
|
||||
env_MASTER_ID = "MASTER_ID"
|
||||
env_BOT_ID = "BOT_ID"
|
||||
|
||||
env_PSQL_HOST = "PSQL_HOST"
|
||||
env_PSQL_PORT = "PSQL_PORT"
|
||||
env_PSQL_USER = "PSQL_USER"
|
||||
env_PSQL_PASSWORD = "PSQL_PASSWORD"
|
||||
env_PSQL_DBNAME = "PSQL_DBNAME"
|
||||
|
||||
env_EXCHANGE_RATE_API_KEY = "EXCHANGE_RATE_API_KEY"
|
||||
env_OKX_MIRROR_API_KEY = "OKX_MIRROR_API_KEY"
|
||||
Guest Permission = 0 // 所有人都可以使用
|
||||
Admin Permission = 3 // 管理员及以上
|
||||
Master Permission = 4 // 仅 Master
|
||||
)
|
||||
|
||||
var BotOwnerGroupIDs = []uint64{
|
||||
948697448,
|
||||
866738031,
|
||||
}
|
||||
var Cfg yamlConfig
|
||||
|
||||
func getEnvString(env string, def string) string {
|
||||
val := os.Getenv(env)
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func getEnvUInt(env string, def uint64) uint64 {
|
||||
val := os.Getenv(env)
|
||||
if val == "" {
|
||||
return def
|
||||
}
|
||||
ret, err := strconv.ParseUint(val, 10, 64)
|
||||
func LoadConfig(configPath string) error {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Parse %s=%s failed: %s", env, val, err.Error())
|
||||
return fmt.Errorf("读取配置文件失败: %w", err)
|
||||
}
|
||||
return ret
|
||||
|
||||
if err := yaml.Unmarshal(data, &Cfg); err != nil {
|
||||
return fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
|
||||
// NapCat 默认值
|
||||
if Cfg.NapcatHttpServer == "" {
|
||||
Cfg.NapcatHttpServer = "http://127.0.0.1:3000"
|
||||
}
|
||||
if Cfg.ReverseHttpServer == "" {
|
||||
Cfg.ReverseHttpServer = "0.0.0.0:3001"
|
||||
}
|
||||
|
||||
// 权限默认值
|
||||
if Cfg.Permissions.MasterID == 0 {
|
||||
Cfg.Permissions.MasterID = 1006554341
|
||||
}
|
||||
if Cfg.Permissions.BotID == 0 {
|
||||
Cfg.Permissions.BotID = 3552586437
|
||||
}
|
||||
|
||||
// PostgreSQL 默认值
|
||||
if Cfg.PostgreSQL.Host == "" {
|
||||
Cfg.PostgreSQL.Host = "127.0.0.1"
|
||||
}
|
||||
if Cfg.PostgreSQL.Port == 0 {
|
||||
Cfg.PostgreSQL.Port = 5432
|
||||
}
|
||||
|
||||
// Longport 默认值
|
||||
if Cfg.ApiKeys.Longport.Region == "" {
|
||||
Cfg.ApiKeys.Longport.Region = "cn"
|
||||
}
|
||||
|
||||
// Python 默认值
|
||||
if Cfg.Python.Interpreter == "" {
|
||||
Cfg.Python.Interpreter = "python3"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEnvPort(env string, def uint16) uint16 {
|
||||
val := os.Getenv(env)
|
||||
if val == "" {
|
||||
return def
|
||||
func LoadConfigFile() {
|
||||
configPathPtr := flag.String("c", "config.yaml", "配置文件路径")
|
||||
flag.Parse()
|
||||
|
||||
configPath = *configPathPtr
|
||||
if err := LoadConfig(configPath); err != nil {
|
||||
log.Fatalf("加载配置失败: %v", err)
|
||||
}
|
||||
ret, err := strconv.ParseUint(val, 10, 16)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func GetUserPermission(userID uint64) Permission {
|
||||
if userID == Cfg.Permissions.MasterID {
|
||||
return Master
|
||||
}
|
||||
if Cfg.IsAdmin(userID) {
|
||||
return Admin
|
||||
}
|
||||
return Guest
|
||||
}
|
||||
|
||||
// IsAdmin 检查用户是否是管理员
|
||||
func (cfg *yamlConfig) IsAdmin(userID uint64) bool {
|
||||
if userID == cfg.Permissions.MasterID {
|
||||
return true
|
||||
}
|
||||
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")
|
||||
}
|
||||
if slices.Contains(Cfg.Permissions.AdminIDs, userID) {
|
||||
return fmt.Errorf("user is already admin")
|
||||
}
|
||||
Cfg.Permissions.AdminIDs = append(Cfg.Permissions.AdminIDs, userID)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
func RemoveAdmin(userID uint64) error {
|
||||
if userID == Cfg.Permissions.MasterID {
|
||||
return fmt.Errorf("cannot remove master")
|
||||
}
|
||||
idx := -1
|
||||
for i, id := range Cfg.Permissions.AdminIDs {
|
||||
if id == userID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("user is not admin")
|
||||
}
|
||||
Cfg.Permissions.AdminIDs = append(Cfg.Permissions.AdminIDs[:idx], Cfg.Permissions.AdminIDs[idx+1:]...)
|
||||
return SaveConfig()
|
||||
}
|
||||
|
||||
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) {
|
||||
configPath = path
|
||||
}
|
||||
|
||||
func SaveConfig() error {
|
||||
if configPath == "" {
|
||||
configPath = "config.yaml"
|
||||
}
|
||||
data, err := yaml.Marshal(&Cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Parse port %s=%s failed: %s", env, val, err.Error())
|
||||
return fmt.Errorf("marshal config failed: %w", err)
|
||||
}
|
||||
return uint16(ret)
|
||||
err = os.WriteFile(configPath, data, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write config file failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
napcatHost := getEnvString(env_NAPCAT_HOST, "127.0.0.1:3001")
|
||||
accessToken := os.Getenv(env_ACCESS_TOKEN)
|
||||
ApiKey = os.Getenv(env_API_KEY)
|
||||
|
||||
NapcatWSURL = "ws://" + napcatHost
|
||||
if accessToken != "" {
|
||||
NapcatWSURL += "?access_token=" + accessToken
|
||||
func ReloadConfig() error {
|
||||
if configPath == "" {
|
||||
configPath = "config.yaml"
|
||||
}
|
||||
|
||||
MasterID = getEnvUInt(env_MASTER_ID, 1006554341)
|
||||
BotID = getEnvUInt(env_BOT_ID, 3552586437)
|
||||
PsqlHost = getEnvString(env_PSQL_HOST, "127.0.0.1")
|
||||
PsqlPort = getEnvPort(env_PSQL_PORT, 5432)
|
||||
PsqlUser = os.Getenv(env_PSQL_USER)
|
||||
PsqlPassword = os.Getenv(env_PSQL_PASSWORD)
|
||||
PsqlDbName = os.Getenv(env_PSQL_DBNAME)
|
||||
ExchangeRateAPIKey = os.Getenv(env_EXCHANGE_RATE_API_KEY)
|
||||
OkxMirrorAPIKey = os.Getenv(env_OKX_MIRROR_API_KEY)
|
||||
return LoadConfig(configPath)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,38 @@
|
||||
version: '3.8'
|
||||
|
||||
version: "3"
|
||||
services:
|
||||
napcat:
|
||||
environment:
|
||||
- NAPCAT_UID=501
|
||||
- NAPCAT_GID=20
|
||||
- ACCOUNT=3552586437
|
||||
volumes:
|
||||
- ./napcat-app:/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
- 3001:3001
|
||||
- 6099:6099
|
||||
container_name: napcat
|
||||
restart: on-failure
|
||||
image: mlikiowa/napcat-docker:latest
|
||||
networks:
|
||||
- qbot
|
||||
|
||||
hurobot-psql:
|
||||
image: postgres:latest
|
||||
container_name: hurobot-psql
|
||||
hostname: hurobot-psql
|
||||
environment:
|
||||
POSTGRES_USER: hurobot
|
||||
POSTGRES_PASSWORD: hurobot
|
||||
POSTGRES_PASSWORD: hurobot114514qwq
|
||||
POSTGRES_DB: hurobot
|
||||
volumes:
|
||||
- ./psql-init:/docker-entrypoint-initdb.d
|
||||
- ./psql-data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- "54321:5432"
|
||||
restart: on-failure
|
||||
networks:
|
||||
- qbot
|
||||
networks:
|
||||
qbot:
|
||||
driver: bridge
|
||||
@@ -43,19 +43,6 @@ CREATE TABLE group_llm_configs (
|
||||
|
||||
CREATE INDEX idx_messages_covering ON messages("group_id", "is_cmd", "time" DESC, "user_id", "content", "msg_id");
|
||||
|
||||
CREATE TABLE user_events (
|
||||
"user_id" BIGINT NOT NULL,
|
||||
"event_idx" INTEGER NOT NULL,
|
||||
"msg_regex" TEXT NOT NULL,
|
||||
"reply_text" TEXT NOT NULL,
|
||||
"rand_prob" REAL NOT NULL DEFAULT 1.0,
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY ("user_id", "event_idx"),
|
||||
FOREIGN KEY ("user_id") REFERENCES users(user_id),
|
||||
CONSTRAINT check_rand_prob CHECK (rand_prob >= 0.0 AND rand_prob <= 1.0),
|
||||
CONSTRAINT check_event_idx CHECK (event_idx >= 0 AND event_idx <= 9)
|
||||
);
|
||||
|
||||
CREATE TABLE group_rcon_configs (
|
||||
"group_id" BIGINT NOT NULL,
|
||||
"address" TEXT NOT NULL,
|
||||
@@ -66,11 +53,3 @@ CREATE TABLE group_rcon_configs (
|
||||
|
||||
INSERT INTO suppliers ("name", "base_url", "api_key", "default_model") VALUES
|
||||
('siliconflow', 'https://api.siliconflow.cn/v1', '', 'deepseek-ai/DeepSeek-V3.1');
|
||||
|
||||
CREATE TABLE legacy_game (
|
||||
"user_id" BIGINT NOT NULL,
|
||||
"energy" INT NOT NULL DEFAULT 0,
|
||||
"balance" INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("user_id"),
|
||||
FOREIGN KEY ("user_id") REFERENCES users(user_id)
|
||||
)
|
||||
|
||||
@@ -1,590 +0,0 @@
|
||||
CREATE TABLE tts_voices (
|
||||
"name" TEXT NOT NULL PRIMARY KEY,
|
||||
"local" TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO tts_voices VALUES ('af-ZA-AdriNeural', 'af-ZA');
|
||||
INSERT INTO tts_voices VALUES ('af-ZA-WillemNeural', 'af-ZA');
|
||||
INSERT INTO tts_voices VALUES ('am-ET-MekdesNeural', 'am-ET');
|
||||
INSERT INTO tts_voices VALUES ('am-ET-AmehaNeural', 'am-ET');
|
||||
INSERT INTO tts_voices VALUES ('ar-AE-FatimaNeural', 'ar-AE');
|
||||
INSERT INTO tts_voices VALUES ('ar-AE-HamdanNeural', 'ar-AE');
|
||||
INSERT INTO tts_voices VALUES ('ar-BH-LailaNeural', 'ar-BH');
|
||||
INSERT INTO tts_voices VALUES ('ar-BH-AliNeural', 'ar-BH');
|
||||
INSERT INTO tts_voices VALUES ('ar-DZ-AminaNeural', 'ar-DZ');
|
||||
INSERT INTO tts_voices VALUES ('ar-DZ-IsmaelNeural', 'ar-DZ');
|
||||
INSERT INTO tts_voices VALUES ('ar-EG-SalmaNeural', 'ar-EG');
|
||||
INSERT INTO tts_voices VALUES ('ar-EG-ShakirNeural', 'ar-EG');
|
||||
INSERT INTO tts_voices VALUES ('ar-IQ-RanaNeural', 'ar-IQ');
|
||||
INSERT INTO tts_voices VALUES ('ar-IQ-BasselNeural', 'ar-IQ');
|
||||
INSERT INTO tts_voices VALUES ('ar-JO-SanaNeural', 'ar-JO');
|
||||
INSERT INTO tts_voices VALUES ('ar-JO-TaimNeural', 'ar-JO');
|
||||
INSERT INTO tts_voices VALUES ('ar-KW-NouraNeural', 'ar-KW');
|
||||
INSERT INTO tts_voices VALUES ('ar-KW-FahedNeural', 'ar-KW');
|
||||
INSERT INTO tts_voices VALUES ('ar-LB-LaylaNeural', 'ar-LB');
|
||||
INSERT INTO tts_voices VALUES ('ar-LB-RamiNeural', 'ar-LB');
|
||||
INSERT INTO tts_voices VALUES ('ar-LY-ImanNeural', 'ar-LY');
|
||||
INSERT INTO tts_voices VALUES ('ar-LY-OmarNeural', 'ar-LY');
|
||||
INSERT INTO tts_voices VALUES ('ar-MA-MounaNeural', 'ar-MA');
|
||||
INSERT INTO tts_voices VALUES ('ar-MA-JamalNeural', 'ar-MA');
|
||||
INSERT INTO tts_voices VALUES ('ar-OM-AyshaNeural', 'ar-OM');
|
||||
INSERT INTO tts_voices VALUES ('ar-OM-AbdullahNeural', 'ar-OM');
|
||||
INSERT INTO tts_voices VALUES ('ar-QA-AmalNeural', 'ar-QA');
|
||||
INSERT INTO tts_voices VALUES ('ar-QA-MoazNeural', 'ar-QA');
|
||||
INSERT INTO tts_voices VALUES ('ar-SA-ZariyahNeural', 'ar-SA');
|
||||
INSERT INTO tts_voices VALUES ('ar-SA-HamedNeural', 'ar-SA');
|
||||
INSERT INTO tts_voices VALUES ('ar-SY-AmanyNeural', 'ar-SY');
|
||||
INSERT INTO tts_voices VALUES ('ar-SY-LaithNeural', 'ar-SY');
|
||||
INSERT INTO tts_voices VALUES ('ar-TN-ReemNeural', 'ar-TN');
|
||||
INSERT INTO tts_voices VALUES ('ar-TN-HediNeural', 'ar-TN');
|
||||
INSERT INTO tts_voices VALUES ('ar-YE-MaryamNeural', 'ar-YE');
|
||||
INSERT INTO tts_voices VALUES ('ar-YE-SalehNeural', 'ar-YE');
|
||||
INSERT INTO tts_voices VALUES ('as-IN-YashicaNeural', 'as-IN');
|
||||
INSERT INTO tts_voices VALUES ('as-IN-PriyomNeural', 'as-IN');
|
||||
INSERT INTO tts_voices VALUES ('az-AZ-BanuNeural', 'az-AZ');
|
||||
INSERT INTO tts_voices VALUES ('az-AZ-BabekNeural', 'az-AZ');
|
||||
INSERT INTO tts_voices VALUES ('bg-BG-KalinaNeural', 'bg-BG');
|
||||
INSERT INTO tts_voices VALUES ('bg-BG-BorislavNeural', 'bg-BG');
|
||||
INSERT INTO tts_voices VALUES ('bn-BD-NabanitaNeural', 'bn-BD');
|
||||
INSERT INTO tts_voices VALUES ('bn-BD-PradeepNeural', 'bn-BD');
|
||||
INSERT INTO tts_voices VALUES ('bn-IN-TanishaaNeural', 'bn-IN');
|
||||
INSERT INTO tts_voices VALUES ('bn-IN-BashkarNeural', 'bn-IN');
|
||||
INSERT INTO tts_voices VALUES ('bs-BA-VesnaNeural', 'bs-BA');
|
||||
INSERT INTO tts_voices VALUES ('bs-BA-GoranNeural', 'bs-BA');
|
||||
INSERT INTO tts_voices VALUES ('ca-ES-JoanaNeural', 'ca-ES');
|
||||
INSERT INTO tts_voices VALUES ('ca-ES-EnricNeural', 'ca-ES');
|
||||
INSERT INTO tts_voices VALUES ('ca-ES-AlbaNeural', 'ca-ES');
|
||||
INSERT INTO tts_voices VALUES ('cs-CZ-VlastaNeural', 'cs-CZ');
|
||||
INSERT INTO tts_voices VALUES ('cs-CZ-AntoninNeural', 'cs-CZ');
|
||||
INSERT INTO tts_voices VALUES ('cy-GB-NiaNeural', 'cy-GB');
|
||||
INSERT INTO tts_voices VALUES ('cy-GB-AledNeural', 'cy-GB');
|
||||
INSERT INTO tts_voices VALUES ('da-DK-ChristelNeural', 'da-DK');
|
||||
INSERT INTO tts_voices VALUES ('da-DK-JeppeNeural', 'da-DK');
|
||||
INSERT INTO tts_voices VALUES ('de-AT-IngridNeural', 'de-AT');
|
||||
INSERT INTO tts_voices VALUES ('de-AT-JonasNeural', 'de-AT');
|
||||
INSERT INTO tts_voices VALUES ('de-CH-LeniNeural', 'de-CH');
|
||||
INSERT INTO tts_voices VALUES ('de-CH-JanNeural', 'de-CH');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-KatjaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-ConradNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-SeraphinaMultilingualNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-FlorianMultilingualNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-AmalaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-BerndNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-ChristophNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-ElkeNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-GiselaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-KasperNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-KillianNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-KlarissaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-KlausNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-LouisaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-MajaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-RalfNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-TanjaNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-Florian:DragonHDLatestNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('de-DE-Seraphina:DragonHDLatestNeural', 'de-DE');
|
||||
INSERT INTO tts_voices VALUES ('el-GR-AthinaNeural', 'el-GR');
|
||||
INSERT INTO tts_voices VALUES ('el-GR-NestorasNeural', 'el-GR');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-NatashaNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-WilliamNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-AnnetteNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-CarlyNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-DarrenNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-DuncanNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-ElsieNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-FreyaNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-JoanneNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-KenNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-KimNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-NeilNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-TimNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-AU-TinaNeural', 'en-AU');
|
||||
INSERT INTO tts_voices VALUES ('en-CA-ClaraNeural', 'en-CA');
|
||||
INSERT INTO tts_voices VALUES ('en-CA-LiamNeural', 'en-CA');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-SoniaNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-RyanNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-LibbyNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-AdaMultilingualNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-OllieMultilingualNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-AbbiNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-AlfieNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-BellaNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-ElliotNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-EthanNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-HollieNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-MaisieNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-NoahNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-OliverNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-OliviaNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-ThomasNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-GB-MiaNeural', 'en-GB');
|
||||
INSERT INTO tts_voices VALUES ('en-HK-YanNeural', 'en-HK');
|
||||
INSERT INTO tts_voices VALUES ('en-HK-SamNeural', 'en-HK');
|
||||
INSERT INTO tts_voices VALUES ('en-IE-EmilyNeural', 'en-IE');
|
||||
INSERT INTO tts_voices VALUES ('en-IE-ConnorNeural', 'en-IE');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-AaravNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-AashiNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-AartiNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-ArjunNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-AnanyaNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-KavyaNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-KunalNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-NeerjaNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-PrabhatNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-IN-RehaanNeural', 'en-IN');
|
||||
INSERT INTO tts_voices VALUES ('en-KE-AsiliaNeural', 'en-KE');
|
||||
INSERT INTO tts_voices VALUES ('en-KE-ChilembaNeural', 'en-KE');
|
||||
INSERT INTO tts_voices VALUES ('en-NG-EzinneNeural', 'en-NG');
|
||||
INSERT INTO tts_voices VALUES ('en-NG-AbeoNeural', 'en-NG');
|
||||
INSERT INTO tts_voices VALUES ('en-NZ-MollyNeural', 'en-NZ');
|
||||
INSERT INTO tts_voices VALUES ('en-NZ-MitchellNeural', 'en-NZ');
|
||||
INSERT INTO tts_voices VALUES ('en-PH-RosaNeural', 'en-PH');
|
||||
INSERT INTO tts_voices VALUES ('en-PH-JamesNeural', 'en-PH');
|
||||
INSERT INTO tts_voices VALUES ('en-SG-LunaNeural', 'en-SG');
|
||||
INSERT INTO tts_voices VALUES ('en-SG-WayneNeural', 'en-SG');
|
||||
INSERT INTO tts_voices VALUES ('en-TZ-ImaniNeural', 'en-TZ');
|
||||
INSERT INTO tts_voices VALUES ('en-TZ-ElimuNeural', 'en-TZ');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AvaMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AndrewMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-EmmaMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AlloyTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-EchoTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-FableTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-OnyxTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-NovaTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-ShimmerTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-BrianMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AvaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AndrewNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-EmmaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-BrianNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-JennyNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-GuyNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AriaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-DavisNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-JaneNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-JasonNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-KaiNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-LunaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-SaraNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-TonyNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-NancyNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-CoraMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-ChristopherMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-BrandonMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AmberNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AnaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AshleyNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-BrandonNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-ChristopherNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-CoraNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-ElizabethNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-EricNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-JacobNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-JennyMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-MichelleNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-MonicaNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-RogerNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-RyanMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-SteffanNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AdamMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AIGenerate1Neural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AIGenerate2Neural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AmandaMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-AshTurboMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-BlueNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-DavisMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-DerekMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-DustinMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-EvelynMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-LewisMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-LolaMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-NancyMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-PhoebeMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-SamuelMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-SerenaMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-SteffanMultilingualNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Adam:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Andrew:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Andrew2:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Ava:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Brian:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Davis:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Emma:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Emma2:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Steffan:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Alloy:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Andrew3:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Aria:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Ava3:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Jenny:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-MultiTalker-Ava-Andrew:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Nova:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Phoebe:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-US-Serena:DragonHDLatestNeural', 'en-US');
|
||||
INSERT INTO tts_voices VALUES ('en-ZA-LeahNeural', 'en-ZA');
|
||||
INSERT INTO tts_voices VALUES ('en-ZA-LukeNeural', 'en-ZA');
|
||||
INSERT INTO tts_voices VALUES ('es-AR-ElenaNeural', 'es-AR');
|
||||
INSERT INTO tts_voices VALUES ('es-AR-TomasNeural', 'es-AR');
|
||||
INSERT INTO tts_voices VALUES ('es-BO-SofiaNeural', 'es-BO');
|
||||
INSERT INTO tts_voices VALUES ('es-BO-MarceloNeural', 'es-BO');
|
||||
INSERT INTO tts_voices VALUES ('es-CL-CatalinaNeural', 'es-CL');
|
||||
INSERT INTO tts_voices VALUES ('es-CL-LorenzoNeural', 'es-CL');
|
||||
INSERT INTO tts_voices VALUES ('es-CO-SalomeNeural', 'es-CO');
|
||||
INSERT INTO tts_voices VALUES ('es-CO-GonzaloNeural', 'es-CO');
|
||||
INSERT INTO tts_voices VALUES ('es-CR-MariaNeural', 'es-CR');
|
||||
INSERT INTO tts_voices VALUES ('es-CR-JuanNeural', 'es-CR');
|
||||
INSERT INTO tts_voices VALUES ('es-CU-BelkysNeural', 'es-CU');
|
||||
INSERT INTO tts_voices VALUES ('es-CU-ManuelNeural', 'es-CU');
|
||||
INSERT INTO tts_voices VALUES ('es-DO-RamonaNeural', 'es-DO');
|
||||
INSERT INTO tts_voices VALUES ('es-DO-EmilioNeural', 'es-DO');
|
||||
INSERT INTO tts_voices VALUES ('es-EC-AndreaNeural', 'es-EC');
|
||||
INSERT INTO tts_voices VALUES ('es-EC-LuisNeural', 'es-EC');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-ElviraNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-AlvaroNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-ArabellaMultilingualNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-IsidoraMultilingualNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-TristanMultilingualNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-XimenaMultilingualNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-AbrilNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-ArnauNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-DarioNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-EliasNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-EstrellaNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-IreneNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-LaiaNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-LiaNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-NilNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-SaulNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-TeoNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-TrianaNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-VeraNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-XimenaNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-Tristan:DragonHDLatestNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-ES-Ximena:DragonHDLatestNeural', 'es-ES');
|
||||
INSERT INTO tts_voices VALUES ('es-GQ-TeresaNeural', 'es-GQ');
|
||||
INSERT INTO tts_voices VALUES ('es-GQ-JavierNeural', 'es-GQ');
|
||||
INSERT INTO tts_voices VALUES ('es-GT-MartaNeural', 'es-GT');
|
||||
INSERT INTO tts_voices VALUES ('es-GT-AndresNeural', 'es-GT');
|
||||
INSERT INTO tts_voices VALUES ('es-HN-KarlaNeural', 'es-HN');
|
||||
INSERT INTO tts_voices VALUES ('es-HN-CarlosNeural', 'es-HN');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-DaliaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-JorgeNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-BeatrizNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-CandelaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-CarlotaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-CecilioNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-GerardoNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-LarissaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-LibertoNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-LucianoNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-MarinaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-NuriaNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-PelayoNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-RenataNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-MX-YagoNeural', 'es-MX');
|
||||
INSERT INTO tts_voices VALUES ('es-NI-YolandaNeural', 'es-NI');
|
||||
INSERT INTO tts_voices VALUES ('es-NI-FedericoNeural', 'es-NI');
|
||||
INSERT INTO tts_voices VALUES ('es-PA-MargaritaNeural', 'es-PA');
|
||||
INSERT INTO tts_voices VALUES ('es-PA-RobertoNeural', 'es-PA');
|
||||
INSERT INTO tts_voices VALUES ('es-PE-CamilaNeural', 'es-PE');
|
||||
INSERT INTO tts_voices VALUES ('es-PE-AlexNeural', 'es-PE');
|
||||
INSERT INTO tts_voices VALUES ('es-PR-KarinaNeural', 'es-PR');
|
||||
INSERT INTO tts_voices VALUES ('es-PR-VictorNeural', 'es-PR');
|
||||
INSERT INTO tts_voices VALUES ('es-PY-TaniaNeural', 'es-PY');
|
||||
INSERT INTO tts_voices VALUES ('es-PY-MarioNeural', 'es-PY');
|
||||
INSERT INTO tts_voices VALUES ('es-SV-LorenaNeural', 'es-SV');
|
||||
INSERT INTO tts_voices VALUES ('es-SV-RodrigoNeural', 'es-SV');
|
||||
INSERT INTO tts_voices VALUES ('es-US-PalomaNeural', 'es-US');
|
||||
INSERT INTO tts_voices VALUES ('es-US-AlonsoNeural', 'es-US');
|
||||
INSERT INTO tts_voices VALUES ('es-UY-ValentinaNeural', 'es-UY');
|
||||
INSERT INTO tts_voices VALUES ('es-UY-MateoNeural', 'es-UY');
|
||||
INSERT INTO tts_voices VALUES ('es-VE-PaolaNeural', 'es-VE');
|
||||
INSERT INTO tts_voices VALUES ('es-VE-SebastianNeural', 'es-VE');
|
||||
INSERT INTO tts_voices VALUES ('et-EE-AnuNeural', 'et-EE');
|
||||
INSERT INTO tts_voices VALUES ('et-EE-KertNeural', 'et-EE');
|
||||
INSERT INTO tts_voices VALUES ('eu-ES-AinhoaNeural', 'eu-ES');
|
||||
INSERT INTO tts_voices VALUES ('eu-ES-AnderNeural', 'eu-ES');
|
||||
INSERT INTO tts_voices VALUES ('fa-IR-DilaraNeural', 'fa-IR');
|
||||
INSERT INTO tts_voices VALUES ('fa-IR-FaridNeural', 'fa-IR');
|
||||
INSERT INTO tts_voices VALUES ('fi-FI-SelmaNeural', 'fi-FI');
|
||||
INSERT INTO tts_voices VALUES ('fi-FI-HarriNeural', 'fi-FI');
|
||||
INSERT INTO tts_voices VALUES ('fi-FI-NooraNeural', 'fi-FI');
|
||||
INSERT INTO tts_voices VALUES ('fil-PH-BlessicaNeural', 'fil-PH');
|
||||
INSERT INTO tts_voices VALUES ('fil-PH-AngeloNeural', 'fil-PH');
|
||||
INSERT INTO tts_voices VALUES ('fr-BE-CharlineNeural', 'fr-BE');
|
||||
INSERT INTO tts_voices VALUES ('fr-BE-GerardNeural', 'fr-BE');
|
||||
INSERT INTO tts_voices VALUES ('fr-CA-SylvieNeural', 'fr-CA');
|
||||
INSERT INTO tts_voices VALUES ('fr-CA-JeanNeural', 'fr-CA');
|
||||
INSERT INTO tts_voices VALUES ('fr-CA-AntoineNeural', 'fr-CA');
|
||||
INSERT INTO tts_voices VALUES ('fr-CA-ThierryNeural', 'fr-CA');
|
||||
INSERT INTO tts_voices VALUES ('fr-CH-ArianeNeural', 'fr-CH');
|
||||
INSERT INTO tts_voices VALUES ('fr-CH-FabriceNeural', 'fr-CH');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-DeniseNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-HenriNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-VivienneMultilingualNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-RemyMultilingualNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-LucienMultilingualNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-AlainNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-BrigitteNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-CelesteNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-ClaudeNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-CoralieNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-EloiseNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-JacquelineNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-JeromeNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-JosephineNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-MauriceNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-YvesNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-YvetteNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-Remy:DragonHDLatestNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('fr-FR-Vivienne:DragonHDLatestNeural', 'fr-FR');
|
||||
INSERT INTO tts_voices VALUES ('ga-IE-OrlaNeural', 'ga-IE');
|
||||
INSERT INTO tts_voices VALUES ('ga-IE-ColmNeural', 'ga-IE');
|
||||
INSERT INTO tts_voices VALUES ('gl-ES-SabelaNeural', 'gl-ES');
|
||||
INSERT INTO tts_voices VALUES ('gl-ES-RoiNeural', 'gl-ES');
|
||||
INSERT INTO tts_voices VALUES ('gu-IN-DhwaniNeural', 'gu-IN');
|
||||
INSERT INTO tts_voices VALUES ('gu-IN-NiranjanNeural', 'gu-IN');
|
||||
INSERT INTO tts_voices VALUES ('he-IL-HilaNeural', 'he-IL');
|
||||
INSERT INTO tts_voices VALUES ('he-IL-AvriNeural', 'he-IL');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-AaravNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-AnanyaNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-AartiNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-ArjunNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-KavyaNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-KunalNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-RehaanNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-SwaraNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hi-IN-MadhurNeural', 'hi-IN');
|
||||
INSERT INTO tts_voices VALUES ('hr-HR-GabrijelaNeural', 'hr-HR');
|
||||
INSERT INTO tts_voices VALUES ('hr-HR-SreckoNeural', 'hr-HR');
|
||||
INSERT INTO tts_voices VALUES ('hu-HU-NoemiNeural', 'hu-HU');
|
||||
INSERT INTO tts_voices VALUES ('hu-HU-TamasNeural', 'hu-HU');
|
||||
INSERT INTO tts_voices VALUES ('hy-AM-AnahitNeural', 'hy-AM');
|
||||
INSERT INTO tts_voices VALUES ('hy-AM-HaykNeural', 'hy-AM');
|
||||
INSERT INTO tts_voices VALUES ('id-ID-GadisNeural', 'id-ID');
|
||||
INSERT INTO tts_voices VALUES ('id-ID-ArdiNeural', 'id-ID');
|
||||
INSERT INTO tts_voices VALUES ('is-IS-GudrunNeural', 'is-IS');
|
||||
INSERT INTO tts_voices VALUES ('is-IS-GunnarNeural', 'is-IS');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-ElsaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-IsabellaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-DiegoNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-AlessioMultilingualNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-IsabellaMultilingualNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-GiuseppeMultilingualNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-MarcelloMultilingualNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-BenignoNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-CalimeroNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-CataldoNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-FabiolaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-FiammaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-GianniNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-GiuseppeNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-ImeldaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-IrmaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-LisandroNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-PalmiraNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-PierinaNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('it-IT-RinaldoNeural', 'it-IT');
|
||||
INSERT INTO tts_voices VALUES ('iu-Cans-CA-SiqiniqNeural', 'iu-Cans-CA');
|
||||
INSERT INTO tts_voices VALUES ('iu-Cans-CA-TaqqiqNeural', 'iu-Cans-CA');
|
||||
INSERT INTO tts_voices VALUES ('iu-Latn-CA-SiqiniqNeural', 'iu-Latn-CA');
|
||||
INSERT INTO tts_voices VALUES ('iu-Latn-CA-TaqqiqNeural', 'iu-Latn-CA');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-NanamiNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-KeitaNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-AoiNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-DaichiNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-MayuNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-NaokiNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-ShioriNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-MasaruMultilingualNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-Masaru:DragonHDLatestNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('ja-JP-Nanami:DragonHDLatestNeural', 'ja-JP');
|
||||
INSERT INTO tts_voices VALUES ('jv-ID-SitiNeural', 'jv-ID');
|
||||
INSERT INTO tts_voices VALUES ('jv-ID-DimasNeural', 'jv-ID');
|
||||
INSERT INTO tts_voices VALUES ('ka-GE-EkaNeural', 'ka-GE');
|
||||
INSERT INTO tts_voices VALUES ('ka-GE-GiorgiNeural', 'ka-GE');
|
||||
INSERT INTO tts_voices VALUES ('kk-KZ-AigulNeural', 'kk-KZ');
|
||||
INSERT INTO tts_voices VALUES ('kk-KZ-DauletNeural', 'kk-KZ');
|
||||
INSERT INTO tts_voices VALUES ('km-KH-SreymomNeural', 'km-KH');
|
||||
INSERT INTO tts_voices VALUES ('km-KH-PisethNeural', 'km-KH');
|
||||
INSERT INTO tts_voices VALUES ('kn-IN-SapnaNeural', 'kn-IN');
|
||||
INSERT INTO tts_voices VALUES ('kn-IN-GaganNeural', 'kn-IN');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-SunHiNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-InJoonNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-HyunsuMultilingualNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-BongJinNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-GookMinNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-HyunsuNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-JiMinNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-SeoHyeonNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-SoonBokNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('ko-KR-YuJinNeural', 'ko-KR');
|
||||
INSERT INTO tts_voices VALUES ('lo-LA-KeomanyNeural', 'lo-LA');
|
||||
INSERT INTO tts_voices VALUES ('lo-LA-ChanthavongNeural', 'lo-LA');
|
||||
INSERT INTO tts_voices VALUES ('lt-LT-OnaNeural', 'lt-LT');
|
||||
INSERT INTO tts_voices VALUES ('lt-LT-LeonasNeural', 'lt-LT');
|
||||
INSERT INTO tts_voices VALUES ('lv-LV-EveritaNeural', 'lv-LV');
|
||||
INSERT INTO tts_voices VALUES ('lv-LV-NilsNeural', 'lv-LV');
|
||||
INSERT INTO tts_voices VALUES ('mk-MK-MarijaNeural', 'mk-MK');
|
||||
INSERT INTO tts_voices VALUES ('mk-MK-AleksandarNeural', 'mk-MK');
|
||||
INSERT INTO tts_voices VALUES ('ml-IN-SobhanaNeural', 'ml-IN');
|
||||
INSERT INTO tts_voices VALUES ('ml-IN-MidhunNeural', 'ml-IN');
|
||||
INSERT INTO tts_voices VALUES ('mn-MN-YesuiNeural', 'mn-MN');
|
||||
INSERT INTO tts_voices VALUES ('mn-MN-BataaNeural', 'mn-MN');
|
||||
INSERT INTO tts_voices VALUES ('mr-IN-AarohiNeural', 'mr-IN');
|
||||
INSERT INTO tts_voices VALUES ('mr-IN-ManoharNeural', 'mr-IN');
|
||||
INSERT INTO tts_voices VALUES ('ms-MY-YasminNeural', 'ms-MY');
|
||||
INSERT INTO tts_voices VALUES ('ms-MY-OsmanNeural', 'ms-MY');
|
||||
INSERT INTO tts_voices VALUES ('mt-MT-GraceNeural', 'mt-MT');
|
||||
INSERT INTO tts_voices VALUES ('mt-MT-JosephNeural', 'mt-MT');
|
||||
INSERT INTO tts_voices VALUES ('my-MM-NilarNeural', 'my-MM');
|
||||
INSERT INTO tts_voices VALUES ('my-MM-ThihaNeural', 'my-MM');
|
||||
INSERT INTO tts_voices VALUES ('nb-NO-PernilleNeural', 'nb-NO');
|
||||
INSERT INTO tts_voices VALUES ('nb-NO-FinnNeural', 'nb-NO');
|
||||
INSERT INTO tts_voices VALUES ('nb-NO-IselinNeural', 'nb-NO');
|
||||
INSERT INTO tts_voices VALUES ('ne-NP-HemkalaNeural', 'ne-NP');
|
||||
INSERT INTO tts_voices VALUES ('ne-NP-SagarNeural', 'ne-NP');
|
||||
INSERT INTO tts_voices VALUES ('nl-BE-DenaNeural', 'nl-BE');
|
||||
INSERT INTO tts_voices VALUES ('nl-BE-ArnaudNeural', 'nl-BE');
|
||||
INSERT INTO tts_voices VALUES ('nl-NL-FennaNeural', 'nl-NL');
|
||||
INSERT INTO tts_voices VALUES ('nl-NL-MaartenNeural', 'nl-NL');
|
||||
INSERT INTO tts_voices VALUES ('nl-NL-ColetteNeural', 'nl-NL');
|
||||
INSERT INTO tts_voices VALUES ('or-IN-SubhasiniNeural', 'or-IN');
|
||||
INSERT INTO tts_voices VALUES ('or-IN-SukantNeural', 'or-IN');
|
||||
INSERT INTO tts_voices VALUES ('pa-IN-OjasNeural', 'pa-IN');
|
||||
INSERT INTO tts_voices VALUES ('pa-IN-VaaniNeural', 'pa-IN');
|
||||
INSERT INTO tts_voices VALUES ('pl-PL-AgnieszkaNeural', 'pl-PL');
|
||||
INSERT INTO tts_voices VALUES ('pl-PL-MarekNeural', 'pl-PL');
|
||||
INSERT INTO tts_voices VALUES ('pl-PL-ZofiaNeural', 'pl-PL');
|
||||
INSERT INTO tts_voices VALUES ('ps-AF-LatifaNeural', 'ps-AF');
|
||||
INSERT INTO tts_voices VALUES ('ps-AF-GulNawazNeural', 'ps-AF');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-FranciscaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-AntonioNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-MacerioMultilingualNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-ThalitaMultilingualNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-BrendaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-DonatoNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-ElzaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-FabioNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-GiovannaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-HumbertoNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-JulioNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-LeilaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-LeticiaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-ManuelaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-NicolauNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-ThalitaNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-ValerioNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-BR-YaraNeural', 'pt-BR');
|
||||
INSERT INTO tts_voices VALUES ('pt-PT-RaquelNeural', 'pt-PT');
|
||||
INSERT INTO tts_voices VALUES ('pt-PT-DuarteNeural', 'pt-PT');
|
||||
INSERT INTO tts_voices VALUES ('pt-PT-FernandaNeural', 'pt-PT');
|
||||
INSERT INTO tts_voices VALUES ('ro-RO-AlinaNeural', 'ro-RO');
|
||||
INSERT INTO tts_voices VALUES ('ro-RO-EmilNeural', 'ro-RO');
|
||||
INSERT INTO tts_voices VALUES ('ru-RU-SvetlanaNeural', 'ru-RU');
|
||||
INSERT INTO tts_voices VALUES ('ru-RU-DmitryNeural', 'ru-RU');
|
||||
INSERT INTO tts_voices VALUES ('ru-RU-DariyaNeural', 'ru-RU');
|
||||
INSERT INTO tts_voices VALUES ('si-LK-ThiliniNeural', 'si-LK');
|
||||
INSERT INTO tts_voices VALUES ('si-LK-SameeraNeural', 'si-LK');
|
||||
INSERT INTO tts_voices VALUES ('sk-SK-ViktoriaNeural', 'sk-SK');
|
||||
INSERT INTO tts_voices VALUES ('sk-SK-LukasNeural', 'sk-SK');
|
||||
INSERT INTO tts_voices VALUES ('sl-SI-PetraNeural', 'sl-SI');
|
||||
INSERT INTO tts_voices VALUES ('sl-SI-RokNeural', 'sl-SI');
|
||||
INSERT INTO tts_voices VALUES ('so-SO-UbaxNeural', 'so-SO');
|
||||
INSERT INTO tts_voices VALUES ('so-SO-MuuseNeural', 'so-SO');
|
||||
INSERT INTO tts_voices VALUES ('sq-AL-AnilaNeural', 'sq-AL');
|
||||
INSERT INTO tts_voices VALUES ('sq-AL-IlirNeural', 'sq-AL');
|
||||
INSERT INTO tts_voices VALUES ('sr-Latn-RS-NicholasNeural', 'sr-Latn-RS');
|
||||
INSERT INTO tts_voices VALUES ('sr-Latn-RS-SophieNeural', 'sr-Latn-RS');
|
||||
INSERT INTO tts_voices VALUES ('sr-RS-SophieNeural', 'sr-RS');
|
||||
INSERT INTO tts_voices VALUES ('sr-RS-NicholasNeural', 'sr-RS');
|
||||
INSERT INTO tts_voices VALUES ('su-ID-TutiNeural', 'su-ID');
|
||||
INSERT INTO tts_voices VALUES ('su-ID-JajangNeural', 'su-ID');
|
||||
INSERT INTO tts_voices VALUES ('sv-SE-SofieNeural', 'sv-SE');
|
||||
INSERT INTO tts_voices VALUES ('sv-SE-MattiasNeural', 'sv-SE');
|
||||
INSERT INTO tts_voices VALUES ('sv-SE-HilleviNeural', 'sv-SE');
|
||||
INSERT INTO tts_voices VALUES ('sw-KE-ZuriNeural', 'sw-KE');
|
||||
INSERT INTO tts_voices VALUES ('sw-KE-RafikiNeural', 'sw-KE');
|
||||
INSERT INTO tts_voices VALUES ('sw-TZ-RehemaNeural', 'sw-TZ');
|
||||
INSERT INTO tts_voices VALUES ('sw-TZ-DaudiNeural', 'sw-TZ');
|
||||
INSERT INTO tts_voices VALUES ('ta-IN-PallaviNeural', 'ta-IN');
|
||||
INSERT INTO tts_voices VALUES ('ta-IN-ValluvarNeural', 'ta-IN');
|
||||
INSERT INTO tts_voices VALUES ('ta-LK-SaranyaNeural', 'ta-LK');
|
||||
INSERT INTO tts_voices VALUES ('ta-LK-KumarNeural', 'ta-LK');
|
||||
INSERT INTO tts_voices VALUES ('ta-MY-KaniNeural', 'ta-MY');
|
||||
INSERT INTO tts_voices VALUES ('ta-MY-SuryaNeural', 'ta-MY');
|
||||
INSERT INTO tts_voices VALUES ('ta-SG-VenbaNeural', 'ta-SG');
|
||||
INSERT INTO tts_voices VALUES ('ta-SG-AnbuNeural', 'ta-SG');
|
||||
INSERT INTO tts_voices VALUES ('te-IN-ShrutiNeural', 'te-IN');
|
||||
INSERT INTO tts_voices VALUES ('te-IN-MohanNeural', 'te-IN');
|
||||
INSERT INTO tts_voices VALUES ('th-TH-PremwadeeNeural', 'th-TH');
|
||||
INSERT INTO tts_voices VALUES ('th-TH-NiwatNeural', 'th-TH');
|
||||
INSERT INTO tts_voices VALUES ('th-TH-AcharaNeural', 'th-TH');
|
||||
INSERT INTO tts_voices VALUES ('tr-TR-EmelNeural', 'tr-TR');
|
||||
INSERT INTO tts_voices VALUES ('tr-TR-AhmetNeural', 'tr-TR');
|
||||
INSERT INTO tts_voices VALUES ('uk-UA-PolinaNeural', 'uk-UA');
|
||||
INSERT INTO tts_voices VALUES ('uk-UA-OstapNeural', 'uk-UA');
|
||||
INSERT INTO tts_voices VALUES ('ur-IN-GulNeural', 'ur-IN');
|
||||
INSERT INTO tts_voices VALUES ('ur-IN-SalmanNeural', 'ur-IN');
|
||||
INSERT INTO tts_voices VALUES ('ur-PK-UzmaNeural', 'ur-PK');
|
||||
INSERT INTO tts_voices VALUES ('ur-PK-AsadNeural', 'ur-PK');
|
||||
INSERT INTO tts_voices VALUES ('uz-UZ-MadinaNeural', 'uz-UZ');
|
||||
INSERT INTO tts_voices VALUES ('uz-UZ-SardorNeural', 'uz-UZ');
|
||||
INSERT INTO tts_voices VALUES ('vi-VN-HoaiMyNeural', 'vi-VN');
|
||||
INSERT INTO tts_voices VALUES ('vi-VN-NamMinhNeural', 'vi-VN');
|
||||
INSERT INTO tts_voices VALUES ('wuu-CN-XiaotongNeural', 'wuu-CN');
|
||||
INSERT INTO tts_voices VALUES ('wuu-CN-YunzheNeural', 'wuu-CN');
|
||||
INSERT INTO tts_voices VALUES ('yue-CN-XiaoMinNeural', 'yue-CN');
|
||||
INSERT INTO tts_voices VALUES ('yue-CN-YunSongNeural', 'yue-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoxiaoNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunxiNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunjianNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoyiNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunyangNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaochenNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaochenMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaohanNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaomengNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaomoNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoqiuNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaorouNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoruiNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoshuangNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoxiaoDialectsNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoxiaoMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoyanNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoyouNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaoyuMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-XiaozhenNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunfengNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunhaoNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunjieNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunxiaNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunyeNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunyiMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunzeNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Xiaochen:DragonHDFlashLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Xiaoxiao:DragonHDFlashLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Xiaoxiao2:DragonHDFlashLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunfanMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Yunxiao:DragonHDFlashLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-YunxiaoMultilingualNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Yunyi:DragonHDFlashLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Xiaochen:DragonHDLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-Yunfan:DragonHDLatestNeural', 'zh-CN');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-guangxi-YunqiNeural', 'zh-CN-guangxi');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-henan-YundengNeural', 'zh-CN-henan');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-liaoning-XiaobeiNeural', 'zh-CN-liaoning');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-liaoning-YunbiaoNeural', 'zh-CN-liaoning');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-shaanxi-XiaoniNeural', 'zh-CN-shaanxi');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-shandong-YunxiangNeural', 'zh-CN-shandong');
|
||||
INSERT INTO tts_voices VALUES ('zh-CN-sichuan-YunxiNeural', 'zh-CN-sichuan');
|
||||
INSERT INTO tts_voices VALUES ('zh-HK-HiuMaanNeural', 'zh-HK');
|
||||
INSERT INTO tts_voices VALUES ('zh-HK-WanLungNeural', 'zh-HK');
|
||||
INSERT INTO tts_voices VALUES ('zh-HK-HiuGaaiNeural', 'zh-HK');
|
||||
INSERT INTO tts_voices VALUES ('zh-TW-HsiaoChenNeural', 'zh-TW');
|
||||
INSERT INTO tts_voices VALUES ('zh-TW-YunJheNeural', 'zh-TW');
|
||||
INSERT INTO tts_voices VALUES ('zh-TW-HsiaoYuNeural', 'zh-TW');
|
||||
INSERT INTO tts_voices VALUES ('zu-ZA-ThandoNeural', 'zu-ZA');
|
||||
INSERT INTO tts_voices VALUES ('zu-ZA-ThembaNeural', 'zu-ZA');
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func messageHandler(c *qbot.Client, msg *qbot.Message) {
|
||||
if msg.UserID != config.BotID {
|
||||
if msg.UserID != config.Cfg.Permissions.BotID {
|
||||
isCommand := cmds.HandleCommand(c, msg)
|
||||
defer qbot.SaveDatabase(msg, isCommand)
|
||||
|
||||
mc.ForwardMessageToMC(c, msg)
|
||||
|
||||
if isCommand {
|
||||
return
|
||||
} else {
|
||||
mc.ForwardMessageToMC(c, msg)
|
||||
}
|
||||
|
||||
if llm.NeedLLMResponse(msg) {
|
||||
@@ -28,8 +28,5 @@ func messageHandler(c *qbot.Client, msg *qbot.Message) {
|
||||
legacy.GameCommandHandle(c, msg)
|
||||
return
|
||||
}
|
||||
if cmds.CheckUserEvents(c, msg) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
llm/llm.go
26
llm/llm.go
@@ -19,7 +19,7 @@ func NeedLLMResponse(msg *qbot.Message) bool {
|
||||
return true
|
||||
} else {
|
||||
for _, item := range msg.Array {
|
||||
if item.Type == qbot.At && item.Content == strconv.FormatUint(config.BotID, 10) {
|
||||
if item.Type == qbot.At && item.Content == strconv.FormatUint(config.Cfg.Permissions.BotID, 10) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func SendLLMRequest(supplier string, messages []openai.ChatCompletionMessagePara
|
||||
|
||||
apiKey := supplierConf.APIKey
|
||||
if apiKey == "" {
|
||||
apiKey = config.ApiKey
|
||||
return nil, fmt.Errorf("supplier %s api_key is empty", supplier)
|
||||
}
|
||||
if supplierConf.BaseURL == "" {
|
||||
return nil, fmt.Errorf("supplier %s base_url is empty", supplier)
|
||||
@@ -115,7 +115,7 @@ userinfo 1006554341 add 喜欢编程
|
||||
First(&llmCustomConfig).Error
|
||||
|
||||
if err != nil || !llmCustomConfig.Enabled {
|
||||
c.SendMsg(msg, err.Error())
|
||||
c.SendMsg(msg.GroupID, msg.UserID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ userinfo 1006554341 add 喜欢编程
|
||||
chatHistory += currentMsgFormatted
|
||||
|
||||
messages = append(messages, openai.UserMessage("以下是最近的聊天记录,请你根据最新的消息生成回复,之前的消息可作为参考。你的id是"+
|
||||
strconv.FormatUint(config.BotID, 10)+"\n"+chatHistory))
|
||||
strconv.FormatUint(config.Cfg.Permissions.BotID, 10)+"\n"+chatHistory))
|
||||
|
||||
resp, err := SendLLMRequest(llmCustomConfig.Supplier, messages, llmCustomConfig.Model, 0.6)
|
||||
if err != nil {
|
||||
@@ -237,21 +237,27 @@ userinfo 1006554341 add 喜欢编程
|
||||
return
|
||||
}
|
||||
|
||||
// 检查响应是否有效
|
||||
if resp == nil || len(resp.Choices) == 0 {
|
||||
c.SendGroupMsg(msg.GroupID, "LLM 返回了空响应", false)
|
||||
return
|
||||
}
|
||||
|
||||
responseContent := resp.Choices[0].Message.Content
|
||||
|
||||
if llmCustomConfig.Debug {
|
||||
c.SendReplyMsg(msg, responseContent)
|
||||
c.SendMsg(msg.GroupID, msg.UserID, responseContent)
|
||||
}
|
||||
|
||||
err = parseAndExecuteCommands(c, msg, responseContent)
|
||||
if err != nil {
|
||||
c.SendPrivateMsg(config.MasterID, "命令解析错误:\n"+err.Error(), false)
|
||||
c.SendPrivateMsg(config.MasterID, responseContent, false)
|
||||
c.SendPrivateMsg(config.MasterID, "消息来源:\ngroup_id="+strconv.FormatUint(msg.GroupID, 10)+"\nuser_id="+strconv.FormatUint(msg.UserID, 10)+"\nmsg="+msg.Content, false)
|
||||
c.SendPrivateMsg(config.Cfg.Permissions.MasterID, "命令解析错误:\n"+err.Error(), false)
|
||||
c.SendPrivateMsg(config.Cfg.Permissions.MasterID, responseContent, false)
|
||||
c.SendPrivateMsg(config.Cfg.Permissions.MasterID, "消息来源:\ngroup_id="+strconv.FormatUint(msg.GroupID, 10)+"\nuser_id="+strconv.FormatUint(msg.UserID, 10)+"\nmsg="+msg.Content, false)
|
||||
return
|
||||
}
|
||||
|
||||
if resp != nil && resp.Usage.TotalTokens > 0 {
|
||||
if resp.Usage.TotalTokens > 0 {
|
||||
go qbot.PsqlDB.Table("users").
|
||||
Where("user_id = ?", msg.UserID).
|
||||
Update("token_usage", gorm.Expr("token_usage + ?", resp.Usage.TotalTokens))
|
||||
@@ -544,7 +550,7 @@ func parseAndExecuteCommands(c *qbot.Client, msg *qbot.Message, content string)
|
||||
if err == nil {
|
||||
saveMsg := &qbot.Message{
|
||||
GroupID: msg.GroupID,
|
||||
UserID: config.BotID,
|
||||
UserID: config.Cfg.Permissions.BotID,
|
||||
Nickname: "狐萝bot",
|
||||
Card: "狐萝bot",
|
||||
Time: uint64(time.Now().Unix()),
|
||||
|
||||
8
main.go
8
main.go
@@ -2,14 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/qbot"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 初始化配置
|
||||
config.LoadConfigFile()
|
||||
qbot.InitDB()
|
||||
|
||||
bot := qbot.NewClient()
|
||||
defer bot.Close()
|
||||
|
||||
|
||||
2
mc/mc.go
2
mc/mc.go
@@ -13,7 +13,7 @@ import (
|
||||
// ForwardMessageToMC forwards a group message to Minecraft server if RCON is enabled
|
||||
func ForwardMessageToMC(c *qbot.Client, msg *qbot.Message) {
|
||||
// Skip bot's own messages
|
||||
if msg.UserID == config.BotID {
|
||||
if msg.UserID == config.Cfg.Permissions.BotID {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
60
qbot/api.go
60
qbot/api.go
@@ -1,5 +1,7 @@
|
||||
package qbot
|
||||
|
||||
import "log"
|
||||
|
||||
func (c *Client) SendPrivateMsg(userID uint64, message string, autoEscape bool) (uint64, error) {
|
||||
if message == "" {
|
||||
message = " "
|
||||
@@ -12,10 +14,11 @@ func (c *Client) SendPrivateMsg(userID uint64, message string, autoEscape bool)
|
||||
"auto_escape": autoEscape,
|
||||
},
|
||||
}
|
||||
resp, err := c.sendJsonWithEcho(&req)
|
||||
resp, err := c.sendWithResponse(&req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
log.Println("send-private: ", message)
|
||||
return resp.Data.MessageId, nil
|
||||
}
|
||||
|
||||
@@ -32,10 +35,11 @@ func (c *Client) SendGroupMsg(groupID uint64, message string, autoEscape bool) (
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := c.sendJsonWithEcho(&req)
|
||||
resp, err := c.sendWithResponse(&req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
log.Println("send-group: ", message)
|
||||
return resp.Data.MessageId, nil
|
||||
}
|
||||
|
||||
@@ -123,26 +127,14 @@ func (c *Client) DeleteMsg(msgID uint64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SendRecord(msg *Message, file string) {
|
||||
c.SendMsg(msg, CQRecord(file))
|
||||
}
|
||||
|
||||
func (c *Client) SendReplyMsg(msg *Message, message string) {
|
||||
c.SendMsg(msg, CQReply(msg.MsgID)+message)
|
||||
}
|
||||
|
||||
func (c *Client) SendMsg(msg *Message, message string) {
|
||||
if msg.GroupID == 0 {
|
||||
c.SendPrivateMsg(msg.UserID, message, false)
|
||||
func (c *Client) SendMsg(groupID uint64, userID uint64, message string) {
|
||||
if groupID == 0 {
|
||||
c.SendPrivateMsg(userID, message, false)
|
||||
} else {
|
||||
c.SendGroupMsg(msg.GroupID, message, false)
|
||||
c.SendGroupMsg(groupID, message, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendImage(msg *Message, url string) {
|
||||
c.SendMsg(msg, CQImage(url))
|
||||
}
|
||||
|
||||
func (c *Client) GetGroupMemberInfo(groupID uint64, userID uint64, noCache bool) (*GroupMemberInfo, error) {
|
||||
req := cqRequest{
|
||||
Action: "get_group_member_info",
|
||||
@@ -152,9 +144,39 @@ func (c *Client) GetGroupMemberInfo(groupID uint64, userID uint64, noCache bool)
|
||||
"no_cache": noCache,
|
||||
},
|
||||
}
|
||||
resp, err := c.sendJsonWithEcho(&req)
|
||||
resp, err := c.sendWithResponse(&req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp.Data.GroupMemberInfo, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetGroupFileUrl(groupID uint64, fileID string, busid int32) (string, error) {
|
||||
req := cqRequest{
|
||||
Action: "get_group_file_url",
|
||||
Params: map[string]any{
|
||||
"group_id": groupID,
|
||||
"file_id": fileID,
|
||||
"busid": busid,
|
||||
},
|
||||
}
|
||||
resp, err := c.sendWithResponse(&req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Data.Url, nil
|
||||
}
|
||||
|
||||
func (c *Client) SendTestAPIRequest(action string, params map[string]interface{}) (string, error) {
|
||||
req := cqRequest{
|
||||
Action: action,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
resp, err := c.sendWithJSONResponse(&req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package qbot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-hurobot/config"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
@@ -52,13 +56,20 @@ type LegacyGame struct {
|
||||
Balance int `gorm:"not null;column:balance;default:0"`
|
||||
}
|
||||
|
||||
func initPsqlDB(dsn string) error {
|
||||
func InitDB() {
|
||||
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
config.Cfg.PostgreSQL.Host,
|
||||
strconv.Itoa(int(config.Cfg.PostgreSQL.Port)),
|
||||
config.Cfg.PostgreSQL.User,
|
||||
config.Cfg.PostgreSQL.Password,
|
||||
config.Cfg.PostgreSQL.DbName,
|
||||
)
|
||||
var err error
|
||||
if PsqlDB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}); err != nil {
|
||||
return err
|
||||
log.Fatalln(err)
|
||||
}
|
||||
PsqlConnected = true
|
||||
return PsqlDB.AutoMigrate(&Users{}, &Messages{}, &UserEvents{}, &GroupRconConfigs{}, &LegacyGame{})
|
||||
PsqlDB.AutoMigrate(&Users{}, &Messages{}, &UserEvents{}, &GroupRconConfigs{}, &LegacyGame{})
|
||||
}
|
||||
|
||||
func SaveDatabase(msg *Message, isCmd bool) error {
|
||||
|
||||
@@ -2,6 +2,7 @@ package qbot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
@@ -50,40 +51,74 @@ func parseMsgJson(raw *messageJson) *Message {
|
||||
}
|
||||
for _, msg := range raw.Message {
|
||||
var jsonData map[string]any
|
||||
if json.Unmarshal([]byte(msg.Data), &jsonData) != nil {
|
||||
return nil
|
||||
if err := json.Unmarshal(msg.Data, &jsonData); err != nil {
|
||||
log.Printf("解析消息数据失败: %v, 原始数据: %s", err, string(msg.Data))
|
||||
continue
|
||||
}
|
||||
switch msg.Type {
|
||||
case "text":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Text,
|
||||
Content: jsonData["text"].(string),
|
||||
})
|
||||
if text, ok := jsonData["text"].(string); ok {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Text,
|
||||
Content: text,
|
||||
})
|
||||
}
|
||||
case "at":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: At,
|
||||
Content: jsonData["qq"].(string),
|
||||
})
|
||||
// qq 可能是 string 或 number
|
||||
var qqStr string
|
||||
if qq, ok := jsonData["qq"].(string); ok {
|
||||
qqStr = qq
|
||||
} else if qq, ok := jsonData["qq"].(float64); ok {
|
||||
qqStr = fmt.Sprintf("%.0f", qq)
|
||||
}
|
||||
if qqStr != "" {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: At,
|
||||
Content: qqStr,
|
||||
})
|
||||
}
|
||||
case "face":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Face,
|
||||
Content: jsonData["id"].(string),
|
||||
})
|
||||
// id 可能是 string 或 number
|
||||
var idStr string
|
||||
if id, ok := jsonData["id"].(string); ok {
|
||||
idStr = id
|
||||
} else if id, ok := jsonData["id"].(float64); ok {
|
||||
idStr = fmt.Sprintf("%.0f", id)
|
||||
}
|
||||
if idStr != "" {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Face,
|
||||
Content: idStr,
|
||||
})
|
||||
}
|
||||
case "image":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Image,
|
||||
Content: jsonData["url"].(string),
|
||||
})
|
||||
if url, ok := jsonData["url"].(string); ok {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Image,
|
||||
Content: url,
|
||||
})
|
||||
}
|
||||
case "record":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Record,
|
||||
Content: jsonData["path"].(string),
|
||||
})
|
||||
if path, ok := jsonData["path"].(string); ok {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Record,
|
||||
Content: path,
|
||||
})
|
||||
}
|
||||
case "reply":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Reply,
|
||||
Content: jsonData["id"].(string),
|
||||
})
|
||||
// reply 的 id 可能是 string 或 number
|
||||
var replyId string
|
||||
if id, ok := jsonData["id"].(string); ok {
|
||||
replyId = id
|
||||
} else if id, ok := jsonData["id"].(float64); ok {
|
||||
replyId = fmt.Sprintf("%.0f", id)
|
||||
}
|
||||
if replyId != "" {
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: Reply,
|
||||
Content: replyId,
|
||||
})
|
||||
}
|
||||
case "file":
|
||||
result.Array = append(result.Array, MsgItem{
|
||||
Type: File,
|
||||
|
||||
282
qbot/qbot.go
282
qbot/qbot.go
@@ -2,187 +2,173 @@
|
||||
package qbot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strconv"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"go-hurobot/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||
config.PsqlHost, strconv.Itoa(int(config.PsqlPort)), config.PsqlUser, config.PsqlPassword, config.PsqlDbName)
|
||||
if err := initPsqlDB(dsn); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
client := &Client{
|
||||
config: &Config{
|
||||
Address: config.NapcatWSURL,
|
||||
Reconnect: 3 * time.Second,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
retryCount: 0,
|
||||
stopChan: make(chan bool),
|
||||
}
|
||||
go client.connect()
|
||||
|
||||
// 启动反向 HTTP 服务器
|
||||
go client.startHTTPServer()
|
||||
|
||||
log.Printf("正向 HTTP 地址: %s", config.Cfg.NapcatHttpServer)
|
||||
log.Printf("反向 HTTP 监听: http://%s", config.Cfg.ReverseHttpServer)
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
if c.stopChan != nil {
|
||||
close(c.stopChan)
|
||||
if c.server != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := c.server.Shutdown(ctx); err != nil {
|
||||
log.Printf("HTTP server shutdown error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) connect() {
|
||||
for {
|
||||
select {
|
||||
case <-c.stopChan:
|
||||
return
|
||||
default:
|
||||
// TODO
|
||||
}
|
||||
// 启动反向 HTTP 服务器,接收 NapCat 推送的消息
|
||||
func (c *Client) startHTTPServer() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", c.handleHTTPEvent)
|
||||
|
||||
dialer := websocket.Dialer{
|
||||
ReadBufferSize: 4096,
|
||||
WriteBufferSize: 4096,
|
||||
HandshakeTimeout: c.config.ReadTimeout,
|
||||
}
|
||||
conn, _, err := dialer.Dial(c.config.Address, nil)
|
||||
if err != nil {
|
||||
log.Printf("Connect failed (%d): %v", c.retryCount+1, err)
|
||||
c.retryCount++
|
||||
time.Sleep(c.config.Reconnect)
|
||||
continue
|
||||
}
|
||||
c.server = &http.Server{
|
||||
Addr: config.Cfg.ReverseHttpServer,
|
||||
Handler: mux,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
conn.SetPongHandler(func(string) error {
|
||||
c.retryCount = 0
|
||||
return nil
|
||||
})
|
||||
if err := c.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("HTTP server error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.conn = conn
|
||||
log.Println("Connected to NapCat")
|
||||
|
||||
go c.messageHandler()
|
||||
// 处理 NapCat 推送的事件
|
||||
func (c *Client) handleHTTPEvent(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) messageHandler() {
|
||||
defer func() {
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// Receive message
|
||||
_, msg, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Printf("read error: %v", err)
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal to map
|
||||
jsonMap := make(map[string]any)
|
||||
if err := json.Unmarshal(msg, &jsonMap); err != nil {
|
||||
log.Printf("parse message error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if jsonMap["echo"] != nil {
|
||||
// Response to sent message
|
||||
var resp cqResponse
|
||||
if err := json.Unmarshal(msg, &resp); err == nil {
|
||||
c.mutex.Lock()
|
||||
if val, ok := c.pendingEcho.Load(resp.Echo); ok {
|
||||
pr := val.(*pendingResponse)
|
||||
pr.timer.Stop()
|
||||
pr.ch <- &resp
|
||||
c.pendingEcho.Delete(resp.Echo)
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
} else if postType, exists := jsonMap["post_type"]; exists {
|
||||
// Server-initiated push
|
||||
if str, ok := postType.(string); ok && str != "" {
|
||||
go c.handleEvents(&str, &msg, &jsonMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) reconnect() {
|
||||
if c.stopChan != nil {
|
||||
close(c.stopChan)
|
||||
}
|
||||
c.stopChan = make(chan bool)
|
||||
c.connect()
|
||||
}
|
||||
|
||||
func (c *Client) sendJson(req *cqRequest) error {
|
||||
jsonBytes, err := json.Marshal(req)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Printf("读取请求体失败: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if c.conn == nil {
|
||||
return fmt.Errorf("connection not ready")
|
||||
defer r.Body.Close()
|
||||
|
||||
// 解析 JSON
|
||||
jsonMap := make(map[string]any)
|
||||
if err := json.Unmarshal(body, &jsonMap); err != nil {
|
||||
log.Printf("解析 JSON 失败: %v", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := c.conn.WriteMessage(websocket.TextMessage, jsonBytes); err != nil {
|
||||
return err
|
||||
|
||||
// 处理事件
|
||||
if postType, exists := jsonMap["post_type"]; exists {
|
||||
if str, ok := postType.(string); ok && str != "" {
|
||||
go c.handleEvents(&str, &body, &jsonMap)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
// 返回成功响应
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"ok"}`))
|
||||
}
|
||||
|
||||
func (c *Client) sendJsonWithEcho(req *cqRequest) (*cqResponse, error) {
|
||||
// Generate echo key
|
||||
echo := uuid.New().String()
|
||||
req.Echo = echo
|
||||
|
||||
respCh := make(chan *cqResponse, 1)
|
||||
timeout := time.NewTimer(5 * time.Second)
|
||||
defer timeout.Stop()
|
||||
|
||||
// Save the key to pendingEcho
|
||||
c.mutex.Lock()
|
||||
c.pendingEcho.Store(echo, &pendingResponse{
|
||||
ch: respCh,
|
||||
timer: timeout,
|
||||
})
|
||||
c.mutex.Unlock()
|
||||
|
||||
// Send request
|
||||
if err := c.sendJson(req); err != nil {
|
||||
// 发送 API 请求到 NapCat(正向 HTTP)
|
||||
// 统一的 HTTP 请求方法
|
||||
func (c *Client) sendRequest(req *cqRequest) (*http.Response, error) {
|
||||
jsonBytes, err := json.Marshal(req.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Wait for response
|
||||
select {
|
||||
case resp := <-respCh:
|
||||
if resp == nil {
|
||||
return nil, fmt.Errorf("response channel closed")
|
||||
} else {
|
||||
log.Printf("Sent message: %v", req.Params)
|
||||
}
|
||||
return resp, nil
|
||||
case <-timeout.C:
|
||||
c.mutex.Lock()
|
||||
c.pendingEcho.Delete(echo)
|
||||
c.mutex.Unlock()
|
||||
return nil, fmt.Errorf("wait response timeout")
|
||||
httpReq, err := http.NewRequest(http.MethodPost, config.Cfg.NapcatHttpServer+"/"+req.Action, bytes.NewBuffer(jsonBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if config.Cfg.ApiKeys.Longport.AccessToken != "" {
|
||||
httpReq.Header.Set("Authorization", "Bearer "+config.Cfg.ApiKeys.Longport.AccessToken)
|
||||
}
|
||||
|
||||
return c.httpClient.Do(httpReq)
|
||||
}
|
||||
|
||||
func (c *Client) sendJson(req *cqRequest) error {
|
||||
resp, err := c.sendRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) sendWithResponse(req *cqRequest) (*cqResponse, error) {
|
||||
resp, err := c.sendRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var cqResp cqResponse
|
||||
if err := json.Unmarshal(body, &cqResp); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
|
||||
return &cqResp, nil
|
||||
}
|
||||
|
||||
// 发送请求并返回 JSON 字符串(用于测试 API)
|
||||
func (c *Client) sendWithJSONResponse(req *cqRequest) (string, error) {
|
||||
resp, err := c.sendRequest(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
log.Printf("API 响应: %s", string(body))
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
@@ -2,26 +2,12 @@ package qbot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Address string `json:"address"`
|
||||
Reconnect time.Duration `json:"reconnect"`
|
||||
ReadTimeout time.Duration `json:"read_timeout"`
|
||||
WriteTimeout time.Duration `json:"write_timeout"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config *Config
|
||||
conn *websocket.Conn
|
||||
retryCount int
|
||||
stopChan chan bool
|
||||
pendingEcho sync.Map
|
||||
mutex sync.Mutex
|
||||
httpClient *http.Client
|
||||
server *http.Server
|
||||
eventHandlers struct {
|
||||
onMessage func(c *Client, msg *Message)
|
||||
}
|
||||
@@ -78,15 +64,11 @@ type messageJson struct {
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
type pendingResponse struct {
|
||||
ch chan *cqResponse
|
||||
timer *time.Timer
|
||||
}
|
||||
// 移除 pendingResponse,HTTP 模式不需要等待响应
|
||||
|
||||
type cqRequest struct {
|
||||
Action string `json:"action"`
|
||||
Params map[string]any `json:"params"`
|
||||
Echo string `json:"echo,omitempty"`
|
||||
}
|
||||
|
||||
type GroupMemberInfo struct {
|
||||
@@ -113,9 +95,9 @@ type cqResponse struct {
|
||||
Retcode int `json:"retcode"`
|
||||
Data struct {
|
||||
MessageId uint64 `json:"message_id"`
|
||||
Url string `json:"url"`
|
||||
GroupMemberInfo
|
||||
}
|
||||
} `json:"data"`
|
||||
Message string `json:"message"`
|
||||
Wording string `json:"wording"`
|
||||
Echo string `json:"echo"`
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NapCat WebSocket 服务器地址和 Token
|
||||
export NAPCAT_HOST='127.0.0.1:3001'
|
||||
export ACCESS_TOKEN='token-for-napcat'
|
||||
|
||||
# 机器人主人QQ号
|
||||
export MASTER_ID=''
|
||||
|
||||
# 机器人QQ号
|
||||
export BOT_ID=''
|
||||
|
||||
# Postgres 数据库配置 AI 对话读取历史记录使用
|
||||
# Postgres 配置请查看 docker/docker-compose.yml
|
||||
export PSQL_HOST='127.0.0.1'
|
||||
export PSQL_PORT='5432'
|
||||
export PSQL_USER='hurobot'
|
||||
export PSQL_PASSWORD='hurobot'
|
||||
export PSQL_DBNAME='hurobot'
|
||||
|
||||
# 硅基流动 API
|
||||
export API_KEY='sk-'
|
||||
|
||||
# Exchange Rate API 配置,用于 fx 命令
|
||||
# 参考 https://www.exchangerate-api.com/docs/
|
||||
export EXCHANGE_RATE_API_KEY=''
|
||||
|
||||
# 长桥证券 API 配置,用于 stock 命令
|
||||
# 参考 https://open.longportapp.com/docs/
|
||||
export LONGPORT_APP_KEY=''
|
||||
export LONGPORT_APP_SECRET=''
|
||||
export LONGPORT_ACCESS_TOKEN=''
|
||||
export LONGPORT_REGION='cn'
|
||||
export LONGPORT_ENABLE_OVERNIGHT='false'
|
||||
|
||||
# OKX 镜像 API 配置,用于 stock 命令
|
||||
# 参考 https://www.okx.com/docs-v5/
|
||||
export OKX_MIRROR_API_KEY=''
|
||||
|
||||
/path/to/go-hurobot
|
||||
Reference in New Issue
Block a user