diff --git a/README.md b/README.md index 45ef232353..9c2a305816 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,9 @@ zerobot -h -t token -u url [-d|w] [-g 监听地址:端口] qq1 qq2 qq3 ... - **cp短打** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_cpstory"` - [x] 组cp[@xxx][@xxx] - [x] 组cp大老师 雪乃 +- **签到得分** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_score"` + - [x] 签到 + - [x] 获得签到背景[@xxx]|获得签到背景 - **TODO...** ## 使用方法 diff --git a/main.go b/main.go index 18594a9d9c..f2149d40a6 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin_novel" // 铅笔小说网搜索 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_omikuji" // 浅草寺求签 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_reborn" // 投胎 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin_score" // 分数 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_shadiao" // 沙雕app _ "github.com/FloatTech/ZeroBot-Plugin/plugin_shindan" // 测定 diff --git a/plugin_mocking_bird/mocking_bird.go b/plugin_mocking_bird/mocking_bird.go index f933fbe1ec..65444d9277 100644 --- a/plugin_mocking_bird/mocking_bird.go +++ b/plugin_mocking_bird/mocking_bird.go @@ -20,7 +20,7 @@ import ( "github.com/FloatTech/ZeroBot-Plugin/control" aireply "github.com/FloatTech/ZeroBot-Plugin/plugin_ai_reply" - fileutil "github.com/FloatTech/ZeroBot-Plugin/utils/file" + "github.com/FloatTech/ZeroBot-Plugin/utils/file" "github.com/FloatTech/ZeroBot-Plugin/utils/web" ) @@ -57,7 +57,7 @@ func init() { syntPath := getSyntPath() fileName := getWav(textReply, syntPath, vocoderList[1], ctx.Event.UserID) // 回复 - ctx.SendChain(message.Record("file:///" + fileutil.BOTPATH + "/" + cachePath + fileName)) + ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cachePath + fileName)) }) } @@ -126,7 +126,7 @@ func getWav(text, syntPath, vocoder string, uid int64) (fileName string) { } defer res.Body.Close() data, _ := ioutil.ReadAll(res.Body) - err = ioutil.WriteFile(cachePath+fileName, data, 0666) + err = os.WriteFile(cachePath+fileName, data, 0666) if err != nil { log.Errorln("[mockingbird]:", err) } diff --git a/plugin_score/data.go b/plugin_score/data.go new file mode 100644 index 0000000000..b86922fae0 --- /dev/null +++ b/plugin_score/data.go @@ -0,0 +1,28 @@ +package score + +import ( + "github.com/FloatTech/ZeroBot-Plugin/utils/process" + log "github.com/sirupsen/logrus" + "os" +) + +const ( + cachePath = dbpath + "cache/" + dbpath = "data/Score/" + dbfile = dbpath + "score.db" +) + +// SDB 得分数据库 +var SDB *DB + +// 加载数据库 +func init() { + go func() { + process.SleepAbout1sTo2s() + _ = os.MkdirAll(dbpath, 0755) + os.RemoveAll(cachePath) + _ = os.MkdirAll(cachePath, 0755) + SDB = Initialize(dbfile) + log.Println("[score]加载score数据库") + }() +} diff --git a/plugin_score/model.go b/plugin_score/model.go new file mode 100644 index 0000000000..628922e526 --- /dev/null +++ b/plugin_score/model.go @@ -0,0 +1,124 @@ +package score + +import ( + "github.com/jinzhu/gorm" + _ "github.com/logoove/sqlite" // import sql + "os" + "time" +) + +// DB 分数数据库 +type DB gorm.DB + +// Score 分数结构体 +type Score struct { + UID int64 `gorm:"column:uid;primary_key"` + Score int `gorm:"column:score;default:0"` +} + +// TableName ... +func (Score) TableName() string { + return "score" +} + +// SignIn 签到结构体 +type SignIn struct { + UID int64 `gorm:"column:uid;primary_key"` + Count int `gorm:"column:count;default:0"` + UpdatedAt time.Time +} + +// TableName ... +func (SignIn) TableName() string { + return "sign_in" +} + +// Initialize 初始化ScoreDB数据库 +func Initialize(dbpath string) *DB { + 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(&Score{}).AutoMigrate(&SignIn{}) + return (*DB)(gdb) +} + +// Open ... +func Open(dbpath string) (*DB, error) { + db, err := gorm.Open("sqlite3", dbpath) + if err != nil { + return nil, err + } + return (*DB)(db), nil +} + +// Close ... +func (sdb *DB) Close() error { + db := (*gorm.DB)(sdb) + return db.Close() +} + +// GetScoreByUID 取得分数 +func (sdb *DB) GetScoreByUID(uid int64) (s Score) { + db := (*gorm.DB)(sdb) + db.Debug().Model(&Score{}).FirstOrCreate(&s, "uid = ? ", uid) + return s +} + +// InsertOrUpdateScoreByUID 插入或更新分数 +func (sdb *DB) InsertOrUpdateScoreByUID(uid int64, score int) (err error) { + db := (*gorm.DB)(sdb) + s := Score{ + UID: uid, + Score: score, + } + if err = db.Debug().Model(&Score{}).First(&s, "uid = ? ", uid).Error; err != nil { + // error handling... + if gorm.IsRecordNotFoundError(err) { + db.Debug().Model(&Score{}).Create(&s) // newUser not user + } + } else { + err = db.Debug().Model(&Score{}).Where("uid = ? ", uid).Update( + map[string]interface{}{ + "score": score, + }).Error + } + return +} + +// GetSignInByUID 取得签到次数 +func (sdb *DB) GetSignInByUID(uid int64) (si SignIn) { + db := (*gorm.DB)(sdb) + db.Debug().Model(&SignIn{}).FirstOrCreate(&si, "uid = ? ", uid) + return si +} + +// InsertOrUpdateSignInCountByUID 插入或更新签到次数 +func (sdb *DB) InsertOrUpdateSignInCountByUID(uid int64, count int) (err error) { + db := (*gorm.DB)(sdb) + si := SignIn{ + UID: uid, + Count: count, + } + if err = db.Debug().Model(&SignIn{}).First(&si, "uid = ? ", uid).Error; err != nil { + // error handling... + if gorm.IsRecordNotFoundError(err) { + db.Debug().Model(&SignIn{}).Create(&si) // newUser not user + } + } else { + err = db.Debug().Model(&SignIn{}).Where("uid = ? ", uid).Update( + map[string]interface{}{ + "count": count, + }).Error + } + return +} diff --git a/plugin_score/sign_in.go b/plugin_score/sign_in.go new file mode 100644 index 0000000000..256f337fdd --- /dev/null +++ b/plugin_score/sign_in.go @@ -0,0 +1,187 @@ +// Package score 签到,答题得分 +package score + +import ( + "fmt" + "github.com/FloatTech/ZeroBot-Plugin/control" + "github.com/FloatTech/ZeroBot-Plugin/utils/ctxext" + "github.com/FloatTech/ZeroBot-Plugin/utils/file" + "github.com/FloatTech/ZeroBot-Plugin/utils/txt2img" + "github.com/FloatTech/ZeroBot-Plugin/utils/web" + "github.com/fogleman/gg" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + "github.com/wdvxdr1123/ZeroBot/utils/helper" + "os" + "strconv" + "sync" + "time" +) + +const ( + prio = 5 + backgroundURL = "https://iw233.cn/API/pc.php?type=json" + referer = "https://iw233.cn/main.html" + ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" + signinMax = 1 + // ScoreMax 分数上限定为120 + ScoreMax = 120 +) + +var ( + engine = control.Register("score", &control.Options{ + DisableOnDefault: false, + Help: "签到得分\n- 签到\n- 获得签到背景[@xxx]|获得签到背景", + }) + levelArray = [...]int{0, 1, 2, 5, 10, 20, 35, 55, 75, 100, 120} + // 下载锁 + mu sync.Mutex +) + +func init() { + engine.OnFullMatch("签到").SetBlock(true).SetPriority(prio). + Handle(func(ctx *zero.Ctx) { + uid := ctx.Event.UserID + now := time.Now() + today := now.Format("20060102") + si := SDB.GetSignInByUID(uid) + picFile := cachePath + strconv.FormatInt(uid, 10) + today + ".png" + if file.IsNotExist(picFile) { + mu.Lock() + initPic(picFile) + mu.Unlock() + } + siUpdateTimeStr := si.UpdatedAt.Format("20060102") + if siUpdateTimeStr != today { + if err := SDB.InsertOrUpdateSignInCountByUID(uid, 0); err != nil { + log.Errorln("[score]:", err) + } + } + if si.Count >= signinMax && siUpdateTimeStr == today { + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("今天你已经签到过了!")) + return + } + if err := SDB.InsertOrUpdateSignInCountByUID(uid, si.Count+1); err != nil { + log.Errorln("[score]:", err) + } + back, err := gg.LoadImage(picFile) + if err != nil { + log.Errorln("[score]:", err) + } + canvas := gg.NewContext(back.Bounds().Size().X, int(float64(back.Bounds().Size().Y)*1.7)) + canvas.SetRGB(1, 1, 1) + canvas.Clear() + canvas.DrawImage(back, 0, 0) + + monthWord := now.Format("01/02") + hourWord := getHourWord(now) + if err = canvas.LoadFontFace(txt2img.BoldFontFile, float64(back.Bounds().Size().X)*0.1); err != nil { + log.Println("[score]:", err) + } + canvas.SetRGB(0, 0, 0) + canvas.DrawString(hourWord, float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.2) + canvas.DrawString(monthWord, float64(back.Bounds().Size().X)*0.6, float64(back.Bounds().Size().Y)*1.2) + nickName := ctxext.CardOrNickName(ctx, uid) + if err = canvas.LoadFontFace(txt2img.FontFile, float64(back.Bounds().Size().X)*0.04); err != nil { + log.Println("[score]:", err) + } + add := 1 + canvas.DrawString(nickName+fmt.Sprintf(" 小熊饼干+%d", add), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.3) + score := SDB.GetScoreByUID(uid).Score + score += add + if score > ScoreMax { + score = ScoreMax + ctx.SendChain(message.At(uid), message.Text("你获得的小熊饼干已经达到上限")) + } + if err := SDB.InsertOrUpdateScoreByUID(uid, score); err != nil { + log.Println("[score]:", err) + } + level := getLevel(score) + canvas.DrawString("当前小熊饼干:"+strconv.FormatInt(int64(score), 10), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.4) + canvas.DrawString("LEVEL:"+strconv.FormatInt(int64(level), 10), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.5) + canvas.DrawRectangle(float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.55, float64(back.Bounds().Size().X)*0.6, float64(back.Bounds().Size().Y)*0.1) + canvas.SetRGB255(150, 150, 150) + canvas.Fill() + var nextLevelScore int + if level < 10 { + nextLevelScore = levelArray[level+1] + } else { + nextLevelScore = ScoreMax + } + canvas.SetRGB255(0, 0, 0) + canvas.DrawRectangle(float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.55, float64(back.Bounds().Size().X)*0.6*float64(score)/float64(nextLevelScore), float64(back.Bounds().Size().Y)*0.1) + canvas.SetRGB255(102, 102, 102) + canvas.Fill() + canvas.DrawString(fmt.Sprintf("%d/%d", score, nextLevelScore), float64(back.Bounds().Size().X)*0.75, float64(back.Bounds().Size().Y)*1.62) + canvasBase64, err := txt2img.CanvasToBase64(canvas) + if err != nil { + log.Println("[score]:", err) + } + ctx.SendChain(message.Image("base64://" + helper.BytesToString(canvasBase64))) + }) + engine.OnPrefix("获得签到背景", zero.OnlyGroup).SetBlock(true).SetPriority(20). + Handle(func(ctx *zero.Ctx) { + param := ctx.State["args"].(string) + var uidStr string + if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" { + uidStr = ctx.Event.Message[1].Data["qq"] + } else if param == "" { + uidStr = strconv.FormatInt(ctx.Event.UserID, 10) + } + picFile := cachePath + uidStr + time.Now().Format("20060102") + ".png" + if file.IsNotExist(picFile) { + mu.Lock() + initPic(picFile) + mu.Unlock() + } + ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + picFile)) + }) +} + +func getHourWord(t time.Time) string { + switch { + case 6 <= t.Hour() && t.Hour() < 12: + return "早上好" + case 12 <= t.Hour() && t.Hour() < 14: + return "中午好" + case 14 <= t.Hour() && t.Hour() < 19: + return "下午好" + case 19 <= t.Hour() && t.Hour() < 24: + return "晚上好" + case 0 <= t.Hour() && t.Hour() < 6: + return "凌晨好" + default: + return "" + } +} + +func getLevel(count int) int { + for k, v := range levelArray { + if count == v { + return k + } else if count < v { + return k - 1 + } + } + return -1 +} + +func initPic(picFile string) { + if file.IsNotExist(picFile) { + data, err := web.ReqWith(backgroundURL, "GET", referer, ua) + if err != nil { + log.Errorln("[score]:", err) + } + picURL := gjson.Get(string(data), "pic").String() + data, err = web.ReqWith(picURL, "GET", "", ua) + if err != nil { + log.Errorln("[score]:", err) + } + err = os.WriteFile(picFile, data, 0666) + if err != nil { + log.Errorln("[score]:", err) + } + } +} diff --git a/plugin_shadiao/shadiao.go b/plugin_shadiao/shadiao.go index 760b46f171..8a7f0e98d3 100644 --- a/plugin_shadiao/shadiao.go +++ b/plugin_shadiao/shadiao.go @@ -28,7 +28,7 @@ const ( ) var ( - engine = control.Register("curse", &control.Options{ + engine = control.Register("shadiao", &control.Options{ DisableOnDefault: false, Help: "沙雕app\n" + "- 骂他[@xxx]|骂他[qq号](停用)\n- 骂我(停用)\n- 哄我\n- 渣我\n- 来碗绿茶\n- 发个朋友圈\n- 来碗毒鸡汤\n- 讲个段子", diff --git a/utils/txt2img/txt2img.go b/utils/txt2img/txt2img.go index e590afc332..718da82d2c 100644 --- a/utils/txt2img/txt2img.go +++ b/utils/txt2img/txt2img.go @@ -18,16 +18,21 @@ import ( const ( whitespace = "\t\n\r\x0b\x0c" - fontpath = "data/Font/" - fontfile = fontpath + "regular.ttf" + // FontPath 通用字体路径 + FontPath = "data/Font/" + // FontFile 苹方字体 + FontFile = FontPath + "regular.ttf" + // BoldFontFile 粗体苹方字体 + BoldFontFile = FontPath + "regular-bold.ttf" ) // 加载数据库 func init() { go func() { process.SleepAbout1sTo2s() - _ = os.MkdirAll(fontpath, 0755) - _, _ = file.GetLazyData(fontfile, false, true) + _ = os.MkdirAll(FontPath, 0755) + _, _ = file.GetLazyData(FontFile, false, true) + _, _ = file.GetLazyData(BoldFontFile, false, true) }() } @@ -35,19 +40,14 @@ func init() { func RenderToBase64(text string, width, fontSize int) (base64Bytes []byte, err error) { canvas, err := Render(text, width, fontSize) if err != nil { - log.Println("err:", err) + log.Println("[txt2img]:", err) return nil, err } - // 转成 base64 - buffer := new(bytes.Buffer) - encoder := base64.NewEncoder(base64.StdEncoding, buffer) - var opt jpeg.Options - opt.Quality = 70 - if err = jpeg.Encode(encoder, canvas.Image(), &opt); err != nil { + base64Bytes, err = CanvasToBase64(canvas) + if err != nil { + log.Println("[txt2img]:", err) return nil, err } - encoder.Close() - base64Bytes = buffer.Bytes() return } @@ -74,12 +74,12 @@ func Render(text string, width, fontSize int) (canvas *gg.Context, err error) { } } - canvas = gg.NewContext((fontSize+3)*width/2, (len(buff)+2)*fontSize) + canvas = gg.NewContext((fontSize+4)*width/2, (len(buff)+2)*fontSize) canvas.SetRGB(1, 1, 1) canvas.Clear() canvas.SetRGB(0, 0, 0) - if err = canvas.LoadFontFace(fontfile, float64(fontSize)); err != nil { - log.Println("err:", err) + if err = canvas.LoadFontFace(FontFile, float64(fontSize)); err != nil { + log.Println("[txt2img]:", err) return nil, err } for i, v := range buff { @@ -89,3 +89,17 @@ func Render(text string, width, fontSize int) (canvas *gg.Context, err error) { } return } + +// CanvasToBase64 gg内容转为base64 +func CanvasToBase64(canvas *gg.Context) (base64Bytes []byte, err error) { + buffer := new(bytes.Buffer) + encoder := base64.NewEncoder(base64.StdEncoding, buffer) + var opt jpeg.Options + opt.Quality = 70 + if err = jpeg.Encode(encoder, canvas.Image(), &opt); err != nil { + return nil, err + } + encoder.Close() + base64Bytes = buffer.Bytes() + return +}