Merge pull request ' 新增功能配置开放' (#81) from hotfix into main
All checks were successful
BuildImage / build-image (push) Successful in 1m33s

Reviewed-on: #81
This commit is contained in:
李寻欢 2024-07-16 11:41:38 +08:00
commit 0f578e6333
10 changed files with 254 additions and 24 deletions

View File

@ -107,6 +107,27 @@ func Assistant(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "assistant.html", result) ctx.HTML(http.StatusOK, "assistant.html", result)
} }
// ManageWithGroupAdmin
// @description: 群组管理(管理员可用)
// @param ctx
func ManageWithGroupAdmin(ctx *gin.Context) {
// 取出id
id := ctx.Query("id")
if id == "" {
ctx.HTML(http.StatusOK, "404.html", nil)
return
}
var result = gin.H{
"msg": "success",
}
result["info"], _ = service.GetFriendInfoById(id)
result["aiModels"] = config.Conf.Ai.Models
result["assistant"], _ = service.GetAllAiAssistant()
// 渲染页面
ctx.HTML(http.StatusOK, "manager-one.html", result)
}
// PageNotFound // PageNotFound
// @description: 404页面 // @description: 404页面
// @param ctx // @param ctx

View File

@ -2,6 +2,8 @@ system:
# 每日新闻接口 Token # 每日新闻接口 Token
# 获取地址: https://admin.alapi.cn/api_manager/token_manager # 获取地址: https://admin.alapi.cn/api_manager/token_manager
alApiToken: xxx alApiToken: xxx
# 系统访问域名
domain: https://wechat.abc.com
# 添加新好友或群之后通知给指定的人 # 添加新好友或群之后通知给指定的人
newFriendNotify: newFriendNotify:
enable: true enable: true
@ -48,7 +50,7 @@ mysql:
# 定时任务 # 定时任务
task: task:
enable: true enable: false
news: news:
enable: false enable: false
cron: '14 11 * * *' # 每天0:30 cron: '14 11 * * *' # 每天0:30

View File

@ -2,6 +2,7 @@ package config
// 系统配置 // 系统配置
type system struct { type system struct {
Domain string `json:"domain" yaml:"domain"` // 域名
AlApiToken string `json:"alApiToken" yaml:"alApiToken"` // AL API Token AlApiToken string `json:"alApiToken" yaml:"alApiToken"` // AL API Token
NewFriendNotify newFriendNotify `json:"newFriendNotify" yaml:"newFriendNotify"` // 新好友通知 NewFriendNotify newFriendNotify `json:"newFriendNotify" yaml:"newFriendNotify"` // 新好友通知
DefaultRule defaultRule `json:"defaultRule" yaml:"defaultRule"` // 默认规则 DefaultRule defaultRule `json:"defaultRule" yaml:"defaultRule"` // 默认规则

View File

@ -60,6 +60,10 @@ func main() {
} }
return "否" return "否"
}, },
"checkIsGroup": func(str string) bool {
// 如果id包含@chatroom就是群组
return strings.HasSuffix(str, "@chatroom")
},
}) })
app.LoadHTMLGlob("views/*.html") app.LoadHTMLGlob("views/*.html")

View File

@ -1,29 +1,30 @@
package entity package entity
import ( import (
"go-wechat/common/types"
"time" "time"
) )
// Friend // Friend
// @description: 好友列表 // @description: 好友列表
type Friend struct { type Friend struct {
Wxid string `json:"wxid"` // 微信原始Id Wxid string `json:"wxid"` // 微信原始Id
CustomAccount string `json:"customAccount"` // 微信号 CustomAccount string `json:"customAccount"` // 微信号
Nickname string `json:"nickname"` // 昵称 Nickname string `json:"nickname"` // 昵称
Pinyin string `json:"pinyin"` // 昵称拼音大写首字母 Pinyin string `json:"pinyin"` // 昵称拼音大写首字母
PinyinAll string `json:"pinyinAll"` // 昵称全拼 PinyinAll string `json:"pinyinAll"` // 昵称全拼
LastActive time.Time `json:"lastActive"` // 最后活跃时间 LastActive types.DateTime `json:"lastActive"` // 最后活跃时间
EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI EnableAi bool `json:"enableAI" gorm:"type:tinyint(1) default 0 not null"` // 是否使用AI
AiModel string `json:"aiModel"` // AI模型 AiModel string `json:"aiModel"` // AI模型
Prompt string `json:"prompt"` // 提示词 Prompt string `json:"prompt"` // 提示词
EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行 EnableChatRank bool `json:"enableChatRank" gorm:"type:tinyint(1) default 0 not null"` // 是否使用聊天排行
EnableWelcome bool `json:"enableWelcome" 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"` // 是否启用总结 EnableSummary bool `json:"enableSummary" gorm:"type:tinyint(1) default 0 not null"` // 是否启用总结
EnableNews bool `json:"enableNews" gorm:"type:tinyint(1) default 0 not null"` // 是否启用新闻 EnableNews bool `json:"enableNews" gorm:"type:tinyint(1) default 0 not null"` // 是否启用新闻
EnableHotTop bool `json:"enableHotTop" gorm:"type:tinyint(1) default 0 not null"` // 是否启用热榜新闻 EnableHotTop bool `json:"enableHotTop" gorm:"type:tinyint(1) default 0 not null"` // 是否启用热榜新闻
ClearMember int `json:"clearMember"` // 清理成员配置(多少天未活跃的) ClearMember int `json:"clearMember"` // 清理成员配置(多少天未活跃的)
IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常 IsOk bool `json:"isOk" gorm:"type:tinyint(1) default 0 not null"` // 是否正常
UsedTokens int `json:"usedTokens"` // 已使用的AI Token数量 UsedTokens int `json:"usedTokens"` // 已使用的AI Token数量
} }
func (Friend) TableName() string { func (Friend) TableName() string {

View File

@ -14,10 +14,11 @@ func Init(g *gin.Engine) {
ctx.Redirect(302, "/index.html") ctx.Redirect(302, "/index.html")
}) })
g.GET("/index.html", app.Index) // 首页 g.GET("/index.html", app.Index) // 首页
g.GET("/friend.html", app.Friend) // 好友列表 g.GET("/friend.html", app.Friend) // 好友列表
g.GET("/group.html", app.Group) // 群组列表 g.GET("/group.html", app.Group) // 群组列表
g.GET("/assistant.html", app.Assistant) // AI角色 g.GET("/assistant.html", app.Assistant) // AI角色
g.GET("/manager.html", app.ManageWithGroupAdmin) // 自己管理配置
g.GET("/404.html", app.PageNotFound) // 群组列表 g.GET("/404.html", app.PageNotFound) // 群组列表

View File

@ -5,6 +5,7 @@ import (
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"go-wechat/client" "go-wechat/client"
"go-wechat/common/constant" "go-wechat/common/constant"
"go-wechat/common/types"
"go-wechat/config" "go-wechat/config"
"go-wechat/model/dto" "go-wechat/model/dto"
"go-wechat/model/entity" "go-wechat/model/entity"
@ -77,7 +78,7 @@ func Sync() {
EnableNews: config.Conf.System.DefaultRule.News, EnableNews: config.Conf.System.DefaultRule.News,
EnableHotTop: config.Conf.System.DefaultRule.HotTop, EnableHotTop: config.Conf.System.DefaultRule.HotTop,
ClearMember: 0, ClearMember: 0,
LastActive: time.Now().Local(), LastActive: types.DateTime(time.Now().Local()),
}).Error }).Error
if err != nil { if err != nil {
log.Printf("新增好友失败: %s", err.Error()) log.Printf("新增好友失败: %s", err.Error())
@ -98,6 +99,14 @@ func Sync() {
utils.SendEmotion(friend.Wxid, conf.Path, 0) utils.SendEmotion(friend.Wxid, conf.Path, 0)
} }
} }
// 发送配置网页
if config.Conf.System.Domain != "" {
title := "欢迎使用微信机器人"
desc := "点我可以配置功能喔,提示非微信官方网页,点击继续访问即可"
url := config.Conf.System.Domain + "/manager.html?id=" + friend.Wxid
utils.SendPublicMsg(friend.Wxid, title, desc, url, 0)
}
} else { } else {
pm := map[string]any{ pm := map[string]any{
"nickname": friend.Nickname, "nickname": friend.Nickname,

View File

@ -188,3 +188,42 @@ func QuitChatroom(chatRoomId string, retryCount int) {
} }
log.Printf("退群结果: %s", resp.String()) log.Printf("退群结果: %s", resp.String())
} }
// SendPublicMsg
// @description: 发送公众号消息
// @param wxId string 群或者好友Id
// @param title string 标题
// @param digest string 摘要
// @param url string 链接
// @param retryCount int 重试次数
func SendPublicMsg(wxId, title, digest, url string, retryCount int) {
if retryCount > 5 {
log.Printf("重试五次失败,停止发送")
return
}
// 组装参数
param := map[string]any{
"appName": "公安部网安局", // 假装是公安部发的,看着都牛逼
"userName": "gh_e406f4bcdf34", // 这个是公安部网安局的公众号id
"title": title,
"digest": digest,
"url": url,
"thumbUrl": "https://gitee.ltd/assets/img/logo.png", // 这个logo写死了懒得搞要改的自己改一下
"wxid": wxId,
}
pbs, _ := json.Marshal(param)
res := resty.New()
resp, err := res.R().
SetHeader("Content-Type", "application/json;chartset=utf-8").
SetBody(string(pbs)).
Post(config.Conf.Wechat.GetURL("/api/forwardPublicMsg"))
if err != nil {
log.Printf("发送公众号消息失败: %s", err.Error())
// 休眠五秒后重新发送
time.Sleep(5 * time.Second)
SendPublicMsg(wxId, title, digest, url, retryCount+1)
}
log.Printf("发送公众号消息结果: %s", resp.String())
}

View File

@ -149,7 +149,8 @@
</dd> </dd>
</div> </div>
</dl> </dl>
{{ end }} </li>
{{ end }}
</ul> </ul>
</div> </div>
</div> </div>

151
views/manager-one.html Normal file
View File

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en" class="h-full bg-gray-100">
<head>
<meta charset="UTF-8">
<title>水群助手</title>
<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"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.min.js"></script>
<script src="assets/js/index.js"></script>
</head>
<body class="h-full">
<div class="min-h-full flex items-center justify-center">
<div class="overflow-hidden rounded-xl border border-gray-200">
<div class="flex items -center gap-x-4 border-b border-gray-900/5 bg-gray-50 p-6">
<img src="assets/img/status-{{ if eq .info.IsOk true }}ok{{else}}fail{{end}}.png" alt="Tuple"
class="h-12 w-12 flex-none rounded-lg bg-white object-cover ring-1 ring-gray-900/10">
<div class="text-sm flex-1">
<div class="font-medium leading-6 text-gray-900">{{ .info.Nickname }}</div>
<div class="font-medium text-gray-500">{{ .info.Wxid }}</div>
{{ template "flagTag" .info.IsOk }}
<!-- <button type="button" class="btn-link float-end text-red-600" onclick="getGroupUsers({{.info.Wxid}}, {{.info.Nickname}})">群成员</button>-->
</div>
</div>
<dl class="-my-3 divide-y divide-gray-100 px-6 py-4 text-sm leading-6">
<!-- 最后活跃时间 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">最后活跃时间</dt>
<dd class="flex items-start gap-x-2">
{{ if eq .info.LastActive.IsNil true }}
无活跃数据
{{ else }}
<time datetime="{{ .LastActive }}">{{ .info.LastActive }}</time>
{{ end }}
</dd>
</div>
<!-- AI -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">AI(模型可选默认或者指定模型)</dt>
<dd class="flex items-start gap-x-2 items-center">
<div>
{{ template "ai" .info }}
</div>
{{ if eq .info.EnableAi true }}
<div class="float-end">
<div>
<label>
<select class="block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-green-600 sm:text-sm sm:leading-6" onchange="aiModelChange(event, {{.info.Wxid}})">
{{$useModel := .info.AiModel}}
{{ range $.aiModels }}
<option value="{{.Model}}" {{ if eq $useModel .Model}}selected{{ end }}>
{{.Name}}
</option>
{{ end }}
</select>
</label>
</div>
<div class="float-end mt-1">
<label>
<select
class="block w-full rounded-md border-0 py-1.5 pl-3 pr-10 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-green-600 sm:text-sm sm:leading-6"
onchange="aiAssistantChange(event, {{.info.Wxid}})">
<option value="" {{ if eq .info.Prompt
"" }}selected{{ end }}>默认</option>
{{$usePrompt := .info.Prompt}}
{{ range $.assistant }}
<option value="{{.Id}}" {{ if eq $usePrompt .Id}}selected{{ end }}>
{{.Name}}
</option>
{{ end }}
</select>
</label>
</div>
</div>
{{ end }}
</dd>
</div>
{{$isGroup := checkIsGroup .info.Wxid}}
{{ if eq $isGroup true }}
<!-- 水群排行榜 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">水群排行榜</dt>
<dd class="flex items-start gap-x-2">
{{ template "chatRank" .info }}
</dd>
</div>
<!-- 群聊总结 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">群聊总结</dt>
<dd class="flex items-start gap-x-2">
{{ template "summary" .info }}
</dd>
</div>
<!-- 迎新 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">迎新</dt>
<dd class="flex items-start gap-x-2">
{{ template "welcome" .info }}
</dd>
</div>
{{ end }}
<!-- 早报 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">早报</dt>
<dd class="flex items-start gap-x-2">
{{ template "news" .info }}
</dd>
</div>
<!-- 热榜 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">热榜</dt>
<dd class="flex items-start gap-x-2">
{{ template "hotTop" .info }}
</dd>
</div>
{{ if eq $isGroup true }}
<!-- 自动清理不活跃成员 -->
<div class="flex justify-between gap-x-4 py-3 items-center">
<dt class="text-gray-500">
末位淘汰(需要机器人是<span class="text-red-500">管理员</span>)
<br/>
<span class="text-red-300">* 清理多少天不说话的成员0表示不清理</span>
</dt>
<dd class="flex items-start gap-x-2 items-center">
<div class="relative rounded-md">
<label>
<input type="number" id="auto-cm-{{ .Wxid }}" min="0" class="block w-1/2 float-end rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" placeholder="N天不活跃自动移除"
value="{{.info.ClearMember}}"
onblur="changeClearMember({{.info.Wxid}}, {{.info.ClearMember}}, this.value)"
>
</label>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<span class="text-gray-500 sm:text-sm" id="price-currency"></span>
</div>
</div>
</dd>
</div>
{{ end }}
</dl>
</div>
</div>
</body>
</html>