1
0
mirror of https://github.com/fumiama/ReiBot.git synced 2026-06-05 17:10:25 +08:00

add plugin

This commit is contained in:
源文雨
2022-06-01 00:39:38 +08:00
parent b2d1f0c249
commit 473331b38a
10 changed files with 529 additions and 46 deletions

View File

@@ -5,9 +5,42 @@ Lightweight Telegram bot framework
This framework is a simple wrapper for [go-telegram-bot-api](https://github.com/go-telegram-bot-api/telegram-bot-api), aiming to make the event processing easier.
## Example
## Quick Start
> Here is a plugin-based example
```go
package main
See under `example` folder or below.
import (
rei "github.com/fumiama/ReiBot"
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
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))
})
rei.Run(rei.Bot{
Token: "",
Buffer: 256,
UpdateConfig: tgba.UpdateConfig{
Offset: 0,
Limit: 0,
Timeout: 60,
},
Debug: true,
})
}
```
## Event-Based
> If Handler in Bot is implemented, the plugin function will be disabled.
![example](https://user-images.githubusercontent.com/41315874/171180885-c888a031-7797-4b4b-a232-9ff23f031b32.png)
@@ -32,7 +65,7 @@ func main() {
Timeout: 60,
},
Debug: true,
Handler: rei.Handler{
Handler: &rei.Handler{
OnMessage: func(updateid int, bot *rei.TelegramClient, msg *tgba.Message) {
if len(msg.Text) <= len("测试") {
return
@@ -60,4 +93,4 @@ func main() {
},
})
}
```
```

2
bot.go
View File

@@ -17,7 +17,7 @@ type Bot struct {
// Debug 控制调试信息的输出与否
Debug bool `json:"debug"`
// Handler 注册对各种事件的处理
Handler Handler
Handler *Handler
// handlers 方便调用的 handler
handlers map[string]GeneralHandleType
}

View File

@@ -18,18 +18,20 @@ type TelegramClient struct {
// NewTelegramClient ...
func NewTelegramClient(c *Bot) (tc TelegramClient) {
tc.b = *c
h := reflect.ValueOf(&tc.b.Handler).Elem()
t := h.Type()
tc.b.handlers = make(map[string]GeneralHandleType, 16)
for i := 0; i < h.NumField(); i++ {
f := h.Field(i)
if f.IsZero() {
continue
if tc.b.Handler != nil {
h := reflect.ValueOf(tc.b.Handler).Elem()
t := h.Type()
tc.b.handlers = make(map[string]GeneralHandleType, 16)
for i := 0; i < h.NumField(); i++ {
f := h.Field(i)
if f.IsZero() {
continue
}
tp := t.Field(i).Name[2:]
log.Println("[INFO] register handler", tp)
handler := f.Interface()
tc.b.handlers[tp] = *(*GeneralHandleType)(unsafe.Add(unsafe.Pointer(&handler), unsafe.Sizeof(uintptr(0))))
}
tp := t.Field(i).Name[2:]
log.Println("[INFO] register handler", tp)
handler := f.Interface()
tc.b.handlers[tp] = *(*GeneralHandleType)(unsafe.Add(unsafe.Pointer(&handler), unsafe.Sizeof(uintptr(0))))
}
return
}

8
context.go Normal file
View File

@@ -0,0 +1,8 @@
package rei
type Ctx struct {
Event
State
Caller *TelegramClient
ma *Matcher
}

176
engine.go Normal file
View File

@@ -0,0 +1,176 @@
package rei
// New 生成空引擎
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
}
// 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...)
}
// 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)
}
// OnMessage 消息触发器
func (e *Engine) OnMessage(rules ...Rule) *Matcher { return e.On("Message", rules...) }
// OnMessage 消息触发器
func OnMessage(rules ...Rule) *Matcher { return On("Message", rules...) }
// OnEditedMessage 修改消息触发器
func (e *Engine) OnEditedMessage(rules ...Rule) *Matcher { return e.On("EditedMessage", rules...) }
// OnEditedMessage 修改消息触发器
func OnEditedMessage(rules ...Rule) *Matcher { return On("EditedMessage", rules...) }
// OnChannelPost ...
func (e *Engine) OnChannelPost(rules ...Rule) *Matcher { return e.On("ChannelPost", rules...) }
// OnChannelPost ...
func OnChannelPost(rules ...Rule) *Matcher { return On("ChannelPost", rules...) }
// OnEditedChannelPost ...
func (e *Engine) OnEditedChannelPost(rules ...Rule) *Matcher {
return e.On("EditedChannelPost", rules...)
}
// OnEditedChannelPost ...
func OnEditedChannelPost(rules ...Rule) *Matcher {
return On("EditedChannelPost", rules...)
}
// OnInlineQuery ...
func (e *Engine) OnInlineQuery(rules ...Rule) *Matcher { return e.On("InlineQuery", rules...) }
// OnInlineQuery ...
func OnInlineQuery(rules ...Rule) *Matcher { return On("InlineQuery", rules...) }
// OnChosenInlineResult ...
func (e *Engine) OnChosenInlineResult(rules ...Rule) *Matcher {
return e.On("ChosenInlineResult", rules...)
}
// OnChosenInlineResult ...
func OnChosenInlineResult(rules ...Rule) *Matcher { return On("ChosenInlineResult", rules...) }
// OnCallbackQuery ...
func (e *Engine) OnCallbackQuery(rules ...Rule) *Matcher { return e.On("CallbackQuery", rules...) }
// OnCallbackQuery ...
func OnCallbackQuery(rules ...Rule) *Matcher { return On("CallbackQuery", rules...) }
// OnShippingQuery ...
func (e *Engine) OnShippingQuery(rules ...Rule) *Matcher { return e.On("ShippingQuery", rules...) }
// OnShippingQuery ...
func OnShippingQuery(rules ...Rule) *Matcher { return On("ShippingQuery", rules...) }
// OnPreCheckoutQuery ...
func (e *Engine) OnPreCheckoutQuery(rules ...Rule) *Matcher {
return e.On("PreCheckoutQuery", rules...)
}
// OnPreCheckoutQuery ...
func OnPreCheckoutQuery(rules ...Rule) *Matcher { return On("PreCheckoutQuery", rules...) }
// OnPoll ...
func (e *Engine) OnPoll(rules ...Rule) *Matcher { return e.On("Poll", rules...) }
// OnPoll ...
func OnPoll(rules ...Rule) *Matcher { return On("Poll", rules...) }
// OnPollAnswer ...
func (e *Engine) OnPollAnswer(rules ...Rule) *Matcher { return e.On("PollAnswer", rules...) }
// OnPollAnswer ...
func OnPollAnswer(rules ...Rule) *Matcher { return On("PollAnswer", rules...) }
// OnMyChatMember ...
func (e *Engine) OnMyChatMember(rules ...Rule) *Matcher { return e.On("MyChatMember", rules...) }
// OnMyChatMember ...
func OnMyChatMember(rules ...Rule) *Matcher { return On("MyChatMember", rules...) }
// OnChatMember ...
func (e *Engine) OnChatMember(rules ...Rule) *Matcher { return e.On("ChatMember", rules...) }
// OnChatMember ...
func OnChatMember(rules ...Rule) *Matcher { return On("ChatMember", rules...) }
// OnChatJoinRequest ...
func (e *Engine) OnChatJoinRequest(rules ...Rule) *Matcher { return e.On("ChatJoinRequest", rules...) }
// OnChatJoinRequest ...
func OnChatJoinRequest(rules ...Rule) *Matcher { return On("ChatJoinRequest", rules...) }
// OnPrefix 前缀触发器
func OnMessagePrefix(prefix string, rules ...Rule) *Matcher {
return defaultEngine.OnMessagePrefix(prefix, rules...)
}
// OnPrefix 前缀触发器
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)
}

View File

@@ -14,7 +14,7 @@ type Event struct {
// UpdateID is the update's unique identifier.
UpdateID int
// Value is the non-null field value in Update
Value reflect.Value
Value any
}
func (tc *TelegramClient) processEvent(update tgba.Update) {
@@ -26,6 +26,29 @@ func (tc *TelegramClient) processEvent(update tgba.Update) {
continue
}
tp := t.Field(i).Name
if tc.b.Handler == nil {
matcherLock.RLock()
n := len(matcherMap[tp])
if n == 0 {
matcherLock.RUnlock()
continue
}
log.Println("[INFO] pass", tp, "event to plugins")
matchers := make([]*Matcher, n)
copy(matchers, matcherMap[tp])
matcherLock.RUnlock()
ctx := &Ctx{
Event: Event{
Type: tp,
UpdateID: update.UpdateID,
Value: f.Interface(),
},
State: State{},
Caller: tc,
}
match(ctx, matchers)
continue
}
h, ok := tc.b.handlers[tp]
if !ok {
continue
@@ -34,3 +57,58 @@ func (tc *TelegramClient) processEvent(update tgba.Update) {
go h(update.UpdateID, tc, f.UnsafePointer())
}
}
func match(ctx *Ctx, matchers []*Matcher) {
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 未满足
continue loop
}
}
}
for _, rule := range m.Rules {
if rule != nil && !rule(ctx) { // 有 Rule 的条件未满足
continue loop
}
}
// mid handler
if m.Engine != nil {
for _, handler := range m.Engine.midHandler {
if !handler(ctx) { // 有 mid handler 未满足
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
}
}
}

48
example/handler/main.go Normal file
View File

@@ -0,0 +1,48 @@
package main
import (
"log"
"strings"
rei "github.com/fumiama/ReiBot"
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
rei.Run(rei.Bot{
Token: "",
Buffer: 256,
UpdateConfig: tgba.UpdateConfig{
Offset: 0,
Limit: 0,
Timeout: 60,
},
Debug: true,
Handler: &rei.Handler{
OnMessage: func(updateid int, bot *rei.TelegramClient, msg *tgba.Message) {
if len(msg.Text) <= len("测试") {
return
}
if !strings.HasPrefix(msg.Text, "测试") {
return
}
_, err := bot.Send(tgba.NewMessage(msg.Chat.ID, msg.Text[len("测试"):]))
if err != nil {
log.Println("[ERRO]", err)
}
},
OnEditedMessage: func(updateid int, bot *rei.TelegramClient, msg *tgba.Message) {
if len(msg.Text) <= len("测试") {
return
}
if !strings.HasPrefix(msg.Text, "测试") {
return
}
_, err := bot.Send(tgba.NewMessage(msg.Chat.ID, "已编辑:"+msg.Text[len("测试"):]))
if err != nil {
log.Println("[ERRO]", err)
}
},
},
})
}

View File

@@ -1,14 +1,20 @@
package main
import (
"log"
"strings"
rei "github.com/fumiama/ReiBot"
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func main() {
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))
})
rei.Run(rei.Bot{
Token: "",
Buffer: 256,
@@ -18,31 +24,5 @@ func main() {
Timeout: 60,
},
Debug: true,
Handler: rei.Handler{
OnMessage: func(updateid int, bot *rei.TelegramClient, msg *tgba.Message) {
if len(msg.Text) <= len("测试") {
return
}
if !strings.HasPrefix(msg.Text, "测试") {
return
}
_, err := bot.Send(tgba.NewMessage(msg.Chat.ID, msg.Text[len("测试"):]))
if err != nil {
log.Println("[ERRO]", err)
}
},
OnEditedMessage: func(updateid int, bot *rei.TelegramClient, msg *tgba.Message) {
if len(msg.Text) <= len("测试") {
return
}
if !strings.HasPrefix(msg.Text, "测试") {
return
}
_, err := bot.Send(tgba.NewMessage(msg.Chat.ID, "已编辑:"+msg.Text[len("测试"):]))
if err != nil {
log.Println("[ERRO]", err)
}
},
},
})
}

130
matcher.go Normal file
View File

@@ -0,0 +1,130 @@
package rei
import (
"sort"
"sync"
)
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
// Priority 优先级,越小优先级越高
Priority int
// Event 当前匹配到的事件
Event *Event
// Type 匹配的事件类型
Type string
// Rules 匹配规则
Rules []Rule
// Process 处理事件的函数
Process Process
// Engine 注册 Matcher 的 EngineEngine可为一系列 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]interface{}
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)
}
// BindEngine bind the matcher to a engine
func (m *Matcher) BindEngine(e *Engine) *Matcher {
m.Engine = e
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
}

28
rules.go Normal file
View File

@@ -0,0 +1,28 @@
package rei
import (
"strings"
tgba "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
// PrefixRule check if the message has the prefix and trim the prefix
//
// 检查消息前缀
func PrefixRule(prefixes ...string) Rule {
return func(ctx *Ctx) bool {
msg, ok := ctx.Value.(*tgba.Message)
if !ok || msg.Text == "" { // 确保无空
return false
}
for _, prefix := range prefixes {
if strings.HasPrefix(msg.Text, prefix) {
ctx.State["prefix"] = prefix
arg := strings.TrimLeft(msg.Text[len(prefix):], " ")
ctx.State["args"] = arg
return true
}
}
return false
}
}