Skip to content

Commit 871de92

Browse files
authored
feat(Pig): 添加猪猪表情包插件 (#1295)
* feat(Pig): 新增猪猪图片插件 * improve comments * fix engine usage * fix typo * fix lint * improve logic based on requested changes * initalize lastUpdateTime at trigger
1 parent 992204d commit 871de92

File tree

3 files changed

+221
-0
lines changed

3 files changed

+221
-0
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,18 @@ print("run[CQ:image,file="+j["img"]+"]")
12311231

12321232
- [x] 抽扑克牌
12331233

1234+
</details>
1235+
<details>
1236+
<summary>来份猪猪</summary>
1237+
1238+
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/pig"`
1239+
1240+
- [x] 随机猪猪
1241+
1242+
- [x] 搜索猪猪[猪名字]
1243+
1244+
- [x] 猪猪id[猪id]
1245+
12341246
</details>
12351247
<details>
12361248
<summary>一群一天一夫一妻制群老婆</summary>

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import (
132132
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆
133133
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
134134
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克
135+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/pig" // 来份猪猪
135136
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
136137
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙
137138
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术

plugin/pig/pig.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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, "\nEXP: 随机猪猪失败,获取数据错误"))
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, "\nEXP: 图片加载失败"))
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, "\nEXP: 搜索猪猪失败,获取数据错误"))
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, "\nEXP: 图片加载失败"))
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, "\nEXP: 精确查找失败,获取数据错误"))
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, "\nEXP: 图片加载失败"))
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

Comments
 (0)