Skip to content

Commit 2cb3397

Browse files
committed
refactor(plugins): 迁移ai对话插件
1 parent 9fb9c10 commit 2cb3397

File tree

10 files changed

+697
-8
lines changed

10 files changed

+697
-8
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/Clov614/wcf-rpc-sdk v0.4.2
1111
github.com/eatmoreapple/openwechat v1.4.10
1212
github.com/gin-gonic/gin v1.10.0
13+
github.com/go-ego/gse v0.80.3
1314
github.com/google/uuid v1.6.0
1415
github.com/rs/zerolog v1.33.0
1516
github.com/zwgblue/yaml-encoder v0.0.0-20221226083717-a0bdbda0d998
@@ -21,6 +22,7 @@ require (
2122
//replace github.com/Clov614/wcf-rpc-sdk v0.4.2 => E:\Applications_Dev\Go_workspace\github.com\Clov614\wcf-rpc-sdk
2223

2324
require (
25+
github.com/Clov614/go-ai-sdk v0.4.4 // indirect
2426
github.com/Microsoft/go-winio v0.5.2 // indirect
2527
github.com/antchfx/xmlquery v1.4.4 // indirect
2628
github.com/antchfx/xpath v1.3.3 // indirect
@@ -45,8 +47,10 @@ require (
4547
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4648
github.com/modern-go/reflect2 v1.0.2 // indirect
4749
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
50+
github.com/robfig/cron/v3 v3.0.1 // indirect
4851
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
4952
github.com/ugorji/go/codec v1.2.12 // indirect
53+
github.com/vcaesar/cedar v0.20.2 // indirect
5054
go.nanomsg.org/mangos/v3 v3.4.2 // indirect
5155
golang.org/x/arch v0.8.0 // indirect
5256
golang.org/x/crypto v0.31.0 // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/Clov614/bilibili v0.1.2 h1:vi1Ykn5+xgrwfpR/xLgdXvoNdulaZitXP9BOgdy0n+I=
22
github.com/Clov614/bilibili v0.1.2/go.mod h1:th+qNf6jfSDH03oCM4s347a+i7IpCgZyNRIHY8AAXe8=
3+
github.com/Clov614/go-ai-sdk v0.4.4 h1:ebSmdp3MoF/OcSwACG1tDrvZwGogCAfbR64MVlaLsk8=
4+
github.com/Clov614/go-ai-sdk v0.4.4/go.mod h1:S/GDuJSLlnNU47uEpY2FrN1GdP+A4mDXBQZQ2i6sCzk=
35
github.com/Clov614/logging v0.1.4 h1:wvTJHIPg1KQdxv23khFcZk5I4rA1jhyCCeCC0UR43AQ=
46
github.com/Clov614/logging v0.1.4/go.mod h1:Vxr9X5cvKXT17F/iBeBj8w6WSAuBQppsvtoCi0UXxe0=
57
github.com/Clov614/wcf-rpc-sdk v0.4.2 h1:RCuPkd2xcAF5eDR83QUb0pbX4/cgeeJLxraiZlDHQLo=
@@ -33,6 +35,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
3335
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
3436
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
3537
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
38+
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
39+
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
3640
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
3741
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
3842
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -77,6 +81,8 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
7781
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7882
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7983
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
84+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
85+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
8086
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
8187
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
8288
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@@ -99,6 +105,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
99105
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
100106
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
101107
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
108+
github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
109+
github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
110+
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
111+
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
102112
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
103113
github.com/zwgblue/yaml-encoder v0.0.0-20221226083717-a0bdbda0d998 h1:nfgqxY/ewt2bYcoPiND18j/uKPn4cbiQa9WyD+HIPKM=
104114
github.com/zwgblue/yaml-encoder v0.0.0-20221226083717-a0bdbda0d998/go.mod h1:gDS9Ro20YdMC2SY41VMVcy6PqyVCseFPIX1+symaFww=
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Package cronplugins
2+
// @Author Clover
3+
// @Data 2024/9/17 下午8:27:00
4+
// @Desc 定时获取天气模块
5+
package cronplugins
6+
7+
import (
8+
"encoding/json"
9+
"fmt"
10+
aisdk "github.com/Clov614/go-ai-sdk"
11+
"github.com/Clov614/go-ai-sdk/example_func_call/weather"
12+
"github.com/Clov614/go-ai-sdk/global"
13+
"github.com/Clov614/logging"
14+
"github.com/Clov614/rikka-bot-wechat/rikkabot/config"
15+
"github.com/Clov614/rikka-bot-wechat/rikkabot/plugins/ai/cron"
16+
"strings"
17+
)
18+
19+
type weatherCfg struct {
20+
Key string `json:"key"`
21+
}
22+
23+
var (
24+
w *weather.Weather
25+
)
26+
27+
type WeatherCronPlugin struct {
28+
*cron.CronJob
29+
*weather.Weather
30+
city string
31+
}
32+
33+
func NewWeatherPlugin(city string, cronJob *cron.CronJob) *WeatherCronPlugin {
34+
return &WeatherCronPlugin{
35+
Weather: w,
36+
CronJob: cronJob,
37+
city: city,
38+
}
39+
}
40+
41+
func (p *WeatherCronPlugin) Call(params string) (jsonStr string, err error) {
42+
type weatherScheduleTaskProperties struct {
43+
Uuid string `json:"uuid"`
44+
IsGroup bool `json:"is_group"`
45+
CronSpec string `json:"cron_spec"`
46+
CityAddr string `json:"city_addr"`
47+
}
48+
var properties weatherScheduleTaskProperties
49+
err = json.Unmarshal([]byte(params), &properties)
50+
if err != nil {
51+
return "", fmt.Errorf("call_err: json.Unmarshal([]byte(params)): %w", err)
52+
}
53+
p.Uuid = properties.Uuid
54+
p.IsGroup = properties.IsGroup
55+
p.Spec = properties.CronSpec
56+
p.JobName = "push_weather"
57+
58+
p.Weather = w
59+
p.city = properties.CityAddr
60+
p.CreateSchedule(p)
61+
res := "[系统] 添加" + p.city + "天气推送成功 cron: " + p.Spec
62+
p.SendText(res)
63+
return res, nil
64+
}
65+
66+
func (p *WeatherCronPlugin) Run() {
67+
weatherResp := p.Weather.GetWeatherByCityAddr(p.city, true)
68+
if len(weatherResp.Forecasts) > 0 {
69+
p.SendText(getMultiDayWeatherString(weatherResp.Forecasts))
70+
}
71+
}
72+
73+
// 美观输出多日天气信息
74+
func getMultiDayWeatherString(forecasts []weather.Forecast) string {
75+
var result strings.Builder
76+
77+
for _, forecast := range forecasts {
78+
result.WriteString(fmt.Sprintf("🏙️ 城市:%s\n", forecast.City))
79+
result.WriteString(fmt.Sprintf("📅 报告时间:%s\n", forecast.ReportTime))
80+
for _, cast := range forecast.Casts {
81+
result.WriteString(fmt.Sprintf("📅 日期:%s\n", cast.Date))
82+
result.WriteString(fmt.Sprintf("🌞 白天天气:%s 🌙 夜晚天气:%s\n", cast.DayWeather, cast.NightWeather))
83+
result.WriteString(fmt.Sprintf("🌡️ 白天温度:%s°C 🌙 夜晚温度:%s°C\n", cast.DayTemp, cast.NightTemp))
84+
result.WriteString(fmt.Sprintf("🌬️ 白天风向:%s 💨 风力:%s\n", cast.DayWind, cast.DayPower))
85+
result.WriteString(fmt.Sprintf("🌬️ 夜晚风向:%s 💨 风力:%s\n", cast.NightWind, cast.NightPower))
86+
}
87+
result.WriteString("--------------------------------------------------\n")
88+
}
89+
90+
return result.String()
91+
}
92+
93+
func init() {
94+
// 获取天气相关配置
95+
cfg := config.GetConfig()
96+
wCfgInterface, ok := cfg.GetCustomPluginCfg("weather_ai")
97+
if !ok {
98+
cfg.SetCustomPluginCfg("weather_ai", weatherCfg{Key: ""})
99+
_ = cfg.Update() // 更新设置
100+
logging.Fatal("weather_ai plugin config loaded empty please write the key about weather api in config.yaml", 12)
101+
}
102+
bytes, err := json.Marshal(wCfgInterface)
103+
if err != nil {
104+
logging.Fatal("weather_ai plugin config marshalling failed", 12)
105+
}
106+
var wcfg weatherCfg
107+
err = json.Unmarshal(bytes, &wcfg)
108+
if err != nil {
109+
logging.Fatal("weather_ai plugin config unmarshal fail", 12)
110+
}
111+
w = weather.NewWeather(wcfg.Key)
112+
113+
// 注册定时任务触发的ai插件
114+
funcCallInfo := aisdk.FuncCallInfo{
115+
Function: aisdk.Function{
116+
Name: "push_weather_schedule",
117+
Description: "定期推送天气信息",
118+
Parameters: aisdk.FunctionParameter{
119+
Type: global.ObjType,
120+
Properties: aisdk.Properties{
121+
"uuid": aisdk.Property{
122+
Type: global.StringType,
123+
Description: "标记定时任务的标识",
124+
},
125+
"is_group": aisdk.Property{
126+
Type: global.BoolType,
127+
Description: "是否为群聊",
128+
},
129+
"cron_spec": aisdk.Property{
130+
Type: global.StringType,
131+
Description: "定时任务cron表达式,请根据我的描述生成6字段的cron表达式,只返回表达式字符串:(每天早上八点半执行: 0 30 8 1/1 * ?)",
132+
},
133+
"city_addr": aisdk.Property{
134+
Type: global.StringType,
135+
Description: "地址,如:国家,城市,县、区地址",
136+
},
137+
},
138+
Required: []string{"uuid", "is_group", "cron_spec", "city_addr"},
139+
},
140+
Strict: false,
141+
},
142+
CallFunc: &WeatherCronPlugin{
143+
Weather: w,
144+
CronJob: &cron.CronJob{},
145+
},
146+
}
147+
148+
aisdk.FuncRegister.Register(&funcCallInfo, []string{"定时任务", "定时", "推送"})
149+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Package cron
2+
// @Author Clover
3+
// @Data 2024/9/17 下午7:36:00
4+
// @Desc 定时器
5+
package cron
6+
7+
import (
8+
"fmt"
9+
"github.com/Clov614/logging"
10+
wcf "github.com/Clov614/wcf-rpc-sdk"
11+
"github.com/robfig/cron/v3"
12+
"github.com/rs/zerolog/log"
13+
)
14+
15+
var cronServer *MyCronServer
16+
17+
func init() {
18+
cronServer = NewCronServer()
19+
}
20+
21+
type MyCronServer struct {
22+
*cron.Cron
23+
jobId2cron map[string]cron.EntryID // JobId为定时任务唯一标识,同一类型的定时任务只能有一个
24+
// todo 保存各个cron插件的信息,利用信息在init中恢复定时任务
25+
}
26+
27+
func NewCronServer() *MyCronServer {
28+
c := cron.New(cron.WithParser(cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)))
29+
// 从设置里读取cronJob相关信息
30+
31+
return &MyCronServer{
32+
Cron: c,
33+
jobId2cron: make(map[string]cron.EntryID),
34+
}
35+
}
36+
37+
func (cs *MyCronServer) init() {
38+
// todo 恢复之前持久化的定时任务
39+
40+
}
41+
42+
func (cs *MyCronServer) NewJob(spec string, job cron.Job, id string) bool {
43+
cs.RemoveCron(id) // 如果定时任务已经存在,则替换
44+
entryID, err := cs.AddJob(spec, job)
45+
if err != nil {
46+
log.Err(err).Msg("添加定时任务失败")
47+
return false
48+
}
49+
cs.jobId2cron[id] = entryID
50+
cs.Start()
51+
return true
52+
}
53+
54+
func (cs *MyCronServer) ResetPluginCron(jobId, spec string) {
55+
logging.Info(fmt.Sprintf("Cron jobId: %s, plugin spec: %s", jobId, spec))
56+
if id, ok := cs.jobId2cron[jobId]; ok {
57+
job := cs.Cron.Entry(id).Job
58+
newId, err := cs.AddJob(spec, job)
59+
if err != nil {
60+
log.Err(err).Msg("重置定时任务时间失败")
61+
return
62+
}
63+
cs.jobId2cron[jobId] = newId
64+
cs.Remove(id)
65+
}
66+
}
67+
68+
func (cs *MyCronServer) RemoveCron(id string) {
69+
if id, ok := cs.jobId2cron[id]; ok {
70+
cs.Remove(id)
71+
}
72+
}
73+
74+
type CronJob struct {
75+
cli *wcf.Client
76+
Spec string
77+
Uuid string
78+
IsGroup bool
79+
JobName string
80+
}
81+
82+
func (cj *CronJob) CreateSchedule(job cron.Job) {
83+
if cronServer.NewJob(cj.Spec, job, cj.GetJobId()) {
84+
cj.SendText("定时任务设置成功")
85+
} else {
86+
cj.SendText("定时任务设置失败")
87+
}
88+
// todo 持久化,管理相关
89+
90+
}
91+
92+
func (cj *CronJob) GetJobId() string {
93+
return cj.Uuid + "_" + cj.JobName
94+
}
95+
96+
func (cj *CronJob) SendText(text string) {
97+
err := cj.cli.SendText(cj.Uuid, text)
98+
if err != nil {
99+
log.Err(err).Msg("定时推送消息错误")
100+
}
101+
}

0 commit comments

Comments
 (0)