Skip to content

Commit b637fe1

Browse files
authored
✨ 添加水群时长统计 (#913)
* ✨ 添加水群时长统计 * 🐛 优化名片逻辑 * 🐛 更新发言时间 * 🎨 格式化 * 🐛 添加锁 * 🎨 改成at * 🎨 添加map * 🎨 修lint * 🐛 修改排序问题 * 🎨 优化lint
1 parent 81e255e commit b637fe1

File tree

4 files changed

+302
-0
lines changed

4 files changed

+302
-0
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,16 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
176176

177177
- [x] 设置温度[正整数]
178178

179+
</details>
180+
<details>
181+
<summary>聊天时长统计</summary>
182+
183+
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chatcount"`
184+
185+
- [x] 查询水群@xxx
186+
187+
- [x] 查看水群排名
188+
179189
</details>
180190
<details>
181191
<summary>睡眠管理</summary>

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import (
3434

3535
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat" // 基础词库
3636

37+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chatcount" // 聊天时长统计
38+
3739
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleepmanage" // 统计睡眠时间
3840

3941
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/atri" // ATRI词库

plugin/chatcount/chatcount.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Package chatcount 聊天时长统计
2+
package chatcount
3+
4+
import (
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
zero "github.com/wdvxdr1123/ZeroBot"
10+
"github.com/wdvxdr1123/ZeroBot/message"
11+
12+
ctrl "github.com/FloatTech/zbpctrl"
13+
"github.com/FloatTech/zbputils/control"
14+
"github.com/FloatTech/zbputils/ctxext"
15+
)
16+
17+
const (
18+
rankSize = 10
19+
)
20+
21+
func init() {
22+
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
23+
DisableOnDefault: false,
24+
Brief: "聊天时长统计",
25+
Help: "- 查询水群@xxx\n- 查看水群排名",
26+
PrivateDataFolder: "chatcount",
27+
})
28+
go func() {
29+
ctdb = initialize(engine.DataFolder() + "chatcount.db")
30+
}()
31+
engine.OnMessage(zero.OnlyGroup).SetBlock(false).
32+
Handle(func(ctx *zero.Ctx) {
33+
remindTime, remindFlag := ctdb.updateChatTime(ctx.Event.GroupID, ctx.Event.UserID)
34+
if remindFlag {
35+
ctx.SendChain(message.At(ctx.Event.UserID), message.Text(fmt.Sprintf("BOT提醒:你今天已经水群%d分钟了!", remindTime)))
36+
}
37+
})
38+
39+
engine.OnPrefix(`查询水群`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
40+
name := ctx.NickName()
41+
todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, ctx.Event.UserID)
42+
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)))
43+
})
44+
engine.OnFullMatch("查看水群排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true).
45+
Handle(func(ctx *zero.Ctx) {
46+
text := strings.Builder{}
47+
text.WriteString("今日水群排行榜:\n")
48+
chatTimeList := ctdb.getChatRank(ctx.Event.GroupID)
49+
for i := 0; i < len(chatTimeList) && i < rankSize; i++ {
50+
text.WriteString("第")
51+
text.WriteString(strconv.Itoa(i + 1))
52+
text.WriteString("名:")
53+
text.WriteString(ctx.CardOrNickName(chatTimeList[i].UserID))
54+
text.WriteString(" - ")
55+
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayMessage, 10))
56+
text.WriteString("条,共")
57+
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayTime/60, 10))
58+
text.WriteString("分")
59+
text.WriteString(strconv.FormatInt(chatTimeList[i].TodayTime%60, 10))
60+
text.WriteString("秒\n")
61+
}
62+
ctx.SendChain(message.Text(text.String()))
63+
})
64+
65+
}

plugin/chatcount/model.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package chatcount
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"sort"
7+
"strconv"
8+
"strings"
9+
"sync"
10+
"time"
11+
12+
"github.com/RomiChan/syncx"
13+
14+
"github.com/jinzhu/gorm"
15+
)
16+
17+
const (
18+
chatInterval = 300
19+
)
20+
21+
var (
22+
// ctdb 聊天时长数据库全局变量
23+
ctdb *chattimedb
24+
// l 水群提醒时间提醒段,单位分钟
25+
l = newLeveler(60, 120, 180, 240, 300)
26+
)
27+
28+
// chattimedb 聊天时长数据库结构体
29+
type chattimedb struct {
30+
// ctdb.userTimestampMap 每个人发言的时间戳 key=groupID_userID
31+
userTimestampMap syncx.Map[string, int64]
32+
// ctdb.userTodayTimeMap 每个人今日水群时间 key=groupID_userID
33+
userTodayTimeMap syncx.Map[string, int64]
34+
// ctdb.userTodayMessageMap 每个人今日水群次数 key=groupID_userID
35+
userTodayMessageMap syncx.Map[string, int64]
36+
// db 数据库
37+
db *gorm.DB
38+
// chatmu 读写添加锁
39+
chatmu sync.Mutex
40+
}
41+
42+
// initialize 初始化
43+
func initialize(dbpath string) *chattimedb {
44+
var err error
45+
if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
46+
// 生成文件
47+
f, err := os.Create(dbpath)
48+
if err != nil {
49+
return nil
50+
}
51+
defer f.Close()
52+
}
53+
gdb, err := gorm.Open("sqlite3", dbpath)
54+
if err != nil {
55+
panic(err)
56+
}
57+
gdb.AutoMigrate(&chatTime{})
58+
return &chattimedb{
59+
db: gdb,
60+
}
61+
}
62+
63+
// Close 关闭
64+
func (ctdb *chattimedb) Close() error {
65+
db := ctdb.db
66+
return db.Close()
67+
}
68+
69+
// chatTime 聊天时长,时间的单位都是秒
70+
type chatTime struct {
71+
ID uint `gorm:"primary_key"`
72+
GroupID int64 `gorm:"column:group_id"`
73+
UserID int64 `gorm:"column:user_id"`
74+
TodayTime int64 `gorm:"-"`
75+
TodayMessage int64 `gorm:"-"`
76+
TotalTime int64 `gorm:"column:total_time;default:0"`
77+
TotalMessage int64 `gorm:"column:total_message;default:0"`
78+
}
79+
80+
// TableName 表名
81+
func (chatTime) TableName() string {
82+
return "chat_time"
83+
}
84+
85+
// updateChatTime 更新发言时间,todayTime的单位是分钟
86+
func (ctdb *chattimedb) updateChatTime(gid, uid int64) (remindTime int64, remindFlag bool) {
87+
ctdb.chatmu.Lock()
88+
defer ctdb.chatmu.Unlock()
89+
db := ctdb.db
90+
now := time.Now()
91+
keyword := fmt.Sprintf("%v_%v", gid, uid)
92+
ts, ok := ctdb.userTimestampMap.Load(keyword)
93+
if !ok {
94+
ctdb.userTimestampMap.Store(keyword, now.Unix())
95+
ctdb.userTodayMessageMap.Store(keyword, 1)
96+
return
97+
}
98+
lastTime := time.Unix(ts, 0)
99+
todayTime, _ := ctdb.userTodayTimeMap.Load(keyword)
100+
totayMessage, _ := ctdb.userTodayMessageMap.Load(keyword)
101+
//这个消息数是必须统计的
102+
ctdb.userTodayMessageMap.Store(keyword, totayMessage+1)
103+
st := chatTime{
104+
GroupID: gid,
105+
UserID: uid,
106+
TotalTime: todayTime,
107+
TotalMessage: totayMessage,
108+
}
109+
110+
// 如果不是同一天,把TotalTime,TotalMessage重置
111+
if lastTime.YearDay() != now.YearDay() {
112+
if err := db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st).Error; err != nil {
113+
if gorm.IsRecordNotFoundError(err) {
114+
db.Model(&st).Create(&st)
115+
}
116+
} else {
117+
db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).Update(
118+
map[string]any{
119+
"total_time": st.TotalTime + todayTime,
120+
"total_message": st.TotalMessage + totayMessage,
121+
})
122+
}
123+
ctdb.userTimestampMap.Store(keyword, now.Unix())
124+
ctdb.userTodayTimeMap.Delete(keyword)
125+
ctdb.userTodayMessageMap.Delete(keyword)
126+
return
127+
}
128+
129+
userChatTime := int64(now.Sub(lastTime).Seconds())
130+
// 当聊天时间在一定范围内的话,则计入时长
131+
if userChatTime < chatInterval {
132+
ctdb.userTodayTimeMap.Store(keyword, todayTime+userChatTime)
133+
remindTime = (todayTime + userChatTime) / 60
134+
remindFlag = l.level(int((todayTime+userChatTime)/60)) > l.level(int(todayTime/60))
135+
}
136+
ctdb.userTimestampMap.Store(keyword, now.Unix())
137+
return
138+
}
139+
140+
// getChatTime 获得用户聊天时长和消息次数,todayTime,totalTime的单位是秒,todayMessage,totalMessage单位是条数
141+
func (ctdb *chattimedb) getChatTime(gid, uid int64) (todayTime, todayMessage, totalTime, totalMessage int64) {
142+
ctdb.chatmu.Lock()
143+
defer ctdb.chatmu.Unlock()
144+
db := ctdb.db
145+
st := chatTime{}
146+
db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st)
147+
keyword := fmt.Sprintf("%v_%v", gid, uid)
148+
todayTime, _ = ctdb.userTodayTimeMap.Load(keyword)
149+
todayMessage, _ = ctdb.userTodayMessageMap.Load(keyword)
150+
totalTime = st.TotalTime
151+
totalMessage = st.TotalMessage
152+
return
153+
}
154+
155+
// getChatRank 获得水群排名,时间单位为秒
156+
func (ctdb *chattimedb) getChatRank(gid int64) (chatTimeList []chatTime) {
157+
ctdb.chatmu.Lock()
158+
defer ctdb.chatmu.Unlock()
159+
chatTimeList = make([]chatTime, 0, 100)
160+
keyList := make([]string, 0, 100)
161+
ctdb.userTimestampMap.Range(func(key string, value int64) bool {
162+
t := time.Unix(value, 0)
163+
if strings.Contains(key, strconv.FormatInt(gid, 10)) && t.YearDay() == time.Now().YearDay() {
164+
keyList = append(keyList, key)
165+
}
166+
return true
167+
})
168+
for _, v := range keyList {
169+
_, a, _ := strings.Cut(v, "_")
170+
uid, _ := strconv.ParseInt(a, 10, 64)
171+
todayTime, _ := ctdb.userTodayTimeMap.Load(v)
172+
todayMessage, _ := ctdb.userTodayMessageMap.Load(v)
173+
chatTimeList = append(chatTimeList, chatTime{
174+
GroupID: gid,
175+
UserID: uid,
176+
TodayTime: todayTime,
177+
TodayMessage: todayMessage,
178+
})
179+
}
180+
sort.Sort(sortChatTime(chatTimeList))
181+
return
182+
}
183+
184+
// leveler 结构体,包含一个 levelArray 字段
185+
type leveler struct {
186+
levelArray []int
187+
}
188+
189+
// newLeveler 构造函数,用于创建 Leveler 实例
190+
func newLeveler(levels ...int) *leveler {
191+
return &leveler{
192+
levelArray: levels,
193+
}
194+
}
195+
196+
// level 方法,封装了 getLevel 函数的逻辑
197+
func (l *leveler) level(t int) int {
198+
for i := len(l.levelArray) - 1; i >= 0; i-- {
199+
if t >= l.levelArray[i] {
200+
return i + 1
201+
}
202+
}
203+
return 0
204+
}
205+
206+
// sortChatTime chatTime排序数组
207+
type sortChatTime []chatTime
208+
209+
// Len 实现 sort.Interface
210+
func (a sortChatTime) Len() int {
211+
return len(a)
212+
}
213+
214+
// Less 实现 sort.Interface,按 TodayTime 降序,TodayMessage 降序
215+
func (a sortChatTime) Less(i, j int) bool {
216+
if a[i].TodayTime == a[j].TodayTime {
217+
return a[i].TodayMessage > a[j].TodayMessage
218+
}
219+
return a[i].TodayTime > a[j].TodayTime
220+
}
221+
222+
// Swap 实现 sort.Interface
223+
func (a sortChatTime) Swap(i, j int) {
224+
a[i], a[j] = a[j], a[i]
225+
}

0 commit comments

Comments
 (0)