Skip to content

Commit 5c620d6

Browse files
guohuiyuanfumiama
andauthored
feat:添加月幕galgame网站图 (#138)
* feat:添加月幕galgame网站图 * fix:修lint * fix:不重复更新 * fix:增加命令提醒语 * fix:增加日语匹配 * fix:换位置加锁 * fix:隐式声明 * fix:copy数组 * Update ai_tts.go Co-authored-by: 源文雨 <[email protected]>
1 parent a469000 commit 5c620d6

File tree

5 files changed

+416
-4
lines changed

5 files changed

+416
-4
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,12 @@ zerobot [-h] [-t token] [-u url] [-n nickname] [-p prefix] [-d|w] [-g 监听地
287287
- **煎蛋网无聊图** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan"`
288288
- [x] 来份屌图
289289
- [x] 更新屌图
290+
- **月幕galgame图** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal"`
291+
- [x] 随机galCG
292+
- [x] 随机gal表情包
293+
- [x] galCG[xxx]
294+
- [x] gal表情包[xxx]
295+
- [x] 更新gal
290296
- **TODO...**
291297

292298
## 使用方法

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ import (
100100
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/vtb_quotation" // vtb语录
101101
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评
102102
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
103+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
103104

104105
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wtf" // 鬼东西
105106
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_push" // b站推送

plugin/ai_reply/tts.go renamed to plugin/ai_reply/ai_tts.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"regexp"
66
"strconv"
7+
"sync"
78

89
"github.com/pkumza/numcn"
910
log "github.com/sirupsen/logrus"
@@ -38,18 +39,26 @@ var (
3839
)
3940

4041
type ttsInstances struct {
42+
sync.RWMutex
4143
m map[string]tts.TTS
4244
l []string
4345
}
4446

4547
func (t *ttsInstances) List() []string {
46-
return t.l
48+
t.RLock()
49+
cl := make([]string, len(t.l))
50+
_ = copy(cl, t.l)
51+
t.RUnlock()
52+
return cl
4753
}
4854

4955
func init() {
5056
engine := control.Register(ttsServiceName, order.AcquirePrio(), &control.Options{
51-
DisableOnDefault: false,
52-
Help: "语音回复(包括拟声鸟和百度)\n- @Bot 任意文本(任意一句话回复)\n- 设置语音模式拟声鸟阿梓 | 设置语音模式拟声鸟药水哥 | 设置语音模式百度女声 | 设置语音模式百度男声| 设置语音模式百度度逍遥 | 设置语音模式百度度丫丫",
57+
DisableOnDefault: true,
58+
Help: "语音回复(包括拟声鸟和百度)\n" +
59+
"- @Bot 任意文本(任意一句话回复)\n" +
60+
"- 设置语音模式[拟声鸟阿梓 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n" +
61+
"- 设置默认语音模式[拟声鸟阿梓 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n",
5362
})
5463
engine.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).
5564
Handle(func(ctx *zero.Ctx) {
@@ -78,7 +87,13 @@ func init() {
7887
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
7988
return
8089
}
81-
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功"))
90+
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前模式为", param))
91+
})
92+
engine.OnRegex(`^设置默认语音模式(.*)$`, ctxext.FirstValueInList(t)).SetBlock(true).
93+
Handle(func(ctx *zero.Ctx) {
94+
param := ctx.State["regex_matched"].([]string)[1]
95+
t.setDefaultSoundMode(param)
96+
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,默认模式为", param))
8297
})
8398
}
8499

@@ -93,12 +108,14 @@ func (t *ttsInstances) setSoundMode(ctx *zero.Ctx, name string) error {
93108
gid = -ctx.Event.UserID
94109
}
95110
var index int64
111+
t.RLock()
96112
for i, s := range t.l {
97113
if s == name {
98114
index = int64(i)
99115
break
100116
}
101117
}
118+
t.RUnlock()
102119
m, ok := control.Lookup(ttsServiceName)
103120
if !ok {
104121
return errors.New("no such plugin")
@@ -113,10 +130,27 @@ func (t *ttsInstances) getSoundMode(ctx *zero.Ctx) (name string) {
113130
}
114131
m, ok := control.Lookup(ttsServiceName)
115132
if ok {
133+
t.RLock()
134+
defer t.RUnlock()
116135
index := m.GetData(gid)
117136
if int(index) < len(t.l) {
118137
return t.l[index]
119138
}
120139
}
121140
return "拟声鸟阿梓"
122141
}
142+
143+
func (t *ttsInstances) setDefaultSoundMode(name string) {
144+
var index int
145+
t.RLock()
146+
for _, s := range t.l {
147+
if s == name {
148+
break
149+
}
150+
index++
151+
}
152+
t.RUnlock()
153+
t.Lock()
154+
t.l[0], t.l[index] = t.l[index], t.l[0]
155+
t.Unlock()
156+
}

plugin/ymgal/model.go

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package ymgal
2+
3+
import (
4+
"fmt"
5+
"github.com/antchfx/htmlquery"
6+
_ "github.com/fumiama/sqlite3" // import sql
7+
"github.com/jinzhu/gorm"
8+
log "github.com/sirupsen/logrus"
9+
"math/rand"
10+
"net/url"
11+
"os"
12+
"regexp"
13+
"strconv"
14+
"sync"
15+
"time"
16+
)
17+
18+
// gdb 得分数据库
19+
var gdb *ymgaldb
20+
21+
// ymgaldb galgame图片数据库
22+
type ymgaldb gorm.DB
23+
24+
var mu sync.RWMutex
25+
26+
// ymgal gal图片储存结构体
27+
type ymgal struct {
28+
ID int64 `gorm:"column:id" `
29+
Title string `gorm:"column:title" `
30+
PictureType string `gorm:"column:picture_type" `
31+
PictureDescription string `gorm:"column:picture_description;type:varchar(1024)" `
32+
PictureList string `gorm:"column:picture_list;type:varchar(20000)" `
33+
}
34+
35+
// TableName ...
36+
func (ymgal) TableName() string {
37+
return "ymgal"
38+
}
39+
40+
// initialize 初始化ymgaldb数据库
41+
func initialize(dbpath string) *ymgaldb {
42+
var err error
43+
if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
44+
// 生成文件
45+
f, err := os.Create(dbpath)
46+
if err != nil {
47+
return nil
48+
}
49+
defer f.Close()
50+
}
51+
gdb, err := gorm.Open("sqlite3", dbpath)
52+
if err != nil {
53+
panic(err)
54+
}
55+
gdb.AutoMigrate(&ymgal{})
56+
return (*ymgaldb)(gdb)
57+
}
58+
59+
func (gdb *ymgaldb) insertOrUpdateYmgalByID(id int64, title, pictureType, pictureDescription, pictureList string) (err error) {
60+
db := (*gorm.DB)(gdb)
61+
y := ymgal{
62+
ID: id,
63+
Title: title,
64+
PictureType: pictureType,
65+
PictureDescription: pictureDescription,
66+
PictureList: pictureList,
67+
}
68+
if err = db.Debug().Model(&ymgal{}).First(&y, "id = ? ", id).Error; err != nil {
69+
if gorm.IsRecordNotFoundError(err) {
70+
err = db.Debug().Model(&ymgal{}).Create(&y).Error // newUser not user
71+
}
72+
} else {
73+
err = db.Debug().Model(&ymgal{}).Where("id = ? ", id).Update(map[string]interface{}{
74+
"title": title,
75+
"picture_type": pictureType,
76+
"picture_description": pictureDescription,
77+
"picture_list": pictureList,
78+
}).Error
79+
}
80+
return
81+
}
82+
83+
func (gdb *ymgaldb) getYmgalByID(id string) (y ymgal) {
84+
db := (*gorm.DB)(gdb)
85+
db.Debug().Model(&ymgal{}).Where("id = ?", id).Take(&y)
86+
return
87+
}
88+
89+
func (gdb *ymgaldb) randomYmgal(pictureType string) (y ymgal) {
90+
db := (*gorm.DB)(gdb)
91+
var count int
92+
s := db.Debug().Model(&ymgal{}).Where("picture_type = ?", pictureType).Count(&count)
93+
if count == 0 {
94+
return
95+
}
96+
s.Offset(rand.Intn(count)).Take(&y)
97+
return
98+
}
99+
100+
func (gdb *ymgaldb) getYmgalByKey(pictureType, key string) (y ymgal) {
101+
db := (*gorm.DB)(gdb)
102+
var count int
103+
s := db.Debug().Model(&ymgal{}).Where("picture_type = ? and (picture_description like ? or title like ?) ", pictureType, "%"+key+"%", "%"+key+"%").Count(&count)
104+
if count == 0 {
105+
return
106+
}
107+
s.Offset(rand.Intn(count)).Take(&y)
108+
return
109+
}
110+
111+
const (
112+
webURL = "https://www.ymgal.com"
113+
cgType = "Gal CG"
114+
emoticonType = "其他"
115+
webPicURL = webURL + "/co/picset/"
116+
reNumber = `\d+`
117+
)
118+
119+
var (
120+
cgURL = webURL + "/search?type=picset&sort=default&category=" + url.QueryEscape(cgType) + "&page="
121+
emoticonURL = webURL + "/search?type=picset&sort=default&category=" + url.QueryEscape(emoticonType) + "&page="
122+
commonPageNumberExpr = "//*[@id='pager-box']/div/a[@class='icon item pager-next']/preceding-sibling::a[1]/text()"
123+
cgIDList []string
124+
emoticonIDList []string
125+
)
126+
127+
func initPageNumber() (maxCgPageNumber, maxEmoticonPageNumber int) {
128+
doc, err := htmlquery.LoadURL(cgURL + "1")
129+
if err != nil {
130+
log.Errorln("[ymgal]:", err)
131+
}
132+
maxCgPageNumber, err = strconv.Atoi(htmlquery.FindOne(doc, commonPageNumberExpr).Data)
133+
if err != nil {
134+
log.Errorln("[ymgal]:", err)
135+
}
136+
doc, err = htmlquery.LoadURL(emoticonURL + "1")
137+
if err != nil {
138+
log.Errorln("[ymgal]:", err)
139+
}
140+
maxEmoticonPageNumber, err = strconv.Atoi(htmlquery.FindOne(doc, commonPageNumberExpr).Data)
141+
if err != nil {
142+
log.Errorln("[ymgal]:", err)
143+
}
144+
return
145+
}
146+
147+
func getPicID(pageNumber int, pictureType string) {
148+
var picURL string
149+
if pictureType == cgType {
150+
picURL = cgURL + strconv.Itoa(pageNumber)
151+
} else if pictureType == emoticonType {
152+
picURL = emoticonURL + strconv.Itoa(pageNumber)
153+
}
154+
doc, err := htmlquery.LoadURL(picURL)
155+
if err != nil {
156+
log.Errorln("[ymgal]:", err)
157+
}
158+
list := htmlquery.Find(doc, "//*[@id='picset-result-list']/ul/div/div[1]/a")
159+
for i := 0; i < len(list); i++ {
160+
re := regexp.MustCompile(reNumber)
161+
picID := re.FindString(list[i].Attr[0].Val)
162+
if pictureType == cgType {
163+
cgIDList = append(cgIDList, picID)
164+
} else if pictureType == emoticonType {
165+
emoticonIDList = append(emoticonIDList, picID)
166+
}
167+
}
168+
169+
}
170+
171+
func updatePic() {
172+
maxCgPageNumber, maxEmoticonPageNumber := initPageNumber()
173+
for i := 1; i <= maxCgPageNumber; i++ {
174+
getPicID(i, cgType)
175+
time.Sleep(time.Millisecond * 500)
176+
}
177+
for i := 1; i <= maxEmoticonPageNumber; i++ {
178+
getPicID(i, emoticonType)
179+
time.Sleep(time.Millisecond * 500)
180+
}
181+
CGLOOP:
182+
for i := len(cgIDList) - 1; i >= 0; i-- {
183+
mu.RLock()
184+
y := gdb.getYmgalByID(cgIDList[i])
185+
mu.RUnlock()
186+
if y.PictureList == "" {
187+
mu.Lock()
188+
storeCgPic(cgIDList[i])
189+
mu.Unlock()
190+
} else {
191+
break CGLOOP
192+
}
193+
time.Sleep(time.Millisecond * 500)
194+
}
195+
EMOTICONLOOP:
196+
for i := len(emoticonIDList) - 1; i >= 0; i-- {
197+
mu.RLock()
198+
y := gdb.getYmgalByID(emoticonIDList[i])
199+
mu.RUnlock()
200+
if y.PictureList == "" {
201+
mu.Lock()
202+
storeEmoticonPic(emoticonIDList[i])
203+
mu.Unlock()
204+
} else {
205+
break EMOTICONLOOP
206+
}
207+
time.Sleep(time.Millisecond * 500)
208+
}
209+
}
210+
211+
func storeCgPic(picIDStr string) {
212+
picID, err := strconv.ParseInt(picIDStr, 10, 64)
213+
if err != nil {
214+
log.Errorln("[ymgal]:", err)
215+
}
216+
pictureType := cgType
217+
doc, err := htmlquery.LoadURL(webPicURL + picIDStr)
218+
if err != nil {
219+
log.Errorln("[ymgal]:", err)
220+
}
221+
title := htmlquery.FindOne(doc, "//meta[@name='name']").Attr[1].Val
222+
pictureDescription := htmlquery.FindOne(doc, "//meta[@name='description']").Attr[1].Val
223+
pictureNumberStr := htmlquery.FindOne(doc, "//div[@class='meta-info']/div[@class='meta-right']/span[2]/text()").Data
224+
re := regexp.MustCompile(reNumber)
225+
pictureNumber, err := strconv.Atoi(re.FindString(pictureNumberStr))
226+
if err != nil {
227+
log.Errorln("[ymgal]:", err)
228+
}
229+
pictureList := ""
230+
for i := 1; i <= pictureNumber; i++ {
231+
picURL := htmlquery.FindOne(doc, fmt.Sprintf("//*[@id='main-picset-warp']/div/div[2]/div/div[@class='swiper-wrapper']/div[%d]", i)).Attr[1].Val
232+
if i == 1 {
233+
pictureList += picURL
234+
} else {
235+
pictureList += "," + picURL
236+
}
237+
}
238+
err = gdb.insertOrUpdateYmgalByID(picID, title, pictureType, pictureDescription, pictureList)
239+
if err != nil {
240+
log.Errorln("[ymgal]:", err)
241+
}
242+
243+
}
244+
245+
func storeEmoticonPic(picIDStr string) {
246+
picID, err := strconv.ParseInt(picIDStr, 10, 64)
247+
if err != nil {
248+
log.Errorln("[ymgal]:", err)
249+
}
250+
pictureType := emoticonType
251+
doc, err := htmlquery.LoadURL(webPicURL + picIDStr)
252+
if err != nil {
253+
log.Errorln("[ymgal]:", err)
254+
}
255+
title := htmlquery.FindOne(doc, "//meta[@name='name']").Attr[1].Val
256+
pictureDescription := htmlquery.FindOne(doc, "//meta[@name='description']").Attr[1].Val
257+
pictureNumberStr := htmlquery.FindOne(doc, "//div[@class='meta-info']/div[@class='meta-right']/span[2]/text()").Data
258+
re := regexp.MustCompile(reNumber)
259+
pictureNumber, err := strconv.Atoi(re.FindString(pictureNumberStr))
260+
if err != nil {
261+
log.Errorln("[ymgal]:", err)
262+
}
263+
pictureList := ""
264+
for i := 1; i <= pictureNumber; i++ {
265+
picURL := htmlquery.FindOne(doc, fmt.Sprintf("//*[@id='main-picset-warp']/div/div[@class='stream-list']/div[%d]/img", i)).Attr[1].Val
266+
if i == 1 {
267+
pictureList += picURL
268+
} else {
269+
pictureList += "," + picURL
270+
}
271+
}
272+
err = gdb.insertOrUpdateYmgalByID(picID, title, pictureType, pictureDescription, pictureList)
273+
if err != nil {
274+
log.Errorln("[ymgal]:", err)
275+
}
276+
}

0 commit comments

Comments
 (0)