You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
3.3 KiB
Go
143 lines
3.3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"database/sql"
|
|
"git.noahlan.cn/northlan/ntools-go/stringn/ac"
|
|
"strings"
|
|
)
|
|
|
|
type (
|
|
Match struct {
|
|
Prefix string // 匹配前缀
|
|
Content []rune // 内容
|
|
}
|
|
CMD struct {
|
|
IsCMD bool // 是否CMD
|
|
Matches []Match // 匹配项
|
|
}
|
|
Parser struct {
|
|
ac ac.AhoCorasick // ac自动机
|
|
distinct bool // 命令是否去重(总开关)
|
|
|
|
patterns []Pattern // 匹配规则
|
|
}
|
|
Pattern struct {
|
|
Prefix string // 前缀
|
|
Alias []string // 前缀别名
|
|
ContentMaxLen int // 内容最大长度,以rune数组长度计算
|
|
Distinct sql.NullBool // Distinct 命令是否去重 单个权重大于总开关
|
|
isAlias bool // alias
|
|
realPattern *Pattern // 作为Alias时指向的实际Pattern
|
|
}
|
|
)
|
|
|
|
func NewCMDParser(patterns ...Pattern) *Parser {
|
|
p := &Parser{}
|
|
|
|
keyPrefixArr := p.initPattern(patterns)
|
|
|
|
builder := ac.NewAhoCorasickBuilder(ac.Opts{
|
|
AsciiCaseInsensitive: true,
|
|
MatchOnlyWholeWords: false,
|
|
MatchKind: ac.LeftMostLongestMatch,
|
|
DFA: true,
|
|
})
|
|
|
|
p.ac = builder.Build(keyPrefixArr)
|
|
return p
|
|
}
|
|
|
|
func (p *Parser) initPattern(patterns []Pattern) []string {
|
|
p.patterns = make([]Pattern, 0, len(patterns))
|
|
|
|
result := make([]string, 0, len(patterns))
|
|
for _, pattern := range patterns {
|
|
p.patterns = append(p.patterns, pattern)
|
|
result = append(result, pattern.Prefix)
|
|
for _, alias := range pattern.Alias {
|
|
p.patterns = append(p.patterns, Pattern{
|
|
Prefix: alias,
|
|
isAlias: true,
|
|
realPattern: &Pattern{
|
|
Prefix: pattern.Prefix,
|
|
Alias: pattern.Alias,
|
|
ContentMaxLen: pattern.ContentMaxLen,
|
|
Distinct: pattern.Distinct,
|
|
},
|
|
})
|
|
result = append(result, alias)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (p *Parser) ParseTest(content string) []ac.Match {
|
|
return p.ac.FindAll(content)
|
|
}
|
|
|
|
// SetDistinct 设置命令去重
|
|
func (p *Parser) SetDistinct(distinct bool) {
|
|
p.distinct = distinct
|
|
}
|
|
|
|
// Parse 从弹幕内容解析命令
|
|
func (p *Parser) Parse(msg string) CMD {
|
|
// 移除多余空格
|
|
tmpContent := strings.TrimSpace(msg)
|
|
resp := CMD{
|
|
Matches: make([]Match, 0),
|
|
}
|
|
|
|
iter := p.ac.Iter(tmpContent)
|
|
for match := iter.Next(); match != nil; {
|
|
resp.IsCMD = true
|
|
pattern := p.patterns[match.Pattern()]
|
|
if pattern.isAlias {
|
|
pattern = *pattern.realPattern
|
|
}
|
|
m := Match{
|
|
Prefix: pattern.Prefix,
|
|
}
|
|
tmpNext := iter.Next()
|
|
|
|
// 避免同类型指令重复
|
|
if pattern.Distinct.Valid && pattern.Distinct.Bool {
|
|
for _, m := range resp.Matches {
|
|
if m.Prefix == pattern.Prefix {
|
|
goto end // continue out for
|
|
}
|
|
}
|
|
}
|
|
if p.distinct {
|
|
if !pattern.Distinct.Valid || pattern.Distinct.Bool {
|
|
for _, m := range resp.Matches {
|
|
if m.Prefix == pattern.Prefix {
|
|
goto end // continue out for
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if tmpNext != nil {
|
|
//nextPattern := p.patterns[tmpNext.Pattern()]
|
|
content := []rune(tmpContent[match.End():tmpNext.Start()])
|
|
if len(content) > pattern.ContentMaxLen {
|
|
resp.IsCMD = false
|
|
break
|
|
}
|
|
m.Content = content
|
|
} else {
|
|
content := []rune(tmpContent[match.End():])
|
|
if len(content) > pattern.ContentMaxLen {
|
|
resp.IsCMD = false
|
|
break
|
|
}
|
|
m.Content = content
|
|
}
|
|
resp.Matches = append(resp.Matches, m)
|
|
end:
|
|
match = tmpNext
|
|
}
|
|
return resp
|
|
}
|