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 }