mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 22:00:11 +08:00
feat:添加月幕galgame网站图 (#138)
* feat:添加月幕galgame网站图 * fix:修lint * fix:不重复更新 * fix:增加命令提醒语 * fix:增加日语匹配 * fix:换位置加锁 * fix:隐式声明 * fix:copy数组 * Update ai_tts.go Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
This commit is contained in:
parent
a469000d7a
commit
5c620d6268
@ -287,6 +287,12 @@ zerobot [-h] [-t token] [-u url] [-n nickname] [-p prefix] [-d|w] [-g 监听地
|
||||
- **煎蛋网无聊图** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan"`
|
||||
- [x] 来份屌图
|
||||
- [x] 更新屌图
|
||||
- **月幕galgame图** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal"`
|
||||
- [x] 随机galCG
|
||||
- [x] 随机gal表情包
|
||||
- [x] galCG[xxx]
|
||||
- [x] gal表情包[xxx]
|
||||
- [x] 更新gal
|
||||
- **TODO...**
|
||||
|
||||
## 使用方法
|
||||
|
||||
1
main.go
1
main.go
@ -100,6 +100,7 @@ import (
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/vtb_quotation" // vtb语录
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
|
||||
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
|
||||
|
||||
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wtf" // 鬼东西
|
||||
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_push" // b站推送
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/pkumza/numcn"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -38,18 +39,26 @@ var (
|
||||
)
|
||||
|
||||
type ttsInstances struct {
|
||||
sync.RWMutex
|
||||
m map[string]tts.TTS
|
||||
l []string
|
||||
}
|
||||
|
||||
func (t *ttsInstances) List() []string {
|
||||
return t.l
|
||||
t.RLock()
|
||||
cl := make([]string, len(t.l))
|
||||
_ = copy(cl, t.l)
|
||||
t.RUnlock()
|
||||
return cl
|
||||
}
|
||||
|
||||
func init() {
|
||||
engine := control.Register(ttsServiceName, order.AcquirePrio(), &control.Options{
|
||||
DisableOnDefault: false,
|
||||
Help: "语音回复(包括拟声鸟和百度)\n- @Bot 任意文本(任意一句话回复)\n- 设置语音模式拟声鸟阿梓 | 设置语音模式拟声鸟药水哥 | 设置语音模式百度女声 | 设置语音模式百度男声| 设置语音模式百度度逍遥 | 设置语音模式百度度丫丫",
|
||||
DisableOnDefault: true,
|
||||
Help: "语音回复(包括拟声鸟和百度)\n" +
|
||||
"- @Bot 任意文本(任意一句话回复)\n" +
|
||||
"- 设置语音模式[拟声鸟阿梓 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n" +
|
||||
"- 设置默认语音模式[拟声鸟阿梓 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n",
|
||||
})
|
||||
engine.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).
|
||||
Handle(func(ctx *zero.Ctx) {
|
||||
@ -78,7 +87,13 @@ func init() {
|
||||
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
|
||||
return
|
||||
}
|
||||
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功"))
|
||||
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前模式为", param))
|
||||
})
|
||||
engine.OnRegex(`^设置默认语音模式(.*)$`, ctxext.FirstValueInList(t)).SetBlock(true).
|
||||
Handle(func(ctx *zero.Ctx) {
|
||||
param := ctx.State["regex_matched"].([]string)[1]
|
||||
t.setDefaultSoundMode(param)
|
||||
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,默认模式为", param))
|
||||
})
|
||||
}
|
||||
|
||||
@ -93,12 +108,14 @@ func (t *ttsInstances) setSoundMode(ctx *zero.Ctx, name string) error {
|
||||
gid = -ctx.Event.UserID
|
||||
}
|
||||
var index int64
|
||||
t.RLock()
|
||||
for i, s := range t.l {
|
||||
if s == name {
|
||||
index = int64(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
t.RUnlock()
|
||||
m, ok := control.Lookup(ttsServiceName)
|
||||
if !ok {
|
||||
return errors.New("no such plugin")
|
||||
@ -113,6 +130,8 @@ func (t *ttsInstances) getSoundMode(ctx *zero.Ctx) (name string) {
|
||||
}
|
||||
m, ok := control.Lookup(ttsServiceName)
|
||||
if ok {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
index := m.GetData(gid)
|
||||
if int(index) < len(t.l) {
|
||||
return t.l[index]
|
||||
@ -120,3 +139,18 @@ func (t *ttsInstances) getSoundMode(ctx *zero.Ctx) (name string) {
|
||||
}
|
||||
return "拟声鸟阿梓"
|
||||
}
|
||||
|
||||
func (t *ttsInstances) setDefaultSoundMode(name string) {
|
||||
var index int
|
||||
t.RLock()
|
||||
for _, s := range t.l {
|
||||
if s == name {
|
||||
break
|
||||
}
|
||||
index++
|
||||
}
|
||||
t.RUnlock()
|
||||
t.Lock()
|
||||
t.l[0], t.l[index] = t.l[index], t.l[0]
|
||||
t.Unlock()
|
||||
}
|
||||
276
plugin/ymgal/model.go
Normal file
276
plugin/ymgal/model.go
Normal file
@ -0,0 +1,276 @@
|
||||
package ymgal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antchfx/htmlquery"
|
||||
_ "github.com/fumiama/sqlite3" // import sql
|
||||
"github.com/jinzhu/gorm"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// gdb 得分数据库
|
||||
var gdb *ymgaldb
|
||||
|
||||
// ymgaldb galgame图片数据库
|
||||
type ymgaldb gorm.DB
|
||||
|
||||
var mu sync.RWMutex
|
||||
|
||||
// ymgal gal图片储存结构体
|
||||
type ymgal struct {
|
||||
ID int64 `gorm:"column:id" `
|
||||
Title string `gorm:"column:title" `
|
||||
PictureType string `gorm:"column:picture_type" `
|
||||
PictureDescription string `gorm:"column:picture_description;type:varchar(1024)" `
|
||||
PictureList string `gorm:"column:picture_list;type:varchar(20000)" `
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (ymgal) TableName() string {
|
||||
return "ymgal"
|
||||
}
|
||||
|
||||
// initialize 初始化ymgaldb数据库
|
||||
func initialize(dbpath string) *ymgaldb {
|
||||
var err error
|
||||
if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
|
||||
// 生成文件
|
||||
f, err := os.Create(dbpath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
}
|
||||
gdb, err := gorm.Open("sqlite3", dbpath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gdb.AutoMigrate(&ymgal{})
|
||||
return (*ymgaldb)(gdb)
|
||||
}
|
||||
|
||||
func (gdb *ymgaldb) insertOrUpdateYmgalByID(id int64, title, pictureType, pictureDescription, pictureList string) (err error) {
|
||||
db := (*gorm.DB)(gdb)
|
||||
y := ymgal{
|
||||
ID: id,
|
||||
Title: title,
|
||||
PictureType: pictureType,
|
||||
PictureDescription: pictureDescription,
|
||||
PictureList: pictureList,
|
||||
}
|
||||
if err = db.Debug().Model(&ymgal{}).First(&y, "id = ? ", id).Error; err != nil {
|
||||
if gorm.IsRecordNotFoundError(err) {
|
||||
err = db.Debug().Model(&ymgal{}).Create(&y).Error // newUser not user
|
||||
}
|
||||
} else {
|
||||
err = db.Debug().Model(&ymgal{}).Where("id = ? ", id).Update(map[string]interface{}{
|
||||
"title": title,
|
||||
"picture_type": pictureType,
|
||||
"picture_description": pictureDescription,
|
||||
"picture_list": pictureList,
|
||||
}).Error
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (gdb *ymgaldb) getYmgalByID(id string) (y ymgal) {
|
||||
db := (*gorm.DB)(gdb)
|
||||
db.Debug().Model(&ymgal{}).Where("id = ?", id).Take(&y)
|
||||
return
|
||||
}
|
||||
|
||||
func (gdb *ymgaldb) randomYmgal(pictureType string) (y ymgal) {
|
||||
db := (*gorm.DB)(gdb)
|
||||
var count int
|
||||
s := db.Debug().Model(&ymgal{}).Where("picture_type = ?", pictureType).Count(&count)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
s.Offset(rand.Intn(count)).Take(&y)
|
||||
return
|
||||
}
|
||||
|
||||
func (gdb *ymgaldb) getYmgalByKey(pictureType, key string) (y ymgal) {
|
||||
db := (*gorm.DB)(gdb)
|
||||
var count int
|
||||
s := db.Debug().Model(&ymgal{}).Where("picture_type = ? and (picture_description like ? or title like ?) ", pictureType, "%"+key+"%", "%"+key+"%").Count(&count)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
s.Offset(rand.Intn(count)).Take(&y)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
webURL = "https://www.ymgal.com"
|
||||
cgType = "Gal CG"
|
||||
emoticonType = "其他"
|
||||
webPicURL = webURL + "/co/picset/"
|
||||
reNumber = `\d+`
|
||||
)
|
||||
|
||||
var (
|
||||
cgURL = webURL + "/search?type=picset&sort=default&category=" + url.QueryEscape(cgType) + "&page="
|
||||
emoticonURL = webURL + "/search?type=picset&sort=default&category=" + url.QueryEscape(emoticonType) + "&page="
|
||||
commonPageNumberExpr = "//*[@id='pager-box']/div/a[@class='icon item pager-next']/preceding-sibling::a[1]/text()"
|
||||
cgIDList []string
|
||||
emoticonIDList []string
|
||||
)
|
||||
|
||||
func initPageNumber() (maxCgPageNumber, maxEmoticonPageNumber int) {
|
||||
doc, err := htmlquery.LoadURL(cgURL + "1")
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
maxCgPageNumber, err = strconv.Atoi(htmlquery.FindOne(doc, commonPageNumberExpr).Data)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
doc, err = htmlquery.LoadURL(emoticonURL + "1")
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
maxEmoticonPageNumber, err = strconv.Atoi(htmlquery.FindOne(doc, commonPageNumberExpr).Data)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getPicID(pageNumber int, pictureType string) {
|
||||
var picURL string
|
||||
if pictureType == cgType {
|
||||
picURL = cgURL + strconv.Itoa(pageNumber)
|
||||
} else if pictureType == emoticonType {
|
||||
picURL = emoticonURL + strconv.Itoa(pageNumber)
|
||||
}
|
||||
doc, err := htmlquery.LoadURL(picURL)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
list := htmlquery.Find(doc, "//*[@id='picset-result-list']/ul/div/div[1]/a")
|
||||
for i := 0; i < len(list); i++ {
|
||||
re := regexp.MustCompile(reNumber)
|
||||
picID := re.FindString(list[i].Attr[0].Val)
|
||||
if pictureType == cgType {
|
||||
cgIDList = append(cgIDList, picID)
|
||||
} else if pictureType == emoticonType {
|
||||
emoticonIDList = append(emoticonIDList, picID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updatePic() {
|
||||
maxCgPageNumber, maxEmoticonPageNumber := initPageNumber()
|
||||
for i := 1; i <= maxCgPageNumber; i++ {
|
||||
getPicID(i, cgType)
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
for i := 1; i <= maxEmoticonPageNumber; i++ {
|
||||
getPicID(i, emoticonType)
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
CGLOOP:
|
||||
for i := len(cgIDList) - 1; i >= 0; i-- {
|
||||
mu.RLock()
|
||||
y := gdb.getYmgalByID(cgIDList[i])
|
||||
mu.RUnlock()
|
||||
if y.PictureList == "" {
|
||||
mu.Lock()
|
||||
storeCgPic(cgIDList[i])
|
||||
mu.Unlock()
|
||||
} else {
|
||||
break CGLOOP
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
EMOTICONLOOP:
|
||||
for i := len(emoticonIDList) - 1; i >= 0; i-- {
|
||||
mu.RLock()
|
||||
y := gdb.getYmgalByID(emoticonIDList[i])
|
||||
mu.RUnlock()
|
||||
if y.PictureList == "" {
|
||||
mu.Lock()
|
||||
storeEmoticonPic(emoticonIDList[i])
|
||||
mu.Unlock()
|
||||
} else {
|
||||
break EMOTICONLOOP
|
||||
}
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
}
|
||||
|
||||
func storeCgPic(picIDStr string) {
|
||||
picID, err := strconv.ParseInt(picIDStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
pictureType := cgType
|
||||
doc, err := htmlquery.LoadURL(webPicURL + picIDStr)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
title := htmlquery.FindOne(doc, "//meta[@name='name']").Attr[1].Val
|
||||
pictureDescription := htmlquery.FindOne(doc, "//meta[@name='description']").Attr[1].Val
|
||||
pictureNumberStr := htmlquery.FindOne(doc, "//div[@class='meta-info']/div[@class='meta-right']/span[2]/text()").Data
|
||||
re := regexp.MustCompile(reNumber)
|
||||
pictureNumber, err := strconv.Atoi(re.FindString(pictureNumberStr))
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
pictureList := ""
|
||||
for i := 1; i <= pictureNumber; i++ {
|
||||
picURL := htmlquery.FindOne(doc, fmt.Sprintf("//*[@id='main-picset-warp']/div/div[2]/div/div[@class='swiper-wrapper']/div[%d]", i)).Attr[1].Val
|
||||
if i == 1 {
|
||||
pictureList += picURL
|
||||
} else {
|
||||
pictureList += "," + picURL
|
||||
}
|
||||
}
|
||||
err = gdb.insertOrUpdateYmgalByID(picID, title, pictureType, pictureDescription, pictureList)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func storeEmoticonPic(picIDStr string) {
|
||||
picID, err := strconv.ParseInt(picIDStr, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
pictureType := emoticonType
|
||||
doc, err := htmlquery.LoadURL(webPicURL + picIDStr)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
title := htmlquery.FindOne(doc, "//meta[@name='name']").Attr[1].Val
|
||||
pictureDescription := htmlquery.FindOne(doc, "//meta[@name='description']").Attr[1].Val
|
||||
pictureNumberStr := htmlquery.FindOne(doc, "//div[@class='meta-info']/div[@class='meta-right']/span[2]/text()").Data
|
||||
re := regexp.MustCompile(reNumber)
|
||||
pictureNumber, err := strconv.Atoi(re.FindString(pictureNumberStr))
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
pictureList := ""
|
||||
for i := 1; i <= pictureNumber; i++ {
|
||||
picURL := htmlquery.FindOne(doc, fmt.Sprintf("//*[@id='main-picset-warp']/div/div[@class='stream-list']/div[%d]/img", i)).Attr[1].Val
|
||||
if i == 1 {
|
||||
pictureList += picURL
|
||||
} else {
|
||||
pictureList += "," + picURL
|
||||
}
|
||||
}
|
||||
err = gdb.insertOrUpdateYmgalByID(picID, title, pictureType, pictureDescription, pictureList)
|
||||
if err != nil {
|
||||
log.Errorln("[ymgal]:", err)
|
||||
}
|
||||
}
|
||||
95
plugin/ymgal/ymgal.go
Normal file
95
plugin/ymgal/ymgal.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Package ymgal 月幕galgame
|
||||
package ymgal
|
||||
|
||||
import (
|
||||
"github.com/FloatTech/zbputils/control"
|
||||
"github.com/FloatTech/zbputils/control/order"
|
||||
"github.com/FloatTech/zbputils/ctxext"
|
||||
"github.com/FloatTech/zbputils/file"
|
||||
zero "github.com/wdvxdr1123/ZeroBot"
|
||||
"github.com/wdvxdr1123/ZeroBot/message"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func init() {
|
||||
engine := control.Register("ymgal", order.AcquirePrio(), &control.Options{
|
||||
DisableOnDefault: false,
|
||||
Help: "月幕galgame\n- 随机galCG\n- 随机gal表情包\n- galCG[xxx]\n- gal表情包[xxx]\n- 更新gal\n",
|
||||
PublicDataFolder: "Ymgal",
|
||||
})
|
||||
dbfile := engine.DataFolder() + "ymgal.db"
|
||||
go func() {
|
||||
defer order.DoneOnExit()()
|
||||
_, _ = file.GetLazyData(dbfile, false, false)
|
||||
gdb = initialize(dbfile)
|
||||
log.Println("[ymgal]加载月幕gal数据库")
|
||||
}()
|
||||
engine.OnRegex("^随机gal(CG|表情包)$").Limit(ctxext.LimitByUser).SetBlock(true).
|
||||
Handle(func(ctx *zero.Ctx) {
|
||||
ctx.Send("少女祈祷中......")
|
||||
pictureType := ctx.State["regex_matched"].([]string)[1]
|
||||
var y ymgal
|
||||
if pictureType == "表情包" {
|
||||
y = gdb.randomYmgal(emoticonType)
|
||||
} else {
|
||||
y = gdb.randomYmgal(cgType)
|
||||
}
|
||||
sendYmgal(y, ctx)
|
||||
})
|
||||
engine.OnRegex("^gal(CG|表情包)([一-龥ぁ-んァ-ヶA-Za-z0-9]{1,25})$").Limit(ctxext.LimitByUser).SetBlock(true).
|
||||
Handle(func(ctx *zero.Ctx) {
|
||||
ctx.Send("少女祈祷中......")
|
||||
pictureType := ctx.State["regex_matched"].([]string)[1]
|
||||
key := ctx.State["regex_matched"].([]string)[2]
|
||||
var y ymgal
|
||||
if pictureType == "CG" {
|
||||
y = gdb.getYmgalByKey(cgType, key)
|
||||
} else {
|
||||
y = gdb.getYmgalByKey(emoticonType, key)
|
||||
}
|
||||
sendYmgal(y, ctx)
|
||||
})
|
||||
engine.OnFullMatch("更新gal", zero.SuperUserPermission).SetBlock(true).Handle(
|
||||
func(ctx *zero.Ctx) {
|
||||
ctx.Send("少女祈祷中......")
|
||||
updatePic()
|
||||
ctx.Send("ymgal数据库已更新")
|
||||
})
|
||||
}
|
||||
|
||||
func sendYmgal(y ymgal, ctx *zero.Ctx) {
|
||||
if y.PictureList == "" {
|
||||
ctx.SendChain(message.Text(zero.BotConfig.NickName[0] + "暂时没有这样的图呢"))
|
||||
return
|
||||
}
|
||||
m := message.Message{
|
||||
message.CustomNode(
|
||||
ctx.Event.Sender.NickName,
|
||||
ctx.Event.UserID,
|
||||
y.Title,
|
||||
)}
|
||||
if y.PictureDescription != "" {
|
||||
m = append(m,
|
||||
message.CustomNode(
|
||||
ctx.Event.Sender.NickName,
|
||||
ctx.Event.UserID,
|
||||
y.PictureDescription,
|
||||
))
|
||||
}
|
||||
for _, v := range strings.Split(y.PictureList, ",") {
|
||||
m = append(m,
|
||||
message.CustomNode(
|
||||
ctx.Event.Sender.NickName,
|
||||
ctx.Event.UserID,
|
||||
[]message.MessageSegment{
|
||||
message.Image(v),
|
||||
}),
|
||||
)
|
||||
}
|
||||
if id := ctx.SendGroupForwardMessage(
|
||||
ctx.Event.GroupID,
|
||||
m).Get("message_id").Int(); id == 0 {
|
||||
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user