22package pigpig
33
44import (
5- "encoding/base64"
65 "encoding/json"
7- "errors" // 【新增】引入 errors 库
6+ "errors"
87 "fmt"
98 "math/rand"
109 "path/filepath"
@@ -18,217 +17,195 @@ import (
1817 "github.com/wdvxdr1123/ZeroBot/message"
1918)
2019
21- // PigResponse 对应精简后的 JSON 结构
22- type PigResponse struct {
20+ // pigResponse 内部结构体
21+ type pigResponse struct {
2322 Total int `json:"total"`
24- Images []PigImage `json:"images"`
23+ Images []pigImage `json:"images"`
2524}
2625
27- // PigImage 图片信息结构
28- type PigImage struct {
26+ // pigImage 内部结构体
27+ type pigImage struct {
2928 ID string `json:"id"`
3029 Title string `json:"title"`
3130 Filename string `json:"filename"`
3231}
3332
3433var (
35- // 数据缓存
36- pigCache []PigImage
37- // 读写锁
38- pigMutex sync.RWMutex
39- // 上次更新时间
34+ pigCache []pigImage
35+ pigMap = make (map [string ]* pigImage ) // 使用 map 方便 ID 查找
36+ pigMutex sync.RWMutex
4037 lastUpdateTime time.Time
41- // 引擎实例
42- engine * control.Engine
43- )
4438
45- func init () {
39+ // 初始化 engine
4640 engine = control .AutoRegister (& ctrl.Options [* zero.Ctx ]{
4741 DisableOnDefault : false ,
4842 Brief : "来份猪猪" ,
4943 Help : "- 随机猪猪:随机发送一张猪猪表情\n - 搜索猪猪 [关键词]:搜索相关猪猪\n - 猪猪id [id]:精确查找" ,
5044 PrivateDataFolder : "Pig" ,
5145 })
46+ )
5247
53- engine .OnRegex (`^随机猪猪$` ).SetBlock (true ).Handle (handleRandomPig )
54- engine .OnRegex (`^搜索猪猪\s+(.+)$` ).SetBlock (true ).Handle (handleSearchPig )
55- engine .OnRegex (`^猪猪id\s+(\d+)$` ).SetBlock (true ).Handle (handlePigByID )
56- }
57-
58- // checkAndUpdateData 检查并更新数据
59- func checkAndUpdateData (ctx * zero.Ctx ) error {
60- pigMutex .Lock ()
61- defer pigMutex .Unlock ()
48+ func init () {
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+ }
6255
63- now := time .Now ()
64- // 如果缓存为空,或者今天是新的一天,则尝试更新
65- shouldUpdate := len (pigCache ) == 0 || now .Format ("2006-01-02" ) != lastUpdateTime .Format ("2006-01-02" )
56+ pigMutex .RLock ()
57+ defer pigMutex .RUnlock ()
6658
67- if shouldUpdate {
68- if ctx != nil {
69- ctx . SendChain ( message . Text ( "🐷 正在同步今日猪猪数据,请稍候..." ))
59+ if len ( pigCache ) == 0 {
60+ ctx . SendChain ( message . Text ( "[Pig] ERROR: 暂无猪猪数据,请联系管理员" ))
61+ return
7062 }
7163
72- // 读取根目录下的 pig_data.json
73- dataBytes , err := engine . GetLazyData ( "pig_data.json" , true )
64+ target := pigCache [ rand . Intn ( len ( pigCache ))]
65+ imgData , err := target . fetch ( )
7466 if err != nil {
75- return errors .New ("读取数据文件失败: " + err .Error ())
67+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
68+ return
7669 }
7770
78- var data PigResponse
79- if err := json .Unmarshal (dataBytes , & data ); err != nil {
80- return errors .New ("解析JSON失败: " + err .Error ())
81- }
71+ ctx .SendChain (
72+ message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
73+ message .ImageBytes (imgData ), // 直接使用 ImageBytes,无需 base64
74+ )
75+ })
8276
83- if len (data .Images ) == 0 {
84- return errors .New ("数据文件为空" )
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
8584 }
8685
87- pigCache = data . Images
88- lastUpdateTime = now
86+ pigMutex . RLock ()
87+ defer pigMutex . RUnlock ()
8988
90- if ctx != nil {
91- ctx .SendChain (message .Text (fmt .Sprintf ("✅ 同步完成!当前共有 %d 只猪猪。" , len (pigCache ))))
89+ var results []pigImage
90+ for _ , p := range pigCache {
91+ if strings .Contains (p .Title , keyword ) {
92+ results = append (results , p )
93+ }
9294 }
93- }
94- return nil
95- }
9695
97- // fetchImageLazy 按需从 assets 文件夹获取图片并转为 Base64
98- func fetchImageLazy (img PigImage ) (string , error ) {
99- if img .Filename == "" {
100- return "" , errors .New ("图片数据异常,缺少文件名" )
101- }
96+ if len (results ) == 0 {
97+ ctx .SendChain (message .Text ("[Pig] ERROR: 未找到包含“" , keyword , "”的猪猪" ))
98+ return
99+ }
102100
103- // 拼接 assets 子目录
104- targetPath := filepath . Join ( "assets " , img . Filename )
101+ var sb strings. Builder
102+ sb . WriteString ( fmt . Sprintf ( "🔎 根据关键词“%s”找到 %d 只猪猪: \n " , keyword , len ( results )) )
105103
106- // false 表示优先使用本地文件,不强制从网络拉取
107- imgData , err := engine .GetLazyData (targetPath , false )
108- if err != nil {
109- return "" , errors .New ("图片资源缺失 (" + targetPath + "): " + err .Error ())
110- }
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+ }
111112
112- return "base64://" + base64 .StdEncoding .EncodeToString (imgData ), nil
113- }
113+ sb .WriteString ("\n 为您返回第一个猪猪:\n 💡 提示:输入“猪猪id [id]”可精确获取" )
114114
115- // handleRandomPig 处理随机猪猪
116- func handleRandomPig (ctx * zero.Ctx ) {
117- if err := checkAndUpdateData (ctx ); err != nil {
118- ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 随机猪猪失败,获取数据错误" ))
119- return
120- }
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+ })
121126
122- pigMutex .RLock ()
123- defer pigMutex .RUnlock ()
127+ // 3. 猪猪id精确查找
128+ engine .OnRegex (`^猪猪id\s+(\d+)$` ).SetBlock (true ).Handle (func (ctx * zero.Ctx ) {
129+ targetID := ctx .State ["regex_matched" ].([]string )[1 ]
124130
125- if len ( pigCache ) == 0 {
126- ctx .SendChain (message .Text ("[Pig] ERROR: 暂无猪猪数据,请联系管理员 " ))
127- return
128- }
131+ if err := checkAndUpdateData (); err != nil {
132+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , " \n EXP: 精确查找失败,获取数据错误 " ))
133+ return
134+ }
129135
130- idx := rand . Intn ( len ( pigCache ) )
131- target := pigCache [ idx ]
136+ pigMutex . RLock ( )
137+ defer pigMutex . RUnlock ()
132138
133- b64Image , err := fetchImageLazy (target )
134- if err != nil {
135- ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
136- return
137- }
139+ // 直接使用 map 进行 O(1) 查找,抛弃 for 循环
140+ target , exists := pigMap [targetID ]
141+ if ! exists {
142+ ctx .SendChain (message .Text ("[Pig] ERROR: 未找到 ID 为 " , targetID , " 的猪猪" ))
143+ return
144+ }
145+
146+ imgData , err := target .fetch ()
147+ if err != nil {
148+ ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
149+ return
150+ }
138151
139- ctx .SendChain (
140- message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
141- message .Image (b64Image ),
142- )
152+ ctx .SendChain (
153+ message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
154+ message .ImageBytes (imgData ), // 直接使用 ImageBytes
155+ )
156+ })
143157}
144158
145- // handleSearchPig 处理搜索猪猪
146- func handleSearchPig ( ctx * zero. Ctx ) {
147- keyword := ctx . State [ "regex_matched" ].([] string )[ 1 ]
148- keyword = strings . TrimSpace ( keyword )
159+ // checkAndUpdateData
160+ func checkAndUpdateData () error {
161+ pigMutex . Lock ()
162+ defer pigMutex . Unlock ( )
149163
150- if err := checkAndUpdateData ( ctx ); err != nil {
151- ctx . SendChain ( message . Text ( "[Pig] ERROR: " , err , " \n EXP: 搜索猪猪失败,获取数据错误" ))
152- return
164+ // 如果有缓存且距上次更新不足 24 小时,直接返回
165+ if len ( pigCache ) > 0 && time . Since ( lastUpdateTime ) < 24 * time . Hour {
166+ return nil
153167 }
154168
155- pigMutex .RLock ()
156- defer pigMutex .RUnlock ()
157-
158- var results []PigImage
159- for _ , p := range pigCache {
160- // 模糊匹配标题
161- if strings .Contains (p .Title , keyword ) {
162- results = append (results , p )
163- }
169+ dataBytes , err := engine .GetLazyData ("pig_data.json" , true )
170+ if err != nil {
171+ return errors .New ("读取数据文件失败: " + err .Error ())
164172 }
165173
166- if len ( results ) == 0 {
167- ctx . SendChain ( message . Text ( "[Pig] ERROR: 未找到包含“" , keyword , "”的猪猪" ))
168- return
174+ var data pigResponse
175+ if err := json . Unmarshal ( dataBytes , & data ); err != nil {
176+ return errors . New ( "解析JSON失败: " + err . Error ())
169177 }
170178
171- var sb strings.Builder
172- sb .WriteString (fmt .Sprintf ("🔎 根据关键词“%s”找到 %d 只猪猪:\n " , keyword , len (results )))
173-
174- maxShow := 10
175- for i , p := range results {
176- if i >= maxShow {
177- sb .WriteString (fmt .Sprintf ("\n ...等共 %d 条结果" , len (results )))
178- break
179- }
180- sb .WriteString (fmt .Sprintf ("%d: %s (ID: %s)\n " , i + 1 , p .Title , p .ID ))
179+ if len (data .Images ) == 0 {
180+ return errors .New ("数据文件为空" )
181181 }
182182
183- sb .WriteString ("\n 为您返回第一个猪猪:\n " )
184- sb .WriteString ("💡 提示:输入“猪猪id [id]”可精确获取" )
183+ pigCache = data .Images
185184
186- b64Image , err := fetchImageLazy ( results [ 0 ])
187- if err != nil {
188- ctx . SendChain ( message . Text ( sb . String (), " \n \n [Pig] ERROR: " , err , " \n EXP: 图片加载失败" ))
189- return
185+ // 更新缓存时,顺便重构一份查询 Map
186+ newMap := make ( map [ string ] * pigImage , len ( pigCache ))
187+ for i := range pigCache {
188+ newMap [ pigCache [ i ]. ID ] = & pigCache [ i ]
190189 }
190+ pigMap = newMap
191191
192- ctx .SendChain (
193- message .Text (sb .String ()),
194- message .Image (b64Image ),
195- )
192+ lastUpdateTime = time .Now ()
193+ return nil
196194}
197195
198- // handlePigByID 处理ID精确查找
199- func handlePigByID (ctx * zero.Ctx ) {
200- targetID := ctx .State ["regex_matched" ].([]string )[1 ]
201-
202- if err := checkAndUpdateData (ctx ); err != nil {
203- ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 精确查找失败,获取数据错误" ))
204- return
205- }
206-
207- pigMutex .RLock ()
208- defer pigMutex .RUnlock ()
209-
210- var target * PigImage
211- for _ , p := range pigCache {
212- if p .ID == targetID {
213- val := p
214- target = & val
215- break
216- }
196+ // fetch 作为 pigImage 的专属方法,直接返回 []byte
197+ func (img * pigImage ) fetch () ([]byte , error ) {
198+ if img .Filename == "" {
199+ return nil , errors .New ("图片数据异常,缺少文件名" )
217200 }
218201
219- if target == nil {
220- ctx .SendChain (message .Text ("[Pig] ERROR: 未找到 ID 为 " , targetID , " 的猪猪" ))
221- return
222- }
202+ targetPath := filepath .Join ("assets" , img .Filename )
223203
224- b64Image , err := fetchImageLazy (* target )
204+ // 使用 true,直接返回字节数组
205+ imgData , err := engine .GetLazyData (targetPath , true )
225206 if err != nil {
226- ctx .SendChain (message .Text ("[Pig] ERROR: " , err , "\n EXP: 图片加载失败" ))
227- return
207+ return nil , errors .New ("图片资源缺失 (" + targetPath + "): " + err .Error ())
228208 }
229209
230- ctx .SendChain (
231- message .Text (fmt .Sprintf ("🐷 ID: %s | %s" , target .ID , target .Title )),
232- message .Image (b64Image ),
233- )
210+ return imgData , nil
234211}
0 commit comments