diff --git a/README.md b/README.md index 51dcae67..f42fa98e 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ $ docker run -itd --name chatgpt -p 8090:8090 \ -e HTTP_PROXY="http://host.docker.internal:15732" \ -e DEFAULT_MODE="单聊" -e MAX_REQUEST=0 -e PORT=8090 \ -e SERVICE_URL="你当前服务外网可访问的URL" -e CHAT_TYPE="0" \ - -e ALLOW_GROUPS=a,b -e ALLOW_USERS=a,b -e ADMIN_USERS=a,b -e APP_SECRETS="xxx,yyy" \ + -e ALLOW_GROUPS=a,b -e ALLOW_USERS=a,b -e DENY_USERS=a,b -e VIP_USERS=a,b -e ADMIN_USERS=a,b -e APP_SECRETS="xxx,yyy" \ -e AZURE_ON="false" -e AZURE_API_VERSION="" -e AZURE_RESOURCE_NAME="" \ -e AZURE_DEPLOYMENT_NAME="" -e AZURE_OPENAI_TOKEN="" \ -e HELP="欢迎使用本工具\n\n你可以查看:[用户指南](https://github.com/eryajf/chatgpt-dingtalk/blob/main/docs/userGuide.md)\n\n这是一个[开源项目](https://github.com/eryajf/chatgpt-dingtalk/) @@ -184,7 +184,7 @@ $ docker run -itd --name chatgpt -p 8090:8090 \ > 运行命令中映射的配置文件参考下边的[配置文件说明](#%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%B4%E6%98%8E)。 - `📢 注意:`如果使用docker部署,那么PORT参数不需要进行任何调整。 -- `📢 注意:`ALLOW_GROUPS,ALLOW_USERS,ADMIN_USERS三个参数为数组,如果需要指定多个,可用英文逗号分割。 +- `📢 注意:`ALLOW_GROUPS,ALLOW_USERS,DENY_USERS,VIP_USERS,ADMIN_USERS 参数为数组,如果需要指定多个,可用英文逗号分割。outgoing机器人模式下这些参数无效。 - `📢 注意:`如果服务器节点本身就在国外或者自定义了`BASE_URL`,那么就把`HTTP_PROXY`参数留空即可。 - `📢 注意:`如果使用docker部署,那么proxy地址可以直接使用如上方式部署,`host.docker.internal`会指向容器所在宿主机的IP,只需要更改端口为你的代理端口即可。参见:[Docker容器如何优雅地访问宿主机网络](https://wiki.eryajf.net/pages/674f53/) @@ -420,10 +420,16 @@ chat_type: "0" # 对话聊天时,如下三个满足其一即可通过校验 allow_groups: - "学无止境" -# 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的名称,比如 ["张三","李四"] -allow_users: ["张三","李四"] -# 指定哪些人为此系统的管理员,如果留空,则表示没有人是管理员,如果要限制,则列表中写用户的userid +# 以下 allow_users、deny_users、vip_users、admin_users 配置中填写的是用户的userid,outgoing机器人模式下不适用这些配置 # 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts +# 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid +allow_users: [] +# 哪些用户不可以进行对话,如果留空,则表示允许所有用户(如allow_user有配置,需满足相应条件),如果要限制,则列表中写用户的userid,黑名单优先级高于白名单 +deny_users: [] +# 哪些用户可以进行无限对话,如果留空,则表示只允许管理员(如max_request配置为0,则允许所有人) +# 如果要针对指定VIP用户放开限制(如max_request配置不为0),则列表中写用户的userid +vip_users: [] +# 指定哪些人为此系统的管理员,如果留空,则表示没有人是管理员,如果要限制,则列表中写用户的userid # 注意:如果下边的app_secrets为空,以及使用outgoing的方式配置机器人,这两种情况下,都表示没有人是管理员 admin_users: [] # 钉钉机器人在应用信息中的AppSecret,为了校验回调的请求是否合法,如果留空,将会忽略校验,则该接口将会存在其他人也能随意调用的安全隐患,因此强烈建议配置正确的secret,如果你的服务对接给多个机器人,这里可以配置多个机器人的secret diff --git a/config.example.yml b/config.example.yml index 21b449de..1142122c 100644 --- a/config.example.yml +++ b/config.example.yml @@ -22,10 +22,16 @@ service_url: "http://xxxxxx" chat_type: "0" # 哪些群组可以进行对话,如果留空,则表示允许所有群组,如果要限制,则列表中写群组的名称,比如 ["aa","bb"] allow_groups: [] -# 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的名称,比如 ["张三","李四"] +# 以下 allow_users、deny_users、vip_users、admin_users 配置中填写的是用户的userid,outgoing机器人模式下不适用这些配置 +# 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts +# 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid allow_users: [] +# 哪些用户不可以进行对话,如果留空,则表示允许所有用户(如allow_user有配置,需满足相应条件),如果要限制,则列表中写用户的userid,黑名单优先级高于白名单 +deny_users: [] +# 哪些用户可以进行无限对话,如果留空,则表示只允许管理员(如max_request配置为0,则允许所有人) +# 如果要针对指定VIP用户放开限制(如max_request配置不为0),则列表中写用户的userid +vip_users: [] # 指定哪些人为此系统的管理员,如果留空,则表示没有人是管理员,如果要限制,则列表中写用户的userid -# 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts # 注意:如果下边的app_secrets为空,以及使用outgoing的方式配置机器人,这两种情况下,都表示没有人是管理员 admin_users: [] # 钉钉机器人在应用信息中的AppSecret,为了校验回调的请求是否合法,如果留空,将会忽略校验,则该接口将会存在其他人也能随意调用的安全隐患,因此强烈建议配置正确的secret,如果你的服务对接给多个机器人,这里可以配置多个机器人的secret diff --git a/config/config.go b/config/config.go index 74426555..5d5345e3 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,10 @@ type Configuration struct { AllowGroups []string `yaml:"allow_groups"` // 哪些用户可以进行对话 AllowUsers []string `yaml:"allow_users"` + // 哪些用户不可以进行对话 + DenyUsers []string `yaml:"deny_users"` + // 哪些Vip用户可以进行无限对话 + VipUsers []string `yaml:"vip_users"` // 指定哪些人为此系统的管理员,必须指定,否则所有人都是 AdminUsers []string `yaml:"admin_users"` // 钉钉机器人在应用信息中的AppSecret,为了校验回调的请求是否合法,如果你的服务对接给多个机器人,这里可以配置多个机器人的secret @@ -134,6 +138,14 @@ func LoadConfig() *Configuration { if allowUsers != "" { config.AllowUsers = strings.Split(allowUsers, ",") } + denyUsers := os.Getenv("DENY_USERS") + if denyUsers != "" { + config.DenyUsers = strings.Split(denyUsers, ",") + } + vipUsers := os.Getenv("VIP_USERS") + if vipUsers != "" { + config.VipUsers = strings.Split(vipUsers, ",") + } adminUsers := os.Getenv("ADMIN_USERS") if adminUsers != "" { config.AdminUsers = strings.Split(adminUsers, ",") diff --git a/docker-compose.yml b/docker-compose.yml index 65313a08..d0eb460f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,11 +18,14 @@ services: SERVICE_URL: "" # 指定服务的地址,就是当前服务可供外网访问的地址(或者直接理解为你配置在钉钉回调那里的地址),用于生成图片时给钉钉做渲染 CHAT_TYPE: "0" # 限定对话类型 0:不限 1:只能单聊 2:只能群聊 ALLOW_GROUPS: "" # 哪些群组可以进行对话,如果留空,则表示允许所有群组,如果要限制,则填写群组的名字,比如 "aa,bb" - ALLOW_USERS: "" # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则填写用户的名字,比如 "张三,李四" - # 指定哪些人为此系统的管理员,如果留空,则表示没有人是管理员,如果要限制,则列表中写用户的userid - # 比如 "1301691029702722,1301691029702733",这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts + # 以下 ALLOW_USERS、DENY_USERS、VIP_USERS、ADMIN_USERS 配置中填写的是用户的userid + # 比如 ["1301691029702722","1301691029702733"],这个信息需要在钉钉管理后台的通讯录当中获取:https://oa.dingtalk.com/contacts.htm#/contacts + # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则列表中写用户的userid + ALLOW_USERS: "" # 哪些用户可以进行对话,如果留空,则表示允许所有用户,如果要限制,则填写用户的userid + DENY_USERS: "" # 哪些用户不可以进行对话,如果留空,则表示允许所有用户(如allow_user有配置,需满足相应条件),如果要限制,则列表中写用户的userid,黑名单优先级高于白名单 + VIP_USERS: "" # 哪些用户可以进行无限对话,如果留空,则表示只允许管理员(如max_request配置为0,则允许所有人),如果要针对指定VIP用户放开限制(如max_request配置不为0),则列表中写用户的userid + ADMIN_USERS: "" # 指定哪些人为此系统的管理员,如果留空,则表示没有人是管理员,如果要限制,则列表中写用户的userid # 注意:如果下边的app_secrets为空,以及使用outgoing的方式配置机器人,这两种情况下,都表示没有人是管理员 - ADMIN_USERS: "" APP_SECRETS: "" # 钉钉机器人在应用信息中的AppSecret,为了校验回调的请求是否合法,如果留空,将会忽略校验,则该接口将会存在其他人也能随意调用的安全隐患,因此强烈建议配置正确的secret,如果你的服务对接给多个机器人,这里可以配置多个机器人的secret,比如 "xxxx,yyyy" AZURE_ON: "false" # 是否走Azure OpenAi API, 默认false ,如果为true,则需要配置下边的四个参数 AZURE_API_VERSION: "" # Azure OpenAi API 版本,比如 "2023-03-15-preview" diff --git a/main.go b/main.go index 3e5396c6..4031a6b8 100644 --- a/main.go +++ b/main.go @@ -49,15 +49,18 @@ func Start() { logger.Debug(fmt.Sprintf("dingtalk callback parameters: %#v", msgObj)) if public.Config.ChatType != "0" && msgObj.ConversationType != public.Config.ChatType { - _, err = msgObj.ReplyToDingtalk(string(dingbot.TEXT), "抱歉,管理员禁用了这种聊天方式,请选择其他聊天方式与机器人对话!") + logger.Info(fmt.Sprintf("🙋 %s使用了禁用的聊天方式", msgObj.SenderNick)) + _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,管理员禁用了这种聊天方式,请选择其他聊天方式与机器人对话!**") if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err } return nil } - if !public.JudgeGroup(msgObj.GetChatTitle()) && !public.JudgeUsers(msgObj.SenderNick) && !public.JudgeAdminUsers(msgObj.SenderStaffId) { - _, err = msgObj.ReplyToDingtalk(string(dingbot.TEXT), "抱歉,您不在该机器人对话功能的白名单当中!") + // 不在允许群组,不在允许用户(包括在黑名单),满足任一条件,拒绝会话;管理员不受限制 + if (!public.JudgeGroup(msgObj.GetChatTitle()) || !public.JudgeUsers(msgObj.SenderStaffId)) && !public.JudgeAdminUsers(msgObj.SenderStaffId) { + logger.Info(fmt.Sprintf("🙋 %s身份信息未被验证通过", msgObj.SenderNick)) + _, err = msgObj.ReplyToDingtalk(string(dingbot.MARKDOWN), "**🤷 抱歉,您的身份信息未被认证通过,无法使用机器人对话功能。**\n>如需继续使用,请联系管理员申请访问权限。") if err != nil { logger.Warning(fmt.Errorf("send message error: %v", err)) return err diff --git a/pkg/process/process_request.go b/pkg/process/process_request.go index 2b7520d4..baf5adb0 100644 --- a/pkg/process/process_request.go +++ b/pkg/process/process_request.go @@ -225,14 +225,19 @@ func CheckRequestTimes(rmsg *dingbot.ReceiveMsg) bool { return true } count := public.UserService.GetUseRequestCount(rmsg.GetSenderIdentifier()) - // 判断访问次数是否超过限制 - if count >= public.Config.MaxRequest { - logger.Info(fmt.Sprintf("亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick)) - _, err := rmsg.ReplyToDingtalk(string(dingbot.TEXT), fmt.Sprintf("一个好的问题,胜过十个好的答案!\n亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick)) - if err != nil { - logger.Warning(fmt.Errorf("send message error: %v", err)) + // 用户是管理员或VIP用户,不判断访问次数是否超过限制 + if public.JudgeAdminUsers(rmsg.SenderStaffId) || public.JudgeVipUsers(rmsg.SenderStaffId) { + return true + } else { + // 用户不是管理员和VIP用户,判断访问次数是否超过限制 + if count >= public.Config.MaxRequest { + logger.Info(fmt.Sprintf("亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick)) + _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Staple] **一个好的问题,胜过十个好的答案!** \n\n亲爱的%s:\n\n您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!\n\n如有需要,可联系管理员升级为VIP用户。", rmsg.SenderNick)) + if err != nil { + logger.Warning(fmt.Errorf("send message error: %v", err)) + } + return false } - return false } // 访问次数未超过限制,将计数加1 public.UserService.SetUseRequestCount(rmsg.GetSenderIdentifier(), count+1) diff --git a/prompt.yml b/prompt.yml index f2f7f280..2d7ac2fa 100644 --- a/prompt.yml +++ b/prompt.yml @@ -35,9 +35,6 @@ - title: "#linux命令" prefix: "我希望你只用 linux 命令回复。不要写解释。我想:" suffix: "" -- title: "#Linux命令" - prefix: "我希望你只用 Linux 命令回复。不要写解释。我想:" - suffix: "" - title: "#英语学术润色" prefix: "Below is a paragraph from an academic paper. Polish the writing to meet the academic style, improve the spelling, grammar, clarity, concision and overall readability. When neccessary, rewrite the whole sentence. Furthermore, list all modification and explain the reasons to do so in markdown table.\n" suffix: "" diff --git a/public/tools.go b/public/tools.go index 875f6fa5..91ae8c33 100644 --- a/public/tools.go +++ b/public/tools.go @@ -44,6 +44,15 @@ func JudgeGroup(s string) bool { // JudgeUsers 判断用户名称是否在白名单 func JudgeUsers(s string) bool { + // 优先判断黑名单,黑名单用户返回:不在白名单 + if len(Config.DenyUsers) != 0 { + for _, v := range Config.DenyUsers { + if v == s { + return false + } + } + } + // 白名单配置逻辑处理 if len(Config.AllowUsers) == 0 { return true } @@ -73,6 +82,30 @@ func JudgeAdminUsers(s string) bool { return false } +// JudgeVipUsers 判断用户是否为VIP用户 +func JudgeVipUsers(s string) bool { + // 如果secret或者用户的userid都为空的话,那么默认不是VIP用户 + if len(Config.AppSecrets) == 0 || s == "" { + return false + } + // 管理员默认是VIP用户 + for _, v := range Config.AdminUsers { + if v == s { + return true + } + } + // 如果没有指定,则没有人是VIP用户 + if len(Config.VipUsers) == 0 { + return false + } + for _, v := range Config.VipUsers { + if v == s { + return true + } + } + return false +} + func GetReadTime(t time.Time) string { return t.Format("2006-01-02 15:04:05") }