@@ -12,9 +12,13 @@ import (
1212 "github.com/FloatTech/floatbox/binary"
1313 fcext "github.com/FloatTech/floatbox/ctxext"
1414 ctrl "github.com/FloatTech/zbpctrl"
15+ "github.com/FloatTech/zbputils/chat"
1516 "github.com/FloatTech/zbputils/control"
1617 "github.com/FloatTech/zbputils/ctxext"
1718 "github.com/FloatTech/zbputils/img/text"
19+ "github.com/fumiama/deepinfra"
20+ "github.com/fumiama/deepinfra/model"
21+ "github.com/pkg/errors"
1822 "github.com/sirupsen/logrus"
1923 zero "github.com/wdvxdr1123/ZeroBot"
2024 "github.com/wdvxdr1123/ZeroBot/message"
@@ -35,6 +39,16 @@ type formation struct {
3539 IsCut bool `json:"is_cut"`
3640 Represent [][]string `json:"represent"`
3741}
42+
43+ type drawResult struct {
44+ Name string
45+ Position string
46+ Description string
47+ Represent string
48+ }
49+
50+ type drawResults []drawResult
51+
3852type cardSet = map [string ]card
3953
4054var (
@@ -52,10 +66,10 @@ func init() {
5266 engine := control .AutoRegister (& ctrl.Options [* zero.Ctx ]{
5367 DisableOnDefault : false ,
5468 Brief : "塔罗牌" ,
55- Help : "- 抽[塔罗牌|大阿卡纳|小阿卡纳]\n " +
69+ Help : "- 抽[塔罗牌|大阿卡纳|小阿卡纳] [询问的事情] \n " +
5670 "- 抽n张[塔罗牌|大阿卡纳|小阿卡纳]\n " +
5771 "- 解塔罗牌[牌名]\n " +
58- "- [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星]" ,
72+ "- [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星] [询问的事情] " ,
5973 PublicDataFolder : "Tarot" ,
6074 }).ApplySingle (ctxext .DefaultSingle )
6175
@@ -112,9 +126,11 @@ func init() {
112126 logrus .Infof ("[tarot]读取%d组塔罗牌阵" , len (formationMap ))
113127 return true
114128 })
115- engine .OnRegex (`^抽(\d{1,2}张)?((塔罗牌|大阿(尔)?卡纳)|小阿(尔)?卡纳)$` , getTarot ).SetBlock (true ).Limit (ctxext .LimitByGroup ).Handle (func (ctx * zero.Ctx ) {
129+ engine .OnRegex (`^抽(\d{1,2}张)?((塔罗牌|大阿(尔)?卡纳)|小阿(尔)?卡纳)\s?(.*) $` , getTarot ).SetBlock (true ).Limit (ctxext .LimitByGroup ).Handle (func (ctx * zero.Ctx ) {
116130 match := ctx .State ["regex_matched" ].([]string )[1 ]
117131 cardType := ctx .State ["regex_matched" ].([]string )[2 ]
132+ question := strings .TrimSpace (ctx .State ["regex_matched" ].([]string )[6 ])
133+ withQuestion := match == "" && question != ""
118134 n := 1
119135 reasons := [... ]string {"您抽到的是~\n " , "锵锵锵,塔罗牌的预言是~\n " , "诶,让我看看您抽到了~\n " }
120136 position := [... ]string {"『正位』" , "『逆位』" }
@@ -154,11 +170,47 @@ func init() {
154170 if err != nil {
155171 // ctx.SendChain(message.Text("ERROR: ", err))
156172 logrus .Infof ("[tarot]获取图片失败: %v" , err )
157- ctx . SendChain ( message . Text ( reasons [ rand . Intn ( len ( reasons ))], position [ p ], "的『" , name , "』 \n 其释义为: " , description ))
158- return
173+ } else {
174+ ctx . SendChain ( message . ImageBytes ( data ))
159175 }
160- ctx .SendChain (message .ImageBytes (data ))
161176 ctx .SendChain (message .Text (reasons [rand .Intn (len (reasons ))], position [p ], "的『" , name , "』\n 其释义为: " , description ))
177+ if ! withQuestion {
178+ return
179+ }
180+ if ! chat .EnsureConfig (ctx ) {
181+ ctx .SendChain (message .Text ("塔罗解析失败: 无法读取 AI 聊天配置" ))
182+ return
183+ }
184+ gid := ctx .Event .GroupID
185+ if gid == 0 {
186+ gid = - ctx .Event .UserID
187+ }
188+ stor , err := chat .NewStorage (ctx , gid )
189+ if err != nil {
190+ ctx .SendChain (message .Text ("塔罗解析失败: " , errors .Wrap (err , "读取 AI 聊天温度配置失败" )))
191+ return
192+ }
193+ reply , err := drawResults {{
194+ Name : name ,
195+ Position : position [p ],
196+ Description : description ,
197+ }}.analyze (question , "" , stor .Temp ())
198+ if err != nil {
199+ logrus .Warnln ("[tarot]大模型解析失败:" , err )
200+ ctx .SendChain (message .Text ("塔罗解析失败: " , err ))
201+ return
202+ }
203+ if reply == "" {
204+ ctx .SendChain (message .Text ("塔罗解析失败: 大模型返回为空" ))
205+ return
206+ }
207+ if id := ctx .Send (makeNodeMessage (
208+ reply ,
209+ ctx .CardOrNickName (ctx .Event .UserID ),
210+ ctx .Event .UserID ,
211+ )).ID (); id == 0 {
212+ ctx .SendChain (message .Text ("ERROR: 可能被风控了" ))
213+ }
162214 return
163215 }
164216 msg := make (message.Message , n )
@@ -238,8 +290,16 @@ func init() {
238290 })
239291 engine .OnRegex (`^((塔罗|大阿(尔)?卡纳)|小阿(尔)?卡纳|混合)牌阵\s?(.*)` , getTarot ).SetBlock (true ).Limit (ctxext .LimitByGroup ).Handle (func (ctx * zero.Ctx ) {
240292 cardType := ctx .State ["regex_matched" ].([]string )[1 ]
241- match := ctx .State ["regex_matched" ].([]string )[5 ]
242- info , ok := formationMap [match ]
293+ rawMatch := strings .TrimSpace (ctx .State ["regex_matched" ].([]string )[5 ])
294+ var match string
295+ for name := range formationMap {
296+ if strings .HasPrefix (rawMatch , name ) && len (name ) > len (match ) {
297+ match = name
298+ }
299+ }
300+ question := strings .TrimSpace (strings .TrimPrefix (rawMatch , match ))
301+ _ , ok := formationMap [match ]
302+ info := formationMap [match ]
243303 position := [... ]string {"『正位』" , "『逆位』" }
244304 reverse := [... ]string {"" , "Reverse/" }
245305 start , length := 0 , 22
@@ -258,6 +318,7 @@ func init() {
258318 build .WriteString (match )
259319 build .WriteString ("\n " )
260320 msg := make (message.Message , info .CardsNum + 1 )
321+ results := make (drawResults , 0 , info .CardsNum )
261322 randomIntMap := make (map [int ]int , 30 )
262323 for i := 0 ; i < info .CardsNum ; i ++ {
263324 j := rand .Intn (length )
@@ -295,6 +356,12 @@ func init() {
295356 build .WriteString ("』\n 其释义为: \n " )
296357 build .WriteString (description )
297358 build .WriteString ("\n " )
359+ results = append (results , drawResult {
360+ Name : name ,
361+ Position : position [p ],
362+ Description : description ,
363+ Represent : info .Represent [0 ][i ],
364+ })
298365 msg [i ] = ctxext .FakeSenderForwardNode (ctx , tarotmsg ... )
299366 }
300367 txt := build .String ()
@@ -307,8 +374,110 @@ func init() {
307374 if id := ctx .Send (msg ).ID (); id == 0 {
308375 ctx .SendChain (message .Text ("ERROR: 可能被风控了" ))
309376 }
377+ if question == "" {
378+ return
379+ }
380+
381+ if ! chat .EnsureConfig (ctx ) {
382+ ctx .SendChain (message .Text ("塔罗解析失败: 无法读取 AI 聊天配置" ))
383+ return
384+ }
385+ gid := ctx .Event .GroupID
386+ if gid == 0 {
387+ gid = - ctx .Event .UserID
388+ }
389+ stor , err := chat .NewStorage (ctx , gid )
390+ if err != nil {
391+ ctx .SendChain (message .Text ("塔罗解析失败: " , errors .Wrap (err , "读取 AI 聊天温度配置失败" )))
392+ return
393+ }
394+ reply , err := results .analyze (question , match , stor .Temp ())
395+ if err != nil {
396+ logrus .Warnln ("[tarot]大模型解析失败:" , err )
397+ ctx .SendChain (message .Text ("塔罗解析失败: " , err ))
398+ return
399+ }
400+ if reply == "" {
401+ ctx .SendChain (message .Text ("塔罗解析失败: 大模型返回为空" ))
402+ return
403+ }
404+ if id := ctx .Send (makeNodeMessage (
405+ reply ,
406+ ctx .CardOrNickName (ctx .Event .UserID ),
407+ ctx .Event .UserID ,
408+ )).ID (); id == 0 {
409+ ctx .SendChain (message .Text ("ERROR: 可能被风控了" ))
410+ }
310411 } else {
311- ctx .SendChain (message .Text ("没有找到" , match , "噢~\n 现有牌阵列表: \n " , strings .Join (formationName , "\n " )))
412+ ctx .SendChain (message .Text ("没有找到" , rawMatch , "噢~\n 现有牌阵列表: \n " , strings .Join (formationName , "\n " )))
312413 }
313414 })
314415}
416+
417+ func (draws drawResults ) analyze (question , formationName string , temperature float32 ) (string , error ) {
418+ var build strings.Builder
419+ build .WriteString ("你是一位谨慎的塔罗牌解读者。请围绕用户的问题和本次牌面解读,先说明牌面,再给出综合建议,字数控制在300-500字。" )
420+ build .WriteString ("不要把占卜结果表述为确定事实。\n " )
421+ build .WriteString ("用户问题: " )
422+ build .WriteString (question )
423+ build .WriteByte ('\n' )
424+ if formationName != "" {
425+ build .WriteString ("牌阵: " )
426+ build .WriteString (formationName )
427+ build .WriteByte ('\n' )
428+ }
429+ build .WriteString ("牌面:\n " )
430+ for i , draw := range draws {
431+ build .WriteString (strconv .Itoa (i + 1 ))
432+ build .WriteString (". " )
433+ if draw .Represent != "" {
434+ build .WriteString ("牌位: " )
435+ build .WriteString (draw .Represent )
436+ build .WriteString ("; " )
437+ }
438+ build .WriteString ("牌: " )
439+ build .WriteString (draw .Name )
440+ build .WriteString ("; 方位: " )
441+ build .WriteString (draw .Position )
442+ build .WriteString ("; 固定释义: " )
443+ build .WriteString (draw .Description )
444+ build .WriteByte ('\n' )
445+ }
446+ topp , maxn := chat .AC .MParams ()
447+ mod , err := chat .AC .Type .Protocol (chat .AC .ModelName , temperature , topp , maxn , chat .AC .ReasoningEffort )
448+ if err != nil {
449+ return "" , errors .Wrap (err , "创建 AI 模型协议失败" )
450+ }
451+
452+ api := deepinfra .NewAPI (chat .AC .API , string (chat .AC .Key ))
453+ data , err := api .Request (mod .User (model .NewContentText (build .String ())))
454+ if err != nil {
455+ return "" , errors .Wrap (err , "请求 AI 模型失败" )
456+ }
457+ return strings .TrimSpace (data ), nil
458+ }
459+
460+ func makeNodeMessage (reply , nickname string , userID int64 ) message.Message {
461+ chunks := splitTextChunks ("塔罗解析:\n " + reply , 1000 )
462+ msg := make (message.Message , 0 , len (chunks ))
463+ for _ , chunk := range chunks {
464+ msg = append (msg , message .CustomNode (nickname , userID , message.Message {message .Text (chunk )}))
465+ }
466+ return msg
467+ }
468+
469+ func splitTextChunks (txt string , maxRunes int ) []string {
470+ runes := []rune (txt )
471+ if maxRunes <= 0 || len (runes ) <= maxRunes {
472+ return []string {txt }
473+ }
474+ chunks := make ([]string , 0 , (len (runes )+ maxRunes - 1 )/ maxRunes )
475+ for len (runes ) > maxRunes {
476+ chunks = append (chunks , string (runes [:maxRunes ]))
477+ runes = runes [maxRunes :]
478+ }
479+ if len (runes ) > 0 {
480+ chunks = append (chunks , string (runes ))
481+ }
482+ return chunks
483+ }
0 commit comments