From 27353f56f1b9e04dbb2651864592a79916610e06 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 7 Oct 2024 14:58:29 +0800 Subject: [PATCH 01/49] add new Matcher `PatternMatcher` add new Rule `PatternRule` (cherry picked from commit 44d6c88c217ed849a15943f4d179cca206a8f056) do not modify OnMessage method --- engine.go | 8 ++++ rules.go | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/engine.go b/engine.go index 5051706..55dd3cc 100644 --- a/engine.go +++ b/engine.go @@ -79,6 +79,14 @@ func OnMessage(rules ...Rule) *Matcher { return On("message", rules...) } // OnMessage 消息触发器 func (e *Engine) OnMessage(rules ...Rule) *Matcher { return e.On("message", rules...) } +// OnPattern 消息模板触发器 +func OnPattern(rules ...PatternSegment) *Matcher { return On("message", PatternRule(rules...)) } + +// OnPattern 消息模板触发器 +func (e *Engine) OnPattern(rules ...PatternSegment) *Matcher { + return e.On("message", PatternRule(rules...)) +} + // OnNotice 系统提示触发器 func OnNotice(rules ...Rule) *Matcher { return On("notice", rules...) } diff --git a/rules.go b/rules.go index 0490d8d..04311b1 100644 --- a/rules.go +++ b/rules.go @@ -1,6 +1,7 @@ package zero import ( + log "github.com/sirupsen/logrus" "hash/crc64" "regexp" "strconv" @@ -11,6 +12,11 @@ import ( "github.com/wdvxdr1123/ZeroBot/utils/helper" ) +const ( + KEY_REGEX = "regex_matched" + KEY_PATTERN = "pattern_matched" +) + // Type check the ctx.Event's type func Type(type_ string) Rule { t := strings.SplitN(type_, "/", 3) @@ -122,6 +128,125 @@ func RegexRule(regexPattern string) Rule { } } +type PatternSegment struct { + Type string + Matcher func(ctx *Ctx, msg message.MessageSegment) bool +} +type Pattern []PatternSegment + +// PatternText KEY_PATTERN type []string +func PatternText(regex string) PatternSegment { + re := regexp.MustCompile(regex) + return PatternSegment{ + Type: "text", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + s := msg.Data["text"] + s = strings.Trim(s, " \n\r\t") + matchString := re.MatchString(s) + if matchString { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), re.FindStringSubmatch(s)) + } + return matchString + }, + } +} +func patternAt(target any) PatternSegment { + switch t := target.(type) { + case int64: + return PatternSegment{ + Type: "at", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + b := msg.Data["qq"] == strconv.FormatInt(t, 10) + if b { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + } + return b + }, + } + case int: + return PatternSegment{ + Type: "at", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + b := msg.Data["qq"] == strconv.FormatInt(int64(t), 10) + if b { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + } + return b + }} + case string: + return PatternSegment{ + Type: "at", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + b := msg.Data["name"] == t + if b { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["name"]) + } + return b + }} + default: + panic("unsupported type") + } +} + +// PatternAt KEY_PATTERN type string +func PatternAt() PatternSegment { + return PatternSegment{ + Type: "at", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + return true + }, + } +} + +// PatternImage KEY_PATTERN type msg.Data +func PatternImage() PatternSegment { + return PatternSegment{ + Type: "image", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + if _, ok := ctx.State["pattern_matched"]; !ok { + ctx.State["pattern_matched"] = make([]interface{}, 0) + } + ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data) + return true + }, + } +} +func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegment) bool { + if len(pattern) != len(msgs) { + return false + } + for i := 0; i < len(pattern); i++ { + if pattern[i].Type != (msgs[i].Type) || !pattern[i].Matcher(ctx, msgs[i]) { + return false + } + } + return true +} + +// PatternRule check if the message can be matched by the pattern +func PatternRule(pattern ...PatternSegment) Rule { + return func(ctx *Ctx) bool { + return patternMatch(ctx, pattern, ctx.Event.Message) + } +} + // ReplyRule check if the message is replying some message func ReplyRule(messageID int64) Rule { return func(ctx *Ctx) bool { From e175810ebc6314801332f654852a4f1977758153 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Wed, 9 Oct 2024 10:03:54 +0800 Subject: [PATCH 02/49] replace with const value --- rules.go | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/rules.go b/rules.go index 04311b1..f3084ba 100644 --- a/rules.go +++ b/rules.go @@ -1,7 +1,6 @@ package zero import ( - log "github.com/sirupsen/logrus" "hash/crc64" "regexp" "strconv" @@ -144,11 +143,11 @@ func PatternText(regex string) PatternSegment { s = strings.Trim(s, " \n\r\t") matchString := re.MatchString(s) if matchString { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), re.FindStringSubmatch(s)) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), re.FindStringSubmatch(s)) } return matchString }, @@ -162,10 +161,10 @@ func patternAt(target any) PatternSegment { Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { b := msg.Data["qq"] == strconv.FormatInt(t, 10) if b { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) } return b }, @@ -176,10 +175,10 @@ func patternAt(target any) PatternSegment { Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { b := msg.Data["qq"] == strconv.FormatInt(int64(t), 10) if b { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) } return b }} @@ -189,10 +188,10 @@ func patternAt(target any) PatternSegment { Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { b := msg.Data["name"] == t if b { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["name"]) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["name"]) } return b }} @@ -206,10 +205,10 @@ func PatternAt() PatternSegment { return PatternSegment{ Type: "at", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data["qq"]) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) return true }, } @@ -220,10 +219,10 @@ func PatternImage() PatternSegment { return PatternSegment{ Type: "image", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - if _, ok := ctx.State["pattern_matched"]; !ok { - ctx.State["pattern_matched"] = make([]interface{}, 0) + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]interface{}, 0) } - ctx.State["pattern_matched"] = append(ctx.State["pattern_matched"].([]interface{}), msg.Data) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data) return true }, } From 816234b8274d01287f70d17f339a965ab966ce42 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Wed, 9 Oct 2024 10:18:08 +0800 Subject: [PATCH 03/49] remove unused function --- rules.go | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/rules.go b/rules.go index f3084ba..4c99583 100644 --- a/rules.go +++ b/rules.go @@ -153,52 +153,6 @@ func PatternText(regex string) PatternSegment { }, } } -func patternAt(target any) PatternSegment { - switch t := target.(type) { - case int64: - return PatternSegment{ - Type: "at", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - b := msg.Data["qq"] == strconv.FormatInt(t, 10) - if b { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) - } - return b - }, - } - case int: - return PatternSegment{ - Type: "at", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - b := msg.Data["qq"] == strconv.FormatInt(int64(t), 10) - if b { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) - } - return b - }} - case string: - return PatternSegment{ - Type: "at", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - b := msg.Data["name"] == t - if b { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["name"]) - } - return b - }} - default: - panic("unsupported type") - } -} // PatternAt KEY_PATTERN type string func PatternAt() PatternSegment { From 392941bcaf5dbc68d6004b0f0b714091dc7c7d2b Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 09:50:56 +0800 Subject: [PATCH 04/49] remove `OnPattern` --- engine.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/engine.go b/engine.go index 55dd3cc..5051706 100644 --- a/engine.go +++ b/engine.go @@ -79,14 +79,6 @@ func OnMessage(rules ...Rule) *Matcher { return On("message", rules...) } // OnMessage 消息触发器 func (e *Engine) OnMessage(rules ...Rule) *Matcher { return e.On("message", rules...) } -// OnPattern 消息模板触发器 -func OnPattern(rules ...PatternSegment) *Matcher { return On("message", PatternRule(rules...)) } - -// OnPattern 消息模板触发器 -func (e *Engine) OnPattern(rules ...PatternSegment) *Matcher { - return e.On("message", PatternRule(rules...)) -} - // OnNotice 系统提示触发器 func OnNotice(rules ...Rule) *Matcher { return On("notice", rules...) } From 22c4ec8eaff5edb45d47cb7b4a45e9de368f2f48 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 09:57:27 +0800 Subject: [PATCH 05/49] remove unused const --- rules.go | 1 - 1 file changed, 1 deletion(-) diff --git a/rules.go b/rules.go index 4c99583..b408905 100644 --- a/rules.go +++ b/rules.go @@ -12,7 +12,6 @@ import ( ) const ( - KEY_REGEX = "regex_matched" KEY_PATTERN = "pattern_matched" ) From ba285e35db994a8ba7ec47d972c598a1d4173df9 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 09:58:34 +0800 Subject: [PATCH 06/49] add `PatternModel` --- extension/model.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extension/model.go b/extension/model.go index 5690a9a..60b2137 100644 --- a/extension/model.go +++ b/extension/model.go @@ -32,3 +32,8 @@ type FullMatchModel struct { type RegexModel struct { Matched []string `zero:"regex_matched"` } + +// PatternModel is model of zero.PatternRule +type PatternModel struct { + Matched []interface{} `zero:"pattern_matched"` +} From 0635fc13310338ade1f5bcf4720dfabacbf2cd43 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 10:31:25 +0800 Subject: [PATCH 07/49] type safe PatternRule state --- extension/model.go | 4 +++- rules.go | 53 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/extension/model.go b/extension/model.go index 60b2137..aec7aa5 100644 --- a/extension/model.go +++ b/extension/model.go @@ -1,5 +1,7 @@ package extension +import zero "github.com/wdvxdr1123/ZeroBot" + // PrefixModel is model of zero.PrefixRule type PrefixModel struct { Prefix string `zero:"prefix"` @@ -35,5 +37,5 @@ type RegexModel struct { // PatternModel is model of zero.PatternRule type PatternModel struct { - Matched []interface{} `zero:"pattern_matched"` + Matched []zero.PatternMatched `zero:"pattern_matched"` } diff --git a/rules.go b/rules.go index b408905..32563db 100644 --- a/rules.go +++ b/rules.go @@ -130,9 +130,35 @@ type PatternSegment struct { Type string Matcher func(ctx *Ctx, msg message.MessageSegment) bool } + type Pattern []PatternSegment -// PatternText KEY_PATTERN type []string +// PatternMatched save PatternRule context +type PatternMatched map[string]interface{} + +func (p PatternMatched) AsText() PatternTextMatched { + return PatternTextMatched{ + Groups: p["groups"].([]string), + } +} +func (p PatternMatched) AsAt() PatternAtMatched { + return PatternAtMatched{UID: p["qq"].(int64)} +} +func (p PatternMatched) AsImage() PatternImageMatched { + return PatternImageMatched{File: p["file"].(string)} +} + +type PatternImageMatched = struct { + File string +} +type PatternAtMatched = struct { + UID int64 +} +type PatternTextMatched = struct { + Groups []string +} + +// PatternText type zero.PatternTextMatched func PatternText(regex string) PatternSegment { re := regexp.MustCompile(regex) return PatternSegment{ @@ -143,39 +169,48 @@ func PatternText(regex string) PatternSegment { matchString := re.MatchString(s) if matchString { if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) + ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), re.FindStringSubmatch(s)) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), + PatternMatched{ + "groups": re.FindStringSubmatch(s), + }) } return matchString }, } } -// PatternAt KEY_PATTERN type string +// PatternAt type zero.PatternAtMatched func PatternAt() PatternSegment { return PatternSegment{ Type: "at", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) + ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data["qq"]) + qq, _ := strconv.ParseInt(msg.Data["qq"], 10, 64) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), + PatternMatched{ + "qq": qq, + }) return true }, } } -// PatternImage KEY_PATTERN type msg.Data +// PatternImage type zero.PatternImageMatched func PatternImage() PatternSegment { return PatternSegment{ Type: "image", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]interface{}, 0) + ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]interface{}), msg.Data) + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{ + "file": msg.Data["file"], + }) return true }, } From ece50eb649b4f4ff0c6f0f2895db568e221c0934 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 10:43:21 +0800 Subject: [PATCH 08/49] add `PatternReply` --- rules.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/rules.go b/rules.go index 32563db..563007a 100644 --- a/rules.go +++ b/rules.go @@ -147,6 +147,9 @@ func (p PatternMatched) AsAt() PatternAtMatched { func (p PatternMatched) AsImage() PatternImageMatched { return PatternImageMatched{File: p["file"].(string)} } +func (p PatternMatched) AsReply() PatternReplyMatched { + return PatternReplyMatched{MessageID: p["file"].(string)} +} type PatternImageMatched = struct { File string @@ -157,6 +160,9 @@ type PatternAtMatched = struct { type PatternTextMatched = struct { Groups []string } +type PatternReplyMatched = struct { + MessageID string +} // PatternText type zero.PatternTextMatched func PatternText(regex string) PatternSegment { @@ -215,6 +221,22 @@ func PatternImage() PatternSegment { }, } } + +// PatternReply type zero.PatternReplyMatched +func PatternReply() PatternSegment { + return PatternSegment{ + Type: "reply", + Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) + } + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{ + "id": msg.Data["id"], + }) + return true + }, + } +} func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegment) bool { if len(pattern) != len(msgs) { return false From 66f9382da8911d85cf00f7f7a983a59f362e9ed3 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 11:17:20 +0800 Subject: [PATCH 09/49] remove redundant `at` element after `reply` element (cherry picked from commit 2af54fb0aefb85d073f277dbaa26a34c96edf64d) --- rules.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rules.go b/rules.go index 563007a..8512fd2 100644 --- a/rules.go +++ b/rules.go @@ -252,7 +252,20 @@ func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegm // PatternRule check if the message can be matched by the pattern func PatternRule(pattern ...PatternSegment) Rule { return func(ctx *Ctx) bool { - return patternMatch(ctx, pattern, ctx.Event.Message) + // copy messages + msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) + msgs = append(msgs, ctx.Event.Message[0]) + for i := 1; i < len(ctx.Event.Message); i++ { + if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] + reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) + if reply.MessageId.String() != ctx.Event.Message[i].Data["id"] { // @ other user in reply + msgs = append(msgs, ctx.Event.Message[i]) + } + } else { + msgs = append(msgs, ctx.Event.Message[i]) + } + } + return patternMatch(ctx, pattern, msgs) } } From 32b5226b7fc29f3cd03c0c47b7c1e25915e0a5f2 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 11:22:24 +0800 Subject: [PATCH 10/49] fix wrong key && catch error --- rules.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rules.go b/rules.go index 8512fd2..5ceeb78 100644 --- a/rules.go +++ b/rules.go @@ -256,9 +256,16 @@ func PatternRule(pattern ...PatternSegment) Rule { msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) msgs = append(msgs, ctx.Event.Message[0]) for i := 1; i < len(ctx.Event.Message); i++ { - if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] + if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { + // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) - if reply.MessageId.String() != ctx.Event.Message[i].Data["id"] { // @ other user in reply + if reply.MessageId.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { + // failed to get history message + msgs = append(msgs, ctx.Event.Message[i]) + continue + } + if strconv.FormatInt(reply.Sender.ID, 10) != ctx.Event.Message[i].Data["qq"] { + // @ other user in reply msgs = append(msgs, ctx.Event.Message[i]) } } else { From 24877edbf50413a5c6a253553fd47fd279c28183 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 11:51:52 +0800 Subject: [PATCH 11/49] Optional PatternSegment --- rules.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/rules.go b/rules.go index 5ceeb78..d456bf1 100644 --- a/rules.go +++ b/rules.go @@ -127,8 +127,19 @@ func RegexRule(regexPattern string) Rule { } type PatternSegment struct { - Type string - Matcher func(ctx *Ctx, msg message.MessageSegment) bool + Type string + Optional bool + Matcher func(ctx *Ctx, msg message.MessageSegment) bool +} + +// SetOptional set Pattern is optional, is v is empty, Optional will be true +func (p *PatternSegment) SetOptional(v ...bool) *PatternSegment { + if len(v) == 1 { + p.Optional = v[0] + } else { + p.Optional = true + } + return p } type Pattern []PatternSegment @@ -137,30 +148,55 @@ type Pattern []PatternSegment type PatternMatched map[string]interface{} func (p PatternMatched) AsText() PatternTextMatched { - return PatternTextMatched{ - Groups: p["groups"].([]string), + if _, ok := p["groups"]; !ok { + return PatternTextMatched{} } + return PatternTextMatched{HasValue: true, Groups: p["groups"].([]string)} } func (p PatternMatched) AsAt() PatternAtMatched { - return PatternAtMatched{UID: p["qq"].(int64)} + if _, ok := p["qq"]; !ok { + return PatternAtMatched{} + } + return PatternAtMatched{HasValue: true, UID: p["qq"].(int64)} } func (p PatternMatched) AsImage() PatternImageMatched { - return PatternImageMatched{File: p["file"].(string)} + if _, ok := p["file"]; !ok { + return PatternImageMatched{} + } + return PatternImageMatched{HasValue: true, File: p["file"].(string)} } func (p PatternMatched) AsReply() PatternReplyMatched { - return PatternReplyMatched{MessageID: p["file"].(string)} + if _, ok := p["id"]; !ok { + return PatternReplyMatched{} + } + return PatternReplyMatched{HasValue: true, MessageID: p["id"].(string)} } +// PatternImageMatched +// HasValue: false if not matched in optional Pattern type PatternImageMatched = struct { - File string + HasValue bool + File string } + +// PatternAtMatched +// HasValue: false if not matched in optional Pattern type PatternAtMatched = struct { - UID int64 + HasValue bool + UID int64 } + +// PatternTextMatched +// HasValue: false if not matched in optional Pattern type PatternTextMatched = struct { - Groups []string + HasValue bool + Groups []string } + +// PatternReplyMatched +// HasValue: false if not matched in optional Pattern type PatternReplyMatched = struct { + HasValue bool MessageID string } @@ -237,12 +273,24 @@ func PatternReply() PatternSegment { }, } } +func containsOptional(pattern []PatternSegment) bool { + for _, p := range pattern { + if p.Optional { + return true + } + } + return false +} func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegment) bool { - if len(pattern) != len(msgs) { + if !containsOptional(pattern) && len(pattern) != len(msgs) { return false } for i := 0; i < len(pattern); i++ { if pattern[i].Type != (msgs[i].Type) || !pattern[i].Matcher(ctx, msgs[i]) { + if pattern[i].Optional { + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{}) + continue + } return false } } From e810fbfde9538ae8736e8bf40c8b0647bc9f08a5 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 11:53:54 +0800 Subject: [PATCH 12/49] use pointer --- rules.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/rules.go b/rules.go index d456bf1..f772f86 100644 --- a/rules.go +++ b/rules.go @@ -201,9 +201,9 @@ type PatternReplyMatched = struct { } // PatternText type zero.PatternTextMatched -func PatternText(regex string) PatternSegment { +func PatternText(regex string) *PatternSegment { re := regexp.MustCompile(regex) - return PatternSegment{ + return &PatternSegment{ Type: "text", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { s := msg.Data["text"] @@ -225,8 +225,8 @@ func PatternText(regex string) PatternSegment { } // PatternAt type zero.PatternAtMatched -func PatternAt() PatternSegment { - return PatternSegment{ +func PatternAt() *PatternSegment { + return &PatternSegment{ Type: "at", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { if _, ok := ctx.State[KEY_PATTERN]; !ok { @@ -243,8 +243,8 @@ func PatternAt() PatternSegment { } // PatternImage type zero.PatternImageMatched -func PatternImage() PatternSegment { - return PatternSegment{ +func PatternImage() *PatternSegment { + return &PatternSegment{ Type: "image", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { if _, ok := ctx.State[KEY_PATTERN]; !ok { @@ -259,8 +259,8 @@ func PatternImage() PatternSegment { } // PatternReply type zero.PatternReplyMatched -func PatternReply() PatternSegment { - return PatternSegment{ +func PatternReply() *PatternSegment { + return &PatternSegment{ Type: "reply", Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { if _, ok := ctx.State[KEY_PATTERN]; !ok { @@ -273,7 +273,7 @@ func PatternReply() PatternSegment { }, } } -func containsOptional(pattern []PatternSegment) bool { +func containsOptional(pattern []*PatternSegment) bool { for _, p := range pattern { if p.Optional { return true @@ -281,7 +281,7 @@ func containsOptional(pattern []PatternSegment) bool { } return false } -func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegment) bool { +func patternMatch(ctx *Ctx, pattern []*PatternSegment, msgs []message.MessageSegment) bool { if !containsOptional(pattern) && len(pattern) != len(msgs) { return false } @@ -298,7 +298,7 @@ func patternMatch(ctx *Ctx, pattern []PatternSegment, msgs []message.MessageSegm } // PatternRule check if the message can be matched by the pattern -func PatternRule(pattern ...PatternSegment) Rule { +func PatternRule(pattern ...*PatternSegment) Rule { return func(ctx *Ctx) bool { // copy messages msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) From e4950df16e4460d0539c2ed948555061c7cf4da3 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Thu, 10 Oct 2024 12:03:29 +0800 Subject: [PATCH 13/49] fix nil --- rules.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rules.go b/rules.go index f772f86..343bcec 100644 --- a/rules.go +++ b/rules.go @@ -285,14 +285,22 @@ func patternMatch(ctx *Ctx, pattern []*PatternSegment, msgs []message.MessageSeg if !containsOptional(pattern) && len(pattern) != len(msgs) { return false } - for i := 0; i < len(pattern); i++ { - if pattern[i].Type != (msgs[i].Type) || !pattern[i].Matcher(ctx, msgs[i]) { + i := 0 + j := 0 + for i < len(pattern) && j < len(msgs) { + if pattern[i].Type != (msgs[j].Type) || !pattern[i].Matcher(ctx, msgs[j]) { if pattern[i].Optional { + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) + } ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{}) + i++ continue } return false } + i++ + j++ } return true } From 7101db5dab90da37ba1817dfbb9ff68785d136fa Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 00:12:05 +0800 Subject: [PATCH 14/49] move patterns to pattern.go chained pattern builder remove useless structs rename function As... => Get... --- pattern.go | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++++ rules.go | 183 +--------------------------------------------------- 2 files changed, 186 insertions(+), 181 deletions(-) create mode 100644 pattern.go diff --git a/pattern.go b/pattern.go new file mode 100644 index 0000000..301234e --- /dev/null +++ b/pattern.go @@ -0,0 +1,184 @@ +package zero + +import ( + "github.com/wdvxdr1123/ZeroBot/message" + "regexp" + "strconv" + "strings" +) + +type Pattern []PatternSegment + +func NewPattern() *Pattern { + pattern := make(Pattern, 0) + return &pattern +} + +type PatternSegment struct { + Type string + Optional bool + Parse func(msg *message.MessageSegment) *PatternParsed +} + +// SetOptional set previous segment is optional, is v is empty, Optional will be true +// if Pattern is empty, panic +func (p *Pattern) SetOptional(v ...bool) *Pattern { + if len(*p) == 0 { + panic("pattern is empty") + } + if len(v) == 1 { + (*p)[len(*p)-1].Optional = v[0] + } else { + (*p)[len(*p)-1].Optional = true + } + return p +} + +// PatternParsed PatternRule parse result +type PatternParsed struct { + Valid bool + Value any + Msg *message.MessageSegment +} + +func (p PatternParsed) GetText() []string { + return p.Value.([]string) +} +func (p PatternParsed) GetAt() string { + return p.Value.(string) +} +func (p PatternParsed) GetImage() string { + return p.Value.(string) +} +func (p PatternParsed) GetReply() string { + return p.Value.(string) +} + +// Text use regex to search a 'text' segment +func (p *Pattern) Text(regex string) *Pattern { + re := regexp.MustCompile(regex) + pattern := PatternSegment{ + Type: "text", + Parse: func(msg *message.MessageSegment) *PatternParsed { + s := msg.Data["text"] + s = strings.Trim(s, " \n\r\t") + matchString := re.MatchString(s) + if matchString { + return &PatternParsed{ + Valid: true, + Value: re.FindStringSubmatch(s), + Msg: msg, + } + } else { + return &PatternParsed{ + Valid: false, + Value: nil, + Msg: nil, + } + } + }, + } + *p = append(*p, pattern) + return p +} + +// At use regex to match an 'at' segment, if id is not empty, only match specific target +func (p *Pattern) At(id ...string) *Pattern { + if len(id) > 1 { + panic("at pattern only support one id") + } + pattern := PatternSegment{ + Type: "at", + Parse: func(msg *message.MessageSegment) *PatternParsed { + qq, _ := strconv.ParseInt(msg.Data["qq"], 10, 64) + if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { + return &PatternParsed{ + Valid: true, + Value: qq, + Msg: msg, + } + } else { + return &PatternParsed{ + Valid: false, + Value: nil, + Msg: nil, + } + } + }, + } + *p = append(*p, pattern) + return p +} + +// Image use regex to match an 'at' segment, if id is not empty, only match specific target +func (p *Pattern) Image() *Pattern { + pattern := PatternSegment{ + Type: "image", + Parse: func(msg *message.MessageSegment) *PatternParsed { + return &PatternParsed{ + Valid: true, + Value: msg.Data["file"], + Msg: msg, + } + }, + } + *p = append(*p, pattern) + return p +} + +// Reply type zero.PatternReplyMatched +func (p *Pattern) Reply() *Pattern { + pattern := PatternSegment{ + Type: "reply", + Parse: func(msg *message.MessageSegment) *PatternParsed { + return &PatternParsed{ + Valid: true, + Value: msg.Data["id"], + Msg: msg, + } + }, + } + *p = append(*p, pattern) + return p +} +func containsOptional(pattern Pattern) bool { + for _, p := range pattern { + if p.Optional { + return true + } + } + return false +} +func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool { + if !containsOptional(pattern) && len(pattern) != len(msgs) { + return false + } + i := 0 + j := 0 + for i < len(pattern) && j < len(msgs) { + var parsed *PatternParsed + if pattern[i].Type == (msgs[j].Type) { + parsed = pattern[i].Parse(&msgs[j]) + } else { + parsed = &PatternParsed{ + Valid: false, + Value: nil, + Msg: nil, + } + } + if pattern[i].Type != (msgs[j].Type) || !parsed.Valid { + if pattern[i].Optional { + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]*PatternParsed, 0, 1) + } + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) + i++ + continue + } + return false + } + i++ + j++ + } + return true +} diff --git a/rules.go b/rules.go index 343bcec..f54d31e 100644 --- a/rules.go +++ b/rules.go @@ -126,187 +126,8 @@ func RegexRule(regexPattern string) Rule { } } -type PatternSegment struct { - Type string - Optional bool - Matcher func(ctx *Ctx, msg message.MessageSegment) bool -} - -// SetOptional set Pattern is optional, is v is empty, Optional will be true -func (p *PatternSegment) SetOptional(v ...bool) *PatternSegment { - if len(v) == 1 { - p.Optional = v[0] - } else { - p.Optional = true - } - return p -} - -type Pattern []PatternSegment - -// PatternMatched save PatternRule context -type PatternMatched map[string]interface{} - -func (p PatternMatched) AsText() PatternTextMatched { - if _, ok := p["groups"]; !ok { - return PatternTextMatched{} - } - return PatternTextMatched{HasValue: true, Groups: p["groups"].([]string)} -} -func (p PatternMatched) AsAt() PatternAtMatched { - if _, ok := p["qq"]; !ok { - return PatternAtMatched{} - } - return PatternAtMatched{HasValue: true, UID: p["qq"].(int64)} -} -func (p PatternMatched) AsImage() PatternImageMatched { - if _, ok := p["file"]; !ok { - return PatternImageMatched{} - } - return PatternImageMatched{HasValue: true, File: p["file"].(string)} -} -func (p PatternMatched) AsReply() PatternReplyMatched { - if _, ok := p["id"]; !ok { - return PatternReplyMatched{} - } - return PatternReplyMatched{HasValue: true, MessageID: p["id"].(string)} -} - -// PatternImageMatched -// HasValue: false if not matched in optional Pattern -type PatternImageMatched = struct { - HasValue bool - File string -} - -// PatternAtMatched -// HasValue: false if not matched in optional Pattern -type PatternAtMatched = struct { - HasValue bool - UID int64 -} - -// PatternTextMatched -// HasValue: false if not matched in optional Pattern -type PatternTextMatched = struct { - HasValue bool - Groups []string -} - -// PatternReplyMatched -// HasValue: false if not matched in optional Pattern -type PatternReplyMatched = struct { - HasValue bool - MessageID string -} - -// PatternText type zero.PatternTextMatched -func PatternText(regex string) *PatternSegment { - re := regexp.MustCompile(regex) - return &PatternSegment{ - Type: "text", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - s := msg.Data["text"] - s = strings.Trim(s, " \n\r\t") - matchString := re.MatchString(s) - if matchString { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) - } - - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), - PatternMatched{ - "groups": re.FindStringSubmatch(s), - }) - } - return matchString - }, - } -} - -// PatternAt type zero.PatternAtMatched -func PatternAt() *PatternSegment { - return &PatternSegment{ - Type: "at", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) - } - qq, _ := strconv.ParseInt(msg.Data["qq"], 10, 64) - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), - PatternMatched{ - "qq": qq, - }) - return true - }, - } -} - -// PatternImage type zero.PatternImageMatched -func PatternImage() *PatternSegment { - return &PatternSegment{ - Type: "image", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{ - "file": msg.Data["file"], - }) - return true - }, - } -} - -// PatternReply type zero.PatternReplyMatched -func PatternReply() *PatternSegment { - return &PatternSegment{ - Type: "reply", - Matcher: func(ctx *Ctx, msg message.MessageSegment) bool { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{ - "id": msg.Data["id"], - }) - return true - }, - } -} -func containsOptional(pattern []*PatternSegment) bool { - for _, p := range pattern { - if p.Optional { - return true - } - } - return false -} -func patternMatch(ctx *Ctx, pattern []*PatternSegment, msgs []message.MessageSegment) bool { - if !containsOptional(pattern) && len(pattern) != len(msgs) { - return false - } - i := 0 - j := 0 - for i < len(pattern) && j < len(msgs) { - if pattern[i].Type != (msgs[j].Type) || !pattern[i].Matcher(ctx, msgs[j]) { - if pattern[i].Optional { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]PatternMatched, 0, 1) - } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]PatternMatched), PatternMatched{}) - i++ - continue - } - return false - } - i++ - j++ - } - return true -} - // PatternRule check if the message can be matched by the pattern -func PatternRule(pattern ...*PatternSegment) Rule { +func PatternRule(pattern *Pattern) Rule { return func(ctx *Ctx) bool { // copy messages msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) @@ -328,7 +149,7 @@ func PatternRule(pattern ...*PatternSegment) Rule { msgs = append(msgs, ctx.Event.Message[i]) } } - return patternMatch(ctx, pattern, msgs) + return patternMatch(ctx, *pattern, msgs) } } From 88963dc33006b33e3853cef4a243d88529abf116 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 08:37:00 +0800 Subject: [PATCH 15/49] set State in `patternMatch` --- extension/model.go | 2 +- pattern.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/extension/model.go b/extension/model.go index aec7aa5..5e4ee1b 100644 --- a/extension/model.go +++ b/extension/model.go @@ -37,5 +37,5 @@ type RegexModel struct { // PatternModel is model of zero.PatternRule type PatternModel struct { - Matched []zero.PatternMatched `zero:"pattern_matched"` + Matched []*zero.PatternParsed `zero:"pattern_matched"` } diff --git a/pattern.go b/pattern.go index 301234e..738249e 100644 --- a/pattern.go +++ b/pattern.go @@ -153,6 +153,9 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool if !containsOptional(pattern) && len(pattern) != len(msgs) { return false } + if _, ok := ctx.State[KEY_PATTERN]; !ok { + ctx.State[KEY_PATTERN] = make([]*PatternParsed, 0, 1) + } i := 0 j := 0 for i < len(pattern) && j < len(msgs) { @@ -168,15 +171,13 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool } if pattern[i].Type != (msgs[j].Type) || !parsed.Valid { if pattern[i].Optional { - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]*PatternParsed, 0, 1) - } ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) i++ continue } return false } + ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) i++ j++ } From ab9dbeb99a53b229b0c36e19a3678741e7c7120a Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 08:38:47 +0800 Subject: [PATCH 16/49] match tests --- pattern_test.go | 119 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 pattern_test.go diff --git a/pattern_test.go b/pattern_test.go new file mode 100644 index 0000000..5969f4d --- /dev/null +++ b/pattern_test.go @@ -0,0 +1,119 @@ +package zero + +import ( + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + "github.com/wdvxdr1123/ZeroBot/message" + "strconv" + "testing" +) + +type mockAPICaller struct{} + +func (m mockAPICaller) CallApi(request APIRequest) (APIResponse, error) { + return APIResponse{ + Status: "", + Data: gjson.Result{}, + Msg: "", + Wording: "", + RetCode: 0, + Echo: 0, + }, nil +} + +// copy from extension.PatternModel +type PatternModel struct { + Matched []*PatternParsed `zero:"pattern_matched"` +} + +// Test Match +func TestText(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("haha"), true}, + {[]message.MessageSegment{message.Text("aaa")}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("实用插件合集"), true}, + {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("nonono"), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := &Ctx{Event: &Event{Message: v.msg}} + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, v.expected, out) + }) + } +} + +func TestImage(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Image(), false}, + {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Image().Image(), false}, + {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Text("haha").Image(), true}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Image(), true}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image(), false}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image(), true}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image().Image(), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := &Ctx{Event: &Event{Message: v.msg}} + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, v.expected, out) + }) + } +} + +func TestAt(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().At(), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().At(), false}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().At(), true}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().At("1919810"), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := &Ctx{Event: &Event{Message: v.msg}} + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, v.expected, out) + }) + } +} + +func TestReply(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Reply(), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Reply(), false}, + {[]message.MessageSegment{message.At(1919810), message.Reply(12345)}, NewPattern().Reply().At(), false}, + {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply().At(), true}, + {[]message.MessageSegment{message.Reply(12345)}, NewPattern().Reply(), true}, + {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply(), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := &Ctx{Event: &Event{Message: v.msg}, caller: APICaller(&mockAPICaller{})} + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, v.expected, out) + }) + } +} From 3752551ff59aa013b6f3f0985533e6c839b879cf Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 09:14:29 +0800 Subject: [PATCH 17/49] fix optional not work if len(msg) < len(pattern) --- pattern.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pattern.go b/pattern.go index 738249e..23346fb 100644 --- a/pattern.go +++ b/pattern.go @@ -158,9 +158,9 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool } i := 0 j := 0 - for i < len(pattern) && j < len(msgs) { + for i < len(pattern) { var parsed *PatternParsed - if pattern[i].Type == (msgs[j].Type) { + if j < len(msgs) && pattern[i].Type == (msgs[j].Type) { parsed = pattern[i].Parse(&msgs[j]) } else { parsed = &PatternParsed{ @@ -169,7 +169,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool Msg: nil, } } - if pattern[i].Type != (msgs[j].Type) || !parsed.Valid { + if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || !parsed.Valid { if pattern[i].Optional { ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) i++ From 63b4e1f489059779197f5886bf486172f3e40c51 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 08:38:47 +0800 Subject: [PATCH 18/49] match tests parse tests Getxxx tests SetOptional tests --- pattern_test.go | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 pattern_test.go diff --git a/pattern_test.go b/pattern_test.go new file mode 100644 index 0000000..b03913e --- /dev/null +++ b/pattern_test.go @@ -0,0 +1,256 @@ +package zero + +import ( + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + "github.com/wdvxdr1123/ZeroBot/message" + "strconv" + "testing" +) + +type mockAPICaller struct{} + +func (m mockAPICaller) CallApi(_ APIRequest) (APIResponse, error) { + return APIResponse{ + Status: "", + Data: gjson.Result{}, + Msg: "", + Wording: "", + RetCode: 0, + Echo: 0, + }, nil +} +func fakeCtx(msg message.Message) *Ctx { + ctx := &Ctx{Event: &Event{Message: msg}, State: map[string]interface{}{}, caller: mockAPICaller{}} + return ctx +} + +// copy from extension.PatternModel +type PatternModel struct { + Matched []*PatternParsed `zero:"pattern_matched"` +} + +// Test Match +func TestPattern_Text(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("haha"), true}, + {[]message.MessageSegment{message.Text("aaa")}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().Text("not match"), false}, + {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("实用插件合集"), true}, + {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("nonono"), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, out, v.expected) + }) + } +} + +func TestPattern_Image(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Image(), false}, + {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Image().Image(), false}, + {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Text("haha").Image(), true}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Image(), true}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image(), false}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image(), true}, + {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image().Image(), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, out, v.expected) + }) + } +} + +func TestPattern_At(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().At(), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().At(), false}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().At(), true}, + {[]message.MessageSegment{message.At(114514)}, NewPattern().At("1919810"), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, out, v.expected) + }) + } +} + +func TestPattern_Reply(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Reply(), false}, + {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Reply(), false}, + {[]message.MessageSegment{message.At(1919810), message.Reply(12345)}, NewPattern().Reply().At(), false}, + {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply().At(), true}, + {[]message.MessageSegment{message.Reply(12345)}, NewPattern().Reply(), true}, + {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply(), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + out := rule(ctx) + assert.Equal(t, out, v.expected) + }) + } +} +func TestPatternParsed_Gets(t *testing.T) { + assert.Equal(t, []string{"gaga"}, PatternParsed{Value: []string{"gaga"}}.GetText()) + assert.Equal(t, "image", PatternParsed{Value: "image"}.GetImage()) + assert.Equal(t, "reply", PatternParsed{Value: "reply"}.GetReply()) + assert.Equal(t, "114514", PatternParsed{Value: "114514"}.GetAt()) +} +func TestPattern_SetOptional(t *testing.T) { + assert.Panics(t, func() { + NewPattern().SetOptional() + }) + tests := [...]struct { + msg message.Message + pattern *Pattern + expected []PatternParsed + }{ + {[]message.MessageSegment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{ + { + Valid: true, + }, { + Valid: false, + }, + }}, + {[]message.MessageSegment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(false), []PatternParsed{}}, + {[]message.MessageSegment{message.Text("happy bear"), message.At(114514)}, NewPattern().Reply().SetOptional().Text(".+").SetOptional().At().SetOptional(false), []PatternParsed{ + { + Valid: false, + }, + { + Valid: true, + }, + { + Valid: true, + }, + }}, + {[]message.MessageSegment{message.Text("happy bear"), message.At(114514)}, NewPattern().Image().SetOptional().Image().SetOptional().Image().SetOptional(), []PatternParsed{ // why you do this + { + Valid: false, + }, + { + Valid: false, + }, + { + Valid: false, + }, + }}, + } + for i, v := range tests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + matched := rule(ctx) + if !matched { + assert.Equal(t, 0, len(v.expected)) + return + } + parsed := &PatternModel{} + err := ctx.Parse(parsed) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(v.expected), len(parsed.Matched)) + for i := range parsed.Matched { + assert.Equal(t, v.expected[i].Valid, parsed.Matched[i].Valid) + } + }) + } +} + +// Test Parse +func TestAllParse(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected []PatternParsed + }{ + {[]message.MessageSegment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{ + { + Valid: true, + Value: []string{"haha", "haha", "ha"}, + }, { + Valid: true, + Value: int64(123), + }, + }}, + {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ + { + Valid: true, + Value: []string{"haha", "h", "a", "h", "a"}, + }, + }}, + {[]message.MessageSegment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ + + { + Valid: true, + Value: "fake reply", + }, + { + Valid: true, + Value: "fake image", + }, + { + Valid: true, + Value: int64(999), + }, + { + Valid: true, + Value: int64(124), + }, + { + Valid: true, + Value: []string{"haha", "h", "a", "h", "a"}, + }, + }}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := PatternRule(v.pattern) + matched := rule(ctx) + parsed := &PatternModel{} + err := ctx.Parse(parsed) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, true, matched) + for i := range parsed.Matched { + assert.Equal(t, v.expected[i].Valid, parsed.Matched[i].Valid) + assert.Equal(t, v.expected[i].Value, parsed.Matched[i].Value) + assert.Equal(t, &(v.msg[i]), parsed.Matched[i].Msg) + } + }) + } +} From 9ecfc51663abe576af6cfcadc6dae69b7ac355ac Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 09:46:32 +0800 Subject: [PATCH 19/49] return empty value if not Valid (cherry picked from commit 5a945cc38a0f9583eaf3b90fea4a44876c9afac5) --- pattern.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pattern.go b/pattern.go index 23346fb..55101e0 100644 --- a/pattern.go +++ b/pattern.go @@ -42,15 +42,27 @@ type PatternParsed struct { } func (p PatternParsed) GetText() []string { + if !p.Valid { + return make([]string, 0) + } return p.Value.([]string) } func (p PatternParsed) GetAt() string { + if !p.Valid { + return "" + } return p.Value.(string) } func (p PatternParsed) GetImage() string { + if !p.Valid { + return "" + } return p.Value.(string) } func (p PatternParsed) GetReply() string { + if !p.Valid { + return "" + } return p.Value.(string) } From d501a637da17f112ab39e28897e6cd06377adf2e Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 10:22:23 +0800 Subject: [PATCH 20/49] fix test failed --- pattern_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pattern_test.go b/pattern_test.go index b03913e..4ce5a74 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -122,10 +122,10 @@ func TestPattern_Reply(t *testing.T) { } } func TestPatternParsed_Gets(t *testing.T) { - assert.Equal(t, []string{"gaga"}, PatternParsed{Value: []string{"gaga"}}.GetText()) - assert.Equal(t, "image", PatternParsed{Value: "image"}.GetImage()) - assert.Equal(t, "reply", PatternParsed{Value: "reply"}.GetReply()) - assert.Equal(t, "114514", PatternParsed{Value: "114514"}.GetAt()) + assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, Value: []string{"gaga"}}.GetText()) + assert.Equal(t, "image", PatternParsed{Valid: true, Value: "image"}.GetImage()) + assert.Equal(t, "reply", PatternParsed{Valid: true, Value: "reply"}.GetReply()) + assert.Equal(t, "114514", PatternParsed{Valid: true, Value: "114514"}.GetAt()) } func TestPattern_SetOptional(t *testing.T) { assert.Panics(t, func() { From 07a5fcb552469f3a70a0dfab35f118dcb64c65f5 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 10:29:06 +0800 Subject: [PATCH 21/49] ignore empty message --- rules.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rules.go b/rules.go index f54d31e..b501c1e 100644 --- a/rules.go +++ b/rules.go @@ -129,6 +129,9 @@ func RegexRule(regexPattern string) Rule { // PatternRule check if the message can be matched by the pattern func PatternRule(pattern *Pattern) Rule { return func(ctx *Ctx) bool { + if len(ctx.Event.Message) == 0 { + return false + } // copy messages msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) msgs = append(msgs, ctx.Event.Message[0]) From 039a7ed20b6694fcf59d55da2c279289c41a3461 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sat, 12 Oct 2024 11:51:36 +0800 Subject: [PATCH 22/49] change `At` parsed value to string --- pattern.go | 4 +--- pattern_test.go | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pattern.go b/pattern.go index 55101e0..7f300d3 100644 --- a/pattern.go +++ b/pattern.go @@ -3,7 +3,6 @@ package zero import ( "github.com/wdvxdr1123/ZeroBot/message" "regexp" - "strconv" "strings" ) @@ -102,11 +101,10 @@ func (p *Pattern) At(id ...string) *Pattern { pattern := PatternSegment{ Type: "at", Parse: func(msg *message.MessageSegment) *PatternParsed { - qq, _ := strconv.ParseInt(msg.Data["qq"], 10, 64) if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { return &PatternParsed{ Valid: true, - Value: qq, + Value: msg.Data["qq"], Msg: msg, } } else { diff --git a/pattern_test.go b/pattern_test.go index 4ce5a74..4423ccc 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -202,7 +202,7 @@ func TestAllParse(t *testing.T) { Value: []string{"haha", "haha", "ha"}, }, { Valid: true, - Value: int64(123), + Value: "123", }, }}, {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ @@ -223,11 +223,11 @@ func TestAllParse(t *testing.T) { }, { Valid: true, - Value: int64(999), + Value: "999", }, { Valid: true, - Value: int64(124), + Value: "124", }, { Valid: true, From 7526ee8e9d9fa98d595ed7631137f4192efa26ad Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 00:30:47 +0800 Subject: [PATCH 23/49] chore: make lint happy --- extension/rate/rate.go | 2 +- extension/single/single.go | 1 + pattern.go | 49 +++++++++++++++++++------------------- rules.go | 6 ++--- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/extension/rate/rate.go b/extension/rate/rate.go index f176e54..24720f3 100644 --- a/extension/rate/rate.go +++ b/extension/rate/rate.go @@ -26,7 +26,7 @@ func NewManager[K comparable](interval time.Duration, burst int) *LimiterManager } // Delete 删除对应限速器 -func (l *LimiterManager[K]) Delete(key K) { +func (l *LimiterManager[K]) Delete(key K) { l.limiters.Delete(key) } diff --git a/extension/single/single.go b/extension/single/single.go index af52909..e4304fb 100644 --- a/extension/single/single.go +++ b/extension/single/single.go @@ -4,6 +4,7 @@ import ( "runtime" "github.com/RomiChan/syncx" + zero "github.com/wdvxdr1123/ZeroBot" ) diff --git a/pattern.go b/pattern.go index 7f300d3..a0ee04b 100644 --- a/pattern.go +++ b/pattern.go @@ -1,9 +1,10 @@ package zero import ( - "github.com/wdvxdr1123/ZeroBot/message" "regexp" "strings" + + "github.com/wdvxdr1123/ZeroBot/message" ) type Pattern []PatternSegment @@ -16,7 +17,7 @@ func NewPattern() *Pattern { type PatternSegment struct { Type string Optional bool - Parse func(msg *message.MessageSegment) *PatternParsed + Parse func(msg *message.Segment) *PatternParsed } // SetOptional set previous segment is optional, is v is empty, Optional will be true @@ -37,7 +38,7 @@ func (p *Pattern) SetOptional(v ...bool) *Pattern { type PatternParsed struct { Valid bool Value any - Msg *message.MessageSegment + Msg *message.Segment } func (p PatternParsed) GetText() []string { @@ -70,7 +71,7 @@ func (p *Pattern) Text(regex string) *Pattern { re := regexp.MustCompile(regex) pattern := PatternSegment{ Type: "text", - Parse: func(msg *message.MessageSegment) *PatternParsed { + Parse: func(msg *message.Segment) *PatternParsed { s := msg.Data["text"] s = strings.Trim(s, " \n\r\t") matchString := re.MatchString(s) @@ -80,12 +81,12 @@ func (p *Pattern) Text(regex string) *Pattern { Value: re.FindStringSubmatch(s), Msg: msg, } - } else { - return &PatternParsed{ - Valid: false, - Value: nil, - Msg: nil, - } + } + + return &PatternParsed{ + Valid: false, + Value: nil, + Msg: nil, } }, } @@ -100,19 +101,19 @@ func (p *Pattern) At(id ...string) *Pattern { } pattern := PatternSegment{ Type: "at", - Parse: func(msg *message.MessageSegment) *PatternParsed { + Parse: func(msg *message.Segment) *PatternParsed { if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { return &PatternParsed{ Valid: true, Value: msg.Data["qq"], Msg: msg, } - } else { - return &PatternParsed{ - Valid: false, - Value: nil, - Msg: nil, - } + } + + return &PatternParsed{ + Valid: false, + Value: nil, + Msg: nil, } }, } @@ -124,7 +125,7 @@ func (p *Pattern) At(id ...string) *Pattern { func (p *Pattern) Image() *Pattern { pattern := PatternSegment{ Type: "image", - Parse: func(msg *message.MessageSegment) *PatternParsed { + Parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ Valid: true, Value: msg.Data["file"], @@ -140,7 +141,7 @@ func (p *Pattern) Image() *Pattern { func (p *Pattern) Reply() *Pattern { pattern := PatternSegment{ Type: "reply", - Parse: func(msg *message.MessageSegment) *PatternParsed { + Parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ Valid: true, Value: msg.Data["id"], @@ -159,12 +160,12 @@ func containsOptional(pattern Pattern) bool { } return false } -func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool { +func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { if !containsOptional(pattern) && len(pattern) != len(msgs) { return false } - if _, ok := ctx.State[KEY_PATTERN]; !ok { - ctx.State[KEY_PATTERN] = make([]*PatternParsed, 0, 1) + if _, ok := ctx.State[KeyPattern]; !ok { + ctx.State[KeyPattern] = make([]*PatternParsed, 0, 1) } i := 0 j := 0 @@ -181,13 +182,13 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.MessageSegment) bool } if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || !parsed.Valid { if pattern[i].Optional { - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) + ctx.State[KeyPattern] = append(ctx.State[KeyPattern].([]*PatternParsed), parsed) i++ continue } return false } - ctx.State[KEY_PATTERN] = append(ctx.State[KEY_PATTERN].([]*PatternParsed), parsed) + ctx.State[KeyPattern] = append(ctx.State[KeyPattern].([]*PatternParsed), parsed) i++ j++ } diff --git a/rules.go b/rules.go index 0f3f226..5c52d7d 100644 --- a/rules.go +++ b/rules.go @@ -12,7 +12,7 @@ import ( ) const ( - KEY_PATTERN = "pattern_matched" + KeyPattern = "pattern_matched" ) // Type check the ctx.Event's type @@ -133,13 +133,13 @@ func PatternRule(pattern *Pattern) Rule { return false } // copy messages - msgs := make([]message.MessageSegment, 0, len(ctx.Event.Message)) + msgs := make([]message.Segment, 0, len(ctx.Event.Message)) msgs = append(msgs, ctx.Event.Message[0]) for i := 1; i < len(ctx.Event.Message); i++ { if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) - if reply.MessageId.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { + if reply.MessageID.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { // failed to get history message msgs = append(msgs, ctx.Event.Message[i]) continue From 6050559e53eb87ed83d39217a043b6ce556c18a9 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 00:34:42 +0800 Subject: [PATCH 24/49] optimize --- pattern.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pattern.go b/pattern.go index a0ee04b..b2a4abe 100644 --- a/pattern.go +++ b/pattern.go @@ -10,7 +10,7 @@ import ( type Pattern []PatternSegment func NewPattern() *Pattern { - pattern := make(Pattern, 0) + pattern := make(Pattern, 0, 4) return &pattern } @@ -43,7 +43,7 @@ type PatternParsed struct { func (p PatternParsed) GetText() []string { if !p.Valid { - return make([]string, 0) + return nil } return p.Value.([]string) } From 0f30441b123475ffeb547eafff7869301f093518 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 00:36:12 +0800 Subject: [PATCH 25/49] chore: make lint happy --- pattern_test.go | 62 ++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/pattern_test.go b/pattern_test.go index 4423ccc..4c5e35e 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -10,7 +10,7 @@ import ( type mockAPICaller struct{} -func (m mockAPICaller) CallApi(_ APIRequest) (APIResponse, error) { +func (m mockAPICaller) CallAPI(_ APIRequest) (APIResponse, error) { return APIResponse{ Status: "", Data: gjson.Result{}, @@ -37,12 +37,12 @@ func TestPattern_Text(t *testing.T) { pattern *Pattern expected bool }{ - {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("haha"), true}, - {[]message.MessageSegment{message.Text("aaa")}, NewPattern().Text("not match"), false}, - {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Text("not match"), false}, - {[]message.MessageSegment{message.At(114514)}, NewPattern().Text("not match"), false}, - {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("实用插件合集"), true}, - {[]message.MessageSegment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("nonono"), false}, + {[]message.Segment{message.Text("haha")}, NewPattern().Text("haha"), true}, + {[]message.Segment{message.Text("aaa")}, NewPattern().Text("not match"), false}, + {[]message.Segment{message.Image("not a image")}, NewPattern().Text("not match"), false}, + {[]message.Segment{message.At(114514)}, NewPattern().Text("not match"), false}, + {[]message.Segment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("实用插件合集"), true}, + {[]message.Segment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern().Text("nonono"), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -60,13 +60,13 @@ func TestPattern_Image(t *testing.T) { pattern *Pattern expected bool }{ - {[]message.MessageSegment{message.Text("haha")}, NewPattern().Image(), false}, - {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Image().Image(), false}, - {[]message.MessageSegment{message.Text("haha"), message.Image("not a image")}, NewPattern().Text("haha").Image(), true}, - {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Image(), true}, - {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image(), false}, - {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image(), true}, - {[]message.MessageSegment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image().Image(), false}, + {[]message.Segment{message.Text("haha")}, NewPattern().Image(), false}, + {[]message.Segment{message.Text("haha"), message.Image("not a image")}, NewPattern().Image().Image(), false}, + {[]message.Segment{message.Text("haha"), message.Image("not a image")}, NewPattern().Text("haha").Image(), true}, + {[]message.Segment{message.Image("not a image")}, NewPattern().Image(), true}, + {[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image(), false}, + {[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image(), true}, + {[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern().Image().Image().Image(), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -84,10 +84,10 @@ func TestPattern_At(t *testing.T) { pattern *Pattern expected bool }{ - {[]message.MessageSegment{message.Text("haha")}, NewPattern().At(), false}, - {[]message.MessageSegment{message.Image("not a image")}, NewPattern().At(), false}, - {[]message.MessageSegment{message.At(114514)}, NewPattern().At(), true}, - {[]message.MessageSegment{message.At(114514)}, NewPattern().At("1919810"), false}, + {[]message.Segment{message.Text("haha")}, NewPattern().At(), false}, + {[]message.Segment{message.Image("not a image")}, NewPattern().At(), false}, + {[]message.Segment{message.At(114514)}, NewPattern().At(), true}, + {[]message.Segment{message.At(114514)}, NewPattern().At("1919810"), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -105,12 +105,12 @@ func TestPattern_Reply(t *testing.T) { pattern *Pattern expected bool }{ - {[]message.MessageSegment{message.Text("haha")}, NewPattern().Reply(), false}, - {[]message.MessageSegment{message.Image("not a image")}, NewPattern().Reply(), false}, - {[]message.MessageSegment{message.At(1919810), message.Reply(12345)}, NewPattern().Reply().At(), false}, - {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply().At(), true}, - {[]message.MessageSegment{message.Reply(12345)}, NewPattern().Reply(), true}, - {[]message.MessageSegment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply(), false}, + {[]message.Segment{message.Text("haha")}, NewPattern().Reply(), false}, + {[]message.Segment{message.Image("not a image")}, NewPattern().Reply(), false}, + {[]message.Segment{message.At(1919810), message.Reply(12345)}, NewPattern().Reply().At(), false}, + {[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply().At(), true}, + {[]message.Segment{message.Reply(12345)}, NewPattern().Reply(), true}, + {[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern().Reply(), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -136,15 +136,15 @@ func TestPattern_SetOptional(t *testing.T) { pattern *Pattern expected []PatternParsed }{ - {[]message.MessageSegment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{ + {[]message.Segment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{ { Valid: true, }, { Valid: false, }, }}, - {[]message.MessageSegment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(false), []PatternParsed{}}, - {[]message.MessageSegment{message.Text("happy bear"), message.At(114514)}, NewPattern().Reply().SetOptional().Text(".+").SetOptional().At().SetOptional(false), []PatternParsed{ + {[]message.Segment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(false), []PatternParsed{}}, + {[]message.Segment{message.Text("happy bear"), message.At(114514)}, NewPattern().Reply().SetOptional().Text(".+").SetOptional().At().SetOptional(false), []PatternParsed{ { Valid: false, }, @@ -155,7 +155,7 @@ func TestPattern_SetOptional(t *testing.T) { Valid: true, }, }}, - {[]message.MessageSegment{message.Text("happy bear"), message.At(114514)}, NewPattern().Image().SetOptional().Image().SetOptional().Image().SetOptional(), []PatternParsed{ // why you do this + {[]message.Segment{message.Text("happy bear"), message.At(114514)}, NewPattern().Image().SetOptional().Image().SetOptional().Image().SetOptional(), []PatternParsed{ // why you do this { Valid: false, }, @@ -196,7 +196,7 @@ func TestAllParse(t *testing.T) { pattern *Pattern expected []PatternParsed }{ - {[]message.MessageSegment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{ + {[]message.Segment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{ { Valid: true, Value: []string{"haha", "haha", "ha"}, @@ -205,13 +205,13 @@ func TestAllParse(t *testing.T) { Value: "123", }, }}, - {[]message.MessageSegment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ + {[]message.Segment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ { Valid: true, Value: []string{"haha", "h", "a", "h", "a"}, }, }}, - {[]message.MessageSegment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ + {[]message.Segment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ { Valid: true, From b3366ef7dfe7bea8208b16b67e3a752680158245 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 08:07:11 +0800 Subject: [PATCH 26/49] move PatternRule to pattern.go --- pattern.go | 31 +++++++++++++++++++++++++++++++ rules.go | 30 ------------------------------ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/pattern.go b/pattern.go index b2a4abe..0fb5116 100644 --- a/pattern.go +++ b/pattern.go @@ -2,11 +2,42 @@ package zero import ( "regexp" + "strconv" "strings" "github.com/wdvxdr1123/ZeroBot/message" ) +// PatternRule check if the message can be matched by the pattern +func PatternRule(pattern *Pattern) Rule { + return func(ctx *Ctx) bool { + if len(ctx.Event.Message) == 0 { + return false + } + // copy messages + msgs := make([]message.Segment, 0, len(ctx.Event.Message)) + msgs = append(msgs, ctx.Event.Message[0]) + for i := 1; i < len(ctx.Event.Message); i++ { + if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { + // [reply][at] + reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) + if reply.MessageID.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { + // failed to get history message + msgs = append(msgs, ctx.Event.Message[i]) + continue + } + if strconv.FormatInt(reply.Sender.ID, 10) != ctx.Event.Message[i].Data["qq"] { + // @ other user in reply + msgs = append(msgs, ctx.Event.Message[i]) + } + } else { + msgs = append(msgs, ctx.Event.Message[i]) + } + } + return patternMatch(ctx, *pattern, msgs) + } +} + type Pattern []PatternSegment func NewPattern() *Pattern { diff --git a/rules.go b/rules.go index 5c52d7d..22721fb 100644 --- a/rules.go +++ b/rules.go @@ -126,36 +126,6 @@ func RegexRule(regexPattern string) Rule { } } -// PatternRule check if the message can be matched by the pattern -func PatternRule(pattern *Pattern) Rule { - return func(ctx *Ctx) bool { - if len(ctx.Event.Message) == 0 { - return false - } - // copy messages - msgs := make([]message.Segment, 0, len(ctx.Event.Message)) - msgs = append(msgs, ctx.Event.Message[0]) - for i := 1; i < len(ctx.Event.Message); i++ { - if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { - // [reply][at] - reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) - if reply.MessageID.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { - // failed to get history message - msgs = append(msgs, ctx.Event.Message[i]) - continue - } - if strconv.FormatInt(reply.Sender.ID, 10) != ctx.Event.Message[i].Data["qq"] { - // @ other user in reply - msgs = append(msgs, ctx.Event.Message[i]) - } - } else { - msgs = append(msgs, ctx.Event.Message[i]) - } - } - return patternMatch(ctx, *pattern, msgs) - } -} - // ReplyRule check if the message is replying some message func ReplyRule(messageID int64) Rule { return func(ctx *Ctx) bool { From 8dadba42512e7f6fa60df36104e20fc0d9b79084 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 08:12:32 +0800 Subject: [PATCH 27/49] move KeyPattern to pattern.go --- pattern.go | 4 ++++ rules.go | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pattern.go b/pattern.go index 0fb5116..2c09a7e 100644 --- a/pattern.go +++ b/pattern.go @@ -8,6 +8,10 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) +const ( + KeyPattern = "pattern_matched" +) + // PatternRule check if the message can be matched by the pattern func PatternRule(pattern *Pattern) Rule { return func(ctx *Ctx) bool { diff --git a/rules.go b/rules.go index 22721fb..67d42af 100644 --- a/rules.go +++ b/rules.go @@ -11,10 +11,6 @@ import ( "github.com/wdvxdr1123/ZeroBot/utils/helper" ) -const ( - KeyPattern = "pattern_matched" -) - // Type check the ctx.Event's type func Type(typ string) Rule { t := strings.SplitN(typ, "/", 3) From 4524132761ea8f506e990daeb68161ee3ce998d0 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 15:34:14 +0800 Subject: [PATCH 28/49] chained PatternRule builder --- pattern.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pattern.go b/pattern.go index 2c09a7e..4c4e3d3 100644 --- a/pattern.go +++ b/pattern.go @@ -12,8 +12,8 @@ const ( KeyPattern = "pattern_matched" ) -// PatternRule check if the message can be matched by the pattern -func PatternRule(pattern *Pattern) Rule { +// AsRule build PatternRule +func (p *Pattern) AsRule() Rule { return func(ctx *Ctx) bool { if len(ctx.Event.Message) == 0 { return false @@ -38,7 +38,7 @@ func PatternRule(pattern *Pattern) Rule { msgs = append(msgs, ctx.Event.Message[i]) } } - return patternMatch(ctx, *pattern, msgs) + return patternMatch(ctx, *p, msgs) } } From 09e5c79a87bea1d1dfa02f24af37c0080372d887 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 15:35:03 +0800 Subject: [PATCH 29/49] rename value getter --- pattern.go | 8 ++++---- pattern_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pattern.go b/pattern.go index 4c4e3d3..8f0b75e 100644 --- a/pattern.go +++ b/pattern.go @@ -76,25 +76,25 @@ type PatternParsed struct { Msg *message.Segment } -func (p PatternParsed) GetText() []string { +func (p PatternParsed) Text() []string { if !p.Valid { return nil } return p.Value.([]string) } -func (p PatternParsed) GetAt() string { +func (p PatternParsed) At() string { if !p.Valid { return "" } return p.Value.(string) } -func (p PatternParsed) GetImage() string { +func (p PatternParsed) Image() string { if !p.Valid { return "" } return p.Value.(string) } -func (p PatternParsed) GetReply() string { +func (p PatternParsed) Reply() string { if !p.Valid { return "" } diff --git a/pattern_test.go b/pattern_test.go index 4c5e35e..1d2137f 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -122,10 +122,10 @@ func TestPattern_Reply(t *testing.T) { } } func TestPatternParsed_Gets(t *testing.T) { - assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, Value: []string{"gaga"}}.GetText()) - assert.Equal(t, "image", PatternParsed{Valid: true, Value: "image"}.GetImage()) - assert.Equal(t, "reply", PatternParsed{Valid: true, Value: "reply"}.GetReply()) - assert.Equal(t, "114514", PatternParsed{Valid: true, Value: "114514"}.GetAt()) + assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, Value: []string{"gaga"}}.Text()) + assert.Equal(t, "image", PatternParsed{Valid: true, Value: "image"}.Image()) + assert.Equal(t, "reply", PatternParsed{Valid: true, Value: "reply"}.Reply()) + assert.Equal(t, "114514", PatternParsed{Valid: true, Value: "114514"}.At()) } func TestPattern_SetOptional(t *testing.T) { assert.Panics(t, func() { From 2c5cfeadabad121c49a3a2d1df721f68b473e1dc Mon Sep 17 00:00:00 2001 From: RikaCelery <94585272+RikaCelery@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:27:35 +0800 Subject: [PATCH 30/49] optimize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- pattern.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pattern.go b/pattern.go index 8f0b75e..e0e0074 100644 --- a/pattern.go +++ b/pattern.go @@ -200,7 +200,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { return false } if _, ok := ctx.State[KeyPattern]; !ok { - ctx.State[KeyPattern] = make([]*PatternParsed, 0, 1) + ctx.State[KeyPattern] = make([]*PatternParsed, 0, 4) } i := 0 j := 0 From 2c51768b559c6a8341e98480b89bd2ebf7100161 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 17:36:24 +0800 Subject: [PATCH 31/49] rename `containsOptional` to `mustMatchAllPatterns` --- pattern.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pattern.go b/pattern.go index 8f0b75e..cfa3174 100644 --- a/pattern.go +++ b/pattern.go @@ -187,16 +187,16 @@ func (p *Pattern) Reply() *Pattern { *p = append(*p, pattern) return p } -func containsOptional(pattern Pattern) bool { +func mustMatchAllPatterns(pattern Pattern) bool { for _, p := range pattern { - if p.Optional { - return true + if !p.Optional { + return false } } - return false + return true } func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { - if !containsOptional(pattern) && len(pattern) != len(msgs) { + if mustMatchAllPatterns(pattern) && len(pattern) != len(msgs) { return false } if _, ok := ctx.State[KeyPattern]; !ok { From c1bd251ba7f7be28aba77e8ca885c732fa009cdd Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 17:21:59 +0800 Subject: [PATCH 32/49] make `PatternParse` fields private remove Valid field (replaced with value!=nil) --- pattern.go | 61 +++++++++++++++++++++---------------------------- pattern_test.go | 28 +++++++++++------------ 2 files changed, 40 insertions(+), 49 deletions(-) diff --git a/pattern.go b/pattern.go index cfa3174..e510c16 100644 --- a/pattern.go +++ b/pattern.go @@ -1,11 +1,10 @@ package zero import ( + "github.com/wdvxdr1123/ZeroBot/message" "regexp" "strconv" "strings" - - "github.com/wdvxdr1123/ZeroBot/message" ) const ( @@ -71,34 +70,33 @@ func (p *Pattern) SetOptional(v ...bool) *Pattern { // PatternParsed PatternRule parse result type PatternParsed struct { - Valid bool - Value any - Msg *message.Segment + value any + msg *message.Segment } func (p PatternParsed) Text() []string { - if !p.Valid { + if p.value == nil { return nil } - return p.Value.([]string) + return p.value.([]string) } func (p PatternParsed) At() string { - if !p.Valid { + if p.value == nil { return "" } - return p.Value.(string) + return p.value.(string) } func (p PatternParsed) Image() string { - if !p.Valid { + if p.value == nil { return "" } - return p.Value.(string) + return p.value.(string) } func (p PatternParsed) Reply() string { - if !p.Valid { + if p.value == nil { return "" } - return p.Value.(string) + return p.value.(string) } // Text use regex to search a 'text' segment @@ -112,16 +110,14 @@ func (p *Pattern) Text(regex string) *Pattern { matchString := re.MatchString(s) if matchString { return &PatternParsed{ - Valid: true, - Value: re.FindStringSubmatch(s), - Msg: msg, + value: re.FindStringSubmatch(s), + msg: msg, } } return &PatternParsed{ - Valid: false, - Value: nil, - Msg: nil, + value: nil, + msg: nil, } }, } @@ -139,16 +135,14 @@ func (p *Pattern) At(id ...string) *Pattern { Parse: func(msg *message.Segment) *PatternParsed { if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { return &PatternParsed{ - Valid: true, - Value: msg.Data["qq"], - Msg: msg, + value: msg.Data["qq"], + msg: msg, } } return &PatternParsed{ - Valid: false, - Value: nil, - Msg: nil, + value: nil, + msg: nil, } }, } @@ -162,9 +156,8 @@ func (p *Pattern) Image() *Pattern { Type: "image", Parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ - Valid: true, - Value: msg.Data["file"], - Msg: msg, + value: msg.Data["file"], + msg: msg, } }, } @@ -178,9 +171,8 @@ func (p *Pattern) Reply() *Pattern { Type: "reply", Parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ - Valid: true, - Value: msg.Data["id"], - Msg: msg, + value: msg.Data["id"], + msg: msg, } }, } @@ -210,12 +202,11 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { parsed = pattern[i].Parse(&msgs[j]) } else { parsed = &PatternParsed{ - Valid: false, - Value: nil, - Msg: nil, + value: nil, + msg: nil, } } - if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || !parsed.Valid { + if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || parsed.value == nil { if pattern[i].Optional { ctx.State[KeyPattern] = append(ctx.State[KeyPattern].([]*PatternParsed), parsed) i++ diff --git a/pattern_test.go b/pattern_test.go index 1d2137f..8300cd4 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -122,10 +122,10 @@ func TestPattern_Reply(t *testing.T) { } } func TestPatternParsed_Gets(t *testing.T) { - assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, Value: []string{"gaga"}}.Text()) - assert.Equal(t, "image", PatternParsed{Valid: true, Value: "image"}.Image()) - assert.Equal(t, "reply", PatternParsed{Valid: true, Value: "reply"}.Reply()) - assert.Equal(t, "114514", PatternParsed{Valid: true, Value: "114514"}.At()) + assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, value: []string{"gaga"}}.Text()) + assert.Equal(t, "image", PatternParsed{Valid: true, value: "image"}.Image()) + assert.Equal(t, "reply", PatternParsed{Valid: true, value: "reply"}.Reply()) + assert.Equal(t, "114514", PatternParsed{Valid: true, value: "114514"}.At()) } func TestPattern_SetOptional(t *testing.T) { assert.Panics(t, func() { @@ -199,39 +199,39 @@ func TestAllParse(t *testing.T) { {[]message.Segment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{ { Valid: true, - Value: []string{"haha", "haha", "ha"}, + value: []string{"haha", "haha", "ha"}, }, { Valid: true, - Value: "123", + value: "123", }, }}, {[]message.Segment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ { Valid: true, - Value: []string{"haha", "h", "a", "h", "a"}, + value: []string{"haha", "h", "a", "h", "a"}, }, }}, {[]message.Segment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ { Valid: true, - Value: "fake reply", + value: "fake reply", }, { Valid: true, - Value: "fake image", + value: "fake image", }, { Valid: true, - Value: "999", + value: "999", }, { Valid: true, - Value: "124", + value: "124", }, { Valid: true, - Value: []string{"haha", "h", "a", "h", "a"}, + value: []string{"haha", "h", "a", "h", "a"}, }, }}, } @@ -248,8 +248,8 @@ func TestAllParse(t *testing.T) { assert.Equal(t, true, matched) for i := range parsed.Matched { assert.Equal(t, v.expected[i].Valid, parsed.Matched[i].Valid) - assert.Equal(t, v.expected[i].Value, parsed.Matched[i].Value) - assert.Equal(t, &(v.msg[i]), parsed.Matched[i].Msg) + assert.Equal(t, v.expected[i].value, parsed.Matched[i].value) + assert.Equal(t, &(v.msg[i]), parsed.Matched[i].msg) } }) } From 8b775507539c6f1562ecbb4125d01f76711458a6 Mon Sep 17 00:00:00 2001 From: RikaCelery <94585272+RikaCelery@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:57:08 +0800 Subject: [PATCH 33/49] optimize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- pattern.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pattern.go b/pattern.go index e0e0074..4d571db 100644 --- a/pattern.go +++ b/pattern.go @@ -25,18 +25,11 @@ func (p *Pattern) AsRule() Rule { if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) - if reply.MessageID.ID() == 0 || reply.Sender == nil || reply.Sender.ID == 0 { - // failed to get history message - msgs = append(msgs, ctx.Event.Message[i]) + if reply.MessageID.ID() != 0 && reply.Sender != nil && reply.Sender.ID != 0 && strconv.FormatInt(reply.Sender.ID, 10) == ctx.Event.Message[i].Data["qq"] { continue } - if strconv.FormatInt(reply.Sender.ID, 10) != ctx.Event.Message[i].Data["qq"] { - // @ other user in reply - msgs = append(msgs, ctx.Event.Message[i]) - } - } else { - msgs = append(msgs, ctx.Event.Message[i]) } + msgs = append(msgs, ctx.Event.Message[i]) } return patternMatch(ctx, *p, msgs) } From 0f48fe724c1e9e3b44621001d264b73bfc55f529 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 17:55:48 +0800 Subject: [PATCH 34/49] optimize --- pattern.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pattern.go b/pattern.go index 29c26f1..ab0918c 100644 --- a/pattern.go +++ b/pattern.go @@ -191,9 +191,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { if mustMatchAllPatterns(pattern) && len(pattern) != len(msgs) { return false } - if _, ok := ctx.State[KeyPattern]; !ok { - ctx.State[KeyPattern] = make([]*PatternParsed, 0, 4) - } + patternState := make([]*PatternParsed, 0, 4) i := 0 j := 0 for i < len(pattern) { @@ -208,15 +206,16 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { } if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || parsed.value == nil { if pattern[i].Optional { - ctx.State[KeyPattern] = append(ctx.State[KeyPattern].([]*PatternParsed), parsed) + patternState = append(patternState, parsed) i++ continue } return false } - ctx.State[KeyPattern] = append(ctx.State[KeyPattern].([]*PatternParsed), parsed) + patternState = append(patternState, parsed) i++ j++ } + ctx.State[KeyPattern] = patternState return true } From 4b95413e779fccc2943b87ff94ff49e29e965655 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 18:01:17 +0800 Subject: [PATCH 35/49] PatternSegment: make `Type` and `Parse` private --- pattern.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/pattern.go b/pattern.go index ab0918c..2d4fde2 100644 --- a/pattern.go +++ b/pattern.go @@ -49,9 +49,9 @@ func NewPattern() *Pattern { } type PatternSegment struct { - Type string + typ string Optional bool - Parse func(msg *message.Segment) *PatternParsed + parse func(msg *message.Segment) *PatternParsed } // SetOptional set previous segment is optional, is v is empty, Optional will be true @@ -103,8 +103,8 @@ func (p PatternParsed) Reply() string { func (p *Pattern) Text(regex string) *Pattern { re := regexp.MustCompile(regex) pattern := PatternSegment{ - Type: "text", - Parse: func(msg *message.Segment) *PatternParsed { + typ: "text", + parse: func(msg *message.Segment) *PatternParsed { s := msg.Data["text"] s = strings.Trim(s, " \n\r\t") matchString := re.MatchString(s) @@ -131,8 +131,8 @@ func (p *Pattern) At(id ...string) *Pattern { panic("at pattern only support one id") } pattern := PatternSegment{ - Type: "at", - Parse: func(msg *message.Segment) *PatternParsed { + typ: "at", + parse: func(msg *message.Segment) *PatternParsed { if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { return &PatternParsed{ value: msg.Data["qq"], @@ -153,8 +153,8 @@ func (p *Pattern) At(id ...string) *Pattern { // Image use regex to match an 'at' segment, if id is not empty, only match specific target func (p *Pattern) Image() *Pattern { pattern := PatternSegment{ - Type: "image", - Parse: func(msg *message.Segment) *PatternParsed { + typ: "image", + parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ value: msg.Data["file"], msg: msg, @@ -168,8 +168,8 @@ func (p *Pattern) Image() *Pattern { // Reply type zero.PatternReplyMatched func (p *Pattern) Reply() *Pattern { pattern := PatternSegment{ - Type: "reply", - Parse: func(msg *message.Segment) *PatternParsed { + typ: "reply", + parse: func(msg *message.Segment) *PatternParsed { return &PatternParsed{ value: msg.Data["id"], msg: msg, @@ -196,15 +196,15 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { j := 0 for i < len(pattern) { var parsed *PatternParsed - if j < len(msgs) && pattern[i].Type == (msgs[j].Type) { - parsed = pattern[i].Parse(&msgs[j]) + if j < len(msgs) && pattern[i].typ == (msgs[j].Type) { + parsed = pattern[i].parse(&msgs[j]) } else { parsed = &PatternParsed{ value: nil, msg: nil, } } - if j >= len(msgs) || pattern[i].Type != (msgs[j].Type) || parsed.value == nil { + if j >= len(msgs) || pattern[i].typ != (msgs[j].Type) || parsed.value == nil { if pattern[i].Optional { patternState = append(patternState, parsed) i++ From 78ce239b4a3ef89aef175cb13288b5e82e386cda Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 18:19:02 +0800 Subject: [PATCH 36/49] fixup! rename `containsOptional` to `mustMatchAllPatterns` --- pattern.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pattern.go b/pattern.go index 9e94f8f..421dcba 100644 --- a/pattern.go +++ b/pattern.go @@ -24,7 +24,7 @@ func (p *Pattern) AsRule() Rule { if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) - if reply.MessageID.ID() != 0 && reply.Sender != nil && reply.Sender.ID != 0 && strconv.FormatInt(reply.Sender.ID, 10) == ctx.Event.Message[i].Data["qq"] { + if reply.MessageID.ID() != 0 && reply.Sender != nil && reply.Sender.ID != 0 && strconv.FormatInt(reply.Sender.ID, 10) == ctx.Event.Message[i].Data["qq"] { continue } } @@ -174,7 +174,7 @@ func (p *Pattern) Reply() *Pattern { } func mustMatchAllPatterns(pattern Pattern) bool { for _, p := range pattern { - if !p.Optional { + if p.Optional { return false } } From 455afde5a6ac9bc93fc6bb01bf8ac44086eb76de Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 18:20:04 +0800 Subject: [PATCH 37/49] make lint happy --- pattern_test.go | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/pattern_test.go b/pattern_test.go index 8300cd4..8d20958 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -47,7 +47,7 @@ func TestPattern_Text(t *testing.T) { for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() out := rule(ctx) assert.Equal(t, out, v.expected) }) @@ -71,9 +71,9 @@ func TestPattern_Image(t *testing.T) { for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() out := rule(ctx) - assert.Equal(t, out, v.expected) + assert.Equal(t, v.expected, out) }) } } @@ -92,7 +92,7 @@ func TestPattern_At(t *testing.T) { for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() out := rule(ctx) assert.Equal(t, out, v.expected) }) @@ -115,17 +115,17 @@ func TestPattern_Reply(t *testing.T) { for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() out := rule(ctx) assert.Equal(t, out, v.expected) }) } } func TestPatternParsed_Gets(t *testing.T) { - assert.Equal(t, []string{"gaga"}, PatternParsed{Valid: true, value: []string{"gaga"}}.Text()) - assert.Equal(t, "image", PatternParsed{Valid: true, value: "image"}.Image()) - assert.Equal(t, "reply", PatternParsed{Valid: true, value: "reply"}.Reply()) - assert.Equal(t, "114514", PatternParsed{Valid: true, value: "114514"}.At()) + assert.Equal(t, []string{"gaga"}, PatternParsed{value: []string{"gaga"}}.Text()) + assert.Equal(t, "image", PatternParsed{value: "image"}.Image()) + assert.Equal(t, "reply", PatternParsed{value: "reply"}.Reply()) + assert.Equal(t, "114514", PatternParsed{value: "114514"}.At()) } func TestPattern_SetOptional(t *testing.T) { assert.Panics(t, func() { @@ -138,39 +138,39 @@ func TestPattern_SetOptional(t *testing.T) { }{ {[]message.Segment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{ { - Valid: true, + value: []string{"/do it", "do", "it"}, }, { - Valid: false, + value: nil, }, }}, {[]message.Segment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(false), []PatternParsed{}}, {[]message.Segment{message.Text("happy bear"), message.At(114514)}, NewPattern().Reply().SetOptional().Text(".+").SetOptional().At().SetOptional(false), []PatternParsed{ { - Valid: false, + value: nil, }, { - Valid: true, + value: "happy bear", }, { - Valid: true, + value: "114514", }, }}, {[]message.Segment{message.Text("happy bear"), message.At(114514)}, NewPattern().Image().SetOptional().Image().SetOptional().Image().SetOptional(), []PatternParsed{ // why you do this { - Valid: false, + value: nil, }, { - Valid: false, + value: nil, }, { - Valid: false, + value: nil, }, }}, } for i, v := range tests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() matched := rule(ctx) if !matched { assert.Equal(t, 0, len(v.expected)) @@ -183,7 +183,7 @@ func TestPattern_SetOptional(t *testing.T) { } assert.Equal(t, len(v.expected), len(parsed.Matched)) for i := range parsed.Matched { - assert.Equal(t, v.expected[i].Valid, parsed.Matched[i].Valid) + assert.Equal(t, v.expected[i].value != nil, parsed.Matched[i].value != nil) } }) } @@ -198,39 +198,31 @@ func TestAllParse(t *testing.T) { }{ {[]message.Segment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{ { - Valid: true, value: []string{"haha", "haha", "ha"}, }, { - Valid: true, value: "123", }, }}, {[]message.Segment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{ { - Valid: true, value: []string{"haha", "h", "a", "h", "a"}, }, }}, {[]message.Segment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ { - Valid: true, value: "fake reply", }, { - Valid: true, value: "fake image", }, { - Valid: true, value: "999", }, { - Valid: true, value: "124", }, { - Valid: true, value: []string{"haha", "h", "a", "h", "a"}, }, }}, @@ -238,7 +230,7 @@ func TestAllParse(t *testing.T) { for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { ctx := fakeCtx(v.msg) - rule := PatternRule(v.pattern) + rule := v.pattern.AsRule() matched := rule(ctx) parsed := &PatternModel{} err := ctx.Parse(parsed) @@ -247,7 +239,6 @@ func TestAllParse(t *testing.T) { } assert.Equal(t, true, matched) for i := range parsed.Matched { - assert.Equal(t, v.expected[i].Valid, parsed.Matched[i].Valid) assert.Equal(t, v.expected[i].value, parsed.Matched[i].value) assert.Equal(t, &(v.msg[i]), parsed.Matched[i].msg) } From f606a7575f3df9b6b6e39068ba386a8278735e3b Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 22:37:54 +0800 Subject: [PATCH 38/49] At Pattern use `message.ID` parameters --- pattern.go | 4 ++-- pattern_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pattern.go b/pattern.go index 421dcba..5ed1811 100644 --- a/pattern.go +++ b/pattern.go @@ -119,14 +119,14 @@ func (p *Pattern) Text(regex string) *Pattern { } // At use regex to match an 'at' segment, if id is not empty, only match specific target -func (p *Pattern) At(id ...string) *Pattern { +func (p *Pattern) At(id ...message.ID) *Pattern { if len(id) > 1 { panic("at pattern only support one id") } pattern := PatternSegment{ typ: "at", parse: func(msg *message.Segment) *PatternParsed { - if len(id) == 0 || len(id) == 1 && id[0] == msg.Data["qq"] { + if len(id) == 0 || len(id) == 1 && id[0].String() == msg.Data["qq"] { return &PatternParsed{ value: msg.Data["qq"], msg: msg, diff --git a/pattern_test.go b/pattern_test.go index 8d20958..aefd3c3 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -87,7 +87,7 @@ func TestPattern_At(t *testing.T) { {[]message.Segment{message.Text("haha")}, NewPattern().At(), false}, {[]message.Segment{message.Image("not a image")}, NewPattern().At(), false}, {[]message.Segment{message.At(114514)}, NewPattern().At(), true}, - {[]message.Segment{message.At(114514)}, NewPattern().At("1919810"), false}, + {[]message.Segment{message.At(114514)}, NewPattern().At(message.NewMessageIDFromString("1919810")), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -208,7 +208,7 @@ func TestAllParse(t *testing.T) { value: []string{"haha", "h", "a", "h", "a"}, }, }}, - {[]message.Segment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At("124").Text("(h)(a)(h)(a)"), []PatternParsed{ + {[]message.Segment{message.Reply("fake reply"), message.Image("fake image"), message.At(999), message.At(124), message.Text("haha")}, NewPattern().Reply().Image().At().At(message.NewMessageIDFromInteger(124)).Text("(h)(a)(h)(a)"), []PatternParsed{ { value: "fake reply", From 6dfbd27e000d425730f6f80dc4748c5d2b6cb99e Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Sun, 13 Oct 2024 22:43:30 +0800 Subject: [PATCH 39/49] PatternSegment: make `optional` private --- pattern.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pattern.go b/pattern.go index 5ed1811..def522c 100644 --- a/pattern.go +++ b/pattern.go @@ -43,20 +43,20 @@ func NewPattern() *Pattern { type PatternSegment struct { typ string - Optional bool + optional bool parse func(msg *message.Segment) *PatternParsed } -// SetOptional set previous segment is optional, is v is empty, Optional will be true +// SetOptional set previous segment is optional, is v is empty, optional will be true // if Pattern is empty, panic func (p *Pattern) SetOptional(v ...bool) *Pattern { if len(*p) == 0 { panic("pattern is empty") } if len(v) == 1 { - (*p)[len(*p)-1].Optional = v[0] + (*p)[len(*p)-1].optional = v[0] } else { - (*p)[len(*p)-1].Optional = true + (*p)[len(*p)-1].optional = true } return p } @@ -174,7 +174,7 @@ func (p *Pattern) Reply() *Pattern { } func mustMatchAllPatterns(pattern Pattern) bool { for _, p := range pattern { - if p.Optional { + if p.optional { return false } } @@ -198,7 +198,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { } } if j >= len(msgs) || pattern[i].typ != (msgs[j].Type) || parsed.value == nil { - if pattern[i].Optional { + if pattern[i].optional { patternState = append(patternState, parsed) i++ continue From 370fda4165df72007ce944c3c578ca9cd980c3f8 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 00:21:35 +0800 Subject: [PATCH 40/49] `Raw` gatter --- pattern.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pattern.go b/pattern.go index def522c..0e8530a 100644 --- a/pattern.go +++ b/pattern.go @@ -92,6 +92,11 @@ func (p PatternParsed) Reply() string { return p.value.(string) } +// Raw 获取原始消息 +func (p PatternParsed) Raw() *message.Segment { + return p.msg +} + // Text use regex to search a 'text' segment func (p *Pattern) Text(regex string) *Pattern { re := regexp.MustCompile(regex) From 18dbda79d9f382666a6cbf5ca80d365a7e93371b Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 00:22:22 +0800 Subject: [PATCH 41/49] `PatternSegment` builder --- pattern.go | 55 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/pattern.go b/pattern.go index 0e8530a..efb94c8 100644 --- a/pattern.go +++ b/pattern.go @@ -67,24 +67,31 @@ type PatternParsed struct { msg *message.Segment } +// Text 获取正则表达式匹配到的文本数组 func (p PatternParsed) Text() []string { if p.value == nil { return nil } return p.value.([]string) } + +// At 获取被@者ID func (p PatternParsed) At() string { if p.value == nil { return "" } return p.value.(string) } + +// Image 获取图片URL func (p PatternParsed) Image() string { if p.value == nil { return "" } return p.value.(string) } + +// Reply 获取被回复的消息ID func (p PatternParsed) Reply() string { if p.value == nil { return "" @@ -97,12 +104,19 @@ func (p PatternParsed) Raw() *message.Segment { return p.msg } +func NewPatternSegment(typ string, optional bool, parse func(msg *message.Segment) *PatternParsed) *PatternSegment { + return &PatternSegment{ + typ: typ, + optional: optional, + parse: parse, + } +} + // Text use regex to search a 'text' segment func (p *Pattern) Text(regex string) *Pattern { re := regexp.MustCompile(regex) - pattern := PatternSegment{ - typ: "text", - parse: func(msg *message.Segment) *PatternParsed { + pattern := NewPatternSegment( + "text", false, func(msg *message.Segment) *PatternParsed { s := msg.Data["text"] s = strings.Trim(s, " \n\r\t") matchString := re.MatchString(s) @@ -118,8 +132,8 @@ func (p *Pattern) Text(regex string) *Pattern { msg: nil, } }, - } - *p = append(*p, pattern) + ) + *p = append(*p, *pattern) return p } @@ -128,9 +142,8 @@ func (p *Pattern) At(id ...message.ID) *Pattern { if len(id) > 1 { panic("at pattern only support one id") } - pattern := PatternSegment{ - typ: "at", - parse: func(msg *message.Segment) *PatternParsed { + pattern := NewPatternSegment( + "at", false, func(msg *message.Segment) *PatternParsed { if len(id) == 0 || len(id) == 1 && id[0].String() == msg.Data["qq"] { return &PatternParsed{ value: msg.Data["qq"], @@ -143,38 +156,36 @@ func (p *Pattern) At(id ...message.ID) *Pattern { msg: nil, } }, - } - *p = append(*p, pattern) + ) + *p = append(*p, *pattern) return p } // Image use regex to match an 'at' segment, if id is not empty, only match specific target func (p *Pattern) Image() *Pattern { - pattern := PatternSegment{ - typ: "image", - parse: func(msg *message.Segment) *PatternParsed { + pattern := NewPatternSegment( + "image", false, func(msg *message.Segment) *PatternParsed { return &PatternParsed{ value: msg.Data["file"], msg: msg, } }, - } - *p = append(*p, pattern) + ) + *p = append(*p, *pattern) return p } // Reply type zero.PatternReplyMatched func (p *Pattern) Reply() *Pattern { - pattern := PatternSegment{ - typ: "reply", - parse: func(msg *message.Segment) *PatternParsed { + pattern := NewPatternSegment( + "reply", false, func(msg *message.Segment) *PatternParsed { return &PatternParsed{ value: msg.Data["id"], msg: msg, } }, - } - *p = append(*p, pattern) + ) + *p = append(*p, *pattern) return p } func mustMatchAllPatterns(pattern Pattern) bool { @@ -194,7 +205,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { j := 0 for i < len(pattern) { var parsed *PatternParsed - if j < len(msgs) && pattern[i].typ == (msgs[j].Type) { + if j < len(msgs) && pattern[i].matchType(msgs[j]) { parsed = pattern[i].parse(&msgs[j]) } else { parsed = &PatternParsed{ @@ -202,7 +213,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { msg: nil, } } - if j >= len(msgs) || pattern[i].typ != (msgs[j].Type) || parsed.value == nil { + if j >= len(msgs) || !pattern[i].matchType(msgs[j]) || parsed.value == nil { if pattern[i].optional { patternState = append(patternState, parsed) i++ From 2a96084e008a3dc6c656c46aef353a7b61517c7d Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 00:24:06 +0800 Subject: [PATCH 42/49] `Any` PatternSegment --- pattern.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pattern.go b/pattern.go index efb94c8..e2b0528 100644 --- a/pattern.go +++ b/pattern.go @@ -188,6 +188,37 @@ func (p *Pattern) Reply() *Pattern { *p = append(*p, *pattern) return p } + +// Any match any segment +func (p *Pattern) Any() *Pattern { + pattern := NewPatternSegment( + "any", false, func(msg *message.Segment) *PatternParsed { + parsed := PatternParsed{ + value: nil, + msg: msg, + } + switch { + case msg.Data["text"] != "": + parsed.value = msg.Data["text"] + case msg.Data["qq"] != "": + parsed.value = msg.Data["qq"] + case msg.Data["file"] != "": + parsed.value = msg.Data["file"] + case msg.Data["id"] != "": + parsed.value = msg.Data["id"] + default: + parsed.value = msg.Data + } + return &parsed + }, + ) + *p = append(*p, *pattern) + return p +} + +func (s *PatternSegment) matchType(msg message.Segment) bool { + return s.typ == msg.Type || s.typ == "any" +} func mustMatchAllPatterns(pattern Pattern) bool { for _, p := range pattern { if p.optional { From 6ac420971aa8e9b1fe4c69f1f01ee48ecc27403c Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 00:32:41 +0800 Subject: [PATCH 43/49] tests for `Any` --- pattern_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pattern_test.go b/pattern_test.go index aefd3c3..8cc8473 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -121,11 +121,44 @@ func TestPattern_Reply(t *testing.T) { }) } } +func TestPattern_Any(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.Segment{message.Text("haha")}, NewPattern().Any(), true}, + {[]message.Segment{message.Image("not a image")}, NewPattern().Any(), true}, + {[]message.Segment{message.At(1919810), message.Reply(12345)}, NewPattern().Any().Reply(), true}, + {[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern().Any().At(), true}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := v.pattern.AsRule() + out := rule(ctx) + assert.Equal(t, out, v.expected) + }) + } + t.Run("get", func(t *testing.T) { + ctx := fakeCtx([]message.Segment{message.Reply("just for test")}) + rule := NewPattern().Any().AsRule() + _ = rule(ctx) + model := PatternModel{} + err := ctx.Parse(&model) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "just for test", model.Matched[0].Reply()) + }) +} func TestPatternParsed_Gets(t *testing.T) { assert.Equal(t, []string{"gaga"}, PatternParsed{value: []string{"gaga"}}.Text()) assert.Equal(t, "image", PatternParsed{value: "image"}.Image()) assert.Equal(t, "reply", PatternParsed{value: "reply"}.Reply()) assert.Equal(t, "114514", PatternParsed{value: "114514"}.At()) + text := message.Text("1234") + assert.Equal(t, &text, PatternParsed{msg: &text}.Raw()) } func TestPattern_SetOptional(t *testing.T) { assert.Panics(t, func() { From 7bbb37bc53a05228ca56ad1b2565bb3ec0c9bf92 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 00:57:13 +0800 Subject: [PATCH 44/49] option for cleaning useless sibling `at` after `reply` --- pattern.go | 38 +++++++++++++++++++++++++++++--------- pattern_test.go | 20 +++++++++++++++++++- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/pattern.go b/pattern.go index e2b0528..0597d28 100644 --- a/pattern.go +++ b/pattern.go @@ -20,7 +20,17 @@ func (p *Pattern) AsRule() Rule { // copy messages msgs := make([]message.Segment, 0, len(ctx.Event.Message)) msgs = append(msgs, ctx.Event.Message[0]) + shouldClean := false + for _, segment := range *p { + if segment.cleanRedundantAt { + shouldClean = true + break + } + } for i := 1; i < len(ctx.Event.Message); i++ { + if !shouldClean { + continue + } if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) @@ -42,9 +52,10 @@ func NewPattern() *Pattern { } type PatternSegment struct { - typ string - optional bool - parse func(msg *message.Segment) *PatternParsed + typ string + optional bool + parse func(msg *message.Segment) *PatternParsed + cleanRedundantAt bool // only for Reply } // SetOptional set previous segment is optional, is v is empty, optional will be true @@ -104,11 +115,16 @@ func (p PatternParsed) Raw() *message.Segment { return p.msg } -func NewPatternSegment(typ string, optional bool, parse func(msg *message.Segment) *PatternParsed) *PatternSegment { +func NewPatternSegment(typ string, optional bool, parse func(msg *message.Segment) *PatternParsed, cleanRedundantAt ...bool) *PatternSegment { + clean := false + if len(cleanRedundantAt) > 0 { + clean = cleanRedundantAt[0] + } return &PatternSegment{ - typ: typ, - optional: optional, - parse: parse, + typ: typ, + optional: optional, + parse: parse, + cleanRedundantAt: clean, } } @@ -176,14 +192,18 @@ func (p *Pattern) Image() *Pattern { } // Reply type zero.PatternReplyMatched -func (p *Pattern) Reply() *Pattern { +func (p *Pattern) Reply(noCleanRedundantAt ...bool) *Pattern { + noClean := false + if len(noCleanRedundantAt) > 0 { + noClean = noCleanRedundantAt[0] + } pattern := NewPatternSegment( "reply", false, func(msg *message.Segment) *PatternParsed { return &PatternParsed{ value: msg.Data["id"], msg: msg, } - }, + }, !noClean, ) *p = append(*p, *pattern) return p diff --git a/pattern_test.go b/pattern_test.go index 8cc8473..668c189 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -13,7 +13,7 @@ type mockAPICaller struct{} func (m mockAPICaller) CallAPI(_ APIRequest) (APIResponse, error) { return APIResponse{ Status: "", - Data: gjson.Result{}, + Data: gjson.Parse(`{"message_id":"12345","sender":{"user_id":12345}}`), // just for reply cleaner Msg: "", Wording: "", RetCode: 0, @@ -121,6 +121,24 @@ func TestPattern_Reply(t *testing.T) { }) } } +func TestPattern_ReplyFilter(t *testing.T) { + textTests := [...]struct { + msg message.Message + pattern *Pattern + expected bool + }{ + {[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern().Reply().Text("1234"), true}, + {[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern().Reply(true).Text("1234"), false}, + } + for i, v := range textTests { + t.Run(strconv.Itoa(i), func(t *testing.T) { + ctx := fakeCtx(v.msg) + rule := v.pattern.AsRule() + out := rule(ctx) + assert.Equal(t, v.expected, out) + }) + } +} func TestPattern_Any(t *testing.T) { textTests := [...]struct { msg message.Message From 6b486c1c8f380eaea25103764c9e944f67fe953f Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 01:11:33 +0800 Subject: [PATCH 45/49] fix wrong clean logic --- pattern.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pattern.go b/pattern.go index 0597d28..be7d641 100644 --- a/pattern.go +++ b/pattern.go @@ -1,10 +1,11 @@ package zero import ( - "github.com/wdvxdr1123/ZeroBot/message" "regexp" "strconv" "strings" + + "github.com/wdvxdr1123/ZeroBot/message" ) const ( @@ -28,10 +29,7 @@ func (p *Pattern) AsRule() Rule { } } for i := 1; i < len(ctx.Event.Message); i++ { - if !shouldClean { - continue - } - if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { + if shouldClean && ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) if reply.MessageID.ID() != 0 && reply.Sender != nil && reply.Sender.ID != 0 && strconv.FormatInt(reply.Sender.ID, 10) == ctx.Event.Message[i].Data["qq"] { From 20775803a072c75a44ae67fc065553d44268a47e Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 08:33:22 +0800 Subject: [PATCH 46/49] refactor: extract parser, custom parser support --- pattern.go | 157 ++++++++++++++++++++++++++++------------------------- 1 file changed, 83 insertions(+), 74 deletions(-) diff --git a/pattern.go b/pattern.go index be7d641..f356b86 100644 --- a/pattern.go +++ b/pattern.go @@ -56,6 +56,8 @@ type PatternSegment struct { cleanRedundantAt bool // only for Reply } +type Parser func(msg *message.Segment) *PatternParsed + // SetOptional set previous segment is optional, is v is empty, optional will be true // if Pattern is empty, panic func (p *Pattern) SetOptional(v ...bool) *Pattern { @@ -113,42 +115,45 @@ func (p PatternParsed) Raw() *message.Segment { return p.msg } -func NewPatternSegment(typ string, optional bool, parse func(msg *message.Segment) *PatternParsed, cleanRedundantAt ...bool) *PatternSegment { +func (p *Pattern) Add(typ string, optional bool, parse Parser, cleanRedundantAt ...bool) *Pattern { clean := false if len(cleanRedundantAt) > 0 { clean = cleanRedundantAt[0] } - return &PatternSegment{ + pattern := &PatternSegment{ typ: typ, optional: optional, parse: parse, cleanRedundantAt: clean, } + *p = append(*p, *pattern) + return p } // Text use regex to search a 'text' segment func (p *Pattern) Text(regex string) *Pattern { - re := regexp.MustCompile(regex) - pattern := NewPatternSegment( - "text", false, func(msg *message.Segment) *PatternParsed { - s := msg.Data["text"] - s = strings.Trim(s, " \n\r\t") - matchString := re.MatchString(s) - if matchString { - return &PatternParsed{ - value: re.FindStringSubmatch(s), - msg: msg, - } - } + p.Add("text", false, NewTextParser(regex)) + return p +} +func NewTextParser(regex string) Parser { + re := regexp.MustCompile(regex) + return func(msg *message.Segment) *PatternParsed { + s := msg.Data["text"] + s = strings.Trim(s, " \n\r\t") + matchString := re.MatchString(s) + if matchString { return &PatternParsed{ - value: nil, - msg: nil, + value: re.FindStringSubmatch(s), + msg: msg, } - }, - ) - *p = append(*p, *pattern) - return p + } + + return &PatternParsed{ + value: nil, + msg: nil, + } + } } // At use regex to match an 'at' segment, if id is not empty, only match specific target @@ -156,84 +161,88 @@ func (p *Pattern) At(id ...message.ID) *Pattern { if len(id) > 1 { panic("at pattern only support one id") } - pattern := NewPatternSegment( - "at", false, func(msg *message.Segment) *PatternParsed { - if len(id) == 0 || len(id) == 1 && id[0].String() == msg.Data["qq"] { - return &PatternParsed{ - value: msg.Data["qq"], - msg: msg, - } - } + p.Add("at", false, NewAtParser(id...)) + return p +} +func NewAtParser(id ...message.ID) Parser { + return func(msg *message.Segment) *PatternParsed { + if len(id) == 0 || len(id) == 1 && id[0].String() == msg.Data["qq"] { return &PatternParsed{ - value: nil, - msg: nil, + value: msg.Data["qq"], + msg: msg, } - }, - ) - *p = append(*p, *pattern) - return p + } + + return &PatternParsed{ + value: nil, + msg: nil, + } + } } // Image use regex to match an 'at' segment, if id is not empty, only match specific target func (p *Pattern) Image() *Pattern { - pattern := NewPatternSegment( - "image", false, func(msg *message.Segment) *PatternParsed { - return &PatternParsed{ - value: msg.Data["file"], - msg: msg, - } - }, - ) - *p = append(*p, *pattern) + p.Add("image", false, NewImageParser()) return p } +func NewImageParser() Parser { + return func(msg *message.Segment) *PatternParsed { + return &PatternParsed{ + value: msg.Data["file"], + msg: msg, + } + } +} + // Reply type zero.PatternReplyMatched func (p *Pattern) Reply(noCleanRedundantAt ...bool) *Pattern { noClean := false if len(noCleanRedundantAt) > 0 { noClean = noCleanRedundantAt[0] } - pattern := NewPatternSegment( - "reply", false, func(msg *message.Segment) *PatternParsed { - return &PatternParsed{ - value: msg.Data["id"], - msg: msg, - } - }, !noClean, - ) - *p = append(*p, *pattern) + p.Add("reply", false, NewReplyParser(), !noClean) return p } +func NewReplyParser() Parser { + return func(msg *message.Segment) *PatternParsed { + return &PatternParsed{ + value: msg.Data["id"], + msg: msg, + } + } +} + // Any match any segment func (p *Pattern) Any() *Pattern { - pattern := NewPatternSegment( - "any", false, func(msg *message.Segment) *PatternParsed { - parsed := PatternParsed{ - value: nil, - msg: msg, - } - switch { - case msg.Data["text"] != "": - parsed.value = msg.Data["text"] - case msg.Data["qq"] != "": - parsed.value = msg.Data["qq"] - case msg.Data["file"] != "": - parsed.value = msg.Data["file"] - case msg.Data["id"] != "": - parsed.value = msg.Data["id"] - default: - parsed.value = msg.Data - } - return &parsed - }, - ) - *p = append(*p, *pattern) + p.Add("any", false, NewAnyParser()) return p } +func NewAnyParser() Parser { + return func(msg *message.Segment) *PatternParsed { + parsed := PatternParsed{ + value: nil, + msg: msg, + } + switch { + case msg.Data["text"] != "": + parsed.value = msg.Data["text"] + case msg.Data["qq"] != "": + parsed.value = msg.Data["qq"] + case msg.Data["file"] != "": + parsed.value = msg.Data["file"] + case msg.Data["id"] != "": + parsed.value = msg.Data["id"] + default: + parsed.value = msg.Data + } + return &parsed + } +} + func (s *PatternSegment) matchType(msg message.Segment) bool { return s.typ == msg.Type || s.typ == "any" } From dbe0b4dfe62ed05cfc462874ded15e5dd77a6be1 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 15:54:41 +0800 Subject: [PATCH 47/49] refactor: make `cleanRedundantAt` an option of `Pattern` better code structure --- pattern.go | 100 +++++++++++++++++++++++------------------------------ 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/pattern.go b/pattern.go index f356b86..dcd688f 100644 --- a/pattern.go +++ b/pattern.go @@ -18,18 +18,15 @@ func (p *Pattern) AsRule() Rule { if len(ctx.Event.Message) == 0 { return false } + if !p.cleanRedundantAt { + return patternMatch(ctx, *p, ctx.Event.Message) + } + // copy messages msgs := make([]message.Segment, 0, len(ctx.Event.Message)) msgs = append(msgs, ctx.Event.Message[0]) - shouldClean := false - for _, segment := range *p { - if segment.cleanRedundantAt { - shouldClean = true - break - } - } for i := 1; i < len(ctx.Event.Message); i++ { - if shouldClean && ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { + if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" { // [reply][at] reply := ctx.GetMessage(ctx.Event.Message[i-1].Data["id"]) if reply.MessageID.ID() != 0 && reply.Sender != nil && reply.Sender.ID != 0 && strconv.FormatInt(reply.Sender.ID, 10) == ctx.Event.Message[i].Data["qq"] { @@ -42,32 +39,42 @@ func (p *Pattern) AsRule() Rule { } } -type Pattern []PatternSegment +type Pattern struct { + cleanRedundantAt bool + segments []PatternSegment +} -func NewPattern() *Pattern { - pattern := make(Pattern, 0, 4) +func NewPattern(cleanRedundantAt ...bool) *Pattern { + clean := false + if len(cleanRedundantAt) > 0 { + clean = cleanRedundantAt[0] + } + pattern := Pattern{ + cleanRedundantAt: clean, + segments: make([]PatternSegment, 0, 4), + } return &pattern } type PatternSegment struct { typ string optional bool - parse func(msg *message.Segment) *PatternParsed + parse Parser cleanRedundantAt bool // only for Reply } -type Parser func(msg *message.Segment) *PatternParsed +type Parser func(msg *message.Segment) PatternParsed // SetOptional set previous segment is optional, is v is empty, optional will be true // if Pattern is empty, panic func (p *Pattern) SetOptional(v ...bool) *Pattern { - if len(*p) == 0 { + if len((*p).segments) == 0 { panic("pattern is empty") } if len(v) == 1 { - (*p)[len(*p)-1].optional = v[0] + (*p).segments[len((*p).segments)-1].optional = v[0] } else { - (*p)[len(*p)-1].optional = true + (*p).segments[len((*p).segments)-1].optional = true } return p } @@ -126,7 +133,7 @@ func (p *Pattern) Add(typ string, optional bool, parse Parser, cleanRedundantAt parse: parse, cleanRedundantAt: clean, } - *p = append(*p, *pattern) + p.segments = append(p.segments, *pattern) return p } @@ -138,21 +145,18 @@ func (p *Pattern) Text(regex string) *Pattern { func NewTextParser(regex string) Parser { re := regexp.MustCompile(regex) - return func(msg *message.Segment) *PatternParsed { + return func(msg *message.Segment) PatternParsed { s := msg.Data["text"] s = strings.Trim(s, " \n\r\t") matchString := re.MatchString(s) if matchString { - return &PatternParsed{ + return PatternParsed{ value: re.FindStringSubmatch(s), msg: msg, } } - return &PatternParsed{ - value: nil, - msg: nil, - } + return PatternParsed{} } } @@ -166,18 +170,14 @@ func (p *Pattern) At(id ...message.ID) *Pattern { } func NewAtParser(id ...message.ID) Parser { - return func(msg *message.Segment) *PatternParsed { + return func(msg *message.Segment) PatternParsed { if len(id) == 0 || len(id) == 1 && id[0].String() == msg.Data["qq"] { - return &PatternParsed{ + return PatternParsed{ value: msg.Data["qq"], msg: msg, } } - - return &PatternParsed{ - value: nil, - msg: nil, - } + return PatternParsed{} } } @@ -188,8 +188,8 @@ func (p *Pattern) Image() *Pattern { } func NewImageParser() Parser { - return func(msg *message.Segment) *PatternParsed { - return &PatternParsed{ + return func(msg *message.Segment) PatternParsed { + return PatternParsed{ value: msg.Data["file"], msg: msg, } @@ -207,8 +207,8 @@ func (p *Pattern) Reply(noCleanRedundantAt ...bool) *Pattern { } func NewReplyParser() Parser { - return func(msg *message.Segment) *PatternParsed { - return &PatternParsed{ + return func(msg *message.Segment) PatternParsed { + return PatternParsed{ value: msg.Data["id"], msg: msg, } @@ -222,7 +222,7 @@ func (p *Pattern) Any() *Pattern { } func NewAnyParser() Parser { - return func(msg *message.Segment) *PatternParsed { + return func(msg *message.Segment) PatternParsed { parsed := PatternParsed{ value: nil, msg: msg, @@ -239,7 +239,7 @@ func NewAnyParser() Parser { default: parsed.value = msg.Data } - return &parsed + return parsed } } @@ -247,7 +247,7 @@ func (s *PatternSegment) matchType(msg message.Segment) bool { return s.typ == msg.Type || s.typ == "any" } func mustMatchAllPatterns(pattern Pattern) bool { - for _, p := range pattern { + for _, p := range pattern.segments { if p.optional { return false } @@ -255,33 +255,21 @@ func mustMatchAllPatterns(pattern Pattern) bool { return true } func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { - if mustMatchAllPatterns(pattern) && len(pattern) != len(msgs) { + if mustMatchAllPatterns(pattern) && len(pattern.segments) != len(msgs) { return false } - patternState := make([]*PatternParsed, 0, 4) - i := 0 + patternState := make([]PatternParsed, len(pattern.segments)) + j := 0 - for i < len(pattern) { - var parsed *PatternParsed - if j < len(msgs) && pattern[i].matchType(msgs[j]) { - parsed = pattern[i].parse(&msgs[j]) + for i := 0; i < len(pattern.segments); i++ { + if j < len(msgs) && pattern.segments[i].matchType(msgs[j]) { + patternState[i] = pattern.segments[i].parse(&msgs[j]) } else { - parsed = &PatternParsed{ - value: nil, - msg: nil, - } - } - if j >= len(msgs) || !pattern[i].matchType(msgs[j]) || parsed.value == nil { - if pattern[i].optional { - patternState = append(patternState, parsed) - i++ + if pattern.segments[i].optional { continue } return false } - patternState = append(patternState, parsed) - i++ - j++ } ctx.State[KeyPattern] = patternState return true From df7234b3dc8dd5fc8ea42ee6c15552a2c320995e Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 16:19:29 +0800 Subject: [PATCH 48/49] fix: fix stupid bugs refactor: remove useless args make lint happy --- pattern.go | 40 ++++++++++++++++------------------------ pattern_test.go | 10 +++++++--- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/pattern.go b/pattern.go index dcd688f..ceacdd4 100644 --- a/pattern.go +++ b/pattern.go @@ -45,7 +45,7 @@ type Pattern struct { } func NewPattern(cleanRedundantAt ...bool) *Pattern { - clean := false + clean := true if len(cleanRedundantAt) > 0 { clean = cleanRedundantAt[0] } @@ -57,10 +57,9 @@ func NewPattern(cleanRedundantAt ...bool) *Pattern { } type PatternSegment struct { - typ string - optional bool - parse Parser - cleanRedundantAt bool // only for Reply + typ string + optional bool + parse Parser } type Parser func(msg *message.Segment) PatternParsed @@ -68,13 +67,13 @@ type Parser func(msg *message.Segment) PatternParsed // SetOptional set previous segment is optional, is v is empty, optional will be true // if Pattern is empty, panic func (p *Pattern) SetOptional(v ...bool) *Pattern { - if len((*p).segments) == 0 { + if len(p.segments) == 0 { panic("pattern is empty") } if len(v) == 1 { - (*p).segments[len((*p).segments)-1].optional = v[0] + p.segments[len(p.segments)-1].optional = v[0] } else { - (*p).segments[len((*p).segments)-1].optional = true + p.segments[len(p.segments)-1].optional = true } return p } @@ -122,16 +121,11 @@ func (p PatternParsed) Raw() *message.Segment { return p.msg } -func (p *Pattern) Add(typ string, optional bool, parse Parser, cleanRedundantAt ...bool) *Pattern { - clean := false - if len(cleanRedundantAt) > 0 { - clean = cleanRedundantAt[0] - } +func (p *Pattern) Add(typ string, optional bool, parse Parser) *Pattern { pattern := &PatternSegment{ - typ: typ, - optional: optional, - parse: parse, - cleanRedundantAt: clean, + typ: typ, + optional: optional, + parse: parse, } p.segments = append(p.segments, *pattern) return p @@ -197,12 +191,8 @@ func NewImageParser() Parser { } // Reply type zero.PatternReplyMatched -func (p *Pattern) Reply(noCleanRedundantAt ...bool) *Pattern { - noClean := false - if len(noCleanRedundantAt) > 0 { - noClean = noCleanRedundantAt[0] - } - p.Add("reply", false, NewReplyParser(), !noClean) +func (p *Pattern) Reply() *Pattern { + p.Add("reply", false, NewReplyParser()) return p } @@ -264,12 +254,14 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { for i := 0; i < len(pattern.segments); i++ { if j < len(msgs) && pattern.segments[i].matchType(msgs[j]) { patternState[i] = pattern.segments[i].parse(&msgs[j]) - } else { + } + if patternState[i].value == nil { if pattern.segments[i].optional { continue } return false } + j++ } ctx.State[KeyPattern] = patternState return true diff --git a/pattern_test.go b/pattern_test.go index 668c189..3ceb4eb 100644 --- a/pattern_test.go +++ b/pattern_test.go @@ -1,6 +1,7 @@ package zero import ( + "fmt" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" "github.com/wdvxdr1123/ZeroBot/message" @@ -27,7 +28,7 @@ func fakeCtx(msg message.Message) *Ctx { // copy from extension.PatternModel type PatternModel struct { - Matched []*PatternParsed `zero:"pattern_matched"` + Matched []PatternParsed `zero:"pattern_matched"` } // Test Match @@ -128,7 +129,7 @@ func TestPattern_ReplyFilter(t *testing.T) { expected bool }{ {[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern().Reply().Text("1234"), true}, - {[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern().Reply(true).Text("1234"), false}, + {[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern(false).Reply().Text("1234"), false}, } for i, v := range textTests { t.Run(strconv.Itoa(i), func(t *testing.T) { @@ -234,7 +235,10 @@ func TestPattern_SetOptional(t *testing.T) { } assert.Equal(t, len(v.expected), len(parsed.Matched)) for i := range parsed.Matched { - assert.Equal(t, v.expected[i].value != nil, parsed.Matched[i].value != nil) + t.Run(strconv.Itoa(i), func(t *testing.T) { + fmt.Println((parsed.Matched[i].value)) + assert.Equal(t, v.expected[i].value != nil, parsed.Matched[i].value != nil) + }) } }) } From 43ae5cd4679dc93386f1b2a6a370caedaf8e6b23 Mon Sep 17 00:00:00 2001 From: RikaCelery Date: Mon, 14 Oct 2024 16:23:23 +0800 Subject: [PATCH 49/49] use for range loop --- pattern.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pattern.go b/pattern.go index ceacdd4..d25d2c5 100644 --- a/pattern.go +++ b/pattern.go @@ -251,7 +251,7 @@ func patternMatch(ctx *Ctx, pattern Pattern, msgs []message.Segment) bool { patternState := make([]PatternParsed, len(pattern.segments)) j := 0 - for i := 0; i < len(pattern.segments); i++ { + for i := range pattern.segments { if j < len(msgs) && pattern.segments[i].matchType(msgs[j]) { patternState[i] = pattern.segments[i].parse(&msgs[j]) }