1+ // Package pigpig 猪猪表情包
2+ package pigpig
3+
4+ import (
5+ "encoding/json"
6+ "errors"
7+ "fmt"
8+ "math/rand"
9+ "path/filepath"
10+ "strings"
11+ "sync"
12+ "time"
13+
14+ ctrl "github.com/FloatTech/zbpctrl"
15+ "github.com/FloatTech/zbputils/control"
16+ zero "github.com/wdvxdr1123/ZeroBot"
17+ "github.com/wdvxdr1123/ZeroBot/message"
18+ )
19+
20+ // pigResponse 内部结构体
21+ type pigResponse struct {
22+ Total int `json:"total"`
23+ Images []pigImage `json:"images"`
24+ }
25+
26+ // pigImage 内部结构体
27+ type pigImage struct {
28+ ID string `json:"id"`
29+ Title string `json:"title"`
30+ Filename string `json:"filename"`
31+ }
32+
33+ var (
34+ pigCache []pigImage
35+ pigMap = make (map [string ]* pigImage )
36+ pigMutex sync.RWMutex
37+ lastUpdateTime = time .Now ()
38+
39+ engine = control .AutoRegister (& ctrl.Options [* zero.Ctx ]{
40+ DisableOnDefault : false ,
41+ Brief : "来份猪猪" ,
42+ Help : "- 随机猪猪:随机发送一张猪猪表情\n - 搜索猪猪 [关键词]:搜索相关猪猪\n - 猪猪id [id]:精确查找" ,
43+ PrivateDataFolder : "Pig" ,
44+ })
45+ )
46+
47+ func init () {
48+ _ = checkAndUpdateData ()
49+ // 1. 随机猪猪
50+ engine .OnRegex (`^(随机猪猪|来份猪猪|抽个猪猪)$` ).SetBlock (true ).Handle (func (ctx * zero.Ctx ) {
51+ if err := checkAndUpdateData (); err != nil {
52+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 随机猪猪失败,获取数据错误" ))
53+ return
54+ }
55+
56+ pigMutex .RLock ()
57+ defer pigMutex .RUnlock ()
58+
59+ if len (pigCache ) == 0 {
60+ ctx .SendChain (message .Text ("[Pig] ERROR: 暂无猪猪数据,请联系管理员" ))
61+ return
62+ }
63+
64+ target := pigCache [rand .Intn (len (pigCache ))]
65+ imgData , err := target .fetch ()
66+ if err != nil {
67+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
68+ return
69+ }
70+
71+ ctx .SendChain (
72+ message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
73+ message .ImageBytes (imgData ),
74+ )
75+ })
76+
77+ // 2. 搜索猪猪
78+ engine .OnRegex (`^搜索猪猪\s+(.+)$` ).SetBlock (true ).Handle (func (ctx * zero.Ctx ) {
79+ keyword := strings .TrimSpace (ctx .State ["regex_matched" ].([]string )[1 ])
80+
81+ if err := checkAndUpdateData (); err != nil {
82+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 搜索猪猪失败,获取数据错误" ))
83+ return
84+ }
85+
86+ pigMutex .RLock ()
87+ defer pigMutex .RUnlock ()
88+
89+ var results []pigImage
90+ for _ , p := range pigCache {
91+ if strings .Contains (p .Title , keyword ) {
92+ results = append (results , p )
93+ }
94+ }
95+
96+ if len (results ) == 0 {
97+ ctx .SendChain (message .Text ("[Pig] ERROR: 未找到包含“" , keyword , "”的猪猪" ))
98+ return
99+ }
100+
101+ var sb strings.Builder
102+ sb .WriteString (fmt .Sprintf ("🔎 根据关键词“%s”找到 %d 只猪猪:\n " , keyword , len (results )))
103+
104+ maxShow := 10
105+ for i , p := range results {
106+ if i >= maxShow {
107+ sb .WriteString (fmt .Sprintf ("\n ...等共 %d 条结果" , len (results )))
108+ break
109+ }
110+ sb .WriteString (fmt .Sprintf ("%d: %s (ID: %s)\n " , i + 1 , p .Title , p .ID ))
111+ }
112+
113+ sb .WriteString ("\n 为您返回第一个猪猪:\n 💡 提示:输入“猪猪id [id]”可精确获取" )
114+
115+ imgData , err := results [0 ].fetch ()
116+ if err != nil {
117+ ctx .SendChain (message .Text (sb .String (), "\n \n [Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
118+ return
119+ }
120+
121+ ctx .SendChain (
122+ message .Text (sb .String ()),
123+ message .ImageBytes (imgData ), // 直接使用 ImageBytes
124+ )
125+ })
126+
127+ // 3. 猪猪id精确查找
128+ engine .OnRegex (`^猪猪id\s+(\d+)$` ).SetBlock (true ).Handle (func (ctx * zero.Ctx ) {
129+ targetID := ctx .State ["regex_matched" ].([]string )[1 ]
130+
131+ if err := checkAndUpdateData (); err != nil {
132+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 精确查找失败,获取数据错误" ))
133+ return
134+ }
135+
136+ pigMutex .RLock ()
137+ defer pigMutex .RUnlock ()
138+
139+ target , exists := pigMap [targetID ]
140+ if ! exists {
141+ ctx .SendChain (message .Text ("[Pig] ERROR: 未找到 ID 为 " , targetID , " 的猪猪" ))
142+ return
143+ }
144+
145+ imgData , err := target .fetch ()
146+ if err != nil {
147+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
148+ return
149+ }
150+
151+ ctx .SendChain (
152+ message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
153+ message .ImageBytes (imgData ),
154+ )
155+ })
156+ }
157+
158+ // checkAndUpdateData
159+ func checkAndUpdateData () error {
160+ pigMutex .Lock ()
161+ defer pigMutex .Unlock ()
162+
163+ // 如果有缓存且距上次更新不足 24 小时,直接返回
164+ if len (pigCache ) > 0 && time .Since (lastUpdateTime ) < 24 * time .Hour {
165+ return nil
166+ }
167+
168+ dataBytes , err := engine .GetLazyData ("pig_data.json" , true )
169+ if err != nil {
170+ return errors .New ("读取数据文件失败: " + err .Error ())
171+ }
172+
173+ var data pigResponse
174+ if err := json .Unmarshal (dataBytes , & data ); err != nil {
175+ return errors .New ("解析JSON失败: " + err .Error ())
176+ }
177+
178+ if len (data .Images ) == 0 {
179+ return errors .New ("数据文件为空" )
180+ }
181+
182+ pigCache = data .Images
183+
184+ // 更新缓存时,顺便重构一份查询 Map
185+ newMap := make (map [string ]* pigImage , len (pigCache ))
186+ for i := range pigCache {
187+ newMap [pigCache [i ].ID ] = & pigCache [i ]
188+ }
189+ pigMap = newMap
190+
191+ lastUpdateTime = time .Now ()
192+ return nil
193+ }
194+
195+ func (img * pigImage ) fetch () ([]byte , error ) {
196+ if img .Filename == "" {
197+ return nil , errors .New ("图片数据异常,缺少文件名" )
198+ }
199+
200+ targetPath := filepath .Join ("assets" , img .Filename )
201+
202+ imgData , err := engine .GetLazyData (targetPath , true )
203+ if err != nil {
204+ return nil , errors .New ("图片资源缺失 (" + targetPath + "): " + err .Error ())
205+ }
206+
207+ return imgData , nil
208+ }
0 commit comments