mirror of
https://github.com/fumiama/ReiBot.git
synced 2026-06-13 06:50:26 +08:00
add more
This commit is contained in:
BIN
.github/Misaki.png
vendored
Normal file
BIN
.github/Misaki.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 406 KiB |
17
README.md
17
README.md
@@ -1,5 +1,14 @@
|
|||||||
# ReiBot
|
<div align="center">
|
||||||
Lightweight Telegram bot framework
|
<a href="https://crypko.ai/crypko/GtWYDpVMx5GYm/">
|
||||||
|
<img src=".github/Misaki.png" alt="看板娘" width = "400">
|
||||||
|
</a><br>
|
||||||
|
|
||||||
|
<h1>ReiBot</h1>
|
||||||
|
Lightweight Telegram bot framework<br><br>
|
||||||
|
|
||||||
|
<img src="http://cmoe.azurewebsites.net/cmoe?name=ReiBot&theme=r34" /><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
@@ -8,7 +17,7 @@ This framework is a simple wrapper for [go-telegram-bot-api](https://github.com/
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
> Here is a plugin-based example
|
> Here is a plugin-based example
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
@@ -45,7 +54,7 @@ func main() {
|
|||||||
|
|
||||||
> If Handler in Bot is implemented, the plugin function will be disabled.
|
> If Handler in Bot is implemented, the plugin function will be disabled.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|||||||
8
bot.go
8
bot.go
@@ -9,13 +9,15 @@ import (
|
|||||||
type Bot struct {
|
type Bot struct {
|
||||||
// Token bot 的 token
|
// Token bot 的 token
|
||||||
// see https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
// see https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
||||||
Token string `json:"token"`
|
Token string
|
||||||
// Buffer 控制消息队列的长度
|
// Buffer 控制消息队列的长度
|
||||||
Buffer int `json:"buffer"`
|
Buffer int
|
||||||
// UpdateConfig 配置消息获取
|
// UpdateConfig 配置消息获取
|
||||||
tgba.UpdateConfig
|
tgba.UpdateConfig
|
||||||
|
// SuperUsers 超级用户
|
||||||
|
SuperUsers []int64
|
||||||
// Debug 控制调试信息的输出与否
|
// Debug 控制调试信息的输出与否
|
||||||
Debug bool `json:"debug"`
|
Debug bool
|
||||||
// Handler 注册对各种事件的处理
|
// Handler 注册对各种事件的处理
|
||||||
Handler *Handler
|
Handler *Handler
|
||||||
// handlers 方便调用的 handler
|
// handlers 方便调用的 handler
|
||||||
|
|||||||
14
context.go
14
context.go
@@ -1,8 +1,22 @@
|
|||||||
package rei
|
package rei
|
||||||
|
|
||||||
|
import tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
|
||||||
type Ctx struct {
|
type Ctx struct {
|
||||||
Event
|
Event
|
||||||
State
|
State
|
||||||
Caller *TelegramClient
|
Caller *TelegramClient
|
||||||
ma *Matcher
|
ma *Matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckSession 判断会话连续性
|
||||||
|
func (ctx *Ctx) CheckSession() Rule {
|
||||||
|
msg := ctx.Value.(*tgba.Message)
|
||||||
|
return func(ctx2 *Ctx) bool {
|
||||||
|
msg2, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.From == nil || msg.Chat == nil || msg2.From == nil || msg2.Chat == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return msg.From.ID == msg2.From.ID && msg.Chat.ID == msg2.Chat.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
180
engine.go
180
engine.go
@@ -159,12 +159,12 @@ func (e *Engine) OnChatJoinRequest(rules ...Rule) *Matcher { return e.On("ChatJo
|
|||||||
// OnChatJoinRequest ...
|
// OnChatJoinRequest ...
|
||||||
func OnChatJoinRequest(rules ...Rule) *Matcher { return On("ChatJoinRequest", rules...) }
|
func OnChatJoinRequest(rules ...Rule) *Matcher { return On("ChatJoinRequest", rules...) }
|
||||||
|
|
||||||
// OnPrefix 前缀触发器
|
// OnMessagePrefix 前缀触发器
|
||||||
func OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
func OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
||||||
return defaultEngine.OnMessagePrefix(prefix, rules...)
|
return defaultEngine.OnMessagePrefix(prefix, rules...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPrefix 前缀触发器
|
// OnMessagePrefix 前缀触发器
|
||||||
func (e *Engine) OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
func (e *Engine) OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
||||||
matcher := &Matcher{
|
matcher := &Matcher{
|
||||||
Type: "Message",
|
Type: "Message",
|
||||||
@@ -174,3 +174,179 @@ func (e *Engine) OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
|
|||||||
e.matchers = append(e.matchers, matcher)
|
e.matchers = append(e.matchers, matcher)
|
||||||
return StoreMatcher(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessageRegex 正则触发器
|
||||||
|
func OnMessageRegex(regexPattern string, rules ...Rule) *Matcher {
|
||||||
|
return defaultEngine.OnMessageRegex(regexPattern, rules...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnRegex 正则触发器
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessageKeyword 关键词触发器
|
||||||
|
func OnMessageKeyword(keyword string, rules ...Rule) *Matcher {
|
||||||
|
return defaultEngine.OnMessageKeyword(keyword, rules...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnKeyword 关键词触发器
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{FullMatchRule(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{KeywordRule(keywords...)}, 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{CommandRule(commands...)}, 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{PrefixRule(prefix...)}, 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{SuffixRule(suffix...)}, 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)
|
||||||
|
}
|
||||||
|
|||||||
18
example/echo/main.go
Normal file
18
example/echo/main.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package echo
|
||||||
|
|
||||||
|
import (
|
||||||
|
rei "github.com/fumiama/ReiBot"
|
||||||
|
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rei.OnMessagePrefix("echo").SetBlock(true).SecondPriority().
|
||||||
|
Handle(func(ctx *rei.Ctx) {
|
||||||
|
args := ctx.State["args"].(string)
|
||||||
|
if args == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := ctx.Value.(*tgba.Message)
|
||||||
|
ctx.Caller.Send(tgba.NewMessage(msg.Chat.ID, args))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/fumiama/ReiBot/example/echo"
|
||||||
|
|
||||||
rei "github.com/fumiama/ReiBot"
|
rei "github.com/fumiama/ReiBot"
|
||||||
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rei.OnMessagePrefix("echo").SetBlock(true).SecondPriority().
|
rei.OnMessageFullMatch("help").SetBlock(true).SecondPriority().
|
||||||
Handle(func(ctx *rei.Ctx) {
|
Handle(func(ctx *rei.Ctx) {
|
||||||
args := ctx.State["args"].(string)
|
|
||||||
if args == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msg := ctx.Value.(*tgba.Message)
|
msg := ctx.Value.(*tgba.Message)
|
||||||
ctx.Caller.Send(tgba.NewMessage(msg.Chat.ID, args))
|
ctx.Caller.Send(tgba.NewMessage(msg.Chat.ID, "echo string"))
|
||||||
})
|
})
|
||||||
rei.Run(rei.Bot{
|
rei.Run(rei.Bot{
|
||||||
Token: "",
|
Token: "",
|
||||||
|
|||||||
98
future.go
Normal file
98
future.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package rei
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
11
go.mod
11
go.mod
@@ -2,4 +2,13 @@ module github.com/fumiama/ReiBot
|
|||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
require (
|
||||||
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
|
github.com/stretchr/testify v1.7.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||||
|
)
|
||||||
|
|||||||
11
go.sum
11
go.sum
@@ -1,2 +1,13 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
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=
|
||||||
|
|||||||
276
rules.go
276
rules.go
@@ -1,12 +1,15 @@
|
|||||||
package rei
|
package rei
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrefixRule check if the message has the prefix and trim the prefix
|
// PrefixRule check if the text message has the prefix and trim the prefix
|
||||||
//
|
//
|
||||||
// 检查消息前缀
|
// 检查消息前缀
|
||||||
func PrefixRule(prefixes ...string) Rule {
|
func PrefixRule(prefixes ...string) Rule {
|
||||||
@@ -26,3 +29,274 @@ func PrefixRule(prefixes ...string) Rule {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuffixRule check if the text message has the suffix and trim the suffix
|
||||||
|
//
|
||||||
|
// 检查消息后缀
|
||||||
|
func SuffixRule(suffixes ...string) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, suffix := range suffixes {
|
||||||
|
if strings.HasSuffix(msg.Text, suffix) {
|
||||||
|
ctx.State["suffix"] = suffix
|
||||||
|
arg := strings.TrimRight(msg.Text[:len(msg.Text)-len(suffix)], " ")
|
||||||
|
ctx.State["args"] = arg
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandRule check if the message is a command and trim the command name
|
||||||
|
func CommandRule(commands ...string) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" || !msg.IsCommand() { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx.State["command"] = msg.CommandWithAt()
|
||||||
|
ctx.State["args"] = msg.CommandArguments()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if matched := regex.FindStringSubmatch(msg.Text); matched != nil {
|
||||||
|
ctx.State["regex_matched"] = matched
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyRule check if the message is replying some message
|
||||||
|
func ReplyRule(messageID int) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.ReplyToMessage == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return messageID == msg.MessageID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeywordRule check if the message has a keyword or keywords
|
||||||
|
func KeywordRule(src ...string) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, str := range src {
|
||||||
|
if strings.Contains(msg.Text, str) {
|
||||||
|
ctx.State["keyword"] = str
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullMatchRule check if src has the same copy of the message
|
||||||
|
func FullMatchRule(src ...string) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, str := range src {
|
||||||
|
if str == msg.Text {
|
||||||
|
ctx.State["matched"] = msg.Text
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShellRule 定义shell-like规则
|
||||||
|
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
|
||||||
|
func OnlyToMe(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Text == "" { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
name := ctx.Caller.Self.String()
|
||||||
|
if strings.HasPrefix(msg.Text, name) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
n := 0
|
||||||
|
for _, e := range msg.Entities {
|
||||||
|
if e.IsMention() && e.Length > 0 && msg.Text[n+1:n+e.Length] == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
n += e.Length
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUser only triggered by specific person
|
||||||
|
func CheckUser(userId ...int64) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.From == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, uid := range userId {
|
||||||
|
if msg.From.ID == uid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckChat only triggered in specific chat
|
||||||
|
func CheckChat(chatId ...int64) Rule {
|
||||||
|
return func(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.Chat == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, cid := range chatId {
|
||||||
|
if msg.Chat.ID == cid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperUserPermission only triggered by the bot's owner
|
||||||
|
func SuperUserPermission(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.From == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, su := range ctx.Caller.b.SuperUsers {
|
||||||
|
if su == msg.From.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreaterPermission only triggered by the group creater or higher permission
|
||||||
|
func CreaterPermission(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.From == nil || msg.Chat == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, su := range ctx.Caller.b.SuperUsers {
|
||||||
|
if su == msg.From.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, err := ctx.Caller.GetChatMember(
|
||||||
|
tgba.GetChatMemberConfig{
|
||||||
|
ChatConfigWithUser: tgba.ChatConfigWithUser{
|
||||||
|
ChatID: msg.Chat.ID,
|
||||||
|
UserID: msg.From.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.IsCreator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminPermission only triggered by the group admins or higher permission
|
||||||
|
func AdminPermission(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || msg.From == nil || msg.Chat == nil { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, su := range ctx.Caller.b.SuperUsers {
|
||||||
|
if su == msg.From.ID {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, err := ctx.Caller.GetChatMember(
|
||||||
|
tgba.GetChatMemberConfig{
|
||||||
|
ChatConfigWithUser: tgba.ChatConfigWithUser{
|
||||||
|
ChatID: msg.Chat.ID,
|
||||||
|
UserID: msg.From.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return m.IsCreator() || m.IsAdministrator()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPhoto 消息是图片返回 true
|
||||||
|
func IsPhoto(ctx *Ctx) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if !ok || len(msg.Photo) == 0 { // 确保无空
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx.State["photos"] = msg.Photo
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustProvidePhoto 消息不存在图片阻塞120秒至有图片,超时返回 false
|
||||||
|
func MustProvidePhoto(ctx *Ctx, needphohint, failhint string) bool {
|
||||||
|
msg, ok := ctx.Value.(*tgba.Message)
|
||||||
|
if ok && len(msg.Photo) > 0 { // 确保无空
|
||||||
|
ctx.State["photos"] = msg.Photo
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 没有图片就索取
|
||||||
|
if needphohint != "" {
|
||||||
|
_, err := ctx.Caller.Send(tgba.NewMessage(msg.Chat.ID, needphohint))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next := NewFutureEvent("message", 999, false, ctx.CheckSession(), IsPhoto).Next()
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second * 120):
|
||||||
|
if failhint != "" {
|
||||||
|
_, _ = ctx.Caller.Send(tgba.NewMessage(msg.Chat.ID, 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 rei
|
||||||
|
|
||||||
|
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 rei
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user