mirror of
https://github.com/awfufu/go-hurobot.git
synced 2026-03-01 05:29:43 +08:00
feat: add user-defined event system for custom auto-replies
This commit is contained in:
@@ -25,7 +25,6 @@ func init() {
|
||||
cmdMap = map[string]CmdHandler{
|
||||
"echo": cmd_echo,
|
||||
"specialtitle": cmd_specialtitle,
|
||||
"sh": cmd_sh,
|
||||
"psql": cmd_psql,
|
||||
"group": cmd_group,
|
||||
"delete": cmd_delete,
|
||||
@@ -37,6 +36,8 @@ func init() {
|
||||
"fx": cmd_er,
|
||||
"stock": cmd_stock,
|
||||
"crypto": cmd_crypto,
|
||||
"event": cmd_event,
|
||||
// "sh": cmd_sh,
|
||||
}
|
||||
|
||||
for key := range cmdMap {
|
||||
|
||||
282
cmds/event.go
Normal file
282
cmds/event.go
Normal file
@@ -0,0 +1,282 @@
|
||||
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
|
||||
}
|
||||
@@ -3,15 +3,10 @@ package main
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go-hurobot/llm"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func customReply(c *qbot.Client, msg *qbot.Message) {
|
||||
if llm.LLMMsgHandle(c, msg) {
|
||||
return
|
||||
}
|
||||
|
||||
// 2025-03-08 晚上,让 bot 在某 mc 群发电加的
|
||||
if msg.GroupID == 738943282 {
|
||||
if strings.Contains(msg.Raw, "厉厉厉害害") || strings.Contains(msg.Raw, "厉厉害害害") {
|
||||
|
||||
@@ -33,3 +33,16 @@ 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)
|
||||
);
|
||||
|
||||
14
handlers.go
14
handlers.go
@@ -3,15 +3,23 @@ package main
|
||||
import (
|
||||
"go-hurobot/cmds"
|
||||
"go-hurobot/config"
|
||||
"go-hurobot/llm"
|
||||
"go-hurobot/qbot"
|
||||
)
|
||||
|
||||
func messageHandler(c *qbot.Client, msg *qbot.Message) {
|
||||
if msg.UserID != config.BotID {
|
||||
isCommand := cmds.HandleCommand(c, msg)
|
||||
if !isCommand {
|
||||
go customReply(c, msg)
|
||||
defer qbot.SaveDatabase(msg, isCommand)
|
||||
if isCommand {
|
||||
return
|
||||
}
|
||||
go qbot.SaveDatabase(msg, isCommand)
|
||||
if llm.LLMMsgHandle(c, msg) {
|
||||
return
|
||||
}
|
||||
if cmds.CheckUserEvents(c, msg) {
|
||||
return
|
||||
}
|
||||
customReply(c, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,11 @@ func SendLLMRequest(supplier string, messages []openai.ChatCompletionMessagePara
|
||||
|
||||
func LLMMsgHandle(c *qbot.Client, msg *qbot.Message) bool {
|
||||
reply := false
|
||||
for _, item := range msg.Array {
|
||||
if item.Type == qbot.At && item.Content == strconv.FormatUint(config.BotID, 10) {
|
||||
reply = true
|
||||
if reply = strings.Contains(msg.Raw, "狐萝卜"); !reply {
|
||||
for _, item := range msg.Array {
|
||||
if item.Type == qbot.At && item.Content == strconv.FormatUint(config.BotID, 10) {
|
||||
reply = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reply {
|
||||
|
||||
@@ -30,13 +30,22 @@ type Messages struct {
|
||||
Time time.Time `gorm:"not null;column:time"`
|
||||
}
|
||||
|
||||
type UserEvents struct {
|
||||
UserID uint64 `gorm:"primaryKey;column:user_id"`
|
||||
EventIdx int `gorm:"primaryKey;column:event_idx"`
|
||||
MsgRegex string `gorm:"not null;column:msg_regex"`
|
||||
ReplyText string `gorm:"not null;column:reply_text"`
|
||||
RandProb float32 `gorm:"not null;column:rand_prob;default:1.0"`
|
||||
CreatedAt time.Time `gorm:"not null;column:created_at;default:now()"`
|
||||
}
|
||||
|
||||
func initPsqlDB(dsn string) error {
|
||||
var err error
|
||||
if PsqlDB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}); err != nil {
|
||||
return err
|
||||
}
|
||||
PsqlConnected = true
|
||||
return PsqlDB.AutoMigrate(&Users{}, &Messages{})
|
||||
return PsqlDB.AutoMigrate(&Users{}, &Messages{}, &UserEvents{})
|
||||
}
|
||||
|
||||
func SaveDatabase(msg *Message, isCmd bool) error {
|
||||
|
||||
Reference in New Issue
Block a user