mirror of
https://github.com/fumiama/NanoBot.git
synced 2026-06-05 02:30:23 +08:00
finish
This commit is contained in:
BIN
.github/nano.jpeg
vendored
Normal file
BIN
.github/nano.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,3 +22,4 @@ go.work
|
||||
|
||||
bot_test.go
|
||||
/test
|
||||
/data
|
||||
|
||||
122
README.md
122
README.md
@@ -1,2 +1,120 @@
|
||||
# NanoBot
|
||||
ZeroBot-like Official QQ Bot Adapter
|
||||
<div align="center">
|
||||
<a href="https://crypko.ai/crypko/GtWYDpVMx5GYm/">
|
||||
<img src=".github/nano.jpeg" alt="东云名乃" width = "256">
|
||||
</a><br>
|
||||
|
||||
<h1>NanoBot</h1>
|
||||
类 ZeroBot 的官方 QQ 频道适配器<br><br>
|
||||
|
||||
<img src="https://counter.seku.su/cmoe?name=NanoBot&theme=r34" /><br>
|
||||
|
||||
</div>
|
||||
|
||||
## Instructions
|
||||
|
||||
> Note: This framework is built mainly for Chinese users thus may display hard-coded Chinese prompts during the interaction.
|
||||
|
||||
参见 QQ 官方[文档](https://bot.q.qq.com/wiki/)。
|
||||
|
||||
## 快速开始(基于插件)
|
||||
> 查看`example`文件夹以获取更多信息
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><img src="https://github.com/fumiama/NanoBot/assets/41315874/6ef9fd95-ae99-449e-85e1-25797271e088"></td>
|
||||
<td align="center"><img src="https://github.com/fumiama/NanoBot/assets/41315874/edd374e4-b8a5-4cff-a463-8c3b30e537c4"></td>
|
||||
<td align="center"><img src="https://github.com/fumiama/NanoBot/assets/41315874/ed1b063f-44b0-4950-ac35-1e72745cf3f4"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center">开始响应</td>
|
||||
<td align="center">服务列表</td>
|
||||
<td align="center">查看用法</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||

|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/fumiama/NanoBot/example/echo"
|
||||
|
||||
nano "github.com/fumiama/NanoBot"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
nano.OpenAPI = nano.SandboxAPI
|
||||
nano.OnMessageFullMatch("help").SetBlock(true).
|
||||
Handle(func(ctx *nano.Ctx) {
|
||||
_, _ = ctx.SendPlainMessage(false, "echo string")
|
||||
})
|
||||
nano.Run(&nano.Bot{
|
||||
AppID: "你的AppID",
|
||||
Token: "你的Token",
|
||||
Secret: "你的Secret, 目前没用到, 可以不填",
|
||||
Intents: nano.IntentPublic,
|
||||
SuperUsers: []string{"用户ID1", "用户ID2"},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 更多选择(传统的事件驱动)
|
||||
|
||||
> 如果声明了 Handler, 所有插件将被禁用
|
||||
|
||||

|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
nano "github.com/fumiama/NanoBot"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
nano.OpenAPI = nano.SandboxAPI
|
||||
nano.Run(&nano.Bot{
|
||||
AppID: "你的AppID",
|
||||
Token: "你的Token",
|
||||
Secret: "你的Secret, 目前没用到, 可以不填",
|
||||
Intents: nano.IntentPublic,
|
||||
Handler: &nano.Handler{
|
||||
OnAtMessageCreate: func(s uint32, bot *nano.Bot, d *nano.Message) {
|
||||
u := ""
|
||||
if len(d.Attachments) > 0 {
|
||||
u = d.Attachments[0].URL
|
||||
if !strings.HasPrefix(u, "http") {
|
||||
u = "http://" + u
|
||||
}
|
||||
}
|
||||
_, err := bot.PostMessageToChannel(d.ChannelID, &nano.MessagePost{
|
||||
Content: "您发送了: " + d.Content,
|
||||
Image: u,
|
||||
ReplyMessageID: d.ID,
|
||||
MessageReference: &nano.MessageReference{
|
||||
MessageID: d.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
bot.PostMessageToChannel(d.ChannelID, &nano.MessagePost{
|
||||
Content: "[ERROR]: " + err.Error(),
|
||||
ReplyMessageID: d.ID,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Thanks
|
||||
|
||||
- [ZeroBot](https://github.com/wdvxdr1123/ZeroBot)
|
||||
|
||||
5
bot.go
5
bot.go
@@ -143,6 +143,11 @@ func (bot *Bot) Authorization() string {
|
||||
return "Bot " + bot.AppID + "." + bot.Token
|
||||
}
|
||||
|
||||
// AtMe 返回 "<@!"+bot.ready.User.ID+">"
|
||||
func (bot *Bot) AtMe() string {
|
||||
return "<@!" + bot.ready.User.ID + ">"
|
||||
}
|
||||
|
||||
// receive 收一个 payload
|
||||
func (bot *Bot) reveive() (payload WebsocketPayload, err error) {
|
||||
err = bot.conn.ReadJSON(&payload)
|
||||
|
||||
69
codegen/engine/engine.yml
Normal file
69
codegen/engine/engine.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
emptyon:
|
||||
- Message
|
||||
|
||||
- GuildCreate
|
||||
- GuildUpdate
|
||||
- GuildDelete
|
||||
- ChannelCreate
|
||||
- ChannelUpdate
|
||||
- ChannelDelete
|
||||
|
||||
- GuildMemberAdd
|
||||
- GuildMemberUpdate
|
||||
- GuildMemberRemove
|
||||
|
||||
- MessageCreate
|
||||
- MessageDelete
|
||||
|
||||
- MessageReactionAdd
|
||||
- MessageReactionRemove
|
||||
|
||||
- DirectMessageCreate
|
||||
- DirectMessageDelete
|
||||
|
||||
- OpenForumThreadCreate
|
||||
- OpenForumThreadUpdate
|
||||
- OpenForumThreadDelete
|
||||
- OpenForumPostCreate
|
||||
- OpenForumPostDelete
|
||||
- OpenForumReplyCreate
|
||||
- OpenForumReplyDelete
|
||||
|
||||
- AudioOrLiveChannelMemberEnter
|
||||
- AudioOrLiveChannelMemberExit
|
||||
|
||||
- MessageAuditPass
|
||||
- MessageAuditReject
|
||||
|
||||
- ForumThreadCreate
|
||||
- ForumThreadUpdate
|
||||
- ForumThreadDelete
|
||||
- ForumPostCreate
|
||||
- ForumPostDelete
|
||||
- ForumReplyCreate
|
||||
- ForumReplyDelete
|
||||
- ForumPublishAuditResult
|
||||
|
||||
- AudioStart
|
||||
- AudioFinish
|
||||
- AudioOnMic
|
||||
- AudioOffMic
|
||||
|
||||
- AtMessageCreate
|
||||
- PublicMessageDelete
|
||||
|
||||
ruleon:
|
||||
Message:
|
||||
- Message
|
||||
Rule:
|
||||
Prefix: [prefix, string]
|
||||
Suffix: [suffix, string]
|
||||
Command: [commands, string]
|
||||
Regex: [regexPattern, string]
|
||||
Keyword: [keyword, string]
|
||||
FullMatch: [src, string]
|
||||
FullMatchGroup: [src, "[]string"]
|
||||
KeywordGroup: [keywords, "[]string"]
|
||||
CommandGroup: [commands, "[]string"]
|
||||
PrefixGroup: [prefix, "[]string"]
|
||||
SuffixGroup: [suffix, "[]string"]
|
||||
114
codegen/engine/main.go
Normal file
114
codegen/engine/main.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const head = `// Code generated by codegen/engine. DO NOT EDIT.
|
||||
|
||||
package nano
|
||||
`
|
||||
|
||||
const emptyon = `
|
||||
// On[Message] ...
|
||||
func (e *Engine) On[Message](rules ...Rule) *Matcher { return e.On("[Message]", rules...) }
|
||||
|
||||
// On[Message] ...
|
||||
func On[Message](rules ...Rule) *Matcher { return On("[Message]", rules...) }
|
||||
`
|
||||
|
||||
const ruleon = `
|
||||
// On[Message][Rule] ...
|
||||
func On[Message][Rule]([Name] [Type], rules ...Rule) *Matcher {
|
||||
return defaultEngine.On[Message][Rule]([Name], rules...)
|
||||
}
|
||||
|
||||
// On[Message][Rule] ...
|
||||
func (e *Engine) On[Message][Rule]([Name] [Type], rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "[Message]",
|
||||
Rules: append([]Rule{[Rule]Rule([Name][...])}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
`
|
||||
|
||||
const ruleonshell = `
|
||||
// On[Message]Shell shell命令触发器
|
||||
func On[Message]Shell(command string, model interface{}, rules ...Rule) *Matcher {
|
||||
return defaultEngine.On[Message]Shell(command, model, rules...)
|
||||
}
|
||||
|
||||
// On[Message]Shell shell命令触发器
|
||||
func (e *Engine) On[Message]Shell(command string, model interface{}, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "[Message]",
|
||||
Rules: append([]Rule{ShellRule(command, model)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
`
|
||||
|
||||
type config struct {
|
||||
EmptyOn []string `yaml:"emptyon"`
|
||||
RuleOn struct {
|
||||
Message []string `yaml:"Message"`
|
||||
Rule map[string][2]string `yaml:"Rule"`
|
||||
} `yaml:"ruleon"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
f, err := os.Create("engine_generated.go")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(head)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ef, err := os.Open("codegen/engine/engine.yml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer ef.Close()
|
||||
cfg := config{}
|
||||
err = yaml.NewDecoder(ef).Decode(&cfg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, msg := range cfg.EmptyOn {
|
||||
_, err = f.WriteString(strings.ReplaceAll(emptyon, "[Message]", msg))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
for _, msg := range cfg.RuleOn.Message {
|
||||
for rule, x := range cfg.RuleOn.Rule {
|
||||
s := strings.ReplaceAll(ruleon, "[Message]", msg)
|
||||
s = strings.ReplaceAll(s, "[Rule]", rule)
|
||||
s = strings.ReplaceAll(s, "[Name]", x[0])
|
||||
s = strings.ReplaceAll(s, "[Type]", x[1])
|
||||
if strings.Contains(rule, "Group") {
|
||||
s = strings.ReplaceAll(s, "[...]", "...")
|
||||
} else {
|
||||
s = strings.ReplaceAll(s, "[...]", "")
|
||||
}
|
||||
_, err = f.WriteString(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
_, err = f.WriteString(strings.ReplaceAll(ruleonshell, "[Message]", msg))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
131
context.go
Normal file
131
context.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Ctx struct {
|
||||
Event
|
||||
State
|
||||
Caller *Bot
|
||||
Message *Message
|
||||
ma *Matcher
|
||||
IsToMe bool
|
||||
}
|
||||
|
||||
// decoder 反射获取的数据
|
||||
type decoder []dec
|
||||
|
||||
type dec struct {
|
||||
index int
|
||||
key string
|
||||
}
|
||||
|
||||
// decoder 缓存
|
||||
var decoderCache = sync.Map{}
|
||||
|
||||
// Parse 将 Ctx.State 映射到结构体
|
||||
func (ctx *Ctx) Parse(model interface{}) (err error) {
|
||||
var (
|
||||
rv = reflect.ValueOf(model).Elem()
|
||||
t = rv.Type()
|
||||
modelDec decoder
|
||||
)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("parse state error: %v", r)
|
||||
}
|
||||
}()
|
||||
d, ok := decoderCache.Load(t)
|
||||
if ok {
|
||||
modelDec = d.(decoder)
|
||||
} else {
|
||||
modelDec = decoder{}
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
t1 := t.Field(i)
|
||||
if key, ok := t1.Tag.Lookup("zero"); ok {
|
||||
modelDec = append(modelDec, dec{
|
||||
index: i,
|
||||
key: key,
|
||||
})
|
||||
}
|
||||
}
|
||||
decoderCache.Store(t, modelDec)
|
||||
}
|
||||
for _, d := range modelDec { // decoder类型非小内存,无法被编译器优化为快速拷贝
|
||||
rv.Field(d.index).Set(reflect.ValueOf(ctx.State[d.key]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckSession 判断会话连续性
|
||||
func (ctx *Ctx) CheckSession() Rule {
|
||||
msg := ctx.Value.(*Message)
|
||||
return func(ctx2 *Ctx) bool {
|
||||
msg2, ok := ctx.Value.(*Message)
|
||||
if !ok || msg.Author == nil || msg2.Author == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
return msg.Author.ID == msg2.Author.ID && msg.ChannelID == msg2.ChannelID
|
||||
}
|
||||
}
|
||||
|
||||
// Send 发送消息到对方
|
||||
func (ctx *Ctx) Send(replytosender bool, post *MessagePost) (*Message, error) {
|
||||
msg := ctx.Value.(*Message)
|
||||
post.ReplyMessageID = msg.ID
|
||||
if replytosender {
|
||||
post.MessageReference = &MessageReference{
|
||||
MessageID: msg.ID,
|
||||
}
|
||||
}
|
||||
return ctx.Caller.PostMessageToChannel(msg.ChannelID, post)
|
||||
}
|
||||
|
||||
// SendPlainMessage 发送纯文本消息到对方
|
||||
func (ctx *Ctx) SendPlainMessage(replytosender bool, printable ...any) (*Message, error) {
|
||||
msg := ctx.Value.(*Message)
|
||||
post := &MessagePost{
|
||||
ReplyMessageID: msg.ID,
|
||||
}
|
||||
if replytosender {
|
||||
post.MessageReference = &MessageReference{
|
||||
MessageID: msg.ID,
|
||||
}
|
||||
}
|
||||
post.Content = fmt.Sprint(printable...)
|
||||
return ctx.Caller.PostMessageToChannel(msg.ChannelID, post)
|
||||
}
|
||||
|
||||
// SendImage 发送带图片消息到对方
|
||||
func (ctx *Ctx) SendImage(file string, replytosender bool, caption ...any) (*Message, error) {
|
||||
msg := ctx.Value.(*Message)
|
||||
post := &MessagePost{
|
||||
ReplyMessageID: msg.ID,
|
||||
}
|
||||
if strings.HasPrefix(file, "http") {
|
||||
post.Image = file
|
||||
} else {
|
||||
post.ImageFile = file
|
||||
}
|
||||
if replytosender {
|
||||
post.MessageReference = &MessageReference{
|
||||
MessageID: msg.ID,
|
||||
}
|
||||
}
|
||||
post.Content = fmt.Sprint(caption...)
|
||||
return ctx.Caller.PostMessageToChannel(msg.ChannelID, post)
|
||||
}
|
||||
|
||||
// Block 匹配成功后阻止后续触发
|
||||
func (ctx *Ctx) Block() {
|
||||
ctx.ma.SetBlock(true)
|
||||
}
|
||||
|
||||
// Block 在 pre, rules, mid 阶段阻止后续触发
|
||||
func (ctx *Ctx) Break() {
|
||||
ctx.ma.Break = true
|
||||
}
|
||||
84
engine.go
Normal file
84
engine.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package nano
|
||||
|
||||
//go:generate go run codegen/engine/main.go
|
||||
|
||||
// 生成空引擎
|
||||
func newEngine() *Engine {
|
||||
return &Engine{
|
||||
preHandler: []Rule{},
|
||||
midHandler: []Rule{},
|
||||
postHandler: []Process{},
|
||||
}
|
||||
}
|
||||
|
||||
var defaultEngine = newEngine()
|
||||
|
||||
// Engine is the pre_handler, mid_handler, post_handler manager
|
||||
type Engine struct {
|
||||
preHandler []Rule
|
||||
midHandler []Rule
|
||||
postHandler []Process
|
||||
matchers []*Matcher
|
||||
prio int
|
||||
service string
|
||||
datafolder string
|
||||
}
|
||||
|
||||
// Delete 移除该 Engine 注册的所有 Matchers
|
||||
func (e *Engine) Delete() {
|
||||
for _, m := range e.matchers {
|
||||
m.Delete()
|
||||
}
|
||||
}
|
||||
|
||||
// UsePreHandler 向该 Engine 添加新 PreHandler(Rule),
|
||||
// 会在 Rule 判断前触发,如果 preHandler
|
||||
// 没有通过,则 Rule, Matcher 不会触发
|
||||
//
|
||||
// 可用于分群组管理插件等
|
||||
func (e *Engine) UsePreHandler(rules ...Rule) {
|
||||
e.preHandler = append(e.preHandler, rules...)
|
||||
}
|
||||
|
||||
// UseMidHandler 向该 Engine 添加新 MidHandler(Rule),
|
||||
// 会在 Rule 判断后, Matcher 触发前触发,如果 midHandler
|
||||
// 没有通过,则 Matcher 不会触发
|
||||
//
|
||||
// 可用于速率限制等
|
||||
func (e *Engine) UseMidHandler(rules ...Rule) {
|
||||
e.midHandler = append(e.midHandler, rules...)
|
||||
}
|
||||
|
||||
// UsePostHandler 向该 Engine 添加新 PostHandler(Rule),
|
||||
// 会在 Matcher 触发后触发,如果 PostHandler 返回 false,
|
||||
// 则后续的 post handler 不会触发
|
||||
//
|
||||
// 可用于速率限制等
|
||||
func (e *Engine) UsePostHandler(handler ...Process) {
|
||||
e.postHandler = append(e.postHandler, handler...)
|
||||
}
|
||||
|
||||
// ApplySingle 应用反并发
|
||||
func (e *Engine) ApplySingle(s *Single[int64]) *Engine {
|
||||
s.Apply(e)
|
||||
return e
|
||||
}
|
||||
|
||||
// DataFolder 本插件数据目录, 默认 data/rbp/
|
||||
func (e *Engine) DataFolder() string {
|
||||
return e.datafolder
|
||||
}
|
||||
|
||||
// On 添加新的指定消息类型的匹配器(默认Engine)
|
||||
func On(typ string, rules ...Rule) *Matcher { return defaultEngine.On(typ, rules...) }
|
||||
|
||||
// On 添加新的指定消息类型的匹配器
|
||||
func (e *Engine) On(typ string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: typ,
|
||||
Rules: rules,
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
441
engine_generated.go
Normal file
441
engine_generated.go
Normal file
@@ -0,0 +1,441 @@
|
||||
// Code generated by codegen/engine. DO NOT EDIT.
|
||||
|
||||
package nano
|
||||
|
||||
// OnMessage ...
|
||||
func (e *Engine) OnMessage(rules ...Rule) *Matcher { return e.On("Message", rules...) }
|
||||
|
||||
// OnMessage ...
|
||||
func OnMessage(rules ...Rule) *Matcher { return On("Message", rules...) }
|
||||
|
||||
// OnGuildCreate ...
|
||||
func (e *Engine) OnGuildCreate(rules ...Rule) *Matcher { return e.On("GuildCreate", rules...) }
|
||||
|
||||
// OnGuildCreate ...
|
||||
func OnGuildCreate(rules ...Rule) *Matcher { return On("GuildCreate", rules...) }
|
||||
|
||||
// OnGuildUpdate ...
|
||||
func (e *Engine) OnGuildUpdate(rules ...Rule) *Matcher { return e.On("GuildUpdate", rules...) }
|
||||
|
||||
// OnGuildUpdate ...
|
||||
func OnGuildUpdate(rules ...Rule) *Matcher { return On("GuildUpdate", rules...) }
|
||||
|
||||
// OnGuildDelete ...
|
||||
func (e *Engine) OnGuildDelete(rules ...Rule) *Matcher { return e.On("GuildDelete", rules...) }
|
||||
|
||||
// OnGuildDelete ...
|
||||
func OnGuildDelete(rules ...Rule) *Matcher { return On("GuildDelete", rules...) }
|
||||
|
||||
// OnChannelCreate ...
|
||||
func (e *Engine) OnChannelCreate(rules ...Rule) *Matcher { return e.On("ChannelCreate", rules...) }
|
||||
|
||||
// OnChannelCreate ...
|
||||
func OnChannelCreate(rules ...Rule) *Matcher { return On("ChannelCreate", rules...) }
|
||||
|
||||
// OnChannelUpdate ...
|
||||
func (e *Engine) OnChannelUpdate(rules ...Rule) *Matcher { return e.On("ChannelUpdate", rules...) }
|
||||
|
||||
// OnChannelUpdate ...
|
||||
func OnChannelUpdate(rules ...Rule) *Matcher { return On("ChannelUpdate", rules...) }
|
||||
|
||||
// OnChannelDelete ...
|
||||
func (e *Engine) OnChannelDelete(rules ...Rule) *Matcher { return e.On("ChannelDelete", rules...) }
|
||||
|
||||
// OnChannelDelete ...
|
||||
func OnChannelDelete(rules ...Rule) *Matcher { return On("ChannelDelete", rules...) }
|
||||
|
||||
// OnGuildMemberAdd ...
|
||||
func (e *Engine) OnGuildMemberAdd(rules ...Rule) *Matcher { return e.On("GuildMemberAdd", rules...) }
|
||||
|
||||
// OnGuildMemberAdd ...
|
||||
func OnGuildMemberAdd(rules ...Rule) *Matcher { return On("GuildMemberAdd", rules...) }
|
||||
|
||||
// OnGuildMemberUpdate ...
|
||||
func (e *Engine) OnGuildMemberUpdate(rules ...Rule) *Matcher { return e.On("GuildMemberUpdate", rules...) }
|
||||
|
||||
// OnGuildMemberUpdate ...
|
||||
func OnGuildMemberUpdate(rules ...Rule) *Matcher { return On("GuildMemberUpdate", rules...) }
|
||||
|
||||
// OnGuildMemberRemove ...
|
||||
func (e *Engine) OnGuildMemberRemove(rules ...Rule) *Matcher { return e.On("GuildMemberRemove", rules...) }
|
||||
|
||||
// OnGuildMemberRemove ...
|
||||
func OnGuildMemberRemove(rules ...Rule) *Matcher { return On("GuildMemberRemove", rules...) }
|
||||
|
||||
// OnMessageCreate ...
|
||||
func (e *Engine) OnMessageCreate(rules ...Rule) *Matcher { return e.On("MessageCreate", rules...) }
|
||||
|
||||
// OnMessageCreate ...
|
||||
func OnMessageCreate(rules ...Rule) *Matcher { return On("MessageCreate", rules...) }
|
||||
|
||||
// OnMessageDelete ...
|
||||
func (e *Engine) OnMessageDelete(rules ...Rule) *Matcher { return e.On("MessageDelete", rules...) }
|
||||
|
||||
// OnMessageDelete ...
|
||||
func OnMessageDelete(rules ...Rule) *Matcher { return On("MessageDelete", rules...) }
|
||||
|
||||
// OnMessageReactionAdd ...
|
||||
func (e *Engine) OnMessageReactionAdd(rules ...Rule) *Matcher { return e.On("MessageReactionAdd", rules...) }
|
||||
|
||||
// OnMessageReactionAdd ...
|
||||
func OnMessageReactionAdd(rules ...Rule) *Matcher { return On("MessageReactionAdd", rules...) }
|
||||
|
||||
// OnMessageReactionRemove ...
|
||||
func (e *Engine) OnMessageReactionRemove(rules ...Rule) *Matcher { return e.On("MessageReactionRemove", rules...) }
|
||||
|
||||
// OnMessageReactionRemove ...
|
||||
func OnMessageReactionRemove(rules ...Rule) *Matcher { return On("MessageReactionRemove", rules...) }
|
||||
|
||||
// OnDirectMessageCreate ...
|
||||
func (e *Engine) OnDirectMessageCreate(rules ...Rule) *Matcher { return e.On("DirectMessageCreate", rules...) }
|
||||
|
||||
// OnDirectMessageCreate ...
|
||||
func OnDirectMessageCreate(rules ...Rule) *Matcher { return On("DirectMessageCreate", rules...) }
|
||||
|
||||
// OnDirectMessageDelete ...
|
||||
func (e *Engine) OnDirectMessageDelete(rules ...Rule) *Matcher { return e.On("DirectMessageDelete", rules...) }
|
||||
|
||||
// OnDirectMessageDelete ...
|
||||
func OnDirectMessageDelete(rules ...Rule) *Matcher { return On("DirectMessageDelete", rules...) }
|
||||
|
||||
// OnOpenForumThreadCreate ...
|
||||
func (e *Engine) OnOpenForumThreadCreate(rules ...Rule) *Matcher { return e.On("OpenForumThreadCreate", rules...) }
|
||||
|
||||
// OnOpenForumThreadCreate ...
|
||||
func OnOpenForumThreadCreate(rules ...Rule) *Matcher { return On("OpenForumThreadCreate", rules...) }
|
||||
|
||||
// OnOpenForumThreadUpdate ...
|
||||
func (e *Engine) OnOpenForumThreadUpdate(rules ...Rule) *Matcher { return e.On("OpenForumThreadUpdate", rules...) }
|
||||
|
||||
// OnOpenForumThreadUpdate ...
|
||||
func OnOpenForumThreadUpdate(rules ...Rule) *Matcher { return On("OpenForumThreadUpdate", rules...) }
|
||||
|
||||
// OnOpenForumThreadDelete ...
|
||||
func (e *Engine) OnOpenForumThreadDelete(rules ...Rule) *Matcher { return e.On("OpenForumThreadDelete", rules...) }
|
||||
|
||||
// OnOpenForumThreadDelete ...
|
||||
func OnOpenForumThreadDelete(rules ...Rule) *Matcher { return On("OpenForumThreadDelete", rules...) }
|
||||
|
||||
// OnOpenForumPostCreate ...
|
||||
func (e *Engine) OnOpenForumPostCreate(rules ...Rule) *Matcher { return e.On("OpenForumPostCreate", rules...) }
|
||||
|
||||
// OnOpenForumPostCreate ...
|
||||
func OnOpenForumPostCreate(rules ...Rule) *Matcher { return On("OpenForumPostCreate", rules...) }
|
||||
|
||||
// OnOpenForumPostDelete ...
|
||||
func (e *Engine) OnOpenForumPostDelete(rules ...Rule) *Matcher { return e.On("OpenForumPostDelete", rules...) }
|
||||
|
||||
// OnOpenForumPostDelete ...
|
||||
func OnOpenForumPostDelete(rules ...Rule) *Matcher { return On("OpenForumPostDelete", rules...) }
|
||||
|
||||
// OnOpenForumReplyCreate ...
|
||||
func (e *Engine) OnOpenForumReplyCreate(rules ...Rule) *Matcher { return e.On("OpenForumReplyCreate", rules...) }
|
||||
|
||||
// OnOpenForumReplyCreate ...
|
||||
func OnOpenForumReplyCreate(rules ...Rule) *Matcher { return On("OpenForumReplyCreate", rules...) }
|
||||
|
||||
// OnOpenForumReplyDelete ...
|
||||
func (e *Engine) OnOpenForumReplyDelete(rules ...Rule) *Matcher { return e.On("OpenForumReplyDelete", rules...) }
|
||||
|
||||
// OnOpenForumReplyDelete ...
|
||||
func OnOpenForumReplyDelete(rules ...Rule) *Matcher { return On("OpenForumReplyDelete", rules...) }
|
||||
|
||||
// OnAudioOrLiveChannelMemberEnter ...
|
||||
func (e *Engine) OnAudioOrLiveChannelMemberEnter(rules ...Rule) *Matcher { return e.On("AudioOrLiveChannelMemberEnter", rules...) }
|
||||
|
||||
// OnAudioOrLiveChannelMemberEnter ...
|
||||
func OnAudioOrLiveChannelMemberEnter(rules ...Rule) *Matcher { return On("AudioOrLiveChannelMemberEnter", rules...) }
|
||||
|
||||
// OnAudioOrLiveChannelMemberExit ...
|
||||
func (e *Engine) OnAudioOrLiveChannelMemberExit(rules ...Rule) *Matcher { return e.On("AudioOrLiveChannelMemberExit", rules...) }
|
||||
|
||||
// OnAudioOrLiveChannelMemberExit ...
|
||||
func OnAudioOrLiveChannelMemberExit(rules ...Rule) *Matcher { return On("AudioOrLiveChannelMemberExit", rules...) }
|
||||
|
||||
// OnMessageAuditPass ...
|
||||
func (e *Engine) OnMessageAuditPass(rules ...Rule) *Matcher { return e.On("MessageAuditPass", rules...) }
|
||||
|
||||
// OnMessageAuditPass ...
|
||||
func OnMessageAuditPass(rules ...Rule) *Matcher { return On("MessageAuditPass", rules...) }
|
||||
|
||||
// OnMessageAuditReject ...
|
||||
func (e *Engine) OnMessageAuditReject(rules ...Rule) *Matcher { return e.On("MessageAuditReject", rules...) }
|
||||
|
||||
// OnMessageAuditReject ...
|
||||
func OnMessageAuditReject(rules ...Rule) *Matcher { return On("MessageAuditReject", rules...) }
|
||||
|
||||
// OnForumThreadCreate ...
|
||||
func (e *Engine) OnForumThreadCreate(rules ...Rule) *Matcher { return e.On("ForumThreadCreate", rules...) }
|
||||
|
||||
// OnForumThreadCreate ...
|
||||
func OnForumThreadCreate(rules ...Rule) *Matcher { return On("ForumThreadCreate", rules...) }
|
||||
|
||||
// OnForumThreadUpdate ...
|
||||
func (e *Engine) OnForumThreadUpdate(rules ...Rule) *Matcher { return e.On("ForumThreadUpdate", rules...) }
|
||||
|
||||
// OnForumThreadUpdate ...
|
||||
func OnForumThreadUpdate(rules ...Rule) *Matcher { return On("ForumThreadUpdate", rules...) }
|
||||
|
||||
// OnForumThreadDelete ...
|
||||
func (e *Engine) OnForumThreadDelete(rules ...Rule) *Matcher { return e.On("ForumThreadDelete", rules...) }
|
||||
|
||||
// OnForumThreadDelete ...
|
||||
func OnForumThreadDelete(rules ...Rule) *Matcher { return On("ForumThreadDelete", rules...) }
|
||||
|
||||
// OnForumPostCreate ...
|
||||
func (e *Engine) OnForumPostCreate(rules ...Rule) *Matcher { return e.On("ForumPostCreate", rules...) }
|
||||
|
||||
// OnForumPostCreate ...
|
||||
func OnForumPostCreate(rules ...Rule) *Matcher { return On("ForumPostCreate", rules...) }
|
||||
|
||||
// OnForumPostDelete ...
|
||||
func (e *Engine) OnForumPostDelete(rules ...Rule) *Matcher { return e.On("ForumPostDelete", rules...) }
|
||||
|
||||
// OnForumPostDelete ...
|
||||
func OnForumPostDelete(rules ...Rule) *Matcher { return On("ForumPostDelete", rules...) }
|
||||
|
||||
// OnForumReplyCreate ...
|
||||
func (e *Engine) OnForumReplyCreate(rules ...Rule) *Matcher { return e.On("ForumReplyCreate", rules...) }
|
||||
|
||||
// OnForumReplyCreate ...
|
||||
func OnForumReplyCreate(rules ...Rule) *Matcher { return On("ForumReplyCreate", rules...) }
|
||||
|
||||
// OnForumReplyDelete ...
|
||||
func (e *Engine) OnForumReplyDelete(rules ...Rule) *Matcher { return e.On("ForumReplyDelete", rules...) }
|
||||
|
||||
// OnForumReplyDelete ...
|
||||
func OnForumReplyDelete(rules ...Rule) *Matcher { return On("ForumReplyDelete", rules...) }
|
||||
|
||||
// OnForumPublishAuditResult ...
|
||||
func (e *Engine) OnForumPublishAuditResult(rules ...Rule) *Matcher { return e.On("ForumPublishAuditResult", rules...) }
|
||||
|
||||
// OnForumPublishAuditResult ...
|
||||
func OnForumPublishAuditResult(rules ...Rule) *Matcher { return On("ForumPublishAuditResult", rules...) }
|
||||
|
||||
// OnAudioStart ...
|
||||
func (e *Engine) OnAudioStart(rules ...Rule) *Matcher { return e.On("AudioStart", rules...) }
|
||||
|
||||
// OnAudioStart ...
|
||||
func OnAudioStart(rules ...Rule) *Matcher { return On("AudioStart", rules...) }
|
||||
|
||||
// OnAudioFinish ...
|
||||
func (e *Engine) OnAudioFinish(rules ...Rule) *Matcher { return e.On("AudioFinish", rules...) }
|
||||
|
||||
// OnAudioFinish ...
|
||||
func OnAudioFinish(rules ...Rule) *Matcher { return On("AudioFinish", rules...) }
|
||||
|
||||
// OnAudioOnMic ...
|
||||
func (e *Engine) OnAudioOnMic(rules ...Rule) *Matcher { return e.On("AudioOnMic", rules...) }
|
||||
|
||||
// OnAudioOnMic ...
|
||||
func OnAudioOnMic(rules ...Rule) *Matcher { return On("AudioOnMic", rules...) }
|
||||
|
||||
// OnAudioOffMic ...
|
||||
func (e *Engine) OnAudioOffMic(rules ...Rule) *Matcher { return e.On("AudioOffMic", rules...) }
|
||||
|
||||
// OnAudioOffMic ...
|
||||
func OnAudioOffMic(rules ...Rule) *Matcher { return On("AudioOffMic", rules...) }
|
||||
|
||||
// OnAtMessageCreate ...
|
||||
func (e *Engine) OnAtMessageCreate(rules ...Rule) *Matcher { return e.On("AtMessageCreate", rules...) }
|
||||
|
||||
// OnAtMessageCreate ...
|
||||
func OnAtMessageCreate(rules ...Rule) *Matcher { return On("AtMessageCreate", rules...) }
|
||||
|
||||
// OnPublicMessageDelete ...
|
||||
func (e *Engine) OnPublicMessageDelete(rules ...Rule) *Matcher { return e.On("PublicMessageDelete", rules...) }
|
||||
|
||||
// OnPublicMessageDelete ...
|
||||
func OnPublicMessageDelete(rules ...Rule) *Matcher { return On("PublicMessageDelete", rules...) }
|
||||
|
||||
// OnMessageKeyword ...
|
||||
func OnMessageKeyword(keyword string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageKeyword(keyword, rules...)
|
||||
}
|
||||
|
||||
// OnMessageKeyword ...
|
||||
func (e *Engine) OnMessageKeyword(keyword string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{KeywordRule(keyword)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageCommandGroup ...
|
||||
func OnMessageCommandGroup(commands []string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageCommandGroup(commands, rules...)
|
||||
}
|
||||
|
||||
// OnMessageCommandGroup ...
|
||||
func (e *Engine) OnMessageCommandGroup(commands []string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{CommandGroupRule(commands...)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageSuffixGroup ...
|
||||
func OnMessageSuffixGroup(suffix []string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageSuffixGroup(suffix, rules...)
|
||||
}
|
||||
|
||||
// OnMessageSuffixGroup ...
|
||||
func (e *Engine) OnMessageSuffixGroup(suffix []string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{SuffixGroupRule(suffix...)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessagePrefix ...
|
||||
func OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessagePrefix(prefix, rules...)
|
||||
}
|
||||
|
||||
// OnMessagePrefix ...
|
||||
func (e *Engine) OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{PrefixRule(prefix)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageRegex ...
|
||||
func OnMessageRegex(regexPattern string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageRegex(regexPattern, rules...)
|
||||
}
|
||||
|
||||
// OnMessageRegex ...
|
||||
func (e *Engine) OnMessageRegex(regexPattern string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{RegexRule(regexPattern)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageFullMatch ...
|
||||
func OnMessageFullMatch(src string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageFullMatch(src, rules...)
|
||||
}
|
||||
|
||||
// OnMessageFullMatch ...
|
||||
func (e *Engine) OnMessageFullMatch(src string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{FullMatchRule(src)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageFullMatchGroup ...
|
||||
func OnMessageFullMatchGroup(src []string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageFullMatchGroup(src, rules...)
|
||||
}
|
||||
|
||||
// OnMessageFullMatchGroup ...
|
||||
func (e *Engine) OnMessageFullMatchGroup(src []string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{FullMatchGroupRule(src...)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageKeywordGroup ...
|
||||
func OnMessageKeywordGroup(keywords []string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageKeywordGroup(keywords, rules...)
|
||||
}
|
||||
|
||||
// OnMessageKeywordGroup ...
|
||||
func (e *Engine) OnMessageKeywordGroup(keywords []string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{KeywordGroupRule(keywords...)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessagePrefixGroup ...
|
||||
func OnMessagePrefixGroup(prefix []string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessagePrefixGroup(prefix, rules...)
|
||||
}
|
||||
|
||||
// OnMessagePrefixGroup ...
|
||||
func (e *Engine) OnMessagePrefixGroup(prefix []string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{PrefixGroupRule(prefix...)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageSuffix ...
|
||||
func OnMessageSuffix(suffix string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageSuffix(suffix, rules...)
|
||||
}
|
||||
|
||||
// OnMessageSuffix ...
|
||||
func (e *Engine) OnMessageSuffix(suffix string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{SuffixRule(suffix)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageCommand ...
|
||||
func OnMessageCommand(commands string, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageCommand(commands, rules...)
|
||||
}
|
||||
|
||||
// OnMessageCommand ...
|
||||
func (e *Engine) OnMessageCommand(commands string, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{CommandRule(commands)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
|
||||
// OnMessageShell shell命令触发器
|
||||
func OnMessageShell(command string, model interface{}, rules ...Rule) *Matcher {
|
||||
return defaultEngine.OnMessageShell(command, model, rules...)
|
||||
}
|
||||
|
||||
// OnMessageShell shell命令触发器
|
||||
func (e *Engine) OnMessageShell(command string, model interface{}, rules ...Rule) *Matcher {
|
||||
matcher := &Matcher{
|
||||
Type: "Message",
|
||||
Rules: append([]Rule{ShellRule(command, model)}, rules...),
|
||||
Engine: e,
|
||||
}
|
||||
e.matchers = append(e.matchers, matcher)
|
||||
return StoreMatcher(matcher)
|
||||
}
|
||||
138
event.go
138
event.go
@@ -3,10 +3,23 @@ package nano
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Event ...
|
||||
type Event struct {
|
||||
// Type is payload.T
|
||||
Type string
|
||||
// Seq 序列号
|
||||
Seq uint32
|
||||
// Value 是 D
|
||||
Value any
|
||||
// value is the reflect value of Value
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// processEvent 处理需要关注的业务事件
|
||||
func (bot *Bot) processEvent(payload *WebsocketPayload) {
|
||||
tp := UnderlineToCamel(payload.T)
|
||||
@@ -25,4 +38,129 @@ func (bot *Bot) processEvent(payload *WebsocketPayload) {
|
||||
go ev.h(payload.S, bot, x.UnsafePointer())
|
||||
return
|
||||
}
|
||||
ctx := &Ctx{
|
||||
Event: Event{
|
||||
Type: tp,
|
||||
Seq: payload.S,
|
||||
},
|
||||
State: State{},
|
||||
Caller: bot,
|
||||
}
|
||||
switch tp {
|
||||
case "DirectMessageCreate":
|
||||
ctx.IsToMe = true
|
||||
fallthrough
|
||||
case "MessageCreate", "AtMessageCreate":
|
||||
tp = "Message"
|
||||
}
|
||||
matcherLock.RLock()
|
||||
n := len(matcherMap[tp])
|
||||
if n == 0 {
|
||||
matcherLock.RUnlock()
|
||||
return
|
||||
}
|
||||
log.Debugln(getLogHeader(), "pass", tp, "event to plugins")
|
||||
matchers := make([]*Matcher, n)
|
||||
copy(matchers, matcherMap[tp])
|
||||
matcherLock.RUnlock()
|
||||
x := reflect.New(types[ctx.Type])
|
||||
err := json.Unmarshal(payload.D, x.Interface())
|
||||
if err != nil {
|
||||
log.Warnln(getLogHeader(), "解析", ctx.Type, "事件时出现错误:", err)
|
||||
return
|
||||
}
|
||||
ctx.Value = x.Interface()
|
||||
ctx.value = x
|
||||
switch tp {
|
||||
case "Message":
|
||||
ctx.Message = (*Message)(x.UnsafePointer())
|
||||
log.Infoln(getLogHeader(), "收到 Guild:", ctx.Message.GuildID, ", Channel:", ctx.Message.ChannelID, "消息", ctx.Message.Author.ID, ":", ctx.Message.Content)
|
||||
}
|
||||
go match(ctx, matchers)
|
||||
}
|
||||
|
||||
func match(ctx *Ctx, matchers []*Matcher) {
|
||||
if ctx.Message != nil && ctx.Message.Content != "" { // 确保无空
|
||||
if !ctx.IsToMe {
|
||||
ctx.IsToMe = func(ctx *Ctx) bool {
|
||||
name := ctx.Caller.ready.User.Username
|
||||
if strings.HasPrefix(ctx.Message.Content, name) {
|
||||
log.Debugln(getLogHeader(), "message before process:", ctx.Message.Content)
|
||||
ctx.Message.Content = strings.TrimLeft(ctx.Message.Content[len(name):], " ")
|
||||
log.Debugln(getLogHeader(), "message after process:", ctx.Message.Content)
|
||||
return true
|
||||
}
|
||||
atme := ctx.Caller.AtMe()
|
||||
if strings.HasPrefix(ctx.Message.Content, atme) {
|
||||
log.Debugln(getLogHeader(), "message before process:", ctx.Message.Content)
|
||||
ctx.Message.Content = strings.TrimLeft(ctx.Message.Content[len(atme):], " ")
|
||||
log.Debugln(getLogHeader(), "message after process:", ctx.Message.Content)
|
||||
return true
|
||||
}
|
||||
return OnlyPrivate(ctx)
|
||||
}(ctx)
|
||||
}
|
||||
}
|
||||
log.Debugln(getLogHeader(), "message is to me:", ctx.IsToMe)
|
||||
loop:
|
||||
for _, matcher := range matchers {
|
||||
for k := range ctx.State { // Clear State
|
||||
delete(ctx.State, k)
|
||||
}
|
||||
matcherLock.RLock()
|
||||
m := matcher.copy()
|
||||
matcherLock.RUnlock()
|
||||
ctx.ma = m
|
||||
|
||||
// pre handler
|
||||
if m.Engine != nil {
|
||||
for _, handler := range m.Engine.preHandler {
|
||||
if !handler(ctx) { // 有 pre handler 未满足
|
||||
if m.Break { // 阻断后续
|
||||
break loop
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range m.Rules {
|
||||
if rule != nil && !rule(ctx) { // 有 Rule 的条件未满足
|
||||
if m.Break { // 阻断后续
|
||||
break loop
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
|
||||
// mid handler
|
||||
if m.Engine != nil {
|
||||
for _, handler := range m.Engine.midHandler {
|
||||
if !handler(ctx) { // 有 mid handler 未满足
|
||||
if m.Break { // 阻断后续
|
||||
break loop
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.Process != nil {
|
||||
m.Process(ctx) // 处理事件
|
||||
}
|
||||
if matcher.Temp { // 临时 Matcher 删除
|
||||
matcher.Delete()
|
||||
}
|
||||
|
||||
if m.Engine != nil {
|
||||
// post handler
|
||||
for _, handler := range m.Engine.postHandler {
|
||||
handler(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if m.Block { // 阻断后续
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
example/echo/main.go
Normal file
20
example/echo/main.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
ctrl "github.com/FloatTech/zbpctrl"
|
||||
nano "github.com/fumiama/NanoBot"
|
||||
)
|
||||
|
||||
func init() {
|
||||
nano.Register("echo", &ctrl.Options[*nano.Ctx]{
|
||||
DisableOnDefault: false,
|
||||
Help: "- echo xxx",
|
||||
}).OnMessagePrefix("echo").SetBlock(true).
|
||||
Handle(func(ctx *nano.Ctx) {
|
||||
args := ctx.State["args"].(string)
|
||||
if args == "" {
|
||||
return
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, args)
|
||||
})
|
||||
}
|
||||
44
example/handler/main.go
Normal file
44
example/handler/main.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
nano "github.com/fumiama/NanoBot"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
nano.OpenAPI = nano.SandboxAPI
|
||||
nano.Run(&nano.Bot{
|
||||
AppID: "你的AppID",
|
||||
Token: "你的Token",
|
||||
Secret: "你的Secret, 目前没用到, 可以不填",
|
||||
Intents: nano.IntentPublic,
|
||||
Handler: &nano.Handler{
|
||||
OnAtMessageCreate: func(s uint32, bot *nano.Bot, d *nano.Message) {
|
||||
u := ""
|
||||
if len(d.Attachments) > 0 {
|
||||
u = d.Attachments[0].URL
|
||||
if !strings.HasPrefix(u, "http") {
|
||||
u = "http://" + u
|
||||
}
|
||||
}
|
||||
_, err := bot.PostMessageToChannel(d.ChannelID, &nano.MessagePost{
|
||||
Content: "您发送了: " + d.Content,
|
||||
Image: u,
|
||||
ReplyMessageID: d.ID,
|
||||
MessageReference: &nano.MessageReference{
|
||||
MessageID: d.ID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
bot.PostMessageToChannel(d.ChannelID, &nano.MessagePost{
|
||||
Content: "[ERROR]: " + err.Error(),
|
||||
ReplyMessageID: d.ID,
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
24
example/main.go
Normal file
24
example/main.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/fumiama/NanoBot/example/echo"
|
||||
|
||||
nano "github.com/fumiama/NanoBot"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
nano.OpenAPI = nano.SandboxAPI
|
||||
nano.OnMessageFullMatch("help").SetBlock(true).
|
||||
Handle(func(ctx *nano.Ctx) {
|
||||
_, _ = ctx.SendPlainMessage(false, "echo string")
|
||||
})
|
||||
nano.Run(&nano.Bot{
|
||||
AppID: "你的AppID",
|
||||
Token: "你的Token",
|
||||
Secret: "你的Secret, 目前没用到, 可以不填",
|
||||
Intents: nano.IntentPublic,
|
||||
SuperUsers: []string{"用户ID1", "用户ID2"},
|
||||
})
|
||||
}
|
||||
98
future.go
Normal file
98
future.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package nano
|
||||
|
||||
// FutureEvent 是 ZeroBot 交互式的核心,用于异步获取指定事件
|
||||
type FutureEvent struct {
|
||||
Type string
|
||||
Priority int
|
||||
Rule []Rule
|
||||
Block bool
|
||||
}
|
||||
|
||||
// NewFutureEvent 创建一个FutureEvent, 并返回其指针
|
||||
func NewFutureEvent(Type string, Priority int, Block bool, rule ...Rule) *FutureEvent {
|
||||
return &FutureEvent{
|
||||
Type: Type,
|
||||
Priority: Priority,
|
||||
Rule: rule,
|
||||
Block: Block,
|
||||
}
|
||||
}
|
||||
|
||||
// FutureEvent 返回一个 FutureEvent 实例指针,用于获取满足 Rule 的 未来事件
|
||||
func (m *Matcher) FutureEvent(Type string, rule ...Rule) *FutureEvent {
|
||||
return &FutureEvent{
|
||||
Type: Type,
|
||||
Priority: m.priority,
|
||||
Block: m.Block,
|
||||
Rule: rule,
|
||||
}
|
||||
}
|
||||
|
||||
// Next 返回一个 chan 用于接收下一个指定事件
|
||||
//
|
||||
// 该 chan 必须接收,如需手动取消监听,请使用 Repeat 方法
|
||||
func (n *FutureEvent) Next() <-chan *Ctx {
|
||||
ch := make(chan *Ctx, 1)
|
||||
StoreTempMatcher(&Matcher{
|
||||
Type: n.Type,
|
||||
Block: n.Block,
|
||||
priority: n.Priority,
|
||||
Rules: n.Rule,
|
||||
Engine: defaultEngine,
|
||||
Process: func(ctx *Ctx) {
|
||||
ch <- ctx
|
||||
close(ch)
|
||||
},
|
||||
})
|
||||
return ch
|
||||
}
|
||||
|
||||
// Repeat 返回一个 chan 用于接收无穷个指定事件,和一个取消监听的函数
|
||||
//
|
||||
// 如果没有取消监听,将不断监听指定事件
|
||||
func (n *FutureEvent) Repeat() (recv <-chan *Ctx, cancel func()) {
|
||||
ch, done := make(chan *Ctx, 1), make(chan struct{})
|
||||
go func() {
|
||||
defer close(ch)
|
||||
in := make(chan *Ctx, 1)
|
||||
matcher := StoreMatcher(&Matcher{
|
||||
Type: n.Type,
|
||||
Block: n.Block,
|
||||
priority: n.Priority,
|
||||
Rules: n.Rule,
|
||||
Engine: defaultEngine,
|
||||
Process: func(ctx *Ctx) {
|
||||
in <- ctx
|
||||
},
|
||||
})
|
||||
for {
|
||||
select {
|
||||
case e := <-in:
|
||||
ch <- e
|
||||
case <-done:
|
||||
matcher.Delete()
|
||||
close(in)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, func() {
|
||||
close(done)
|
||||
}
|
||||
}
|
||||
|
||||
// Take 基于 Repeat 封装,返回一个 chan 接收指定数量的事件
|
||||
//
|
||||
// 该 chan 对象必须接收,否则将有 goroutine 泄漏,如需手动取消请使用 Repeat
|
||||
func (n *FutureEvent) Take(num int) <-chan *Ctx {
|
||||
recv, cancel := n.Repeat()
|
||||
ch := make(chan *Ctx, num)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
for i := 0; i < num; i++ {
|
||||
ch <- <-recv
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
25
go.mod
25
go.mod
@@ -3,14 +3,35 @@ module github.com/fumiama/NanoBot
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/FloatTech/floatbox v0.0.0-20230827160415-f0865337a824
|
||||
github.com/FloatTech/zbpctrl v1.5.2
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
|
||||
github.com/fumiama/go-base16384 v1.7.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/wdvxdr1123/ZeroBot v1.7.4
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
github.com/FloatTech/sqlite v0.5.0 // indirect
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fumiama/cron v1.3.0 // indirect
|
||||
github.com/fumiama/go-registry v0.2.6 // indirect
|
||||
github.com/fumiama/go-simple-protobuf v0.1.0 // indirect
|
||||
github.com/fumiama/gofastTEA v0.0.10 // indirect
|
||||
github.com/fumiama/sqlite3 v1.14.6 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
modernc.org/libc v1.14.6 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.0.5 // indirect
|
||||
)
|
||||
|
||||
193
go.sum
193
go.sum
@@ -1,3 +1,11 @@
|
||||
github.com/FloatTech/floatbox v0.0.0-20230827160415-f0865337a824 h1:w72fzQg1Y9+VLSRl7iKzaZ6fG3myyMJfpOSajcjaMDM=
|
||||
github.com/FloatTech/floatbox v0.0.0-20230827160415-f0865337a824/go.mod h1:FwQm6wk+b4wuW54KCKn3zccMX47Q5apnHD/Yakzv0fI=
|
||||
github.com/FloatTech/sqlite v0.5.0 h1:U7J5Omc534PqmH6csfu+ypCo3DS8L91l5lTsxUu3b/U=
|
||||
github.com/FloatTech/sqlite v0.5.0/go.mod h1:i33d92OtR8jcp5fBUvQtospf27+MkfUxnGwnZ95E/dA=
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJGLDNIdRX3BI546D3O7k7vrVueZw=
|
||||
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
|
||||
github.com/FloatTech/zbpctrl v1.5.2 h1:5ap0t2KgROpfTVHqMd9vHKXLeLmRFGI3ZrTPASgFP6s=
|
||||
github.com/FloatTech/zbpctrl v1.5.2/go.mod h1:BVPivMDJCBImPSdwgizb6sqb7rcDaRE65ZjfgthoC7g=
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
||||
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
|
||||
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs=
|
||||
@@ -5,23 +13,200 @@ github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0Uc
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
|
||||
github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
|
||||
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
|
||||
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
|
||||
github.com/fumiama/go-registry v0.2.6 h1:+vEeBUwa1+GC87ujW3Km42fi8O/H7QcpVJWu1iuGNh0=
|
||||
github.com/fumiama/go-registry v0.2.6/go.mod h1:HjYagPZXzR2xCCxaSQerqX7JRzC0yiv2kslDdBiTq/g=
|
||||
github.com/fumiama/go-simple-protobuf v0.1.0 h1:rLzJgNqB6LHNDVMl81yyNt6ZKziWtVfu+ioF0edlEVw=
|
||||
github.com/fumiama/go-simple-protobuf v0.1.0/go.mod h1:5yYNapXq1tQMOZg9bOIVhQlZk9pQqpuFIO4DZLbsdy4=
|
||||
github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ=
|
||||
github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
|
||||
github.com/fumiama/sqlite3 v1.14.6 h1:+e+iygyiDXQJVi7xeXIviBvR7hAc5y20WA9hRwfKn10=
|
||||
github.com/fumiama/sqlite3 v1.14.6/go.mod h1:Xx9a2/OtHuy9pBjow0N+bE/RhNeZ7zZz5xh25vqbA5A=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/wdvxdr1123/ZeroBot v1.7.4 h1:+148rELpf/FCDW2EuvKqpb9bNKcwKRtoh16s2sIb5SE=
|
||||
github.com/wdvxdr1123/ZeroBot v1.7.4/go.mod h1:y29UIOy0RD3P+0meDNIWRhcJF3jtWPN9xP9hgt/AJAU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
|
||||
modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag=
|
||||
modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw=
|
||||
modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ=
|
||||
modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c=
|
||||
modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo=
|
||||
modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg=
|
||||
modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I=
|
||||
modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs=
|
||||
modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8=
|
||||
modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE=
|
||||
modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk=
|
||||
modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w=
|
||||
modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE=
|
||||
modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8=
|
||||
modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc=
|
||||
modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU=
|
||||
modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE=
|
||||
modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk=
|
||||
modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI=
|
||||
modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE=
|
||||
modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg=
|
||||
modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74=
|
||||
modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU=
|
||||
modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU=
|
||||
modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc=
|
||||
modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM=
|
||||
modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/EhQ=
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.84/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccgo/v3 v3.12.86/go.mod h1:dN7S26DLTgVSni1PVA3KxxHTcykyDurf3OgUzNqTSrU=
|
||||
modernc.org/ccgo/v3 v3.12.90/go.mod h1:obhSc3CdivCRpYZmrvO88TXlW0NvoSVvdh/ccRjJYko=
|
||||
modernc.org/ccgo/v3 v3.12.92/go.mod h1:5yDdN7ti9KWPi5bRVWPl8UNhpEAtCjuEE7ayQnzzqHA=
|
||||
modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi4=
|
||||
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
|
||||
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
|
||||
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
|
||||
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
|
||||
modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M=
|
||||
modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU=
|
||||
modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE=
|
||||
modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso=
|
||||
modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8=
|
||||
modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8=
|
||||
modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I=
|
||||
modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk=
|
||||
modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY=
|
||||
modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE=
|
||||
modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg=
|
||||
modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM=
|
||||
modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg=
|
||||
modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo=
|
||||
modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8=
|
||||
modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ=
|
||||
modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA=
|
||||
modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM=
|
||||
modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg=
|
||||
modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE=
|
||||
modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM=
|
||||
modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU=
|
||||
modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw=
|
||||
modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M=
|
||||
modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18=
|
||||
modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8=
|
||||
modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/libc v1.11.88/go.mod h1:h3oIVe8dxmTcchcFuCcJ4nAWaoiwzKCdv82MM0oiIdQ=
|
||||
modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
|
||||
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
|
||||
modernc.org/libc v1.12.0/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
|
||||
modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
|
||||
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
|
||||
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
|
||||
modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
|
||||
modernc.org/libc v1.14.6 h1:SSiZiE5199iYsGM9gtkDj90xqcXVwubWG8CtoYE+Mnk=
|
||||
modernc.org/libc v1.14.6/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
12
handler.go
12
handler.go
@@ -14,6 +14,18 @@ type eventHandlerType struct {
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
var types map[string]reflect.Type // types 便于反射初始化的 types
|
||||
|
||||
func init() {
|
||||
h := reflect.ValueOf(&Handler{}).Elem()
|
||||
t := h.Type()
|
||||
types = make(map[string]reflect.Type, h.NumField()*4)
|
||||
for i := 0; i < h.NumField(); i++ {
|
||||
tp := t.Field(i).Name[2:] // skip On
|
||||
types[tp] = t.Field(i).Type.In(2).Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Handler 事件订阅
|
||||
//
|
||||
// https://bot.q.qq.com/wiki/develop/api/gateway/intents.html
|
||||
|
||||
20
lazy.go
Normal file
20
lazy.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/FloatTech/floatbox/file"
|
||||
)
|
||||
|
||||
// 下载并获取本 engine 文件夹下的懒加载数据
|
||||
func (e *Engine) GetLazyData(filename string, isDataMustEqual bool) ([]byte, error) {
|
||||
if e.datafolder == "" {
|
||||
return nil, errors.New("datafolder is empty")
|
||||
}
|
||||
if !strings.HasSuffix(e.datafolder, "/") || !strings.HasPrefix(e.datafolder, "data/") || !unicode.IsUpper(rune(e.datafolder[5])) {
|
||||
return nil, errors.New("invalid datafolder")
|
||||
}
|
||||
return file.GetLazyData(e.datafolder+filename, "data/control/stor.spb", isDataMustEqual)
|
||||
}
|
||||
106
manager.go
Normal file
106
manager.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"unicode"
|
||||
|
||||
"github.com/FloatTech/floatbox/file"
|
||||
ctrl "github.com/FloatTech/zbpctrl"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Manager ctrl.Manager[*Ctx]
|
||||
|
||||
var (
|
||||
enmap = make(map[string]*Engine)
|
||||
priomap = make(map[int]string) // priomap is map[prio]service
|
||||
foldermap = make(map[string]string) // foldermap is map[folder]service
|
||||
prio uint64
|
||||
m = ctrl.NewManager[*Ctx]("data/control/plugins.db")
|
||||
)
|
||||
|
||||
// Register 注册插件控制器
|
||||
func Register(service string, o *ctrl.Options[*Ctx]) *Engine {
|
||||
prio := int(atomic.AddUint64(&prio, 10))
|
||||
e := newEngine()
|
||||
s, ok := priomap[prio]
|
||||
if ok {
|
||||
panic(fmt.Sprint("prio", prio, "is used by", s))
|
||||
}
|
||||
priomap[prio] = service
|
||||
logrus.Debugln("[control]插件", service, "已设置优先级", prio)
|
||||
e.UsePreHandler(newctrl(service, o))
|
||||
e.prio = prio
|
||||
e.service = service
|
||||
switch {
|
||||
case o.PublicDataFolder != "":
|
||||
if unicode.IsLower([]rune(o.PublicDataFolder)[0]) {
|
||||
panic("public data folder " + o.PublicDataFolder + " must start with an upper case letter")
|
||||
}
|
||||
e.datafolder = "data/" + o.PublicDataFolder + "/"
|
||||
case o.PrivateDataFolder != "":
|
||||
if unicode.IsUpper([]rune(o.PrivateDataFolder)[0]) {
|
||||
panic("private data folder " + o.PrivateDataFolder + " must start with an lower case letter")
|
||||
}
|
||||
e.datafolder = "data/" + o.PrivateDataFolder + "/"
|
||||
default:
|
||||
e.datafolder = "data/nano/"
|
||||
}
|
||||
if e.datafolder != "data/nano/" {
|
||||
s, ok := foldermap[e.datafolder]
|
||||
if ok {
|
||||
panic("folder " + e.datafolder + " has been required by service " + s)
|
||||
}
|
||||
foldermap[e.datafolder] = service
|
||||
}
|
||||
if file.IsNotExist(e.datafolder) {
|
||||
err := os.MkdirAll(e.datafolder, 0755)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
logrus.Debugln("[control]插件", service, "已设置数据目录", e.datafolder)
|
||||
enmap[service] = e
|
||||
return e
|
||||
}
|
||||
|
||||
// Delete 删除插件控制器, 不会删除数据
|
||||
func Delete(service string) {
|
||||
engine, ok := enmap[service]
|
||||
if ok {
|
||||
engine.Delete()
|
||||
m.RLock()
|
||||
_, ok = m.M[service]
|
||||
m.RUnlock()
|
||||
if ok {
|
||||
m.Lock()
|
||||
delete(m.M, service)
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachByPrio iterates through managers by their priority.
|
||||
func ForEachByPrio(iterator func(i int, manager *ctrl.Control[*Ctx]) bool) {
|
||||
for i, v := range cpmp2lstbyprio() {
|
||||
if !iterator(i, v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cpmp2lstbyprio() []*ctrl.Control[*Ctx] {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
ret := make([]*ctrl.Control[*Ctx], 0, len(m.M))
|
||||
for _, v := range m.M {
|
||||
ret = append(ret, v)
|
||||
}
|
||||
sort.SliceStable(ret, func(i, j int) bool {
|
||||
return enmap[ret[i].Service].prio < enmap[ret[j].Service].prio
|
||||
})
|
||||
return ret
|
||||
}
|
||||
150
matcher.go
Normal file
150
matcher.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/wdvxdr1123/ZeroBot/extension/rate"
|
||||
)
|
||||
|
||||
type (
|
||||
// Rule filter the event
|
||||
Rule func(ctx *Ctx) bool
|
||||
// Process 事件处理函数
|
||||
Process func(ctx *Ctx)
|
||||
)
|
||||
|
||||
// Matcher 是 ZeroBot 匹配和处理事件的最小单元
|
||||
type Matcher struct {
|
||||
// Temp 是否为临时Matcher,临时 Matcher 匹配一次后就会删除当前 Matcher
|
||||
Temp bool
|
||||
// Block 是否阻断后续 Matcher,为 true 时当前Matcher匹配成功后,后续Matcher不参与匹配
|
||||
Block bool
|
||||
// Break 是否退出后续匹配流程, 只有 rule 返回 false 且此值为真才会退出, 且不对 mid handler 以下的 rule 生效
|
||||
Break bool
|
||||
// priority 优先级,越小优先级越高
|
||||
priority int
|
||||
// Event 当前匹配到的事件
|
||||
Event *Event
|
||||
// Type 匹配的事件类型
|
||||
Type string
|
||||
// Rules 匹配规则
|
||||
Rules []Rule
|
||||
// Process 处理事件的函数
|
||||
Process Process
|
||||
// Engine 注册 Matcher 的 Engine,Engine可为一系列 Matcher 添加通用 Rule 和 其他钩子
|
||||
Engine *Engine
|
||||
}
|
||||
|
||||
var (
|
||||
// 所有主匹配器列表
|
||||
matcherMap = make(map[string][]*Matcher, 0)
|
||||
// Matcher 修改读写锁
|
||||
matcherLock = sync.RWMutex{}
|
||||
)
|
||||
|
||||
// State store the context of a matcher.
|
||||
type State map[string]any
|
||||
|
||||
func sortMatcher(typ string) {
|
||||
sort.Slice(matcherMap[typ], func(i, j int) bool { // 按优先级排序
|
||||
return matcherMap[typ][i].priority < matcherMap[typ][j].priority
|
||||
})
|
||||
}
|
||||
|
||||
// SetBlock 设置是否阻断后面的 Matcher 触发
|
||||
func (m *Matcher) SetBlock(block bool) *Matcher {
|
||||
m.Block = block
|
||||
return m
|
||||
}
|
||||
|
||||
// setPriority 设置当前 Matcher 优先级
|
||||
func (m *Matcher) setPriority(priority int) *Matcher {
|
||||
matcherLock.Lock()
|
||||
defer matcherLock.Unlock()
|
||||
m.priority = priority
|
||||
sortMatcher(m.Type)
|
||||
return m
|
||||
}
|
||||
|
||||
/*
|
||||
// firstPriority 设置当前 Matcher 优先级 - 0
|
||||
func (m *Matcher) firstPriority() *Matcher {
|
||||
return m.setPriority(0)
|
||||
}
|
||||
*/
|
||||
|
||||
// secondPriority 设置当前 Matcher 优先级 - 1
|
||||
func (m *Matcher) secondPriority() *Matcher {
|
||||
return m.setPriority(1)
|
||||
}
|
||||
|
||||
/*
|
||||
// thirdPriority 设置当前 Matcher 优先级 - 2
|
||||
func (m *Matcher) thirdPriority() *Matcher {
|
||||
return m.setPriority(2)
|
||||
}
|
||||
*/
|
||||
|
||||
// Limit 限速器
|
||||
//
|
||||
// postfn 当请求被拒绝时的操作
|
||||
func (m *Matcher) Limit(limiterfn func(*Ctx) *rate.Limiter, postfn ...func(*Ctx)) *Matcher {
|
||||
m.Rules = append(m.Rules, func(ctx *Ctx) bool {
|
||||
if limiterfn(ctx).Acquire() {
|
||||
return true
|
||||
}
|
||||
if len(postfn) > 0 {
|
||||
for _, fn := range postfn {
|
||||
fn(ctx)
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
return m
|
||||
}
|
||||
|
||||
// StoreMatcher store a matcher to matcher list.
|
||||
func StoreMatcher(m *Matcher) *Matcher {
|
||||
matcherLock.Lock()
|
||||
defer matcherLock.Unlock()
|
||||
matcherMap[m.Type] = append(matcherMap[m.Type], m)
|
||||
sortMatcher(m.Type)
|
||||
return m
|
||||
}
|
||||
|
||||
// StoreTempMatcher store a matcher only triggered once.
|
||||
func StoreTempMatcher(m *Matcher) *Matcher {
|
||||
m.Temp = true
|
||||
StoreMatcher(m)
|
||||
return m
|
||||
}
|
||||
|
||||
// Delete remove the matcher from list
|
||||
func (m *Matcher) Delete() {
|
||||
matcherLock.Lock()
|
||||
defer matcherLock.Unlock()
|
||||
for i, matcher := range matcherMap[m.Type] {
|
||||
if m == matcher {
|
||||
matcherMap[m.Type] = append(matcherMap[m.Type][:i], matcherMap[m.Type][i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Matcher) copy() *Matcher {
|
||||
return &Matcher{
|
||||
Type: m.Type,
|
||||
Rules: m.Rules,
|
||||
Block: m.Block,
|
||||
priority: m.priority,
|
||||
Process: m.Process,
|
||||
Temp: m.Temp,
|
||||
Engine: m.Engine,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 直接处理事件
|
||||
func (m *Matcher) Handle(handler Process) *Matcher {
|
||||
m.Process = handler
|
||||
return m
|
||||
}
|
||||
368
rule.go
Normal file
368
rule.go
Normal file
@@ -0,0 +1,368 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/FloatTech/floatbox/process"
|
||||
ctrl "github.com/FloatTech/zbpctrl"
|
||||
"github.com/wdvxdr1123/ZeroBot/extension"
|
||||
"github.com/wdvxdr1123/ZeroBot/extension/rate"
|
||||
)
|
||||
|
||||
func newctrl(service string, o *ctrl.Options[*Ctx]) Rule {
|
||||
c := m.NewControl(service, o)
|
||||
return func(ctx *Ctx) bool {
|
||||
ctx.State["manager"] = c
|
||||
gid, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
uid, _ := strconv.ParseUint(ctx.Message.Author.ID, 10, 64)
|
||||
return c.Handler(uintptr(unsafe.Pointer(ctx)), int64(gid), int64(uid))
|
||||
}
|
||||
}
|
||||
|
||||
func Lookup(service string) (*ctrl.Control[*Ctx], bool) {
|
||||
return m.Lookup(service)
|
||||
}
|
||||
|
||||
// respLimiterManager 请求响应限速器管理
|
||||
//
|
||||
// 每 1d 4次触发
|
||||
var respLimiterManager = rate.NewManager[string](time.Hour*24, 4)
|
||||
|
||||
func init() {
|
||||
process.NewCustomOnce(&m).Do(func() {
|
||||
OnMessageCommandGroup([]string{
|
||||
"响应", "response", "沉默", "silence",
|
||||
}, UserOrGrpAdmin).SetBlock(true).Limit(func(ctx *Ctx) *rate.Limiter {
|
||||
return respLimiterManager.Load(ctx.Message.ChannelID)
|
||||
}).secondPriority().Handle(func(ctx *Ctx) {
|
||||
grp, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
msg := ""
|
||||
switch ctx.State["command"] {
|
||||
case "响应", "response":
|
||||
if m.CanResponse(int64(grp)) {
|
||||
msg = ctx.Caller.ready.User.Username + "已经在工作了哦~"
|
||||
break
|
||||
}
|
||||
if SuperUserPermission(ctx) {
|
||||
err := m.Response(int64(grp))
|
||||
if err == nil {
|
||||
msg = ctx.Caller.ready.User.Username + "将开始在此工作啦~"
|
||||
} else {
|
||||
msg = "ERROR: " + err.Error()
|
||||
}
|
||||
break
|
||||
}
|
||||
case "沉默", "silence":
|
||||
if !m.CanResponse(int64(grp)) {
|
||||
msg = ctx.Caller.ready.User.Username + "已经在休息了哦~"
|
||||
break
|
||||
}
|
||||
err := m.Silence(int64(grp))
|
||||
if err == nil {
|
||||
msg = ctx.Caller.ready.User.Username + "将开始休息啦~"
|
||||
} else {
|
||||
msg = "ERROR: " + err.Error()
|
||||
}
|
||||
if SuperUserPermission(ctx) {
|
||||
break
|
||||
}
|
||||
default:
|
||||
msg = "ERROR: bad command\"" + fmt.Sprint(ctx.State["command"]) + "\""
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, msg)
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"全局响应", "allresponse", "全局沉默", "allsilence",
|
||||
}, SuperUserPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
msg := ""
|
||||
cmd := ctx.State["command"].(string)
|
||||
switch {
|
||||
case strings.Contains(cmd, "响应") || strings.Contains(cmd, "response"):
|
||||
err := m.Response(0)
|
||||
if err == nil {
|
||||
msg = ctx.Caller.ready.User.Username + "将开始在此工作啦~"
|
||||
} else {
|
||||
msg = "ERROR: " + err.Error()
|
||||
}
|
||||
case strings.Contains(cmd, "沉默") || strings.Contains(cmd, "silence"):
|
||||
err := m.Silence(0)
|
||||
if err == nil {
|
||||
msg = ctx.Caller.ready.User.Username + "将开始休息啦~"
|
||||
} else {
|
||||
msg = "ERROR: " + err.Error()
|
||||
}
|
||||
default:
|
||||
msg = "ERROR: bad command\"" + cmd + "\""
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, msg)
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"启用", "enable", "禁用", "disable",
|
||||
}, UserOrGrpAdmin).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
grp, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
if !m.CanResponse(int64(grp)) {
|
||||
return
|
||||
}
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
service, ok := Lookup(model.Args)
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
if strings.Contains(model.Command, "启用") || strings.Contains(model.Command, "enable") {
|
||||
service.Enable(int64(grp))
|
||||
if service.Options.OnEnable != nil {
|
||||
service.Options.OnEnable(ctx)
|
||||
} else {
|
||||
_, _ = ctx.SendPlainMessage(false, "已启用服务: ", model.Args)
|
||||
}
|
||||
} else {
|
||||
service.Disable(int64(grp))
|
||||
if service.Options.OnDisable != nil {
|
||||
service.Options.OnDisable(ctx)
|
||||
} else {
|
||||
_, _ = ctx.SendPlainMessage(false, "已禁用服务: ", model.Args)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"全局启用", "allenable", "全局禁用", "alldisable",
|
||||
}, OnlyToMe, SuperUserPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
service, ok := Lookup(model.Args)
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
if strings.Contains(model.Command, "启用") || strings.Contains(model.Command, "enable") {
|
||||
service.Enable(0)
|
||||
_, _ = ctx.SendPlainMessage(false, "已全局启用服务: ", model.Args)
|
||||
} else {
|
||||
service.Disable(0)
|
||||
_, _ = ctx.SendPlainMessage(false, "已全局禁用服务: ", model.Args)
|
||||
}
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{"还原", "reset"}, UserOrGrpAdmin).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
grp, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
if !m.CanResponse(int64(grp)) {
|
||||
return
|
||||
}
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
service, ok := Lookup(model.Args)
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
service.Reset(int64(grp))
|
||||
_, _ = ctx.SendPlainMessage(false, "已还原服务的默认启用状态: ", model.Args)
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"禁止", "ban", "允许", "permit",
|
||||
}, AdminPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
grp, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
if !m.CanResponse(int64(grp)) {
|
||||
return
|
||||
}
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
args := strings.Split(model.Args, " ")
|
||||
if len(args) >= 2 {
|
||||
service, ok := Lookup(args[0])
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
grp, _ := strconv.ParseUint(ctx.Message.ChannelID, 10, 64)
|
||||
msg := "*" + args[0] + "报告*"
|
||||
issu := SuperUserPermission(ctx)
|
||||
if strings.Contains(model.Command, "允许") || strings.Contains(model.Command, "permit") {
|
||||
for _, usr := range args[1:] {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
if issu {
|
||||
service.Permit(uid, int64(grp))
|
||||
msg += "\n+ 已允许" + usr
|
||||
} else {
|
||||
member, err := ctx.Caller.GetGuildMemberOf(ctx.Message.GuildID, usr)
|
||||
if err == nil && !member.Pending {
|
||||
service.Permit(uid, int64(grp))
|
||||
msg += "\n+ 已允许" + usr
|
||||
} else {
|
||||
msg += "\nx " + usr + " 不在本群"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, usr := range args[1:] {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
if issu {
|
||||
service.Ban(uid, int64(grp))
|
||||
msg += "\n- 已禁止" + usr
|
||||
} else {
|
||||
member, err := ctx.Caller.GetGuildMemberOf(ctx.Message.GuildID, usr)
|
||||
if err == nil && !member.Pending {
|
||||
service.Ban(uid, int64(grp))
|
||||
msg += "\n- 已禁止" + usr
|
||||
} else {
|
||||
msg += "\nx " + usr + " 不在本群"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, msg)
|
||||
return
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, "参数错误!")
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"全局禁止", "allban", "全局允许", "allpermit",
|
||||
}, SuperUserPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
args := strings.Split(model.Args, " ")
|
||||
if len(args) >= 2 {
|
||||
service, ok := Lookup(args[0])
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
msg := "*" + args[0] + "全局报告*"
|
||||
if strings.Contains(model.Command, "允许") || strings.Contains(model.Command, "permit") {
|
||||
for _, usr := range args[1:] {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
service.Permit(uid, 0)
|
||||
msg += "\n+ 已允许" + usr
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, usr := range args[1:] {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
service.Ban(uid, 0)
|
||||
msg += "\n- 已禁止" + usr
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, msg)
|
||||
return
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, "参数错误!")
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"封禁", "block", "解封", "unblock",
|
||||
}, SuperUserPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
args := strings.Split(model.Args, " ")
|
||||
if len(args) >= 1 {
|
||||
msg := "*报告*"
|
||||
if strings.Contains(model.Command, "解") || strings.Contains(model.Command, "un") {
|
||||
for _, usr := range args {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
if m.DoUnblock(uid) == nil {
|
||||
msg += "\n- 已解封" + usr
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, usr := range args {
|
||||
uid, err := strconv.ParseInt(usr, 10, 64)
|
||||
if err == nil {
|
||||
if m.DoBlock(uid) == nil {
|
||||
msg += "\n+ 已封禁" + usr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, msg)
|
||||
return
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, "参数错误!")
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{
|
||||
"改变默认启用状态", "allflip",
|
||||
}, SuperUserPermission).SetBlock(true).secondPriority().Handle(func(ctx *Ctx) {
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
service, ok := Lookup(model.Args)
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
err := service.Flip()
|
||||
if err != nil {
|
||||
_, _ = ctx.SendPlainMessage(false, "ERROR: ", err)
|
||||
return
|
||||
}
|
||||
_, _ = ctx.SendPlainMessage(false, "已改变全局默认启用状态: ", model.Args)
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{"用法", "usage"}, UserOrGrpAdmin).SetBlock(true).secondPriority().
|
||||
Handle(func(ctx *Ctx) {
|
||||
model := extension.CommandModel{}
|
||||
_ = ctx.Parse(&model)
|
||||
service, ok := Lookup(model.Args)
|
||||
if !ok {
|
||||
_, _ = ctx.SendPlainMessage(false, "没有找到指定服务!")
|
||||
return
|
||||
}
|
||||
if service.Options.Help != "" {
|
||||
gid := ctx.Message.ChannelID
|
||||
grp, _ := strconv.ParseUint(gid, 10, 64)
|
||||
_, _ = ctx.SendPlainMessage(false, service.EnableMarkIn(int64(grp)), " ", service)
|
||||
} else {
|
||||
_, _ = ctx.SendPlainMessage(false, "该服务无帮助!")
|
||||
}
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{"服务列表", "service_list"}, UserOrGrpAdmin).SetBlock(true).secondPriority().
|
||||
Handle(func(ctx *Ctx) {
|
||||
gid := ctx.Message.ChannelID
|
||||
m.RLock()
|
||||
msg := make([]any, 1, len(m.M)*4+1)
|
||||
m.RUnlock()
|
||||
msg[0] = "--------服务列表--------\n发送\"/用法 name\"查看详情\n发送\"/响应\"启用会话"
|
||||
ForEachByPrio(func(i int, service *ctrl.Control[*Ctx]) bool {
|
||||
grp, _ := strconv.ParseUint(gid, 10, 64)
|
||||
msg = append(msg, "\n", i+1, ": ", service.EnableMarkIn(int64(grp)), service.Service)
|
||||
return true
|
||||
})
|
||||
_, _ = ctx.SendPlainMessage(false, msg...)
|
||||
})
|
||||
|
||||
OnMessageCommandGroup([]string{"服务详情", "service_detail"}, UserOrGrpAdmin).SetBlock(true).secondPriority().
|
||||
Handle(func(ctx *Ctx) {
|
||||
gid := ctx.Message.ChannelID
|
||||
m.RLock()
|
||||
msgs := make([]any, 1, len(m.M)*7+1)
|
||||
m.RUnlock()
|
||||
msgs[0] = "---服务详情---\n"
|
||||
ForEachByPrio(func(i int, service *ctrl.Control[*Ctx]) bool {
|
||||
grp, _ := strconv.ParseUint(gid, 10, 64)
|
||||
msgs = append(msgs, i+1, ": ", service.EnableMarkIn(int64(grp)), service.Service, "\n", service, "\n\n")
|
||||
return true
|
||||
})
|
||||
_, _ = ctx.SendPlainMessage(false, msgs...)
|
||||
})
|
||||
})
|
||||
}
|
||||
450
rules.go
Normal file
450
rules.go
Normal file
@@ -0,0 +1,450 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PrefixRule check if the text message has the prefix and trim the prefix
|
||||
//
|
||||
// 检查消息前缀
|
||||
func PrefixRule(prefix string) Rule {
|
||||
return PrefixGroupRule(prefix)
|
||||
}
|
||||
|
||||
// PrefixGroupRule check if the text message has the prefix and trim the prefix
|
||||
//
|
||||
// 检查消息前缀
|
||||
func PrefixGroupRule(prefixes ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(msg.Content, prefix) {
|
||||
ctx.State["prefix"] = prefix
|
||||
arg := strings.TrimLeft(msg.Content[len(prefix):], " ")
|
||||
ctx.State["args"] = arg
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SuffixRule check if the text message has the suffix and trim the suffix
|
||||
//
|
||||
// 检查消息后缀
|
||||
func SuffixRule(suffix string) Rule {
|
||||
return SuffixGroupRule(suffix)
|
||||
}
|
||||
|
||||
// SuffixGroupRule check if the text message has the suffix and trim the suffix
|
||||
//
|
||||
// 检查消息后缀
|
||||
func SuffixGroupRule(suffixes ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(msg.Content, suffix) {
|
||||
ctx.State["suffix"] = suffix
|
||||
arg := strings.TrimRight(msg.Content[:len(msg.Content)-len(suffix)], " ")
|
||||
ctx.State["args"] = arg
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CommandRule check if the message is a command and trim the command name
|
||||
//
|
||||
// this rule only supports Message
|
||||
func CommandRule(command string) Rule {
|
||||
return CommandGroupRule(command)
|
||||
}
|
||||
|
||||
// CommandGroupRule check if the message is a command and trim the command name
|
||||
//
|
||||
// this rule only supports Message
|
||||
func CommandGroupRule(commands ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
msg, ok := ctx.Value.(*Message)
|
||||
if !ok || msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
msg.Content = strings.TrimSpace(msg.Content)
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
cmdMessage := ""
|
||||
args := ""
|
||||
switch {
|
||||
case strings.HasPrefix(msg.Content, "/"):
|
||||
cmdMessage, args, _ = strings.Cut(msg.Content, " ")
|
||||
cmdMessage, _, _ = strings.Cut(cmdMessage, "@")
|
||||
cmdMessage = cmdMessage[1:]
|
||||
default:
|
||||
return false
|
||||
}
|
||||
for _, command := range commands {
|
||||
if strings.HasPrefix(cmdMessage, command) {
|
||||
ctx.State["command"] = command
|
||||
ctx.State["args"] = args
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// RegexRule check if the message can be matched by the regex pattern
|
||||
func RegexRule(regexPattern string) Rule {
|
||||
regex := regexp.MustCompile(regexPattern)
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
if matched := regex.FindStringSubmatch(msg.Content); matched != nil {
|
||||
ctx.State["regex_matched"] = matched
|
||||
return true
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReplyRule check if the message is replying some message
|
||||
//
|
||||
// this rule only supports Message
|
||||
func ReplyRule(messageID string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
msg, ok := ctx.Value.(*Message)
|
||||
if !ok || msg.MessageReference == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
return messageID == msg.MessageReference.MessageID
|
||||
}
|
||||
}
|
||||
|
||||
func KeywordRule(src string) Rule {
|
||||
return KeywordGroupRule(src)
|
||||
}
|
||||
|
||||
// KeywordGroupRule check if the message has a keyword or keywords
|
||||
func KeywordGroupRule(src ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, str := range src {
|
||||
if strings.Contains(msg.Content, str) {
|
||||
ctx.State["keyword"] = str
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FullMatchRule check if src has the same copy of the message
|
||||
func FullMatchRule(src string) Rule {
|
||||
return FullMatchGroupRule(src)
|
||||
}
|
||||
|
||||
// FullMatchGroupRule check if src has the same copy of the message
|
||||
func FullMatchGroupRule(src ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Content == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, str := range src {
|
||||
if str == msg.Content {
|
||||
ctx.State["matched"] = msg.Content
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ShellRule 定义shell-like规则
|
||||
//
|
||||
// this rule only supports Message
|
||||
func ShellRule(cmd string, model interface{}) Rule {
|
||||
cmdRule := CommandRule(cmd)
|
||||
t := reflect.TypeOf(model)
|
||||
return func(ctx *Ctx) bool {
|
||||
if !cmdRule(ctx) {
|
||||
return false
|
||||
}
|
||||
// bind flag to struct
|
||||
args := ParseShell(ctx.State["args"].(string))
|
||||
val := reflect.New(t)
|
||||
fs := registerFlag(t, val)
|
||||
err := fs.Parse(args)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ctx.State["args"] = fs.Args()
|
||||
ctx.State["flag"] = val.Interface()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyToMe only triggered in conditions of @bot or begin with the nicknames
|
||||
//
|
||||
// this rule only supports Message
|
||||
func OnlyToMe(ctx *Ctx) bool {
|
||||
return ctx.IsToMe
|
||||
}
|
||||
|
||||
// CheckUser only triggered by specific person
|
||||
func CheckUser(userID ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Author == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, uid := range userID {
|
||||
if msg.Author.ID == uid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckChannel only triggered in specific channel
|
||||
func CheckChannel(channelID ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.ChannelID == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, cid := range channelID {
|
||||
if msg.ChannelID == cid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckGuild only triggered in specific guild
|
||||
func CheckGuild(guildID ...string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.GuildID == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, gid := range guildID {
|
||||
if msg.GuildID == gid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OnlyPrivate requires that the ctx.Event is direct message
|
||||
func OnlyPrivate(ctx *Ctx) bool {
|
||||
if ctx.Type == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(ctx.Type, "Direct")
|
||||
}
|
||||
|
||||
// OnlyPublic requires that the ctx.Event is channel message
|
||||
func OnlyPublic(ctx *Ctx) bool {
|
||||
if ctx.Type == "" { // 确保无空
|
||||
return false
|
||||
}
|
||||
return !strings.HasPrefix(ctx.Type, "Direct")
|
||||
}
|
||||
|
||||
// OnlyChannel requires that the ctx.Event is channel message
|
||||
func OnlyChannel(ctx *Ctx) bool {
|
||||
return OnlyPublic(ctx)
|
||||
}
|
||||
|
||||
// SuperUserPermission only triggered by the bot's owner
|
||||
func SuperUserPermission(ctx *Ctx) bool {
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Author == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, su := range ctx.Caller.SuperUsers {
|
||||
if su == msg.Author.ID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CreaterPermission only triggered by the creater or higher permission
|
||||
func CreaterPermission(ctx *Ctx) bool {
|
||||
if SuperUserPermission(ctx) {
|
||||
return true
|
||||
}
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Author == nil || msg.Member == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, role := range msg.Member.Roles {
|
||||
if role == RoleIDCreater {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// AdminPermission only triggered by the admins or higher permission
|
||||
func AdminPermission(ctx *Ctx) bool {
|
||||
if SuperUserPermission(ctx) {
|
||||
return true
|
||||
}
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Author == nil || msg.Member == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, role := range msg.Member.Roles {
|
||||
if role == RoleIDCreater || role == RoleIDAdmin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelAdminPermission only triggered by the channel admins or higher permission
|
||||
func ChannelAdminPermission(ctx *Ctx) bool {
|
||||
if SuperUserPermission(ctx) {
|
||||
return true
|
||||
}
|
||||
switch msg := ctx.Value.(type) {
|
||||
case *Message:
|
||||
if msg.Author == nil || msg.Member == nil { // 确保无空
|
||||
return false
|
||||
}
|
||||
for _, role := range msg.Member.Roles {
|
||||
if role == RoleIDCreater || role == RoleIDAdmin || role == RoleIDChannelAdmin {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// UserOrGrpAdmin 允许用户单独使用或群管使用
|
||||
func UserOrGrpAdmin(ctx *Ctx) bool {
|
||||
if OnlyPublic(ctx) {
|
||||
return AdminPermission(ctx)
|
||||
}
|
||||
return OnlyToMe(ctx)
|
||||
}
|
||||
|
||||
// UserOrChannelAdmin 允许用户单独使用或频道管理使用
|
||||
func UserOrChannelAdmin(ctx *Ctx) bool {
|
||||
if OnlyPublic(ctx) {
|
||||
return ChannelAdminPermission(ctx)
|
||||
}
|
||||
return OnlyToMe(ctx)
|
||||
}
|
||||
|
||||
// HasAttachments 消息包含 Attachments (典型: 图片) 返回 true
|
||||
func HasAttachments(ctx *Ctx) bool {
|
||||
msg, ok := ctx.Value.(*Message)
|
||||
if !ok || len(msg.Attachments) == 0 { // 确保无空
|
||||
return false
|
||||
}
|
||||
ctx.State["attachments"] = msg.Attachments
|
||||
return true
|
||||
}
|
||||
|
||||
// MustProvidePhoto 消息不存在图片阻塞120秒至有图片,超时返回 false
|
||||
func MustProvidePhoto(onmessage string, needphohint, failhint string) Rule {
|
||||
return func(ctx *Ctx) bool {
|
||||
msg, ok := ctx.Value.(*Message)
|
||||
if ok && len(msg.Attachments) > 0 { // 确保无空
|
||||
ctx.State["attachments"] = msg.Attachments
|
||||
return true
|
||||
}
|
||||
// 没有图片就索取
|
||||
if needphohint != "" {
|
||||
_, err := ctx.Caller.PostMessageToChannel(msg.ChannelID, &MessagePost{
|
||||
Content: needphohint,
|
||||
MessageReference: &MessageReference{MessageID: msg.ID},
|
||||
ReplyMessageID: msg.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
next := NewFutureEvent(onmessage, 999, false, ctx.CheckSession(), HasAttachments).Next()
|
||||
select {
|
||||
case <-time.After(time.Second * 120):
|
||||
if failhint != "" {
|
||||
_, _ = ctx.SendPlainMessage(true, failhint)
|
||||
}
|
||||
return false
|
||||
case newCtx := <-next:
|
||||
ctx.State["photos"] = newCtx.State["photos"]
|
||||
ctx.Event = newCtx.Event
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
131
shell.go
Normal file
131
shell.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
switch r {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type argType int
|
||||
|
||||
const (
|
||||
argNo argType = iota
|
||||
argSingle
|
||||
argQuoted
|
||||
)
|
||||
|
||||
// ParseShell 将指令转换为指令参数.
|
||||
// modified from https://github.com/mattn/go-shellwords
|
||||
func ParseShell(s string) []string {
|
||||
var args []string
|
||||
buf := strings.Builder{}
|
||||
var escaped, doubleQuoted, singleQuoted, backQuote bool
|
||||
backtick := ""
|
||||
|
||||
got := argNo
|
||||
|
||||
for _, r := range s {
|
||||
if escaped {
|
||||
buf.WriteRune(r)
|
||||
escaped = false
|
||||
got = argSingle
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
if singleQuoted {
|
||||
buf.WriteRune(r)
|
||||
} else {
|
||||
escaped = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if isSpace(r) {
|
||||
if singleQuoted || doubleQuoted || backQuote {
|
||||
buf.WriteRune(r)
|
||||
backtick += string(r)
|
||||
} else if got != argNo {
|
||||
args = append(args, buf.String())
|
||||
buf.Reset()
|
||||
got = argNo
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '`':
|
||||
if !singleQuoted && !doubleQuoted {
|
||||
backtick = ""
|
||||
backQuote = !backQuote
|
||||
}
|
||||
case '"':
|
||||
if !singleQuoted {
|
||||
if doubleQuoted {
|
||||
got = argQuoted
|
||||
}
|
||||
doubleQuoted = !doubleQuoted
|
||||
}
|
||||
case '\'':
|
||||
if !doubleQuoted {
|
||||
if singleQuoted {
|
||||
got = argSingle
|
||||
}
|
||||
singleQuoted = !singleQuoted
|
||||
}
|
||||
default:
|
||||
got = argSingle
|
||||
buf.WriteRune(r)
|
||||
if backQuote {
|
||||
backtick += string(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if got != argNo {
|
||||
args = append(args, buf.String())
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
var (
|
||||
boolType = reflect.TypeOf(false)
|
||||
intType = reflect.TypeOf(0)
|
||||
stringType = reflect.TypeOf("")
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
)
|
||||
|
||||
func registerFlag(t reflect.Type, v reflect.Value) *flag.FlagSet {
|
||||
v = v.Elem()
|
||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
name := field.Tag.Get("flag")
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
help := field.Tag.Get("help")
|
||||
switch field.Type {
|
||||
case boolType:
|
||||
fs.BoolVar(v.Field(i).Addr().Interface().(*bool), name, false, help)
|
||||
case intType:
|
||||
fs.IntVar(v.Field(i).Addr().Interface().(*int), name, 0, help)
|
||||
case stringType:
|
||||
fs.StringVar(v.Field(i).Addr().Interface().(*string), name, "", help)
|
||||
case float64Type:
|
||||
fs.Float64Var(v.Field(i).Addr().Interface().(*float64), name, 0, help)
|
||||
default:
|
||||
panic("unsupported type")
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
45
shell_test.go
Normal file
45
shell_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_parse(t *testing.T) {
|
||||
shellTests := [...]struct {
|
||||
shell string
|
||||
expected []string
|
||||
}{
|
||||
{`rm -rf /*`, []string{"rm", "-rf", "/*"}},
|
||||
{`echo "cat cat" -n`, []string{"echo", "cat cat", "-n"}},
|
||||
{`shutdown halt init`, []string{"shutdown", "halt", "init"}},
|
||||
{`test test2`, []string{"test", "test2"}},
|
||||
}
|
||||
for i, v := range shellTests {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
out := ParseShell(v.shell)
|
||||
assert.Equal(t, v.expected, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_registerFlag(t *testing.T) {
|
||||
type args struct {
|
||||
RF bool `flag:"rf"`
|
||||
File string `flag:"file"`
|
||||
Count int `flag:"count"`
|
||||
}
|
||||
got := args{}
|
||||
expected := args{
|
||||
RF: true,
|
||||
File: "123",
|
||||
Count: 10,
|
||||
}
|
||||
fs := registerFlag(reflect.TypeOf(args{}), reflect.ValueOf(&got))
|
||||
err := fs.Parse([]string{"-rf", "-file=123", "-count", "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, got)
|
||||
}
|
||||
61
single.go
Normal file
61
single.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package nano
|
||||
|
||||
import (
|
||||
"github.com/RomiChan/syncx"
|
||||
)
|
||||
|
||||
// Option 配置项
|
||||
type Option[K comparable] func(*Single[K])
|
||||
|
||||
// Single 反并发
|
||||
type Single[K comparable] struct {
|
||||
group syncx.Map[K, struct{}]
|
||||
key func(ctx *Ctx) K
|
||||
post func(ctx *Ctx)
|
||||
}
|
||||
|
||||
// WithKeyFn 指定反并发的 Key
|
||||
func WithKeyFn[K comparable](fn func(ctx *Ctx) K) Option[K] {
|
||||
return func(s *Single[K]) {
|
||||
s.key = fn
|
||||
}
|
||||
}
|
||||
|
||||
// WithPostFn 指定反并发拦截后的操作
|
||||
func WithPostFn[K comparable](fn func(ctx *Ctx)) Option[K] {
|
||||
return func(s *Single[K]) {
|
||||
s.post = fn
|
||||
}
|
||||
}
|
||||
|
||||
// NewSingle 创建反并发中间件
|
||||
func NewSingle[K comparable](op ...Option[K]) *Single[K] {
|
||||
s := Single[K]{}
|
||||
for _, option := range op {
|
||||
option(&s)
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// Apply 为指定 Engine 添加反并发功能
|
||||
func (s *Single[K]) Apply(engine *Engine) {
|
||||
engine.UseMidHandler(func(ctx *Ctx) bool {
|
||||
if s.key == nil {
|
||||
return true
|
||||
}
|
||||
key := s.key(ctx)
|
||||
if _, ok := s.group.Load(key); ok {
|
||||
if s.post != nil {
|
||||
defer s.post(ctx)
|
||||
}
|
||||
return false
|
||||
}
|
||||
s.group.Store(key, struct{}{})
|
||||
ctx.State["__single-key__"] = key
|
||||
return true
|
||||
})
|
||||
|
||||
engine.UsePostHandler(func(ctx *Ctx) {
|
||||
s.group.Delete(ctx.State["__single-key__"].(K))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user