forked from FloatTech/ZeroBot-Plugin
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* ✨ 添加水群时长统计 * 🐛 优化名片逻辑 * 🐛 更新发言时间 * 🎨 格式化 * 🐛 添加锁 * 🎨 改成at * 🎨 添加map * 🎨 修lint * 🐛 修改排序问题 * 🎨 优化lint
- Loading branch information
1 parent
81e255e
commit b637fe1
Showing
4 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Package chatcount 聊天时长统计 | ||
package chatcount | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
zero "github.com/wdvxdr1123/ZeroBot" | ||
"github.com/wdvxdr1123/ZeroBot/message" | ||
|
||
ctrl "github.com/FloatTech/zbpctrl" | ||
"github.com/FloatTech/zbputils/control" | ||
"github.com/FloatTech/zbputils/ctxext" | ||
) | ||
|
||
const ( | ||
rankSize = 10 | ||
) | ||
|
||
func init() { | ||
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ | ||
DisableOnDefault: false, | ||
Brief: "聊天时长统计", | ||
Help: "- 查询水群@xxx\n- 查看水群排名", | ||
PrivateDataFolder: "chatcount", | ||
}) | ||
go func() { | ||
ctdb = initialize(engine.DataFolder() + "chatcount.db") | ||
}() | ||
engine.OnMessage(zero.OnlyGroup).SetBlock(false). | ||
Handle(func(ctx *zero.Ctx) { | ||
remindTime, remindFlag := ctdb.updateChatTime(ctx.Event.GroupID, ctx.Event.UserID) | ||
if remindFlag { | ||
ctx.SendChain(message.At(ctx.Event.UserID), message.Text(fmt.Sprintf("BOT提醒:你今天已经水群%d分钟了!", remindTime))) | ||
} | ||
}) | ||
|
||
engine.OnPrefix(`查询水群`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { | ||
name := ctx.NickName() | ||
todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, ctx.Event.UserID) | ||
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("%s今天水了%d分%d秒,发了%d条消息;总计水了%d分%d秒,发了%d条消息。", name, todayTime/60, todayTime%60, todayMessage, totalTime/60, totalTime%60, totalMessage))) | ||
}) | ||
engine.OnFullMatch("查看水群排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true). | ||
Handle(func(ctx *zero.Ctx) { | ||
text := strings.Builder{} | ||
text.WriteString("今日水群排行榜:\n") | ||
chatTimeList := ctdb.getChatRank(ctx.Event.GroupID) | ||
for i := 0; i < len(chatTimeList) && i < rankSize; i++ { | ||
text.WriteString("第") | ||
text.WriteString(strconv.Itoa(i + 1)) | ||
text.WriteString("名:") | ||
text.WriteString(ctx.CardOrNickName(chatTimeList[i].UserID)) | ||
text.WriteString(" - ") | ||
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayMessage, 10)) | ||
text.WriteString("条,共") | ||
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayTime/60, 10)) | ||
text.WriteString("分") | ||
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayTime%60, 10)) | ||
text.WriteString("秒\n") | ||
} | ||
ctx.SendChain(message.Text(text.String())) | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
package chatcount | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
"github.com/RomiChan/syncx" | ||
|
||
"github.com/jinzhu/gorm" | ||
) | ||
|
||
const ( | ||
chatInterval = 300 | ||
) | ||
|
||
var ( | ||
// ctdb 聊天时长数据库全局变量 | ||
ctdb *chattimedb | ||
// l 水群提醒时间提醒段,单位分钟 | ||
l = newLeveler(60, 120, 180, 240, 300) | ||
) | ||
|
||
// chattimedb 聊天时长数据库结构体 | ||
type chattimedb struct { | ||
// ctdb.userTimestampMap 每个人发言的时间戳 key=groupID_userID | ||
userTimestampMap syncx.Map[string, int64] | ||
// ctdb.userTodayTimeMap 每个人今日水群时间 key=groupID_userID | ||
userTodayTimeMap syncx.Map[string, int64] | ||
// ctdb.userTodayMessageMap 每个人今日水群次数 key=groupID_userID | ||
userTodayMessageMap syncx.Map[string, int64] | ||
// db 数据库 | ||
db *gorm.DB | ||
// chatmu 读写添加锁 | ||
chatmu sync.Mutex | ||
} | ||
|
||
// initialize 初始化 | ||
func initialize(dbpath string) *chattimedb { | ||
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(&chatTime{}) | ||
return &chattimedb{ | ||
db: gdb, | ||
} | ||
} | ||
|
||
// Close 关闭 | ||
func (ctdb *chattimedb) Close() error { | ||
db := ctdb.db | ||
return db.Close() | ||
} | ||
|
||
// chatTime 聊天时长,时间的单位都是秒 | ||
type chatTime struct { | ||
ID uint `gorm:"primary_key"` | ||
GroupID int64 `gorm:"column:group_id"` | ||
UserID int64 `gorm:"column:user_id"` | ||
TodayTime int64 `gorm:"-"` | ||
TodayMessage int64 `gorm:"-"` | ||
TotalTime int64 `gorm:"column:total_time;default:0"` | ||
TotalMessage int64 `gorm:"column:total_message;default:0"` | ||
} | ||
|
||
// TableName 表名 | ||
func (chatTime) TableName() string { | ||
return "chat_time" | ||
} | ||
|
||
// updateChatTime 更新发言时间,todayTime的单位是分钟 | ||
func (ctdb *chattimedb) updateChatTime(gid, uid int64) (remindTime int64, remindFlag bool) { | ||
ctdb.chatmu.Lock() | ||
defer ctdb.chatmu.Unlock() | ||
db := ctdb.db | ||
now := time.Now() | ||
keyword := fmt.Sprintf("%v_%v", gid, uid) | ||
ts, ok := ctdb.userTimestampMap.Load(keyword) | ||
if !ok { | ||
ctdb.userTimestampMap.Store(keyword, now.Unix()) | ||
ctdb.userTodayMessageMap.Store(keyword, 1) | ||
return | ||
} | ||
lastTime := time.Unix(ts, 0) | ||
todayTime, _ := ctdb.userTodayTimeMap.Load(keyword) | ||
totayMessage, _ := ctdb.userTodayMessageMap.Load(keyword) | ||
//这个消息数是必须统计的 | ||
ctdb.userTodayMessageMap.Store(keyword, totayMessage+1) | ||
st := chatTime{ | ||
GroupID: gid, | ||
UserID: uid, | ||
TotalTime: todayTime, | ||
TotalMessage: totayMessage, | ||
} | ||
|
||
// 如果不是同一天,把TotalTime,TotalMessage重置 | ||
if lastTime.YearDay() != now.YearDay() { | ||
if err := db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st).Error; err != nil { | ||
if gorm.IsRecordNotFoundError(err) { | ||
db.Model(&st).Create(&st) | ||
} | ||
} else { | ||
db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).Update( | ||
map[string]any{ | ||
"total_time": st.TotalTime + todayTime, | ||
"total_message": st.TotalMessage + totayMessage, | ||
}) | ||
} | ||
ctdb.userTimestampMap.Store(keyword, now.Unix()) | ||
ctdb.userTodayTimeMap.Delete(keyword) | ||
ctdb.userTodayMessageMap.Delete(keyword) | ||
return | ||
} | ||
|
||
userChatTime := int64(now.Sub(lastTime).Seconds()) | ||
// 当聊天时间在一定范围内的话,则计入时长 | ||
if userChatTime < chatInterval { | ||
ctdb.userTodayTimeMap.Store(keyword, todayTime+userChatTime) | ||
remindTime = (todayTime + userChatTime) / 60 | ||
remindFlag = l.level(int((todayTime+userChatTime)/60)) > l.level(int(todayTime/60)) | ||
} | ||
ctdb.userTimestampMap.Store(keyword, now.Unix()) | ||
return | ||
} | ||
|
||
// getChatTime 获得用户聊天时长和消息次数,todayTime,totalTime的单位是秒,todayMessage,totalMessage单位是条数 | ||
func (ctdb *chattimedb) getChatTime(gid, uid int64) (todayTime, todayMessage, totalTime, totalMessage int64) { | ||
ctdb.chatmu.Lock() | ||
defer ctdb.chatmu.Unlock() | ||
db := ctdb.db | ||
st := chatTime{} | ||
db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st) | ||
keyword := fmt.Sprintf("%v_%v", gid, uid) | ||
todayTime, _ = ctdb.userTodayTimeMap.Load(keyword) | ||
todayMessage, _ = ctdb.userTodayMessageMap.Load(keyword) | ||
totalTime = st.TotalTime | ||
totalMessage = st.TotalMessage | ||
return | ||
} | ||
|
||
// getChatRank 获得水群排名,时间单位为秒 | ||
func (ctdb *chattimedb) getChatRank(gid int64) (chatTimeList []chatTime) { | ||
ctdb.chatmu.Lock() | ||
defer ctdb.chatmu.Unlock() | ||
chatTimeList = make([]chatTime, 0, 100) | ||
keyList := make([]string, 0, 100) | ||
ctdb.userTimestampMap.Range(func(key string, value int64) bool { | ||
t := time.Unix(value, 0) | ||
if strings.Contains(key, strconv.FormatInt(gid, 10)) && t.YearDay() == time.Now().YearDay() { | ||
keyList = append(keyList, key) | ||
} | ||
return true | ||
}) | ||
for _, v := range keyList { | ||
_, a, _ := strings.Cut(v, "_") | ||
uid, _ := strconv.ParseInt(a, 10, 64) | ||
todayTime, _ := ctdb.userTodayTimeMap.Load(v) | ||
todayMessage, _ := ctdb.userTodayMessageMap.Load(v) | ||
chatTimeList = append(chatTimeList, chatTime{ | ||
GroupID: gid, | ||
UserID: uid, | ||
TodayTime: todayTime, | ||
TodayMessage: todayMessage, | ||
}) | ||
} | ||
sort.Sort(sortChatTime(chatTimeList)) | ||
return | ||
} | ||
|
||
// leveler 结构体,包含一个 levelArray 字段 | ||
type leveler struct { | ||
levelArray []int | ||
} | ||
|
||
// newLeveler 构造函数,用于创建 Leveler 实例 | ||
func newLeveler(levels ...int) *leveler { | ||
return &leveler{ | ||
levelArray: levels, | ||
} | ||
} | ||
|
||
// level 方法,封装了 getLevel 函数的逻辑 | ||
func (l *leveler) level(t int) int { | ||
for i := len(l.levelArray) - 1; i >= 0; i-- { | ||
if t >= l.levelArray[i] { | ||
return i + 1 | ||
} | ||
} | ||
return 0 | ||
} | ||
|
||
// sortChatTime chatTime排序数组 | ||
type sortChatTime []chatTime | ||
|
||
// Len 实现 sort.Interface | ||
func (a sortChatTime) Len() int { | ||
return len(a) | ||
} | ||
|
||
// Less 实现 sort.Interface,按 TodayTime 降序,TodayMessage 降序 | ||
func (a sortChatTime) Less(i, j int) bool { | ||
if a[i].TodayTime == a[j].TodayTime { | ||
return a[i].TodayMessage > a[j].TodayMessage | ||
} | ||
return a[i].TodayTime > a[j].TodayTime | ||
} | ||
|
||
// Swap 实现 sort.Interface | ||
func (a sortChatTime) Swap(i, j int) { | ||
a[i], a[j] = a[j], a[i] | ||
} |