Skip to content

Commit

Permalink
feat(pattern): AtPattern 识别 @+qq 号 (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikaCelery authored Nov 9, 2024
1 parent 2883440 commit 55d9542
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 47 deletions.
75 changes: 66 additions & 9 deletions pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strconv"
"strings"

"github.com/tidwall/gjson"

"github.com/wdvxdr1123/ZeroBot/message"
)

Expand All @@ -18,39 +20,94 @@ func (p *Pattern) AsRule() Rule {
if len(ctx.Event.Message) == 0 {
return false
}
if !p.cleanRedundantAt {
if !p.cleanRedundantAt && !p.fuzzyAt {
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])
for i := 1; i < len(ctx.Event.Message); i++ {
if ctx.Event.Message[i-1].Type == "reply" && ctx.Event.Message[i].Type == "at" {
for i := 0; i < len(ctx.Event.Message); i++ {
if i > 0 && 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"] {
continue
}
}
if ctx.Event.Message[i].Type == "text" && atRegexp.MatchString(ctx.Event.Message[i].Data["text"]) {
// xxxx @11232123 xxxxx
msgs = append(msgs, ctx.splitAtInText(i)...)
continue
}
msgs = append(msgs, ctx.Event.Message[i])
}
return patternMatch(ctx, *p, msgs)
}
}

var atRegexp = regexp.MustCompile(`@([\d\S]*)`)

func (ctx *Ctx) splitAtInText(index int) []message.Segment {
msg := ctx.Event.Message[index].String()
splited := atRegexp.Split(msg, -1)
ats := atRegexp.FindAllStringSubmatch(msg, -1)
var tmp = make([]message.Segment, 0, len(splited)+len(ats))
var list []gjson.Result
for i, s := range splited {
if strings.TrimSpace(s) == "" {
continue
}
tmp = append(tmp, message.Text(s))
// append at
if i > len(ats)-1 {
continue
}
uid, err := strconv.ParseInt(ats[i][1], 10, 64)
// TODO numeric username
if err != nil {
// assume is username
if list == nil {
list = ctx.GetThisGroupMemberList().Array()
}
for _, member := range list {
if member.Get("card").Str != ats[i][1] && member.Get("nickname").Str != ats[i][1] {
continue
}
uid = member.Get("user_id").Int()
}
}
tmp = append(tmp, message.At(uid))
}
return tmp
}

type Pattern struct {
cleanRedundantAt bool
fuzzyAt bool
segments []PatternSegment
}

func NewPattern(cleanRedundantAt ...bool) *Pattern {
clean := true
if len(cleanRedundantAt) > 0 {
clean = cleanRedundantAt[0]
// PatternOption pattern option
type PatternOption struct {
CleanRedundantAt bool
FuzzyAt bool
}

// NewPattern new pattern
// defaults:
//
// CleanRedundantAt: true
// FuzzyAt: false
func NewPattern(option *PatternOption) *Pattern {
if option == nil {
option = &PatternOption{
CleanRedundantAt: true,
FuzzyAt: false,
}
}
pattern := Pattern{
cleanRedundantAt: clean,
cleanRedundantAt: option.CleanRedundantAt,
fuzzyAt: option.FuzzyAt,
segments: make([]PatternSegment, 0, 4),
}
return &pattern
Expand Down
120 changes: 82 additions & 38 deletions pattern_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ func TestPattern_Text(t *testing.T) {
pattern *Pattern
expected bool
}{
{[]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},
{[]message.Segment{message.Text("/haha")}, NewPattern(nil).Text(`/(\S+)`), true},
{[]message.Segment{message.Text("haha")}, NewPattern(nil).Text("haha"), true},
{[]message.Segment{message.Text("aaa")}, NewPattern(nil).Text("not match"), false},
{[]message.Segment{message.Image("not a image")}, NewPattern(nil).Text("not match"), false},
{[]message.Segment{message.At(114514)}, NewPattern(nil).Text("not match"), false},
{[]message.Segment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern(nil).Text("实用插件合集"), true},
{[]message.Segment{message.Text("你说的对但是ZeroBot-Plugin 是 ZeroBot 的 实用插件合集")}, NewPattern(nil).Text("nonono"), false},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -61,13 +62,13 @@ func TestPattern_Image(t *testing.T) {
pattern *Pattern
expected bool
}{
{[]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},
{[]message.Segment{message.Text("haha")}, NewPattern(nil).Image(), false},
{[]message.Segment{message.Text("haha"), message.Image("not a image")}, NewPattern(nil).Image().Image(), false},
{[]message.Segment{message.Text("haha"), message.Image("not a image")}, NewPattern(nil).Text("haha").Image(), true},
{[]message.Segment{message.Image("not a image")}, NewPattern(nil).Image(), true},
{[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern(nil).Image(), false},
{[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern(nil).Image().Image(), true},
{[]message.Segment{message.Image("not a image"), message.Image("not a image")}, NewPattern(nil).Image().Image().Image(), false},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -79,16 +80,56 @@ func TestPattern_Image(t *testing.T) {
}
}

func TestPattern_FuzzyAt(t *testing.T) {
textTests := [...]struct {
msg message.Message
pattern *Pattern
expected bool
}{
{[]message.Segment{message.Text("haha @114514")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At(), true},
{[]message.Segment{message.Text("haha 114514")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At(), false},
{[]message.Segment{message.Text("haha @你好")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At(), true},
{[]message.Segment{message.Text("haha @")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At(), true},
{[]message.Segment{message.Text("haha @ 你说的对")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At().Text("你说的对"), true},
{[]message.Segment{message.Text("haha @114514 你说的对")}, NewPattern(&PatternOption{
CleanRedundantAt: true,
FuzzyAt: true,
}).Text("haha").At().Text("你说的对"), true},
}
for _, v := range textTests {
t.Run(v.msg.String(), func(t *testing.T) {
ctx := fakeCtx(v.msg)
rule := v.pattern.AsRule()
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.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(message.NewMessageIDFromString("1919810")), false},
{[]message.Segment{message.Text("haha")}, NewPattern(nil).At(), false},
{[]message.Segment{message.Image("not a image")}, NewPattern(nil).At(), false},
{[]message.Segment{message.At(114514)}, NewPattern(nil).At(), true},
{[]message.Segment{message.At(114514)}, NewPattern(nil).At(message.NewMessageIDFromString("1919810")), false},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -106,12 +147,12 @@ func TestPattern_Reply(t *testing.T) {
pattern *Pattern
expected bool
}{
{[]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},
{[]message.Segment{message.Text("haha")}, NewPattern(nil).Reply(), false},
{[]message.Segment{message.Image("not a image")}, NewPattern(nil).Reply(), false},
{[]message.Segment{message.At(1919810), message.Reply(12345)}, NewPattern(nil).Reply().At(), false},
{[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern(nil).Reply().At(), true},
{[]message.Segment{message.Reply(12345)}, NewPattern(nil).Reply(), true},
{[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern(nil).Reply(), false},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -128,8 +169,11 @@ func TestPattern_ReplyFilter(t *testing.T) {
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(false).Reply().Text("1234"), false},
{[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern(nil).Reply().Text("1234"), true},
{[]message.Segment{message.Reply(12345), message.At(12345), message.Text("1234")}, NewPattern(&PatternOption{
CleanRedundantAt: false,
FuzzyAt: false,
}).Reply().Text("1234"), false},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -146,10 +190,10 @@ func TestPattern_Any(t *testing.T) {
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},
{[]message.Segment{message.Text("haha")}, NewPattern(nil).Any(), true},
{[]message.Segment{message.Image("not a image")}, NewPattern(nil).Any(), true},
{[]message.Segment{message.At(1919810), message.Reply(12345)}, NewPattern(nil).Any().Reply(), true},
{[]message.Segment{message.Reply(12345), message.At(1919810)}, NewPattern(nil).Any().At(), true},
}
for i, v := range textTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand All @@ -161,7 +205,7 @@ func TestPattern_Any(t *testing.T) {
}
t.Run("get", func(t *testing.T) {
ctx := fakeCtx([]message.Segment{message.Reply("just for test")})
rule := NewPattern().Any().AsRule()
rule := NewPattern(nil).Any().AsRule()
_ = rule(ctx)
model := PatternModel{}
err := ctx.Parse(&model)
Expand All @@ -181,22 +225,22 @@ func TestPatternParsed_Gets(t *testing.T) {
}
func TestPattern_SetOptional(t *testing.T) {
assert.Panics(t, func() {
NewPattern().SetOptional()
NewPattern(nil).SetOptional()
})
tests := [...]struct {
msg message.Message
pattern *Pattern
expected []PatternParsed
}{
{[]message.Segment{message.Text("/do it")}, NewPattern().Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{
{[]message.Segment{message.Text("/do it")}, NewPattern(nil).Text("/(do) (.*)").At().SetOptional(true), []PatternParsed{
{
value: []string{"/do it", "do", "it"},
}, {
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{
{[]message.Segment{message.Text("/do it")}, NewPattern(nil).Text("/(do) (.*)").At().SetOptional(false), []PatternParsed{}},
{[]message.Segment{message.Text("happy bear"), message.At(114514)}, NewPattern(nil).Reply().SetOptional().Text(".+").SetOptional().At().SetOptional(false), []PatternParsed{
{
value: nil,
},
Expand All @@ -207,7 +251,7 @@ func TestPattern_SetOptional(t *testing.T) {
value: "114514",
},
}},
{[]message.Segment{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(nil).Image().SetOptional().Image().SetOptional().Image().SetOptional(), []PatternParsed{ // why you do this
{
value: nil,
},
Expand Down Expand Up @@ -251,19 +295,19 @@ func TestAllParse(t *testing.T) {
pattern *Pattern
expected []PatternParsed
}{
{[]message.Segment{message.Text("test haha test"), message.At(123)}, NewPattern().Text("((ha)+)").At(), []PatternParsed{
{[]message.Segment{message.Text("test haha test"), message.At(123)}, NewPattern(nil).Text("((ha)+)").At(), []PatternParsed{
{
value: []string{"haha", "haha", "ha"},
}, {
value: "123",
},
}},
{[]message.Segment{message.Text("haha")}, NewPattern().Text("(h)(a)(h)(a)"), []PatternParsed{
{[]message.Segment{message.Text("haha")}, NewPattern(nil).Text("(h)(a)(h)(a)"), []PatternParsed{
{
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(message.NewMessageIDFromInteger(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(nil).Reply().Image().At().At(message.NewMessageIDFromInteger(124)).Text("(h)(a)(h)(a)"), []PatternParsed{

{
value: "fake reply",
Expand Down

0 comments on commit 55d9542

Please sign in to comment.