Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
542a2d5ef6 | ||
|
|
c13771be22 | ||
|
|
982ddef0da | ||
|
|
99447d332d | ||
|
|
3478f4f9e2 | ||
|
|
b3ed0fcc6f | ||
|
|
3da8b327d0 | ||
|
|
0262fdd9f7 | ||
|
|
f775f1d67d | ||
|
|
43f9d590b7 | ||
|
|
22c7d3683b | ||
|
|
d589a9615e | ||
|
|
13205e445f | ||
|
|
a414a98a51 | ||
|
|
d21089ab69 | ||
|
|
a274f085f8 | ||
|
|
8ddc40cc01 | ||
|
|
c0c810d02e | ||
|
|
905dab5ab8 | ||
|
|
ddb0db0b6a | ||
|
|
8bac050a02 | ||
|
|
a548af9de2 | ||
|
|
64c3c9c78b | ||
|
|
0f7cf5515d | ||
|
|
a2d84dea08 | ||
|
|
d3f8a59390 | ||
|
|
165fefdb48 | ||
|
|
22474efc57 | ||
|
|
797821e2ed | ||
|
|
79cbeb6ea5 | ||
|
|
49705a03a8 | ||
|
|
40e97608d3 | ||
|
|
fe7bc02c08 | ||
|
|
bb2c4e919e | ||
|
|
0ea189fb82 | ||
|
|
778db8e349 | ||
|
|
4b7ad4126c | ||
|
|
1413181bd4 | ||
|
|
a11ba51246 | ||
|
|
ea4262adf0 | ||
|
|
a9f6c9ff0d | ||
|
|
244bff5714 | ||
|
|
b46271a111 | ||
|
|
5d11cc7c8a | ||
|
|
c6da056f98 | ||
|
|
61e03a6a7b |
@@ -16,6 +16,13 @@ type changeStatusParam struct {
|
||||
UserId string `json:"userId"`
|
||||
}
|
||||
|
||||
// changeUseAiModelParam
|
||||
// @description: 修改使用的AI模型用的参数集
|
||||
type changeUseAiModelParam struct {
|
||||
WxId string `json:"wxid" binding:"required"` // 群Id或微信Id
|
||||
Model string `json:"model" binding:"required"` // 模型代码
|
||||
}
|
||||
|
||||
// ChangeEnableAiStatus
|
||||
// @description: 修改是否开启AI
|
||||
// @param ctx
|
||||
@@ -39,6 +46,27 @@ func ChangeEnableAiStatus(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, "操作成功")
|
||||
}
|
||||
|
||||
// ChangeUseAiModel
|
||||
// @description: 修改使用的AI模型
|
||||
// @param ctx
|
||||
func ChangeUseAiModel(ctx *gin.Context) {
|
||||
var p changeUseAiModelParam
|
||||
if err := ctx.ShouldBind(&p); err != nil {
|
||||
ctx.String(http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
err := client.MySQL.Model(&entity.Friend{}).
|
||||
Where("wxid = ?", p.WxId).
|
||||
Update("`ai_model`", p.Model).Error
|
||||
if err != nil {
|
||||
log.Printf("修改【%s】的AI模型失败:%s", p.WxId, err)
|
||||
ctx.String(http.StatusInternalServerError, "操作失败: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.String(http.StatusOK, "操作成功")
|
||||
}
|
||||
|
||||
// ChangeEnableGroupRankStatus
|
||||
// @description: 修改是否开启水群排行榜
|
||||
// @param ctx
|
||||
@@ -62,6 +90,29 @@ func ChangeEnableGroupRankStatus(ctx *gin.Context) {
|
||||
ctx.String(http.StatusOK, "操作成功")
|
||||
}
|
||||
|
||||
// ChangeEnableSummaryStatus
|
||||
// @description: 修改是否开启聊天记录总结
|
||||
// @param ctx
|
||||
func ChangeEnableSummaryStatus(ctx *gin.Context) {
|
||||
var p changeStatusParam
|
||||
if err := ctx.ShouldBindJSON(&p); err != nil {
|
||||
ctx.String(http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
log.Printf("待修改的群Id:%s", p.WxId)
|
||||
|
||||
err := client.MySQL.Model(&entity.Friend{}).
|
||||
Where("wxid = ?", p.WxId).
|
||||
Update("`enable_summary`", gorm.Expr(" !`enable_summary`")).Error
|
||||
if err != nil {
|
||||
log.Printf("修改开启聊天记录总结失败:%s", err)
|
||||
ctx.String(http.StatusInternalServerError, "操作失败: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.String(http.StatusOK, "操作成功")
|
||||
}
|
||||
|
||||
// ChangeEnableWelcomeStatus
|
||||
// @description: 修改是否开启迎新
|
||||
// @param ctx
|
||||
|
||||
@@ -23,6 +23,7 @@ func Index(ctx *gin.Context) {
|
||||
result["friends"] = friends
|
||||
result["groups"] = groups
|
||||
result["vnc"] = config.Conf.Wechat.VncUrl
|
||||
result["aiModels"] = config.Conf.Ai.Models
|
||||
// 渲染页面
|
||||
ctx.HTML(http.StatusOK, "index.html", result)
|
||||
}
|
||||
|
||||
52
config.yaml
52
config.yaml
@@ -3,7 +3,7 @@ wechat:
|
||||
# 微信HOOK接口地址
|
||||
host: 10.0.0.73:19088
|
||||
# 微信容器映射出来的vnc页面地址,没有就不填
|
||||
vncUrl: http://192.168.1.175:19087/vnc_lite.html
|
||||
# vncUrl: http://192.168.1.175:19087/vnc_lite.html
|
||||
# 是否在启动的时候自动设置hook服务的回调
|
||||
autoSetCallback: false
|
||||
# 回调IP,如果是Docker运行,本参数必填(填auto表示自动,不适用于 docker 环境),如果Docker修改了映射,格式为 ip:port
|
||||
@@ -14,24 +14,41 @@ wechat:
|
||||
|
||||
# 数据库
|
||||
mysql:
|
||||
drive: mysql # 使用的数据库驱动,支持 mysql、postgres
|
||||
host: 10.0.0.31
|
||||
port: 3307
|
||||
user: wechat
|
||||
password: wechat123
|
||||
db: wechat
|
||||
schema: public # postgres 专用
|
||||
|
||||
task:
|
||||
enable: false
|
||||
enable: true
|
||||
syncFriends:
|
||||
enable: false
|
||||
cron: '*/5 * * * *' # 五分钟一次
|
||||
enable: false
|
||||
cron: '*/5 * * * *' # 五分钟一次
|
||||
groupSummary:
|
||||
enable: true
|
||||
cron: '30 0 * * *' # 每天0:30
|
||||
waterGroup:
|
||||
enable: true
|
||||
cron:
|
||||
yesterday: '30 9 * * *' # 每天9:30
|
||||
week: '30 9 * * 1' # 每周一9:30
|
||||
month: '30 9 1 * *' # 每月1号9:30
|
||||
year: '0 9 1 1 *' # 每年1月1号9:30
|
||||
enable: false
|
||||
cron:
|
||||
yesterday: '30 9 * * *' # 每天9:30
|
||||
week: '30 9 * * 1' # 每周一9:30
|
||||
month: '30 9 1 * *' # 每月1号9:30
|
||||
year: '0 9 1 1 *' # 每年1月1号9:30
|
||||
|
||||
# MQ配置
|
||||
mq:
|
||||
# 是否启用
|
||||
enable: false
|
||||
# RabbitMQ配置
|
||||
rabbitmq:
|
||||
host: 10.0.0.247
|
||||
port: 5672
|
||||
user: wechat
|
||||
password: wechat123
|
||||
vhost: wechat
|
||||
|
||||
# AI回复
|
||||
ai:
|
||||
@@ -39,12 +56,27 @@ ai:
|
||||
enable: false
|
||||
# 模型,不填默认gpt-3.5-turbo-0613
|
||||
model: gpt-3.5-turbo-0613
|
||||
# 群聊总结模型
|
||||
summaryModel: gpt-4-0613
|
||||
# OpenAI Api key
|
||||
apiKey: sk-xxxx
|
||||
# 接口代理域名,不填默认ChatGPT官方地址
|
||||
baseUrl: https://sxxx
|
||||
# 人设
|
||||
personality: 你的名字叫张三,你是一个百科机器人,你的爱好是看电影,你的性格是开朗的,你的专长是讲故事,你的梦想是当一名童话故事作家。你对政治没有一点儿兴趣,也不会讨论任何与政治相关的话题,你甚至可以拒绝回答这一类话题。
|
||||
models:
|
||||
- name: ChatGPT-4
|
||||
model: gpt-4-0613
|
||||
- name: 讯飞星火v3
|
||||
model: SparkDesk3
|
||||
- name: 讯飞星火随机
|
||||
model: SparkDesk
|
||||
- name: 月之暗面-8k
|
||||
model: moonshot-v1-8k
|
||||
- name: 月之暗面-32k
|
||||
model: moonshot-v1-32k
|
||||
- name: 月之暗面-128k
|
||||
model: moonshot-v1-128k
|
||||
|
||||
# 资源配置
|
||||
# map[k]v结构,k 会变成全小写,所以这儿不能用大写字母
|
||||
|
||||
19
config/ai.go
19
config/ai.go
@@ -3,9 +3,18 @@ package config
|
||||
// ai
|
||||
// @description: AI配置
|
||||
type ai struct {
|
||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用AI
|
||||
Model string `json:"model" yaml:"model"` // 模型
|
||||
ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key
|
||||
BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址
|
||||
Personality string `json:"personality" yaml:"personality"` // 人设
|
||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用AI
|
||||
Model string `json:"model" yaml:"model"` // 模型
|
||||
SummaryModel string `json:"summaryModel" yaml:"summaryModel"` // 总结模型
|
||||
ApiKey string `json:"apiKey" yaml:"apiKey"` // API Key
|
||||
BaseUrl string `json:"baseUrl" yaml:"baseUrl"` // API地址
|
||||
Personality string `json:"personality" yaml:"personality"` // 人设
|
||||
Models []aiModel `json:"models" yaml:"models"` // 模型列表
|
||||
}
|
||||
|
||||
// aiModel
|
||||
// @description: AI模型
|
||||
type aiModel struct {
|
||||
Name string `json:"name" yaml:"name"` // 模型名称
|
||||
Model string `json:"model" yaml:"model"` // 模型代码
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ type conf struct {
|
||||
Task task `json:"task" yaml:"task"` // 定时任务配置
|
||||
MySQL mysql `json:"mysql" yaml:"mysql"` // MySQL 配置
|
||||
Wechat wechat `json:"wechat" yaml:"wechat"` // 微信助手
|
||||
Mq mq `json:"mq" yaml:"mq"` // MQ 配置
|
||||
Ai ai `json:"ai" yaml:"ai"` // AI配置
|
||||
Resource map[string]resourceItem `json:"resource" yaml:"resource"` // 资源配置
|
||||
}
|
||||
|
||||
32
config/mq.go
Normal file
32
config/mq.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// mq
|
||||
// @description: MQ配置
|
||||
type mq struct {
|
||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用
|
||||
RabbitMQ rabbitMq `json:"rabbitmq" yaml:"rabbitmq"` // RabbitMQ配置
|
||||
}
|
||||
|
||||
// rabbitMq
|
||||
// @description: RabbitMQ配置
|
||||
type rabbitMq struct {
|
||||
Host string `json:"host" yaml:"host"` // 主机地址
|
||||
Port int `json:"port" yaml:"port"` // 端口
|
||||
User string `json:"user" yaml:"user"` // 用户名
|
||||
Password string `json:"password" yaml:"password"` // 密码
|
||||
VHost string `json:"vhost" yaml:"vhost"` // 虚拟主机
|
||||
}
|
||||
|
||||
// GetURL
|
||||
// @description: 获取MQ连接地址
|
||||
// @receiver r
|
||||
// @return string
|
||||
func (r rabbitMq) GetURL() string {
|
||||
port := r.Port
|
||||
if port == 0 {
|
||||
port = 5672
|
||||
}
|
||||
return fmt.Sprintf("amqp://%s:%s@%s:%d/%s", r.User, r.Password, r.Host, port, r.VHost)
|
||||
}
|
||||
@@ -3,9 +3,10 @@ package config
|
||||
// task
|
||||
// @description: 定时任务
|
||||
type task struct {
|
||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用
|
||||
SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友
|
||||
WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜
|
||||
Enable bool `json:"enable" yaml:"enable"` // 是否启用
|
||||
SyncFriends syncFriends `json:"syncFriends" yaml:"syncFriends"` // 同步好友
|
||||
WaterGroup waterGroup `json:"waterGroup" yaml:"waterGroup"` // 水群排行榜
|
||||
GroupSummary syncFriends `json:"groupSummary" yaml:"groupSummary"` // 群聊总结
|
||||
}
|
||||
|
||||
// syncFriends
|
||||
|
||||
@@ -23,9 +23,6 @@ services:
|
||||
image: mysql:8
|
||||
container_name: gw-db
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
wechat:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=wechat
|
||||
- MYSQL_USER=wechat
|
||||
@@ -41,6 +38,7 @@ services:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mysql
|
||||
- wechat
|
||||
volumes:
|
||||
# 配置文件请参阅项目根目录的config.yaml文件
|
||||
- ./config/config.yaml:/app/config.yaml
|
||||
|
||||
@@ -7,16 +7,18 @@ import (
|
||||
// Friend
|
||||
// @description: 好友列表
|
||||
type Friend struct {
|
||||
Wxid string `json:"wxid"` // 微信原始Id
|
||||
CustomAccount string `json:"customAccount"` // 微信号
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Pinyin string `json:"pinyin"` // 昵称拼音大写首字母
|
||||
PinyinAll string `json:"pinyinAll"` // 昵称全拼
|
||||
EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI
|
||||
AiModel string `json:"aiModel"` // AI模型
|
||||
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
|
||||
EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新
|
||||
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
|
||||
Wxid string `json:"wxid"` // 微信原始Id
|
||||
CustomAccount string `json:"customAccount"` // 微信号
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
Pinyin string `json:"pinyin"` // 昵称拼音大写首字母
|
||||
PinyinAll string `json:"pinyinAll"` // 昵称全拼
|
||||
LastActive time.Time `json:"lastActive"` // 最后活跃时间
|
||||
EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI
|
||||
AiModel string `json:"aiModel"` // AI模型
|
||||
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
|
||||
EnableWelcome bool `json:"enableWelcome" gorm:"type:tinyint(1) default 0 not null"` // 是否启用迎新
|
||||
EnableSummary bool `json:"enableSummary" gorm:"type:tinyint(1) default 0 not null"` // 是否启用总结
|
||||
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
|
||||
}
|
||||
|
||||
func (Friend) TableName() string {
|
||||
@@ -34,6 +36,7 @@ type GroupUser struct {
|
||||
IsMember bool `json:"isMember" gorm:"type:tinyint(1) default 0 not null"` // 是否群成员
|
||||
IsAdmin bool `json:"isAdmin" gorm:"type:tinyint(1) default 0 not null"` // 是否群主
|
||||
JoinTime time.Time `json:"joinTime"` // 加入时间
|
||||
LastActive time.Time `json:"lastActive"` // 最后活跃时间
|
||||
LeaveTime *time.Time `json:"leaveTime"` // 离开时间
|
||||
SkipChatRank bool `json:"skipChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否跳过聊天排行
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -8,6 +8,7 @@ require (
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/rabbitmq/amqp091-go v1.9.0
|
||||
github.com/sashabaranov/go-openai v1.17.11
|
||||
github.com/spf13/viper v1.18.2
|
||||
gorm.io/driver/mysql v1.5.2
|
||||
|
||||
33
go.sum
33
go.sum
@@ -14,8 +14,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/duke-git/lancet/v2 v2.2.7 h1:u9zr6HR+MDUvZEtTlAFtSTIgZfEFsN7cKi27n5weZsw=
|
||||
github.com/duke-git/lancet/v2 v2.2.7/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/duke-git/lancet/v2 v2.2.8 h1:wlruXhliDe4zls1e2cYmz4qLc+WtcvrpcCnk1VJdEaA=
|
||||
github.com/duke-git/lancet/v2 v2.2.8/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
@@ -28,8 +26,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-co-op/gocron v1.36.1 h1:BnyJ7kOixY/oGW/hkUvuLYOmAHBiY8Mr6kadX2DN1hE=
|
||||
github.com/go-co-op/gocron v1.36.1/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@@ -40,8 +36,6 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
||||
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
@@ -49,12 +43,9 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -92,14 +83,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
@@ -110,8 +101,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sashabaranov/go-openai v1.17.9 h1:QEoBiGKWW68W79YIfXWEFZ7l5cEgZBV4/Ow3uy+5hNY=
|
||||
github.com/sashabaranov/go-openai v1.17.9/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sashabaranov/go-openai v1.17.11 h1:XVr00J8JymJVx8Hjbh/5mG0V4PQHRarBU3v7k2x6MR0=
|
||||
github.com/sashabaranov/go-openai v1.17.11/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
@@ -122,8 +111,6 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.0 h1:pN6W1ub/G4OfnM+NR9p7xP9R6TltLUzp5JG9yZD3Qg0=
|
||||
github.com/spf13/viper v1.18.0/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -147,22 +134,18 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
|
||||
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
|
||||
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -173,8 +156,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -189,8 +170,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -214,10 +193,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
2
main.go
2
main.go
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go-wechat/config"
|
||||
"go-wechat/initialization"
|
||||
"go-wechat/mq"
|
||||
"go-wechat/router"
|
||||
"go-wechat/tasks"
|
||||
"go-wechat/tcpserver"
|
||||
@@ -20,6 +21,7 @@ func init() {
|
||||
initialization.InitWechatRobotInfo() // 初始化机器人信息
|
||||
initialization.Plugin() // 注册插件
|
||||
tasks.InitTasks() // 初始化定时任务
|
||||
mq.Init() // 初始化MQ
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -88,7 +88,13 @@ func (m Message) IsRevokeMsg() bool {
|
||||
// @receiver m
|
||||
// @return bool
|
||||
func (m Message) IsNewUserJoin() bool {
|
||||
sysFlag := m.Type == types.MsgTypeSys && strings.Contains(m.Content, "\"邀请\"") && strings.Contains(m.Content, "\"加入了群聊")
|
||||
if m.Type != types.MsgTypeSys {
|
||||
return false
|
||||
}
|
||||
|
||||
isInvitation := strings.Contains(m.Content, "\"邀请\"") && strings.Contains(m.Content, "\"加入了群聊")
|
||||
isScanQrCode := strings.Contains(m.Content, "通过扫描") && strings.Contains(m.Content, "加入群聊")
|
||||
sysFlag := isInvitation || isScanQrCode
|
||||
if sysFlag {
|
||||
return true
|
||||
}
|
||||
@@ -97,7 +103,7 @@ func (m Message) IsNewUserJoin() bool {
|
||||
if err := xml.Unmarshal([]byte(m.Content), &d); err != nil {
|
||||
return false
|
||||
}
|
||||
return m.Type == types.MsgTypeSys && d.Type == "delchatroommember"
|
||||
return d.Type == "delchatroommember"
|
||||
}
|
||||
|
||||
// IsAt
|
||||
|
||||
46
mq/handler.go
Normal file
46
mq/handler.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package mq
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"go-wechat/common/current"
|
||||
"go-wechat/model"
|
||||
"go-wechat/types"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parse
|
||||
// @description: 解析消息
|
||||
// @param msg
|
||||
func parse(msg []byte) {
|
||||
var m model.Message
|
||||
if err := json.Unmarshal(msg, &m); err != nil {
|
||||
log.Printf("消息解析失败: %v", err)
|
||||
log.Printf("消息内容: %d -> %v", len(msg), string(msg))
|
||||
return
|
||||
}
|
||||
// 记录原始数据
|
||||
m.Raw = string(msg)
|
||||
|
||||
// 提取出群成员信息
|
||||
// Sys类型的消息正文不包含微信 Id,所以不需要处理
|
||||
if m.IsGroup() && m.Type != types.MsgTypeSys {
|
||||
// 群消息,处理一下消息和发信人
|
||||
groupUser := strings.Split(m.Content, "\n")[0]
|
||||
groupUser = strings.ReplaceAll(groupUser, ":", "")
|
||||
// 如果两个id一致,说明是系统发的
|
||||
if m.FromUser != groupUser {
|
||||
m.GroupUser = groupUser
|
||||
}
|
||||
// 用户的操作单独提出来处理一下
|
||||
m.Content = strings.Join(strings.Split(m.Content, "\n")[1:], "\n")
|
||||
}
|
||||
log.Printf("收到新微信消息\n消息来源: %s\n群成员: %s\n消息类型: %v\n消息内容: %s", m.FromUser, m.GroupUser, m.Type, m.Content)
|
||||
|
||||
// 插件不为空,开始执行
|
||||
if p := current.GetRobotMessageHandler(); p != nil {
|
||||
p(&m)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
118
mq/rabbitmq.go
Normal file
118
mq/rabbitmq.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package mq
|
||||
|
||||
import (
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"go-wechat/config"
|
||||
"log"
|
||||
)
|
||||
|
||||
// MQ连接对象
|
||||
var conn *amqp.Connection
|
||||
var channel *amqp.Channel
|
||||
|
||||
// 交换机名称
|
||||
const exchangeName = "wechat-message"
|
||||
|
||||
// Init
|
||||
// @description: 初始化MQ
|
||||
func Init() {
|
||||
if !config.Conf.Mq.Enable {
|
||||
log.Println("未启用MQ")
|
||||
return
|
||||
}
|
||||
// 读取MQ连接配置
|
||||
mqUrl := config.Conf.Mq.RabbitMQ.GetURL()
|
||||
if mqUrl == "" {
|
||||
log.Panicf("MQ配置异常")
|
||||
}
|
||||
|
||||
var err error
|
||||
// 创建MQ连接
|
||||
if conn, err = amqp.Dial(mqUrl); err != nil {
|
||||
log.Panicf("RabbitMQ连接失败: %s", err)
|
||||
}
|
||||
|
||||
//获取channel
|
||||
if channel, err = conn.Channel(); err != nil {
|
||||
log.Panicf("打开Channel失败: %s", err)
|
||||
}
|
||||
log.Println("RabbitMQ连接成功")
|
||||
go Receive()
|
||||
log.Println("开始监听消息")
|
||||
}
|
||||
|
||||
// Receive
|
||||
// @description: 接收消息
|
||||
func Receive() (err error) {
|
||||
// 创建交换机
|
||||
if err = channel.ExchangeDeclare(
|
||||
exchangeName,
|
||||
"fanout",
|
||||
true,
|
||||
false,
|
||||
//true表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
); err != nil {
|
||||
log.Printf("声明Exchange失败: %s", err)
|
||||
return
|
||||
}
|
||||
// 创建队列
|
||||
var q amqp.Queue
|
||||
q, err = channel.QueueDeclare(
|
||||
"", //随机生产队列名称
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("无法声明Queue: %s", err)
|
||||
return
|
||||
}
|
||||
// 绑定队列到 exchange 中
|
||||
err = channel.QueueBind(
|
||||
q.Name,
|
||||
//在pub/sub模式下,这里的key要为空
|
||||
"",
|
||||
exchangeName,
|
||||
false,
|
||||
nil)
|
||||
if err != nil {
|
||||
log.Printf("绑定队列失败: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 消费消息
|
||||
var messages <-chan amqp.Delivery
|
||||
messages, err = channel.Consume(
|
||||
q.Name,
|
||||
"",
|
||||
false, // 不自动ack,手动处理,这样即使消费者挂掉,消息也不会丢失
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("无法使用队列: %s", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
msg, ok := <-messages
|
||||
if !ok {
|
||||
log.Printf("获取消息失败")
|
||||
return Receive()
|
||||
}
|
||||
log.Printf("收到消息: %s", msg.Body)
|
||||
parse(msg.Body)
|
||||
// ACK消息
|
||||
if err = msg.Ack(true); err != nil {
|
||||
log.Printf("ACK消息失败: %s", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,8 @@ func Command(m *plugin.MessageContext) {
|
||||
command.LeiGodCmd(m.FromUser, msgArray[1], msgArray[2:]...)
|
||||
case "/肯德基", "/kfc":
|
||||
command.KfcCrazyThursdayCmd(m.FromUser)
|
||||
case "/ai":
|
||||
command.AiCmd(m.FromUser, m.GroupUser, msgArray[1])
|
||||
default:
|
||||
utils.SendMessage(m.FromUser, m.GroupUser, "指令错误", 0)
|
||||
}
|
||||
|
||||
65
plugin/plugins/command/ai.go
Normal file
65
plugin/plugins/command/ai.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/client"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AiCmd
|
||||
// @description: AI指令
|
||||
// @param userId
|
||||
// @param groupUserId
|
||||
// @param cmd
|
||||
func AiCmd(userId, groupUserId, cmd string) {
|
||||
// 判断发信人是不是群主
|
||||
can := false
|
||||
if strings.Contains(userId, "@chatroom") {
|
||||
// 判断是不是群主
|
||||
err := client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", userId).
|
||||
Where("wxid = ?", groupUserId).
|
||||
Pluck("is_admin", &can).Error
|
||||
if err != nil {
|
||||
log.Printf("查询群主失败: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !can {
|
||||
utils.SendMessage(userId, groupUserId, "您不是群主,无法使用指令", 0)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
replyMsg := "操作成功"
|
||||
|
||||
switch cmd {
|
||||
case "enable", "启用", "打开":
|
||||
err = setAiEnable(userId, true)
|
||||
case "disable", "停用", "禁用", "关闭":
|
||||
err = setAiEnable(userId, false)
|
||||
default:
|
||||
replyMsg = "指令错误"
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("AI指令执行失败: %v", err)
|
||||
replyMsg = fmt.Sprintf("指令执行错误: %v", err)
|
||||
}
|
||||
utils.SendMessage(userId, groupUserId, replyMsg, 0)
|
||||
}
|
||||
|
||||
// setAiEnable
|
||||
// @description: 设置AI启用状态
|
||||
// @param userId
|
||||
// @param enable
|
||||
// @return err
|
||||
func setAiEnable(userId string, enable bool) (err error) {
|
||||
// 更新
|
||||
err = client.MySQL.Model(&entity.Friend{}).
|
||||
Where("wxid = ?", userId).
|
||||
Update("enable_ai", enable).Error
|
||||
return
|
||||
}
|
||||
@@ -23,6 +23,13 @@ option: 指令选项,可选值:
|
||||
|
||||
#2. 肯德基疯狂星期四文案
|
||||
/kfc、/肯德基
|
||||
|
||||
#3. AI助手
|
||||
/ai option
|
||||
option: 指令选项,可选值:
|
||||
启用: '启用'、'打开'、'enable'
|
||||
停用: '停用'、'禁用'、'关闭'、'disable'
|
||||
|
||||
`
|
||||
utils.SendMessage(m.FromUser, m.GroupUser, str, 0)
|
||||
|
||||
|
||||
17
readme.md
17
readme.md
@@ -48,7 +48,7 @@ version: '3.9'
|
||||
|
||||
services:
|
||||
wechat:
|
||||
image: lxh01/wxhelper-docker:3.9.5.81-v11
|
||||
image: lxh01/wxhelper-docker:3.9.5.81-v11-novnc # 如果不用noVNC网页,就删掉后面的-novnc
|
||||
container_name: gw-wechat
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -56,8 +56,9 @@ services:
|
||||
volumes:
|
||||
- ./data/wechat:/home/app/.wine/drive_c/users/app/Documents/WeChat\ Files
|
||||
ports:
|
||||
- "19087:8080"
|
||||
- "19088:19088"
|
||||
- "19086:5900" # vnc端口
|
||||
- "19087:8080" # noVNC端口
|
||||
- "19088:19088" # 微信HOOK端口
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:19088/api/checkLogin"]
|
||||
interval: 60s
|
||||
@@ -69,9 +70,6 @@ services:
|
||||
image: mysql:8
|
||||
container_name: gw-db
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
wechat:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=wechat
|
||||
- MYSQL_USER=wechat
|
||||
@@ -87,6 +85,7 @@ services:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mysql
|
||||
- wechat
|
||||
volumes:
|
||||
# 配置文件请参阅项目根目录的config.yaml文件
|
||||
- ./config/config.yaml:/app/config.yaml
|
||||
@@ -100,4 +99,8 @@ services:
|
||||
# 以下命令选个能用的就行
|
||||
docker-compose up -d # 老版本
|
||||
docker compose up -d # 新版本
|
||||
```
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
1. 宿主机必须是`debian`系,因为`wine`的玄学`BUG`,其他系统可能会出现各种问题
|
||||
2. 登录微信可以用`vnc viewer`连接`5900`端口或者访问`noVNC`端口的`vnc_lite.html`页面进行扫码登录
|
||||
@@ -22,9 +22,11 @@ func Init(g *gin.Engine) {
|
||||
// 接口
|
||||
api := g.Group("/api")
|
||||
api.PUT("/ai/status", app.ChangeEnableAiStatus) // 修改是否开启AI状态
|
||||
api.POST("/ai/model", app.ChangeUseAiModel) // 修改使用的AI模型
|
||||
api.PUT("/welcome/status", app.ChangeEnableWelcomeStatus) // 修改是否开启迎新状态
|
||||
api.PUT("/command/status", app.ChangeEnableCommandStatus) // 修改是否开启指令状态
|
||||
api.PUT("/grouprank/status", app.ChangeEnableGroupRankStatus) // 修改是否开启水群排行榜状态
|
||||
api.PUT("/grouprank/skip", app.ChangeSkipGroupRankStatus) // 修改是否跳过水群排行榜状态
|
||||
api.GET("/group/users", app.GetGroupUsers) // 获取群成员列表
|
||||
api.PUT("/summary/status", app.ChangeEnableSummaryStatus) // 修改是否开启群聊总结状态
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"go-wechat/client"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/vo"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -16,10 +17,11 @@ func GetAllFriend() (friends, groups []vo.FriendItem, err error) {
|
||||
var records []vo.FriendItem
|
||||
err = client.MySQL.
|
||||
Table("t_friend AS tf").
|
||||
Joins("LEFT JOIN t_message AS tm ON tf.wxid = tm.from_user").
|
||||
Select("tf.*", "MAX(tm.create_at) AS last_active_time").
|
||||
Group("tf.wxid").
|
||||
Order("last_active_time DESC").
|
||||
//Joins("LEFT JOIN t_message AS tm ON tf.wxid = tm.from_user").
|
||||
//Select("tf.*", "MAX(tm.create_at) AS last_active").
|
||||
Select("tf.*").
|
||||
//Group("tf.wxid").
|
||||
Order("tf.last_active DESC").
|
||||
Find(&records).Error
|
||||
if err != nil {
|
||||
return
|
||||
@@ -51,6 +53,15 @@ func GetAllEnableChatRank() (records []entity.Friend, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllEnableSummary
|
||||
// @description: 取出所有启用了总结的群组
|
||||
// @return records
|
||||
// @return err
|
||||
func GetAllEnableSummary() (records []entity.Friend, err error) {
|
||||
err = client.MySQL.Where("enable_summary = ?", 1).Where("wxid LIKE '%@chatroom'").Find(&records).Error
|
||||
return
|
||||
}
|
||||
|
||||
// CheckIsEnableCommand
|
||||
// @description: 检查用户是否启用了指令
|
||||
// @param userId
|
||||
@@ -60,3 +71,27 @@ func CheckIsEnableCommand(userId string) (flag bool) {
|
||||
client.MySQL.Model(&entity.Friend{}).Where("enable_command = 1").Where("wxid = ?", userId).Count(&coo)
|
||||
return coo > 0
|
||||
}
|
||||
|
||||
// updateLastActive
|
||||
// @description: 更新最后活跃时间
|
||||
// @param msg
|
||||
func updateLastActive(msg entity.Message) {
|
||||
var err error
|
||||
// 如果是群,更新群成员最后活跃时间
|
||||
if strings.HasSuffix(msg.FromUser, "@chatroom") {
|
||||
err = client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", msg.FromUser).
|
||||
Where("wxid = ?", msg.GroupUser).
|
||||
Update("last_active", msg.CreateAt).Error
|
||||
if err != nil {
|
||||
log.Printf("更新群成员最后活跃时间失败, 错误信息: %v", err)
|
||||
}
|
||||
}
|
||||
// 更新群或者好友活跃时间
|
||||
err = client.MySQL.Model(&entity.Friend{}).
|
||||
Where("wxid = ?", msg.FromUser).
|
||||
Update("last_active", msg.CreateAt).Error
|
||||
if err != nil {
|
||||
log.Printf("更新群或者好友活跃时间失败, 错误信息: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,9 @@ import (
|
||||
func GetGroupUsersByGroupId(groupId string) (records []vo.GroupUserItem, err error) {
|
||||
err = client.MySQL.
|
||||
Table("t_group_user AS tgu").
|
||||
Joins("LEFT JOIN t_message AS tm ON tm.from_user = tgu.group_id AND tm.group_user = tgu.wxid").
|
||||
//Select("tgu.wxid", "tgu.nickname", "tgu.head_image", "tgu.is_member", "tgu.leave_time",
|
||||
// "tgu.skip_chat_rank", "MAX(tm.create_at) AS last_active_time").
|
||||
Select("tgu.*", "MAX(tm.create_at) AS last_active_time").
|
||||
//Joins("LEFT JOIN t_message AS tm ON tm.from_user = tgu.group_id AND tm.group_user = tgu.wxid").
|
||||
//Select("tgu.*", "MAX(tm.create_at) AS last_active").
|
||||
Select("tgu.*").
|
||||
Where("tgu.group_id = ?", groupId).
|
||||
Group("tgu.group_id, tgu.wxid").
|
||||
Order("tgu.join_time DESC").
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"go-wechat/client"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/vo"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -32,4 +33,29 @@ func SaveMessage(msg entity.Message) {
|
||||
log.Printf("消息入库失败, 错误信息: %v", err)
|
||||
}
|
||||
log.Printf("消息入库成功,消息Id: %d", msg.MsgId)
|
||||
|
||||
// 更新最后活跃时间
|
||||
// 只处理收到的消息
|
||||
if msg.MsgId > 1 {
|
||||
go updateLastActive(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// GetTextMessagesById
|
||||
// @description: 根据群id或者用户Id获取消息
|
||||
// @param id
|
||||
// @return records
|
||||
// @return err
|
||||
func GetTextMessagesById(id string) (records []vo.TextMessageItem, err error) {
|
||||
tx := client.MySQL.
|
||||
Table("`t_message` AS tm").
|
||||
Joins("LEFT JOIN t_group_user AS tgu ON tm.group_user = tgu.wxid AND tgu.group_id = tm.from_user").
|
||||
Select("tgu.nickname", "IF( tm.type = 49, EXTRACTVALUE ( tm.content, \"/msg/appmsg/title\" ), tm.content ) AS message").
|
||||
Where("tm.`from_user` = ?", id).
|
||||
Where(`(tm.type = 1 OR ( tm.type = 49 AND EXTRACTVALUE ( tm.content, "/msg/appmsg/type" ) = '57' ))`).
|
||||
Where("DATE ( tm.create_at ) = DATE ( CURDATE() - INTERVAL 1 DAY )").
|
||||
Order("tm.create_at ASC")
|
||||
|
||||
err = tx.Find(&records).Error
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/model"
|
||||
"go-wechat/utils"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
"slices"
|
||||
@@ -66,11 +67,14 @@ func Sync() {
|
||||
PinyinAll: friend.PinyinAll,
|
||||
Wxid: friend.Wxid,
|
||||
IsOk: true,
|
||||
LastActive: time.Now().Local(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("新增好友失败: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
// 发送一条新消息
|
||||
utils.SendMessage(friend.Wxid, "", "大家好,我是一个AI机器人,可以直接@我询问你想问的问题。该功能默认未启用,请群主艾特我并发送 /ai enable 指令启用", 0)
|
||||
} else {
|
||||
pm := map[string]any{
|
||||
"nickname": friend.Nickname,
|
||||
@@ -149,14 +153,15 @@ func syncGroupUsers(tx *gorm.DB, gid string) {
|
||||
if count == 0 {
|
||||
// 新增
|
||||
err = tx.Create(&entity.GroupUser{
|
||||
GroupId: gid,
|
||||
Account: cp.Account,
|
||||
HeadImage: cp.HeadImage,
|
||||
Nickname: cp.Nickname,
|
||||
Wxid: cp.Wxid,
|
||||
IsMember: true,
|
||||
IsAdmin: wxid == baseResp.Data.Admin,
|
||||
JoinTime: time.Now().Local(),
|
||||
GroupId: gid,
|
||||
Account: cp.Account,
|
||||
HeadImage: cp.HeadImage,
|
||||
Nickname: cp.Nickname,
|
||||
Wxid: cp.Wxid,
|
||||
IsMember: true,
|
||||
IsAdmin: wxid == baseResp.Data.Admin,
|
||||
JoinTime: time.Now().Local(),
|
||||
LastActive: time.Now().Local(),
|
||||
}).Error
|
||||
if err != nil {
|
||||
log.Printf("新增群成员失败: %s", err.Error())
|
||||
|
||||
81
tasks/summary/summary.go
Normal file
81
tasks/summary/summary.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package summary
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/sashabaranov/go-openai"
|
||||
"go-wechat/config"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"go-wechat/vo"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AiSummary
|
||||
// @description: AI总结群聊记录
|
||||
func AiSummary() {
|
||||
groups, err := service.GetAllEnableSummary()
|
||||
if err != nil {
|
||||
log.Printf("获取启用了聊天排行榜的群组失败, 错误信息: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
// 获取对话记录
|
||||
var records []vo.TextMessageItem
|
||||
if records, err = service.GetTextMessagesById(group.Wxid); err != nil {
|
||||
log.Printf("获取群[%s]对话记录失败, 错误信息: %v", group.Wxid, err)
|
||||
continue
|
||||
}
|
||||
if len(records) < 100 {
|
||||
log.Printf("群[%s]对话记录不足100条,跳过总结", group.Wxid)
|
||||
continue
|
||||
}
|
||||
// 组装对话记录为字符串
|
||||
var content []string
|
||||
for _, record := range records {
|
||||
content = append(content, fmt.Sprintf("%s: %s\n-----end-----", record.Nickname, record.Message))
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("请帮我总结一下一下的群聊内容的梗概,生成的梗概尽可能详细,并且最好带上名字。\n"+
|
||||
"注意,他们可能是多个话题,请仔细甄别。\n"+
|
||||
"每一行代表一个人的发言,每一行的的格式为: \n{nickname}: {content}\n-----end-----"+
|
||||
"\n\n聊天记录如下: \n%s", strings.Join(content, "\n"))
|
||||
|
||||
// AI总结
|
||||
messages := []openai.ChatCompletionMessage{
|
||||
{
|
||||
Role: openai.ChatMessageRoleUser,
|
||||
Content: msg,
|
||||
},
|
||||
}
|
||||
|
||||
// 默认使用AI回复
|
||||
conf := openai.DefaultConfig(config.Conf.Ai.ApiKey)
|
||||
if config.Conf.Ai.BaseUrl != "" {
|
||||
conf.BaseURL = fmt.Sprintf("%s/v1", config.Conf.Ai.BaseUrl)
|
||||
}
|
||||
ai := openai.NewClientWithConfig(conf)
|
||||
var resp openai.ChatCompletionResponse
|
||||
resp, err = ai.CreateChatCompletion(
|
||||
context.Background(),
|
||||
openai.ChatCompletionRequest{
|
||||
Model: config.Conf.Ai.SummaryModel,
|
||||
Messages: messages,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("群聊记录总结失败: %v", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// 返回消息为空
|
||||
if resp.Choices[0].Message.Content == "" {
|
||||
continue
|
||||
}
|
||||
replyMsg := fmt.Sprintf("#昨日消息总结\n\n%s", resp.Choices[0].Message.Content)
|
||||
utils.SendMessage(group.Wxid, "", replyMsg, 0)
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/go-co-op/gocron"
|
||||
"go-wechat/config"
|
||||
"go-wechat/tasks/friends"
|
||||
"go-wechat/tasks/summary"
|
||||
"go-wechat/tasks/watergroup"
|
||||
"log"
|
||||
"time"
|
||||
@@ -37,6 +38,13 @@ func InitTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
// 群聊总结
|
||||
if config.Conf.Task.GroupSummary.Enable {
|
||||
log.Printf("群聊总结任务已启用,执行表达式: %s", config.Conf.Task.GroupSummary.Cron)
|
||||
_, _ = s.Cron(config.Conf.Task.GroupSummary.Cron).Do(summary.AiSummary)
|
||||
|
||||
}
|
||||
|
||||
// 更新好友列表
|
||||
if config.Conf.Task.SyncFriends.Enable {
|
||||
log.Printf("更新好友列表任务已启用,执行表达式: %s", config.Conf.Task.SyncFriends.Cron)
|
||||
|
||||
@@ -2,7 +2,9 @@ package watergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/client"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -55,14 +57,48 @@ func dealMonth(gid string) {
|
||||
log.Printf("上月群[%s]无对话记录", gid)
|
||||
return
|
||||
}
|
||||
// 计算消息总数
|
||||
var msgCount int64
|
||||
for _, v := range records {
|
||||
msgCount += v.Count
|
||||
|
||||
// 查询群成员总数
|
||||
var groupUsers int64
|
||||
err = client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", gid).
|
||||
Where("is_member IS TRUE").
|
||||
Count(&groupUsers).Error
|
||||
if err != nil {
|
||||
log.Printf("查询群成员总数失败, 错误信息: %v", err)
|
||||
}
|
||||
// 计算活跃度
|
||||
showActivity := err == nil && groupUsers > 0
|
||||
activity := "0.00"
|
||||
if groupUsers > 0 {
|
||||
activity = fmt.Sprintf("%.2f", (float64(len(records))/float64(groupUsers))*100)
|
||||
}
|
||||
|
||||
// 计算消息总数、中位数、前十位消息总数
|
||||
var msgCount, medianCount, topTenCount int64
|
||||
for idx, v := range records {
|
||||
msgCount += v.Count
|
||||
if idx == (len(records)/2)-1 {
|
||||
medianCount = v.Count
|
||||
}
|
||||
if len(records) > 10 && idx < 10 {
|
||||
topTenCount += v.Count
|
||||
}
|
||||
}
|
||||
// 计算活跃用户人均消息条数
|
||||
avgMsgCount := int(float64(msgCount) / float64(len(records)))
|
||||
|
||||
// 组装消息总数推送信息
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
notifyMsgs = append(notifyMsgs, fmt.Sprintf("🗣️ %s本群 %d 位朋友共产生 %d 条发言", monthStr, len(records), msgCount))
|
||||
if showActivity {
|
||||
m := fmt.Sprintf("🎭 活跃度: %s%%,人均消息条数: %d,中位数: %d", activity, avgMsgCount, medianCount)
|
||||
// 计算前十占比
|
||||
if topTenCount > 0 {
|
||||
m += fmt.Sprintf(",前十名占比: %.2f%%", float64(topTenCount)/float64(msgCount)*100)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, m)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, "\n🏵 活跃用户排行榜 🏵")
|
||||
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
|
||||
@@ -18,7 +18,7 @@ type rankUser struct {
|
||||
// @return err
|
||||
func getRankData(groupId, date string) (rank []rankUser, err error) {
|
||||
tx := client.MySQL.Table("t_message AS tm").
|
||||
Joins("LEFT JOIN t_group_user AS tgu ON tgu.wxid = tm.group_user AND tm.from_user = tgu.group_id AND tgu.skip_chat_rank = 0").
|
||||
Joins("LEFT JOIN t_group_user AS tgu ON tgu.wxid = tm.group_user AND tm.from_user = tgu.group_id AND tgu.skip_chat_rank = 0 AND is_member = 1").
|
||||
Select("tm.group_user", "tgu.nickname", "count( 1 ) AS `count`").
|
||||
Where("tm.from_user = ?", groupId).
|
||||
Where("tm.type < 10000").
|
||||
|
||||
@@ -2,7 +2,9 @@ package watergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/client"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -54,14 +56,48 @@ func dealWeek(gid string) {
|
||||
log.Printf("上周群[%s]无对话记录", gid)
|
||||
return
|
||||
}
|
||||
// 计算消息总数
|
||||
var msgCount int64
|
||||
for _, v := range records {
|
||||
msgCount += v.Count
|
||||
|
||||
// 查询群成员总数
|
||||
var groupUsers int64
|
||||
err = client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", gid).
|
||||
Where("is_member IS TRUE").
|
||||
Count(&groupUsers).Error
|
||||
if err != nil {
|
||||
log.Printf("查询群成员总数失败, 错误信息: %v", err)
|
||||
}
|
||||
// 计算活跃度
|
||||
showActivity := err == nil && groupUsers > 0
|
||||
activity := "0.00"
|
||||
if groupUsers > 0 {
|
||||
activity = fmt.Sprintf("%.2f", (float64(len(records))/float64(groupUsers))*100)
|
||||
}
|
||||
|
||||
// 计算消息总数、中位数、前十位消息总数
|
||||
var msgCount, medianCount, topTenCount int64
|
||||
for idx, v := range records {
|
||||
msgCount += v.Count
|
||||
if idx == (len(records)/2)-1 {
|
||||
medianCount = v.Count
|
||||
}
|
||||
if len(records) > 10 && idx < 10 {
|
||||
topTenCount += v.Count
|
||||
}
|
||||
}
|
||||
// 计算活跃用户人均消息条数
|
||||
avgMsgCount := int(float64(msgCount) / float64(len(records)))
|
||||
|
||||
// 组装消息总数推送信息
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
notifyMsgs = append(notifyMsgs, fmt.Sprintf("🗣️ 上周本群 %d 位朋友共产生 %d 条发言", len(records), msgCount))
|
||||
if showActivity {
|
||||
m := fmt.Sprintf("🎭 活跃度: %s%%,人均消息条数: %d,中位数: %d", activity, avgMsgCount, medianCount)
|
||||
// 计算前十占比
|
||||
if topTenCount > 0 {
|
||||
m += fmt.Sprintf(",前十名占比: %.2f%%", float64(topTenCount)/float64(msgCount)*100)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, m)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, "\n🏵 活跃用户排行榜 🏵")
|
||||
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
|
||||
@@ -2,7 +2,9 @@ package watergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/client"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -54,11 +56,37 @@ func dealYear(gid string) {
|
||||
log.Printf("去年本群[%s]无对话记录", gid)
|
||||
return
|
||||
}
|
||||
// 计算消息总数
|
||||
var msgCount int64
|
||||
for _, v := range records {
|
||||
msgCount += v.Count
|
||||
|
||||
// 查询群成员总数
|
||||
var groupUsers int64
|
||||
err = client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", gid).
|
||||
Where("is_member IS TRUE").
|
||||
Count(&groupUsers).Error
|
||||
if err != nil {
|
||||
log.Printf("查询群成员总数失败, 错误信息: %v", err)
|
||||
}
|
||||
// 计算活跃度
|
||||
showActivity := err == nil && groupUsers > 0
|
||||
activity := "0.00"
|
||||
if groupUsers > 0 {
|
||||
activity = fmt.Sprintf("%.2f", (float64(len(records))/float64(groupUsers))*100)
|
||||
}
|
||||
|
||||
// 计算消息总数、中位数、前十位消息总数
|
||||
var msgCount, medianCount, topTenCount int64
|
||||
for idx, v := range records {
|
||||
msgCount += v.Count
|
||||
if idx == (len(records)/2)-1 {
|
||||
medianCount = v.Count
|
||||
}
|
||||
if len(records) > 10 && idx < 10 {
|
||||
topTenCount += v.Count
|
||||
}
|
||||
}
|
||||
// 计算活跃用户人均消息条数
|
||||
avgMsgCount := int(float64(msgCount) / float64(len(records)))
|
||||
|
||||
// 组装消息总数推送信息
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
notifyMsgs = append(notifyMsgs, "亲爱的群友们,新年已经悄悄来临,让我们一起迎接这充满希望和美好的时刻。在这个特殊的日子里,我要向你们致以最真挚的祝福。")
|
||||
@@ -70,6 +98,14 @@ func dealYear(gid string) {
|
||||
notifyMsgs = append(notifyMsgs, fmt.Sprintf("祝福你们新年快乐!让我们一起迎接%d年的到来!", time.Now().Local().Year()))
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
notifyMsgs = append(notifyMsgs, fmt.Sprintf("🗣️ 去年本群 %d 位朋友共产生 %d 条发言", len(records), msgCount))
|
||||
if showActivity {
|
||||
m := fmt.Sprintf("🎭 活跃度: %s%%,人均消息条数: %d,中位数: %d", activity, avgMsgCount, medianCount)
|
||||
// 计算前十占比
|
||||
if topTenCount > 0 {
|
||||
m += fmt.Sprintf(",前十名占比: %.2f%%", float64(topTenCount)/float64(msgCount)*100)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, m)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, "\n🏵 活跃用户排行榜 🏵")
|
||||
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
|
||||
@@ -2,7 +2,9 @@ package watergroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go-wechat/client"
|
||||
"go-wechat/config"
|
||||
"go-wechat/entity"
|
||||
"go-wechat/service"
|
||||
"go-wechat/utils"
|
||||
"log"
|
||||
@@ -56,14 +58,48 @@ func dealYesterday(gid string) {
|
||||
log.Printf("昨日群[%s]无对话记录", gid)
|
||||
return
|
||||
}
|
||||
// 计算消息总数
|
||||
var msgCount int64
|
||||
for _, v := range records {
|
||||
msgCount += v.Count
|
||||
|
||||
// 查询群成员总数
|
||||
var groupUsers int64
|
||||
err = client.MySQL.Model(&entity.GroupUser{}).
|
||||
Where("group_id = ?", gid).
|
||||
Where("is_member IS TRUE").
|
||||
Count(&groupUsers).Error
|
||||
if err != nil {
|
||||
log.Printf("查询群成员总数失败, 错误信息: %v", err)
|
||||
}
|
||||
// 计算活跃度
|
||||
showActivity := err == nil && groupUsers > 0
|
||||
activity := "0.00"
|
||||
if groupUsers > 0 {
|
||||
activity = fmt.Sprintf("%.2f", (float64(len(records))/float64(groupUsers))*100)
|
||||
}
|
||||
|
||||
// 计算消息总数、中位数、前十位消息总数
|
||||
var msgCount, medianCount, topTenCount int64
|
||||
for idx, v := range records {
|
||||
msgCount += v.Count
|
||||
if idx == (len(records)/2)-1 {
|
||||
medianCount = v.Count
|
||||
}
|
||||
if len(records) > 10 && idx < 10 {
|
||||
topTenCount += v.Count
|
||||
}
|
||||
}
|
||||
// 计算活跃用户人均消息条数
|
||||
avgMsgCount := int(float64(msgCount) / float64(len(records)))
|
||||
|
||||
// 组装消息总数推送信息
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
notifyMsgs = append(notifyMsgs, fmt.Sprintf("🗣️ 昨日本群 %d 位朋友共产生 %d 条发言", len(records), msgCount))
|
||||
if showActivity {
|
||||
m := fmt.Sprintf("🎭 活跃度: %s%%,人均消息条数: %d,中位数: %d", activity, avgMsgCount, medianCount)
|
||||
// 计算前十占比
|
||||
if topTenCount > 0 {
|
||||
m += fmt.Sprintf(",前十名占比: %.2f%%", float64(topTenCount)/float64(msgCount)*100)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, m)
|
||||
}
|
||||
notifyMsgs = append(notifyMsgs, "\n🏵 活跃用户排行榜 🏵")
|
||||
|
||||
notifyMsgs = append(notifyMsgs, " ")
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>水群助手</title>
|
||||
|
||||
<!-- <link href="https://cdn.jsdelivr.net/npm/daisyui@4.4.14/dist/full.min.css" rel="stylesheet" type="text/css" />-->
|
||||
<link href="assets/css/daisyui-4.4.14-full.min.css" rel="stylesheet" type="text/css" />
|
||||
<link href="assets/css/index.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
@@ -47,10 +46,10 @@
|
||||
<td>{{ .CustomAccount }}</td>
|
||||
<td>{{ .Nickname }}</td>
|
||||
<td>
|
||||
{{ if eq .LastActiveTime.IsNil true }}
|
||||
{{ if eq .LastActive.IsNil true }}
|
||||
无数据
|
||||
{{ else }}
|
||||
{{ .LastActiveTime }}
|
||||
{{ .LastActive }}
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
@@ -71,6 +70,16 @@
|
||||
<div class="swap-on">✔️已启用</div>
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
{{ if .EnableAi }}
|
||||
<br />
|
||||
<select class="select select-success select-xs w-1/2 max-w-xs" onchange="aiModelChange(event, {{.Wxid}})">
|
||||
<option value="" {{ if eq .AiModel ""}}selected{{ end }}>默认(gpt-3.5-turbo-0613)</option>
|
||||
{{$useModel := .AiModel}}
|
||||
{{ range $.aiModels }}
|
||||
<option value="{{.Model}}" {{ if eq $useModel .Model}}selected{{ end }}>{{.Name}}({{.Model}})</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
<label class="swap swap-flip {{ checkSwap .EnableCommand }}">
|
||||
@@ -99,6 +108,7 @@
|
||||
<th>是否在通讯录</th>
|
||||
<th>是否启用AI</th>
|
||||
<th>是否启用水群排行榜</th>
|
||||
<th>是否启用聊天记录总结</th>
|
||||
<th>是否启用迎新</th>
|
||||
<th>是否启用指令</th>
|
||||
<th>操作</th>
|
||||
@@ -110,10 +120,10 @@
|
||||
<td>{{ .Wxid }}</td>
|
||||
<td>{{ .Nickname }}</td>
|
||||
<td>
|
||||
{{ if eq .LastActiveTime.IsNil true }}
|
||||
{{ if eq .LastActive.IsNil true }}
|
||||
无数据
|
||||
{{ else }}
|
||||
{{ .LastActiveTime }}
|
||||
{{ .LastActive }}
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
@@ -135,6 +145,16 @@
|
||||
<div class="swap-on">✔️已启用</div>
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
{{ if .EnableAi }}
|
||||
<br />
|
||||
<select class="select select-success select-xs w-1/2 max-w-xs" onchange="aiModelChange(event, {{.Wxid}})">
|
||||
<option value="" {{ if eq .AiModel ""}}selected{{ end }}>默认(gpt-3.5-turbo-0613)</option>
|
||||
{{$useModel := .AiModel}}
|
||||
{{ range $.aiModels }}
|
||||
<option value="{{.Model}}" {{ if eq $useModel .Model}}selected{{ end }}>{{.Name}}({{.Model}})</option>
|
||||
{{ end }}
|
||||
</select>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
<!-- EnableChatRank -->
|
||||
@@ -145,6 +165,15 @@
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<!-- EnableSummary -->
|
||||
<label class="swap swap-flip {{ checkSwap .EnableSummary }}">
|
||||
<input type="checkbox" onclick="changeSummaryEnableStatus({{.Wxid}})"/>
|
||||
|
||||
<div class="swap-on">✔️已启用</div>
|
||||
<div class="swap-off">❌已禁用</div>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label class="swap swap-flip {{ checkSwap .EnableWelcome }}">
|
||||
<input type="checkbox" onclick="changeWelcomeEnableStatus({{.Wxid}})"/>
|
||||
|
||||
@@ -12,6 +12,7 @@ function changeAiEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@@ -29,6 +30,25 @@ function changeGroupRankEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
})
|
||||
}
|
||||
|
||||
// 修改水群排行榜状态
|
||||
function changeSummaryEnableStatus(wxId) {
|
||||
// console.log("修改聊天记录总结开启状态: ", wxId)
|
||||
axios({
|
||||
method: 'put',
|
||||
url: '/api/summary/status',
|
||||
data: {
|
||||
wxId: wxId
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@@ -45,6 +65,7 @@ function changeWelcomeEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@@ -61,6 +82,7 @@ function changeCommandEnableStatus(wxId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@@ -79,6 +101,7 @@ function changeUserGroupRankSkipStatus(groupId, userId) {
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
@@ -113,7 +136,8 @@ function getGroupUsers(groupId, groupName) {
|
||||
const groupUsers = response.data
|
||||
// 循环渲染数据
|
||||
groupUsers.forEach((groupUser, i) => {
|
||||
const { wxid, nickname, isMember, isAdmin, joinTime, lastActiveTime, leaveTime, skipChatRank } = groupUser;
|
||||
console.log(groupUser)
|
||||
const { wxid, nickname, isMember, isAdmin, joinTime, lastActive, leaveTime, skipChatRank } = groupUser;
|
||||
|
||||
let row = tbody.insertRow(i);
|
||||
// Insert data into cells
|
||||
@@ -122,7 +146,7 @@ function getGroupUsers(groupId, groupName) {
|
||||
row.insertCell(2).innerHTML = `<div class="badge badge-${isMember ? 'info' : 'error'} gap-2">${isMember ? '是' : '否'}</div>`;
|
||||
row.insertCell(3).innerHTML = `<div class="badge badge-${isAdmin ? 'info' : 'error'} gap-2">${isAdmin ? '是' : '否'}</div>`;
|
||||
row.insertCell(4).innerHTML = joinTime;
|
||||
row.insertCell(5).innerHTML = lastActiveTime;
|
||||
row.insertCell(5).innerHTML = lastActive;
|
||||
row.insertCell(6).innerHTML = leaveTime;
|
||||
row.insertCell(7).innerHTML = `<input type="checkbox" class="toggle toggle-error" ${skipChatRank ? 'checked' : ''} onclick="changeUserGroupRankSkipStatus('${groupId}', '${wxid}')" />`;
|
||||
});
|
||||
@@ -133,4 +157,25 @@ function getGroupUsers(groupId, groupName) {
|
||||
// loading.style.display = "none"
|
||||
groupNameTag.innerHTML = groupName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// AI模型变动
|
||||
function aiModelChange(event, wxid) {
|
||||
// 取出变动后的值
|
||||
const modelStr = event.target.value;
|
||||
console.log("AI模型变动: ", wxid, modelStr)
|
||||
axios({
|
||||
method: 'post',
|
||||
url: '/api/ai/model',
|
||||
data: {
|
||||
wxid: wxid,
|
||||
model: modelStr
|
||||
}
|
||||
}).then(function (response) {
|
||||
console.log(`返回结果: ${JSON.stringify(response)}`);
|
||||
alert(`${response.data}`)
|
||||
}).catch(function (error) {
|
||||
console.log(`错误信息: ${error}`);
|
||||
alert("修改失败")
|
||||
})
|
||||
}
|
||||
|
||||
23
vo/friend.go
23
vo/friend.go
@@ -12,26 +12,27 @@ type FriendItem struct {
|
||||
Pinyin string // 昵称拼音大写首字母
|
||||
PinyinAll string // 昵称全拼
|
||||
Wxid string // 微信原始Id
|
||||
LastActive types.DateTime // 最后活跃时间
|
||||
EnableAi bool // 是否使用AI
|
||||
AiModel string // AI模型
|
||||
EnableChatRank bool // 是否使用聊天排行
|
||||
EnableWelcome bool // 是否使用迎新
|
||||
EnableCommand bool // 是否启用指令
|
||||
EnableSummary bool // 是否启用总结
|
||||
IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
|
||||
LastActiveTime types.DateTime // 最后活跃时间
|
||||
}
|
||||
|
||||
// GroupUserItem
|
||||
// @description: 群成员列表数据
|
||||
type GroupUserItem struct {
|
||||
Wxid string `json:"wxid"` // 微信Id
|
||||
Account string `json:"account"` // 账号
|
||||
HeadImage string `json:"headImage"` // 头像
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
IsMember bool `json:"isMember" ` // 是否群成员
|
||||
IsAdmin bool `json:"isAdmin"` // 是否群主
|
||||
JoinTime types.DateTime `json:"joinTime"` // 加入时间
|
||||
LastActiveTime types.DateTime `json:"lastActiveTime"` // 最后活跃时间
|
||||
LeaveTime types.DateTime `json:"leaveTime"` // 离开时间
|
||||
SkipChatRank bool `json:"skipChatRank" ` // 是否跳过聊天排行
|
||||
Wxid string `json:"wxid"` // 微信Id
|
||||
Account string `json:"account"` // 账号
|
||||
HeadImage string `json:"headImage"` // 头像
|
||||
Nickname string `json:"nickname"` // 昵称
|
||||
IsMember bool `json:"isMember" ` // 是否群成员
|
||||
IsAdmin bool `json:"isAdmin"` // 是否群主
|
||||
JoinTime types.DateTime `json:"joinTime"` // 加入时间
|
||||
LastActive types.DateTime `json:"lastActive"` // 最后活跃时间
|
||||
LeaveTime types.DateTime `json:"leaveTime"` // 离开时间
|
||||
SkipChatRank bool `json:"skipChatRank" ` // 是否跳过聊天排行
|
||||
}
|
||||
|
||||
8
vo/message.go
Normal file
8
vo/message.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package vo
|
||||
|
||||
// TextMessageItem
|
||||
// @description: 文字消息
|
||||
type TextMessageItem struct {
|
||||
Nickname string `json:"nickname"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
Reference in New Issue
Block a user