Skip to content

Commit 9fb9c10

Browse files
committed
feat(plugins): bili链接解析模块新增、actionFunc增加返回是否发送消息的布尔值用于更好控制消息回复、plugin中支持调用wcfClient
1 parent d1e58f6 commit 9fb9c10

File tree

11 files changed

+279
-120
lines changed

11 files changed

+279
-120
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ go 1.22
55
toolchain go1.22.5
66

77
require (
8+
github.com/Clov614/bilibili v0.1.2
89
github.com/Clov614/logging v0.1.4
9-
github.com/Clov614/wcf-rpc-sdk v0.4.1
10+
github.com/Clov614/wcf-rpc-sdk v0.4.2
1011
github.com/eatmoreapple/openwechat v1.4.10
1112
github.com/gin-gonic/gin v1.10.0
1213
github.com/google/uuid v1.6.0
@@ -17,7 +18,7 @@ require (
1718
)
1819

1920
//replace github.com/eatmoreapple/openwechat => github.com/Clov614/openwechat v1.4.8
20-
//replace github.com/Clov614/wcf-rpc-sdk v0.4.1 => E:\Applications_Dev\Go_workspace\github.com\Clov614\wcf-rpc-sdk
21+
//replace github.com/Clov614/wcf-rpc-sdk v0.4.2 => E:\Applications_Dev\Go_workspace\github.com\Clov614\wcf-rpc-sdk
2122

2223
require (
2324
github.com/Microsoft/go-winio v0.5.2 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
github.com/Clov614/bilibili v0.1.2 h1:vi1Ykn5+xgrwfpR/xLgdXvoNdulaZitXP9BOgdy0n+I=
2+
github.com/Clov614/bilibili v0.1.2/go.mod h1:th+qNf6jfSDH03oCM4s347a+i7IpCgZyNRIHY8AAXe8=
13
github.com/Clov614/logging v0.1.4 h1:wvTJHIPg1KQdxv23khFcZk5I4rA1jhyCCeCC0UR43AQ=
24
github.com/Clov614/logging v0.1.4/go.mod h1:Vxr9X5cvKXT17F/iBeBj8w6WSAuBQppsvtoCi0UXxe0=
3-
github.com/Clov614/wcf-rpc-sdk v0.4.1 h1:Vb2hJuy+rk1G2tW/Dz2dQf5gkXN3q38xNnWYHaRvHHw=
4-
github.com/Clov614/wcf-rpc-sdk v0.4.1/go.mod h1:ZBJKTyIs6Tkip9n81iE00lUg0eD+dLt7/vfvWcEiAX4=
5+
github.com/Clov614/wcf-rpc-sdk v0.4.2 h1:RCuPkd2xcAF5eDR83QUb0pbX4/cgeeJLxraiZlDHQLo=
6+
github.com/Clov614/wcf-rpc-sdk v0.4.2/go.mod h1:ZBJKTyIs6Tkip9n81iE00lUg0eD+dLt7/vfvWcEiAX4=
57
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
68
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
79
github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=

main.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ package main
33
import (
44
"context"
55
"flag"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
"time"
10+
611
"github.com/Clov614/logging"
712
"github.com/Clov614/rikka-bot-wechat/rikkabot"
813
"github.com/Clov614/rikka-bot-wechat/rikkabot/adapter"
914
"github.com/Clov614/rikka-bot-wechat/rikkabot/onebot/httpapi"
1015
wcf "github.com/Clov614/wcf-rpc-sdk"
1116
"github.com/gin-gonic/gin"
1217
"github.com/rs/zerolog"
13-
"time"
1418
)
1519

1620
func main() {
@@ -84,6 +88,18 @@ func main() {
8488
rbot.ExitWithErr(1101, "微信未登录或掉线")
8589
}()
8690

91+
// 捕获 Ctrl + C 信号
92+
sigCh := make(chan os.Signal, 1)
93+
defer close(sigCh)
94+
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
95+
96+
// 启动一个 goroutine 来处理信号
97+
go func() {
98+
<-sigCh // 阻塞,直到接收到信号
99+
logging.Info("接收到退出信号 (Ctrl + C),正在退出...")
100+
rbot.Exit() // 优雅地退出 RikkaBot
101+
}()
102+
87103
// 阻塞主goroutine, 直到发生异常或者用户主动退出
88104
err := rbot.Block()
89105
if err != nil {

rikkabot/adapter/adapter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ func (a *Adapter) receiveMsg(msg *wcf.Message) {
223223
}
224224

225225
func (a *Adapter) sendMsg(sendMsg *message.Message) error {
226+
if sendMsg == nil {
227+
return fmt.Errorf("sendMsg is nil")
228+
}
226229
if sendMsg.MetaData == nil {
227230
logging.Debug("MetaData is nil", map[string]interface{}{"sendMsg": sendMsg})
228231
return fmt.Errorf("can't send msg, sendMsg err: %w", ErrMetaDateNil)

rikkabot/plugins/admin/admin.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,20 @@ func init() {
7171
opMulAction.AsChild(delOp)
7272
opMulAction.AsChild(opList)
7373
opMulAction.AsChild(opPBase) // 插件管理父行动
74-
opMulAction.AsChild(help.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
74+
opMulAction.AsChild(help.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
7575
recvMsg.Content = admin.help()
76-
return *recvMsg, nil
76+
return *recvMsg, true, nil
7777
}))
7878

7979
opAddM := opM.And().N(atSomeOneM)
8080
opAddByAt := plugins.DefaultActionHandler("op_add", true).AsMatcher(opAddM)
81-
opAddByAt.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
81+
opAddByAt.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
8282
content, ok := admin.addOpByAt(recvMsg)
8383
if !ok {
8484
logging.Debug("艾特方式添加管理员失败了", map[string]interface{}{"msg": *recvMsg})
8585
}
8686
recvMsg.Content = content
87-
return *recvMsg, nil
87+
return *recvMsg, true, nil
8888
})
8989

9090
// 绑定action
@@ -95,55 +95,55 @@ func init() {
9595

9696
func PLFunc() *plugins.ActionHandler {
9797
PLM := matcher.Default().And().N(matcher.PrefixMatcher{Prefix: "list", IsCut: true, IsCaseSensitive: false})
98-
PL := plugins.DefaultActionHandler("op -p list", true).AsMatcher(PLM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
98+
PL := plugins.DefaultActionHandler("op -p list", true).AsMatcher(PLM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
9999
var buf bytes.Buffer
100100
buf.WriteString("插件列表\n")
101101
register := plugins.GetAutoRegister()
102102
for i, p := range register.Plugins() {
103-
buf.WriteString(fmt.Sprintf("【%d 插件名称: %s \n状态: %v\n等级: 第%d级 】", i+1, (*p).GetName(), (*p).GetPluginOpt().Enable, (*p).GetPluginOpt().Level))
103+
buf.WriteString(fmt.Sprintf("【%d 插件名称: %s \n状态: %v\n等级: 第%d级 】\n", i+1, (*p).GetName(), (*p).GetPluginOpt().Enable, (*p).GetPluginOpt().Level))
104104
}
105105
recvMsg.Content = buf.String()
106-
return *recvMsg, nil
106+
return *recvMsg, true, nil
107107
})
108108
return PL
109109
}
110110

111111
func onPFunc() *plugins.ActionHandler {
112112
onPM := matcher.Default().And().N(matcher.PrefixMatcher{Prefix: "on", IsCut: true, IsCaseSensitive: false})
113-
onP := plugins.DefaultActionHandler("op -p on", true).AsMatcher(onPM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
113+
onP := plugins.DefaultActionHandler("op -p on", true).AsMatcher(onPM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
114114
name := strings.TrimSpace(recvMsg.Content)
115115
ar := plugins.GetAutoRegister()
116116
if name == "admin" {
117117
recvMsg.Content = "管理员插件禁止操作"
118-
return *recvMsg, nil
118+
return *recvMsg, true, nil
119119
}
120120
b := ar.EnableByName(name) // 启用某插件
121121
if b {
122122
recvMsg.Content = fmt.Sprintf("启用 %s 成功", name)
123123
} else {
124124
recvMsg.Content = fmt.Sprintf("启用 %s 失败", name)
125125
}
126-
return *recvMsg, nil
126+
return *recvMsg, true, nil
127127
})
128128
return onP
129129
}
130130

131131
func offPFunc() *plugins.ActionHandler {
132132
offPM := matcher.Default().And().N(matcher.PrefixMatcher{Prefix: "off", IsCut: true, IsCaseSensitive: false})
133-
offP := plugins.DefaultActionHandler("op -p on", true).AsMatcher(offPM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
133+
offP := plugins.DefaultActionHandler("op -p on", true).AsMatcher(offPM).AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
134134
name := strings.TrimSpace(recvMsg.Content)
135135
ar := plugins.GetAutoRegister()
136136
if name == "admin" {
137137
recvMsg.Content = "管理员插件禁止操作"
138-
return *recvMsg, nil
138+
return *recvMsg, true, nil
139139
}
140140
b := ar.DisableByName(name) // 启用某插件
141141
if b {
142142
recvMsg.Content = fmt.Sprintf("禁用 %s 成功", name)
143143
} else {
144144
recvMsg.Content = fmt.Sprintf("禁用 %s 失败", name)
145145
}
146-
return *recvMsg, nil
146+
return *recvMsg, true, nil
147147
})
148148
return offP
149149
}
@@ -157,13 +157,13 @@ func helpFunc(isSelfJudge matcher.BaseMatcher, adminJudge matcher.Custom) *plugi
157157
func delOpFunc(isSelfJudge matcher.BaseMatcher, adminJudge matcher.Custom, admin *Admin) *plugins.ActionHandler {
158158
delM := matcher.Default().And().N(matcher.PrefixMatcher{Prefix: "-d", IsCaseSensitive: false, IsCut: true}, matcher.Default().Or().N(isSelfJudge, adminJudge))
159159
delOp := plugins.DefaultActionHandler("op -d", true).AsMatcher(delM)
160-
delOp.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
160+
delOp.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
161161
content, ok := admin.delOpByAt(recvMsg)
162162
if !ok {
163163
logging.Debug("艾特方式删除管理员失败了", map[string]interface{}{"msg": *recvMsg})
164164
}
165165
recvMsg.Content = content
166-
return *recvMsg, nil
166+
return *recvMsg, ok, nil
167167
})
168168
return delOp
169169
}
@@ -177,13 +177,13 @@ func opPluginFunc(isSelfJudge matcher.BaseMatcher, adminJudge matcher.Custom) (m
177177
func opListFunc(isSelfJudge matcher.BaseMatcher, adminJudge matcher.Custom, admin *Admin) *plugins.ActionHandler {
178178
opListM := matcher.Default().And().N(matcher.PrefixMatcher{Prefix: "list", IsCaseSensitive: false, IsCut: true}, matcher.Default().Or().N(isSelfJudge, adminJudge))
179179
opList := plugins.DefaultActionHandler("op list", true).AsMatcher(opListM)
180-
opList.Action = func(ctx context.Context, recvMsg *message.Message) (reply message.Message, err error) {
180+
opList.Action = func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
181181
var buf bytes.Buffer
182182
buf.WriteString("管理员列表\n")
183183
idList := admin.cache.AdminIdList()
184184
if idList == nil || len(idList) == 0 {
185185
recvMsg.Content = "暂无管理员"
186-
return *recvMsg, nil
186+
return *recvMsg, true, nil
187187
}
188188
for _, wxid := range idList {
189189
m := admin.Cli.GetMember(wxid, true)
@@ -192,7 +192,7 @@ func opListFunc(isSelfJudge matcher.BaseMatcher, adminJudge matcher.Custom, admi
192192
}
193193
}
194194
recvMsg.Content = buf.String()
195-
return *recvMsg, nil
195+
return *recvMsg, true, nil
196196
}
197197
return opList
198198
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Package biliDecoder
2+
// @Author Clover
3+
// @Data 2025/3/17 下午1:59:00
4+
// @Desc
5+
package biliDecoder
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"encoding/xml"
11+
"regexp"
12+
"strconv"
13+
14+
"github.com/Clov614/bilibili"
15+
"github.com/Clov614/rikka-bot-wechat/rikkabot/message"
16+
"github.com/Clov614/rikka-bot-wechat/rikkabot/plugins"
17+
"github.com/Clov614/rikka-bot-wechat/rikkabot/plugins/matcher"
18+
"github.com/Clov614/rikka-bot-wechat/rikkabot/processor/cache"
19+
"github.com/rs/zerolog/log"
20+
)
21+
22+
type biliDecoder struct {
23+
cache *cache.Cache
24+
*plugins.Plugin
25+
}
26+
27+
var baseM = matcher.Default().And().N(matcher.BaseMatcher{AllowMsgType: message.MsgTypeText | message.MsgTypeApp, Rules: matcher.IsGroupRule}) // 允许群聊 文本\转发xml消息
28+
29+
func init() {
30+
bili := &biliDecoder{
31+
cache: cache.GetCache(),
32+
Plugin: plugins.DefaultPlugin("biliDecoder").AsLevel(plugins.MediumLevel).AsEnable(),
33+
}
34+
decoderM := baseM.And().N(matcher.Default().Or().N(matcher.RegexMatcher{Regex: regexp.MustCompile(`(BV[\w\d]+)`)},
35+
matcher.RegexMatcher{Regex: regexp.MustCompile(`https:\/\/www\.biliDecoder\.com\/video\/(BV[\w\d]+)\/?`)},
36+
matcher.RegexMatcher{Regex: regexp.MustCompile(`https:\/\/b23\.tv\/([\w\d]+)`)}))
37+
decoderAction := plugins.DefaultActionHandler("url decoder", true).AsMatcher(decoderM)
38+
decoderActionFunc(decoderAction, bili) // 关键逻辑
39+
40+
bili.AsAction(decoderAction)
41+
plugins.GetAutoRegister().RegisterPlugin(bili) // 注册插件
42+
}
43+
44+
func decoderActionFunc(decoderAction *plugins.ActionHandler, bili *biliDecoder) *plugins.ActionHandler {
45+
return decoderAction.AsActionFunc(func(ctx context.Context, recvMsg *message.Message) (reply message.Message, ok bool, err error) {
46+
reply = *recvMsg
47+
switch recvMsg.Msgtype {
48+
case message.MsgTypeApp:
49+
var xmlMsg message.XMLMsg
50+
err = xml.Unmarshal([]byte(recvMsg.Content), &xmlMsg)
51+
if err != nil {
52+
log.Err(err).Msg("xml.Unmarshal fail at biliPlugin")
53+
return
54+
}
55+
// 解析链接
56+
if xmlMsg.AppInfo.AppName == "哔哩哔哩" {
57+
var videoInfo *bilibili.VideoInfo
58+
videoInfo, err = bilibili.NewUrlDecoder().Parse(xmlMsg.AppMsg.URL)
59+
if err != nil {
60+
log.Err(err).Msg("bilibili.NewUrlDecoder fail at biliPlugin")
61+
return
62+
}
63+
output := buildOutput(videoInfo)
64+
recvMsg.Content = output
65+
return
66+
}
67+
case message.MsgTypeText:
68+
regexBV := regexp.MustCompile(`(BV[\w\d]+)`)
69+
regexBilibili := regexp.MustCompile(`https:\/\/www\.bilibili\.com\/video\/(BV[\w\d]+)\/?`)
70+
regexShort := regexp.MustCompile(`https:\/\/b23\.tv\/([\w\d]+)`)
71+
var videoInfo *bilibili.VideoInfo
72+
urlParser := bilibili.NewUrlDecoder()
73+
if match := regexBV.FindStringSubmatch(recvMsg.Content); len(match) > 0 {
74+
videoInfo, err = urlParser.ParseByBvid(match[1])
75+
if err != nil {
76+
log.Err(err).Msg("bilibili.urlParser.ParseByBvid fail at biliPlugin")
77+
return
78+
}
79+
} else if match = regexBilibili.FindStringSubmatch(recvMsg.Content); len(match) > 0 {
80+
videoInfo, err = urlParser.Parse(match[1])
81+
if err != nil {
82+
log.Err(err).Msg("bilibili.urlParser.ParseByBvid fail at biliPlugin")
83+
return
84+
}
85+
} else if match = regexShort.FindStringSubmatch(recvMsg.Content); len(match) > 0 {
86+
videoInfo, err = urlParser.Parse(match[1])
87+
if err != nil {
88+
log.Err(err).Msg("bilibili.urlParser.ParseByBvid fail at biliPlugin")
89+
return
90+
}
91+
}
92+
output := buildOutput(videoInfo)
93+
sender := recvMsg.RoomId
94+
if sender == "" {
95+
sender = recvMsg.WxId
96+
}
97+
if nil != videoInfo {
98+
_ = bili.Cli.SendImage(sender, videoInfo.Pic)
99+
}
100+
_ = bili.Cli.SendText(sender, output)
101+
default:
102+
// nothing to do !!
103+
}
104+
return *recvMsg, false, nil
105+
})
106+
}
107+
108+
func buildOutput(videoInfo *bilibili.VideoInfo) string {
109+
// 构建输出视频信息
110+
videoUrl := "https://www.bilibili.com/video/" + videoInfo.Bvid
111+
var buf bytes.Buffer
112+
buf.WriteString("🎬 标题: " + videoInfo.Title + "\n")
113+
buf.WriteString("📂 分区: " + videoInfo.Tname + "\n\n")
114+
buf.WriteString("📊 数据:\n")
115+
buf.WriteString(" - 👀 播放量: " + strconv.Itoa(videoInfo.View) + "\n")
116+
buf.WriteString(" - 👍 点赞: " + strconv.Itoa(videoInfo.Like) + "\n")
117+
buf.WriteString(" - 🪙 投币: " + strconv.Itoa(videoInfo.Coin) + "\n")
118+
buf.WriteString(" - ⭐️ 收藏: " + strconv.Itoa(videoInfo.Favorite) + "\n")
119+
buf.WriteString(" - 📤 分享: " + strconv.Itoa(videoInfo.Share) + "\n\n")
120+
buf.WriteString("---\n") // 分割线
121+
buf.WriteString(" " + videoUrl + "\n") // 链接前空两格
122+
buf.WriteString("---\n") // 分割线
123+
return buf.String()
124+
}

rikkabot/plugins/matcher/matcher.go

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,14 @@ func (bm BaseMatcher) Match(ctx context.Context, msg *message.Message) bool {
192192
return false
193193
}
194194
if bm.Rules != 0 {
195-
switch {
196-
case bm.Rules&IsGroupRule != 0:
197-
if !msg.IsGroup {
198-
return false
199-
}
200-
case bm.Rules&IsAtMeRule != 0:
201-
if !msg.IsAtMe {
202-
return false
203-
}
204-
case bm.Rules&IsSelf != 0:
205-
if !msg.IsMySelf {
206-
return false
207-
}
195+
if bm.Rules&IsGroupRule != 0 && !msg.IsGroup {
196+
return false
197+
}
198+
if bm.Rules&IsAtMeRule != 0 && !msg.IsAtMe {
199+
return false
200+
}
201+
if bm.Rules&IsSelf != 0 && !msg.IsMySelf {
202+
return false
208203
}
209204
}
210205
select {

0 commit comments

Comments
 (0)