mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-19 22:00:11 +08:00
添加签到,引入分数机制 (#101)
* feat:添加签到功能,引入分数机制 * feat:修lint * fix:加宽 * fix:修lint和加锁 * fix:解决冲突 * fix:二次判断
This commit is contained in:
parent
d8991ec016
commit
2ed25c6991
@ -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...**
|
||||
|
||||
## 使用方法
|
||||
|
||||
1
main.go
1
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" // 测定
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
28
plugin_score/data.go
Normal file
28
plugin_score/data.go
Normal file
@ -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数据库")
|
||||
}()
|
||||
}
|
||||
124
plugin_score/model.go
Normal file
124
plugin_score/model.go
Normal file
@ -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
|
||||
}
|
||||
187
plugin_score/sign_in.go
Normal file
187
plugin_score/sign_in.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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- 讲个段子",
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user