Compare commits

...

50 Commits

Author SHA1 Message Date
李寻欢
70fc7add78 Merge pull request '🎨 优化AI对话角色提示词' (#40) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/40
2024-04-19 09:55:27 +08:00
李寻欢
ab5a8092f7 🎨 优化AI对话角色提示词 2024-04-19 09:54:50 +08:00
李寻欢
21a6f75ed3 Merge pull request '🎨 优化AI对话角色设置,支持为群或者好友单独设置角色(需手动修改数据库)' (#39) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/39
2024-04-16 17:19:31 +08:00
李寻欢
daa36f308b 🎨 优化AI对话角色设置,支持为群或者好友单独设置角色(需手动修改数据库) 2024-04-16 17:19:08 +08:00
李寻欢
e78f6a6d6e Merge pull request '🎨 优化群聊总结返回消息内容' (#38) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/38
2024-04-15 16:22:43 +08:00
李寻欢
bb92c67069 🎨 优化群聊总结返回消息内容 2024-04-15 16:22:17 +08:00
李寻欢
542a2d5ef6 Merge pull request '🎨 优化消息总结提示词' (#37) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/37
2024-04-15 15:10:32 +08:00
李寻欢
c13771be22 🎨 优化消息总结提示词 2024-04-15 15:09:40 +08:00
李寻欢
982ddef0da Merge pull request '🎨 总结群聊时,不足100条记录的不总结' (#36) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/36
2024-04-12 11:40:51 +08:00
李寻欢
99447d332d 🎨 总结群聊时,不足100条记录的不总结 2024-04-12 11:40:40 +08:00
李寻欢
3478f4f9e2 Merge pull request 'hotfix' (#35) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/35
2024-04-12 11:38:10 +08:00
李寻欢
b3ed0fcc6f 🆕 新增群聊对话记录总结 2024-04-12 11:37:21 +08:00
李寻欢
3da8b327d0 chore(go-wxhelper): add AI command functionality 🤖
Add AI command functionality to handle AI commands in the WeChat plugin. Includes options to enable or disable AI features.
2024-04-12 10:48:46 +08:00
李寻欢
0262fdd9f7 Merge pull request 'hotfix' (#34) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/34
2024-04-09 14:43:20 +08:00
李寻欢
f775f1d67d Merge remote-tracking branch 'origin/hotfix' into hotfix 2024-04-09 14:42:59 +08:00
李寻欢
43f9d590b7 🎨 脚本优化 2024-04-09 14:42:47 +08:00
李寻欢
22c7d3683b Merge pull request 'hotfix' (#33) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/33
2024-03-22 23:07:45 +08:00
李寻欢
d589a9615e Merge remote-tracking branch 'origin/hotfix' into hotfix 2024-03-22 23:07:00 +08:00
李寻欢
13205e445f 🎨 新增 MQ 开关 2024-03-22 23:06:54 +08:00
李寻欢
a414a98a51 Merge pull request ' 群消息统计新增前十名消息数占比' (#31) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/31
2024-03-13 10:33:15 +08:00
李寻欢
d21089ab69 群消息统计新增前十名消息数占比 2024-03-13 10:32:20 +08:00
李寻欢
a274f085f8 Merge pull request 'hotfix' (#30) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/30
2024-03-13 10:03:10 +08:00
李寻欢
8ddc40cc01 群消息统计新增活跃用户人均消息条数和中位数 2024-03-13 10:02:40 +08:00
李寻欢
c0c810d02e 群消息统计新增活跃用户人均消息条数和中位数 2024-03-13 10:01:52 +08:00
李寻欢
905dab5ab8 Merge pull request '🐛 Fix a bug.' (#29) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/29
2024-03-12 08:37:32 +08:00
李寻欢
ddb0db0b6a 🐛 Fix a bug. 2024-03-12 08:37:05 +08:00
李寻欢
8bac050a02 Merge pull request 'hotfix' (#28) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/28
2024-03-11 14:56:45 +08:00
李寻欢
a548af9de2 🐛 修复活跃度不发送的BUG 2024-03-11 14:55:10 +08:00
李寻欢
64c3c9c78b 🎨 逻辑优化 2024-03-10 07:36:10 +08:00
李寻欢
0f7cf5515d Merge pull request '🐛 Fix a bug.' (#27) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/27
2024-03-08 07:45:51 +08:00
李寻欢
a2d84dea08 🐛 Fix a bug. 2024-03-08 07:45:05 +08:00
李寻欢
d3f8a59390 Merge pull request 'hotfix' (#26) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/26
2024-03-07 10:04:47 +08:00
李寻欢
165fefdb48 🐛 修复扫码加入群聊的成员无法识别的BUG 2024-03-07 10:04:03 +08:00
李寻欢
22474efc57 🎨 逻辑完善 2024-03-07 09:49:37 +08:00
李寻欢
797821e2ed 🎨 逻辑完善 2024-03-07 09:48:52 +08:00
李寻欢
79cbeb6ea5 Merge pull request '🆕 水群排行榜新增群活跃度' (#25) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/25
2024-03-07 09:43:27 +08:00
李寻欢
49705a03a8 🆕 水群排行榜新增群活跃度 2024-03-07 09:42:59 +08:00
李寻欢
40e97608d3 Merge pull request '📝 完善文档' (#24) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/24
2024-02-26 15:30:56 +08:00
李寻欢
fe7bc02c08 📝 完善文档 2024-02-26 15:30:40 +08:00
李寻欢
bb2c4e919e Merge pull request 'hotfix' (#23) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/23
2024-02-26 15:27:05 +08:00
李寻欢
0ea189fb82 📝 完善文档 2024-02-26 15:26:37 +08:00
李寻欢
778db8e349 📝 完善文档 2024-02-26 15:24:42 +08:00
李寻欢
4b7ad4126c Merge pull request '🆕 新增MQ消息通道' (#22) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/22
2024-02-19 14:17:59 +08:00
李寻欢
1413181bd4 🆕 新增MQ消息通道 2024-02-19 14:17:26 +08:00
李寻欢
a11ba51246 Merge pull request '🆕 控制页面新增设置AI模型' (#21) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/21
2024-01-31 12:03:01 +08:00
李寻欢
ea4262adf0 🆕 控制页面新增设置AI模型 2024-01-31 12:02:33 +08:00
李寻欢
a9f6c9ff0d Merge pull request '🐛 Fix a bug.' (#20) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/20
2024-01-26 12:00:53 +08:00
李寻欢
244bff5714 🐛 Fix a bug. 2024-01-26 12:00:29 +08:00
李寻欢
b46271a111 Merge pull request '🐛 Fix a bug.' (#19) from hotfix into main
Reviewed-on: https://gitee.ltd/lxh/go-wxhelper/pulls/19
2024-01-25 16:33:59 +08:00
李寻欢
5d11cc7c8a 🐛 Fix a bug. 2024-01-25 16:33:45 +08:00
35 changed files with 814 additions and 101 deletions

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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,37 +14,69 @@ 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 * * * *' # 五分钟一次
waterGroup:
groupSummary:
enable: true
cron: '30 0 * * *' # 每天0:30
waterGroup:
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:
# 是否启用
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 会变成全小写,所以这儿不能用大写字母

View File

@@ -5,7 +5,16 @@ package config
type ai struct {
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"` // 模型代码
}

View File

@@ -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
View 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)
}

View File

@@ -6,6 +6,7 @@ type task struct {
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

View File

@@ -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

View File

@@ -1,7 +1,6 @@
package entity
import (
"go-wechat/common/types"
"time"
)
@@ -13,11 +12,13 @@ type Friend struct {
Nickname string `json:"nickname"` // 昵称
Pinyin string `json:"pinyin"` // 昵称拼音大写首字母
PinyinAll string `json:"pinyinAll"` // 昵称全拼
LastActive types.DateTime `json:"lastActive"` // 最后活跃时间
LastActive time.Time `json:"lastActive"` // 最后活跃时间
EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI
AiModel string `json:"aiModel"` // AI模型
Prompt string `json:"prompt"` // 提示词
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"` // 是否正常
}

1
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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() {

View File

@@ -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
View 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
View 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
}
}
}

View File

@@ -46,13 +46,19 @@ func AI(m *plugin.MessageContext) {
m.Content = strings.Replace(m.Content, matches[0], "", 1)
}
// 处理预设角色,默认是配置文件里的,如果数据库配置不为空,则使用数据库配置
prompt := config.Conf.Ai.Personality
if friendInfo.Prompt != "" {
prompt = friendInfo.Prompt
}
// 组装消息体
messages := make([]openai.ChatCompletionMessage, 0)
if config.Conf.Ai.Personality != "" {
// 填充人设
messages = append(messages, openai.ChatCompletionMessage{
Role: openai.ChatMessageRoleSystem,
Content: config.Conf.Ai.Personality,
Content: prompt,
})
}

View File

@@ -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)
}

View 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
}

View File

@@ -23,6 +23,13 @@ option: 指令选项,可选值:
#2. 肯德基疯狂星期四文案
/kfc、/肯德基
#3. AI助手
/ai option
option: 指令选项,可选值:
启用: '启用'、'打开'、'enable'
停用: '停用'、'禁用'、'关闭'、'disable'
`
utils.SendMessage(m.FromUser, m.GroupUser, str, 0)

View File

@@ -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
@@ -101,3 +100,7 @@ services:
docker-compose up -d # 老版本
docker compose up -d # 新版本
```
## 注意事项
1. 宿主机必须是`debian`系,因为`wine`的玄学`BUG`,其他系统可能会出现各种问题
2. 登录微信可以用`vnc viewer`连接`5900`端口或者访问`noVNC`端口的`vnc_lite.html`页面进行扫码登录

View File

@@ -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) // 修改是否开启群聊总结状态
}

View File

@@ -53,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
@@ -73,7 +82,7 @@ func updateLastActive(msg entity.Message) {
err = client.MySQL.Model(&entity.GroupUser{}).
Where("group_id = ?", msg.FromUser).
Where("wxid = ?", msg.GroupUser).
Update("last_active", msg.CreateTime).Error
Update("last_active", msg.CreateAt).Error
if err != nil {
log.Printf("更新群成员最后活跃时间失败, 错误信息: %v", err)
}
@@ -81,7 +90,7 @@ func updateLastActive(msg entity.Message) {
// 更新群或者好友活跃时间
err = client.MySQL.Model(&entity.Friend{}).
Where("wxid = ?", msg.FromUser).
Update("last_active", msg.CreateTime).Error
Update("last_active", msg.CreateAt).Error
if err != nil {
log.Printf("更新群或者好友活跃时间失败, 错误信息: %v", err)
}

View File

@@ -3,6 +3,7 @@ package service
import (
"go-wechat/client"
"go-wechat/entity"
"go-wechat/vo"
"log"
"os"
"strconv"
@@ -39,3 +40,22 @@ func SaveMessage(msg entity.Message) {
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
}

View File

@@ -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,
@@ -157,6 +161,7 @@ func syncGroupUsers(tx *gorm.DB, gid string) {
IsMember: true,
IsAdmin: wxid == baseResp.Data.Admin,
JoinTime: time.Now().Local(),
LastActive: time.Now().Local(),
}).Error
if err != nil {
log.Printf("新增群成员失败: %s", err.Error())

86
tasks/summary/summary.go Normal file
View File

@@ -0,0 +1,86 @@
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"}--end--`, record.Nickname, strings.ReplaceAll(record.Message, "\n", "。。")))
}
msgTmp := `请帮我总结一下一下的群聊内容的梗概,生成的梗概需要尽可能详细,需要带上一些聊天关键信息,并且带上群友名字。
注意,他们可能是多个话题,请仔细甄别。
每一行代表一个人的发言,每一行的的格式为: {"{nickname}": "{content}"}--end--
聊天记录如下:
%s
`
msg := fmt.Sprintf(msgTmp, 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\n%s", resp.Choices[0].Message.Content)
utils.SendMessage(group.Wxid, "", replyMsg, 0)
}
}

View File

@@ -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)

View File

@@ -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, " ")

View File

@@ -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").

View File

@@ -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, " ")

View File

@@ -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, " ")

View File

@@ -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, " ")

View File

@@ -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" />
@@ -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>
@@ -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}})"/>

View File

@@ -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("修改失败")
@@ -135,3 +158,24 @@ function getGroupUsers(groupId, groupName) {
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("修改失败")
})
}

View File

@@ -18,6 +18,7 @@ type FriendItem struct {
EnableChatRank bool // 是否使用聊天排行
EnableWelcome bool // 是否使用迎新
EnableCommand bool // 是否启用指令
EnableSummary bool // 是否启用总结
IsOk bool // 是否还在通讯库(群聊是要还在群里也算)
}

8
vo/message.go Normal file
View File

@@ -0,0 +1,8 @@
package vo
// TextMessageItem
// @description: 文字消息
type TextMessageItem struct {
Nickname string `json:"nickname"`
Message string `json:"message"`
}