diff --git a/README.md b/README.md index 52b3507669..b39fba59ce 100644 --- a/README.md +++ b/README.md @@ -656,6 +656,24 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 教你一篇小作文[作文] + +
+ 多功能抽签 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots"` + + 支持图包文件夹和gif抽签 + + - [x] (刷新)抽签列表 + + - [x] 抽签-[签名] + + - [x] 查看抽签[gif签名] + + - [x] 添加抽签[签名][gif图片] + + - [x] 删除抽签[gif签名] +
女装 diff --git a/main.go b/main.go index b1d99c87cc..0dac58e48d 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots" // 多功能抽签 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dress" // 女装 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/drift_bottle" // 漂流瓶 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji diff --git a/plugin/drawlots/main.go b/plugin/drawlots/main.go new file mode 100644 index 0000000000..aa13785ed8 --- /dev/null +++ b/plugin/drawlots/main.go @@ -0,0 +1,249 @@ +// Package drawlots 多功能抽签插件 +package drawlots + +import ( + "errors" + "image" + "image/gif" + "math/rand" + "os" + "strconv" + "strings" + + "github.com/FloatTech/floatbox/file" + "github.com/FloatTech/imgfactory" + ctrl "github.com/FloatTech/zbpctrl" + control "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" + "github.com/FloatTech/zbputils/img/text" + "github.com/fumiama/jieba/util/helper" + "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +type info struct { + lotsType string // 文件后缀 + quantity int // 签数 +} + +var ( + lotsList = func() map[string]info { + lotsList, err := getList() + if err != nil { + logrus.Infoln("[drawlots]加载失败:", err) + } else { + logrus.Infoln("[drawlots]加载", len(lotsList), "个抽签") + } + return lotsList + }() + en = control.Register("drawlots", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "多功能抽签", + Help: "支持图包文件夹和gif抽签\n-------------\n- (刷新)抽签列表\n- 抽[签名]签\n- 看签[gif签名]\n- 加签[签名][gif图片]\n- 删签[gif签名]", + PrivateDataFolder: "drawlots", + }).ApplySingle(ctxext.DefaultSingle) + datapath = file.BOTPATH + "/" + en.DataFolder() +) + +func init() { + en.OnFullMatchGroup([]string{"抽签列表", "刷新抽签列表"}).SetBlock(true).Handle(func(ctx *zero.Ctx) { + lotsList, err := getList() // 刷新列表 + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + messageText := &strings.Builder{} + messageText.WriteString(" 签 名 [ 类 型 ]----签数\n") + messageText.WriteString("———————————\n") + for name, fileInfo := range lotsList { + messageText.WriteString(name + "[" + fileInfo.lotsType + "]----" + strconv.Itoa(fileInfo.quantity) + "\n") + messageText.WriteString("----------\n") + } + textPic, err := text.RenderToBase64(messageText.String(), text.BoldFontFile, 400, 50) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + ctx.SendChain(message.Image("base64://" + helper.BytesToString(textPic))) + }) + en.OnRegex(`^抽(.*)签$`).SetBlock(true).Handle(func(ctx *zero.Ctx) { + lotsType := ctx.State["regex_matched"].([]string)[1] + fileInfo, ok := lotsList[lotsType] + if !ok { + ctx.SendChain(message.Text("签名[", lotsType, "]不存在")) + return + } + if fileInfo.lotsType == "folder" { + picPath, err := randFile(lotsType, 3) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Image("file:///"+picPath)) + return + } + lotsImg, err := randGif(lotsType + "." + fileInfo.lotsType) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + // 生成图片 + data, err := imgfactory.ToBytes(lotsImg) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.ImageBytes(data)) + }) + en.OnPrefix("看签", zero.UserOrGrpAdmin).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { + id := ctx.Event.MessageID + lotsName := strings.TrimSpace(ctx.State["args"].(string)) + fileInfo, ok := lotsList[lotsName] + if !ok { + ctx.Send(message.ReplyWithMessage(id, message.Text("签名[", lotsName, "]不存在"))) + return + } + if fileInfo.lotsType == "folder" { + ctx.Send(message.ReplyWithMessage(id, message.Text("仅支持查看gif抽签"))) + return + } + ctx.Send(message.ReplyWithMessage(id, message.Image("file:///"+datapath+lotsName+"."+fileInfo.lotsType))) + }) + en.OnPrefix("加签", zero.SuperUserPermission, zero.MustProvidePicture).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { + id := ctx.Event.MessageID + lotsName := strings.TrimSpace(ctx.State["args"].(string)) + if lotsName == "" { + ctx.Send(message.ReplyWithMessage(id, message.Text("请使用正确的指令形式"))) + return + } + picURL := ctx.State["image_url"].([]string)[0] + err := file.DownloadTo(picURL, datapath+"/"+lotsName+".gif") + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + file, err := os.Open(datapath + "/" + lotsName + ".gif") + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + im, err := gif.DecodeAll(file) + _ = file.Close() + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + lotsList[lotsName] = info{ + lotsType: "gif", + quantity: len(im.Image), + } + ctx.Send(message.ReplyWithMessage(id, message.Text("成功"))) + }) + en.OnPrefix("删签", zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { + id := ctx.Event.MessageID + lotsName := strings.TrimSpace(ctx.State["args"].(string)) + fileInfo, ok := lotsList[lotsName] + if !ok { + ctx.Send(message.ReplyWithMessage(id, message.Text("签名[", lotsName, "]不存在"))) + return + } + if fileInfo.lotsType == "folder" { + ctx.Send(message.ReplyWithMessage(id, message.Text("图包请手动移除(保护图源误删),谢谢"))) + return + } + err := os.Remove(datapath + lotsName + "." + fileInfo.lotsType) + if err != nil { + ctx.SendChain(message.Text("ERROR:", err)) + return + } + delete(lotsList, lotsName) + ctx.Send(message.ReplyWithMessage(id, message.Text("成功"))) + }) +} + +func getList() (list map[string]info, err error) { + list = make(map[string]info, 100) + files, err := os.ReadDir(datapath) + if err != nil { + return + } + if len(files) == 0 { + err = errors.New("不存在任何抽签") + return + } + for _, lots := range files { + if lots.IsDir() { + files, _ := os.ReadDir(datapath + "/" + lots.Name()) + list[lots.Name()] = info{ + lotsType: "folder", + quantity: len(files), + } + continue + } + before, after, ok := strings.Cut(lots.Name(), ".") + if !ok || before == "" { + continue + } + file, err := os.Open(datapath + "/" + lots.Name()) + if err != nil { + return nil, err + } + im, err := gif.DecodeAll(file) + _ = file.Close() + if err != nil { + return nil, err + } + list[before] = info{ + lotsType: after, + quantity: len(im.Image), + } + } + return +} + +func randFile(path string, indexMax int) (string, error) { + picPath := datapath + path + files, err := os.ReadDir(picPath) + if err != nil { + return "", err + } + if len(files) > 0 { + drawFile := files[rand.Intn(len(files))] + // 如果是文件夹就递归 + if drawFile.IsDir() { + indexMax-- + if indexMax <= 0 { + return "", errors.New("图包[" + path + "]存在太多非图片文件,请清理") + } + return randFile(path, indexMax) + } + return picPath + "/" + drawFile.Name(), err + } + return "", errors.New("图包[" + path + "]不存在签内容") +} + +func randGif(gifName string) (image.Image, error) { + name := datapath + gifName + file, err := os.Open(name) + if err != nil { + return nil, err + } + im, err := gif.DecodeAll(file) + _ = file.Close() + if err != nil { + return nil, err + } + /* + firstImg, err := imgfactory.Load(name) + if err != nil { + return nil, err + } + v := im.Image[rand.Intn(len(im.Image))] + return imgfactory.Size(firstImg, firstImg.Bounds().Max.X, firstImg.Bounds().Max.Y).InsertUpC(v, 0, 0, firstImg.Bounds().Max.X/2, firstImg.Bounds().Max.Y/2).Clone().Image(),err + /*/ + // 如果gif图片出现信息缺失请使用上面注释掉的代码,把下面注释了(上面代码部分图存在bug) + v := im.Image[rand.Intn(len(im.Image))] + return imgfactory.Size(v, v.Bounds().Max.X, v.Bounds().Max.Y).Image(), err + // */ +}