process_request.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package process
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. "github.com/eryajf/chatgpt-dingtalk/pkg/db"
  7. "github.com/eryajf/chatgpt-dingtalk/pkg/dingbot"
  8. "github.com/eryajf/chatgpt-dingtalk/pkg/logger"
  9. "github.com/eryajf/chatgpt-dingtalk/public"
  10. "github.com/solywsh/chatgpt"
  11. )
  12. // ProcessRequest 分析处理请求逻辑
  13. func ProcessRequest(rmsg *dingbot.ReceiveMsg) error {
  14. if CheckRequestTimes(rmsg) {
  15. content := strings.TrimSpace(rmsg.Text.Content)
  16. timeoutStr := ""
  17. if content != public.Config.DefaultMode {
  18. timeoutStr = fmt.Sprintf("\n\n>%s 后将恢复默认聊天模式:%s", FormatTimeDuation(public.Config.SessionTimeout), public.Config.DefaultMode)
  19. }
  20. switch content {
  21. case "单聊":
  22. public.UserService.SetUserMode(rmsg.GetSenderIdentifier(), content)
  23. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("**[Concentrate] 现在进入与 %s 的单聊模式**%s", rmsg.SenderNick, timeoutStr))
  24. if err != nil {
  25. logger.Warning(fmt.Errorf("send message error: %v", err))
  26. }
  27. case "串聊":
  28. public.UserService.SetUserMode(rmsg.GetSenderIdentifier(), content)
  29. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("**[Concentrate] 现在进入与 %s 的串聊模式**%s", rmsg.SenderNick, timeoutStr))
  30. if err != nil {
  31. logger.Warning(fmt.Errorf("send message error: %v", err))
  32. }
  33. case "重置", "退出", "结束":
  34. // 重置用户对话模式
  35. public.UserService.ClearUserMode(rmsg.GetSenderIdentifier())
  36. // 清空用户对话上下文
  37. public.UserService.ClearUserSessionContext(rmsg.GetSenderIdentifier())
  38. // 清空用户对话的答案ID
  39. public.UserService.ClearAnswerID(rmsg.SenderNick, rmsg.GetChatTitle())
  40. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[RecyclingSymbol]已重置与**%s** 的对话模式\n\n> 可以开始新的对话 [Bubble]", rmsg.SenderNick))
  41. if err != nil {
  42. logger.Warning(fmt.Errorf("send message error: %v", err))
  43. }
  44. case "模板":
  45. var title string
  46. for _, v := range *public.Prompt {
  47. title = title + v.Title + " | "
  48. }
  49. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("%s 您好,当前程序内置集成了这些提示词:\n\n-----\n\n| %s \n\n-----\n\n您可以选择某个提示词作为对话内容的开头。\n\n以周报为例,可发送\"#周报 我本周用Go写了一个钉钉集成ChatGPT的聊天应用\",可将工作内容填充为一篇完整的周报。\n\n-----\n\n若您不清楚某个提示词的所代表的含义,您可以直接发送提示词,例如直接发送\"#周报\"", rmsg.SenderNick, title))
  50. if err != nil {
  51. logger.Warning(fmt.Errorf("send message error: %v", err))
  52. }
  53. case "图片":
  54. if public.Config.AzureOn {
  55. _, err := rmsg.ReplyToDingtalk(string(dingbot.
  56. MARKDOWN), "azure 模式下暂不支持图片创作功能")
  57. if err != nil {
  58. logger.Warning(fmt.Errorf("send message error: %v", err))
  59. }
  60. return err
  61. }
  62. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), "发送以 **#图片** 开头的内容,将会触发绘画能力,图片生成之后,将会通过消息回复给您。建议尽可能描述需要生成的图片内容及相关细节。\n 如果你绘图没有思路,可以在这两个网站寻找灵感。\n - [https://lexica.art/](https://lexica.art/)\n- [https://www.clickprompt.org/zh-CN/](https://www.clickprompt.org/zh-CN/)")
  63. if err != nil {
  64. logger.Warning(fmt.Errorf("send message error: %v", err))
  65. }
  66. case "余额":
  67. if public.JudgeAdminUsers(rmsg.SenderStaffId) {
  68. cacheMsg := public.UserService.GetUserMode("system_balance")
  69. if cacheMsg == "" {
  70. rst, err := public.GetBalance()
  71. if err != nil {
  72. logger.Warning(fmt.Errorf("get balance error: %v", err))
  73. return err
  74. }
  75. cacheMsg = rst
  76. }
  77. _, err := rmsg.ReplyToDingtalk(string(dingbot.TEXT), cacheMsg)
  78. if err != nil {
  79. logger.Warning(fmt.Errorf("send message error: %v", err))
  80. }
  81. }
  82. case "查对话":
  83. if public.JudgeAdminUsers(rmsg.SenderStaffId) {
  84. msg := "使用如下指令进行查询:\n\n---\n\n**#查对话 username:张三**\n\n---\n\n需要注意格式必须严格与上边一致,否则将会查询失败\n\n只有程序系统管理员有权限查询,即config.yml中的admin_users指定的人员。"
  85. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), msg)
  86. if err != nil {
  87. logger.Warning(fmt.Errorf("send message error: %v", err))
  88. }
  89. }
  90. default:
  91. if public.FirstCheck(rmsg) {
  92. return Do("串聊", rmsg)
  93. } else {
  94. return Do("单聊", rmsg)
  95. }
  96. }
  97. }
  98. return nil
  99. }
  100. // 执行处理请求
  101. func Do(mode string, rmsg *dingbot.ReceiveMsg) error {
  102. // 先把模式注入
  103. public.UserService.SetUserMode(rmsg.GetSenderIdentifier(), mode)
  104. switch mode {
  105. case "单聊":
  106. qObj := db.Chat{
  107. Username: rmsg.SenderNick,
  108. Source: rmsg.GetChatTitle(),
  109. ChatType: db.Q,
  110. ParentContent: 0,
  111. Content: rmsg.Text.Content,
  112. }
  113. qid, err := qObj.Add()
  114. if err != nil {
  115. logger.Error("往MySQL新增数据失败,错误信息:", err)
  116. }
  117. reply, err := chatgpt.SingleQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
  118. if err != nil {
  119. logger.Info(fmt.Errorf("gpt request error: %v", err))
  120. if strings.Contains(fmt.Sprintf("%v", err), "maximum question length exceeded") {
  121. public.UserService.ClearUserSessionContext(rmsg.GetSenderIdentifier())
  122. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v\n\n> 已超过最大文本限制,请缩短提问文字的字数。", err))
  123. if err != nil {
  124. logger.Warning(fmt.Errorf("send message error: %v", err))
  125. return err
  126. }
  127. } else {
  128. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v", err))
  129. if err != nil {
  130. logger.Warning(fmt.Errorf("send message error: %v", err))
  131. return err
  132. }
  133. }
  134. }
  135. if reply == "" {
  136. logger.Warning(fmt.Errorf("get gpt result falied: %v", err))
  137. return nil
  138. } else {
  139. reply = strings.TrimSpace(reply)
  140. reply = strings.Trim(reply, "\n")
  141. aObj := db.Chat{
  142. Username: rmsg.SenderNick,
  143. Source: rmsg.GetChatTitle(),
  144. ChatType: db.A,
  145. ParentContent: qid,
  146. Content: reply,
  147. }
  148. _, err := aObj.Add()
  149. if err != nil {
  150. logger.Error("往MySQL新增数据失败,错误信息:", err)
  151. }
  152. logger.Info(fmt.Sprintf("🤖 %s得到的答案: %#v", rmsg.SenderNick, reply))
  153. if public.JudgeSensitiveWord(reply) {
  154. reply = public.SolveSensitiveWord(reply)
  155. }
  156. // 回复@我的用户
  157. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), FormatMarkdown(reply))
  158. if err != nil {
  159. logger.Warning(fmt.Errorf("send message error: %v", err))
  160. return err
  161. }
  162. }
  163. case "串聊":
  164. lastAid := public.UserService.GetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle())
  165. qObj := db.Chat{
  166. Username: rmsg.SenderNick,
  167. Source: rmsg.GetChatTitle(),
  168. ChatType: db.Q,
  169. ParentContent: lastAid,
  170. Content: rmsg.Text.Content,
  171. }
  172. qid, err := qObj.Add()
  173. if err != nil {
  174. logger.Error("往MySQL新增数据失败,错误信息:", err)
  175. }
  176. cli, reply, err := chatgpt.ContextQa(rmsg.Text.Content, rmsg.GetSenderIdentifier())
  177. if err != nil {
  178. logger.Info(fmt.Sprintf("gpt request error: %v", err))
  179. if strings.Contains(fmt.Sprintf("%v", err), "maximum text length exceeded") {
  180. public.UserService.ClearUserSessionContext(rmsg.GetSenderIdentifier())
  181. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v\n\n> 串聊已超过最大文本限制,对话已重置,请重新发起。", err))
  182. if err != nil {
  183. logger.Warning(fmt.Errorf("send message error: %v", err))
  184. return err
  185. }
  186. } else {
  187. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Wrong] 请求 OpenAI 失败了\n\n> 错误信息:%v", err))
  188. if err != nil {
  189. logger.Warning(fmt.Errorf("send message error: %v", err))
  190. return err
  191. }
  192. }
  193. }
  194. if reply == "" {
  195. logger.Warning(fmt.Errorf("get gpt result falied: %v", err))
  196. return nil
  197. } else {
  198. reply = strings.TrimSpace(reply)
  199. reply = strings.Trim(reply, "\n")
  200. aObj := db.Chat{
  201. Username: rmsg.SenderNick,
  202. Source: rmsg.GetChatTitle(),
  203. ChatType: db.A,
  204. ParentContent: qid,
  205. Content: reply,
  206. }
  207. aid, err := aObj.Add()
  208. if err != nil {
  209. logger.Error("往MySQL新增数据失败,错误信息:", err)
  210. }
  211. // 将当前回答的ID放入缓存
  212. public.UserService.SetAnswerID(rmsg.SenderNick, rmsg.GetChatTitle(), aid)
  213. logger.Info(fmt.Sprintf("🤖 %s得到的答案: %#v", rmsg.SenderNick, reply))
  214. if public.JudgeSensitiveWord(reply) {
  215. reply = public.SolveSensitiveWord(reply)
  216. }
  217. // 回复@我的用户
  218. _, err = rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), FormatMarkdown(reply))
  219. if err != nil {
  220. logger.Warning(fmt.Errorf("send message error: %v", err))
  221. return err
  222. }
  223. _ = cli.ChatContext.SaveConversation(rmsg.GetSenderIdentifier())
  224. }
  225. default:
  226. }
  227. return nil
  228. }
  229. // FormatTimeDuation 格式化时间
  230. // 主要提示单聊/群聊切换时多久后恢复默认聊天模式
  231. func FormatTimeDuation(duration time.Duration) string {
  232. minutes := int64(duration.Minutes())
  233. seconds := int64(duration.Seconds()) - minutes*60
  234. timeoutStr := ""
  235. if seconds == 0 {
  236. timeoutStr = fmt.Sprintf("%d分钟", minutes)
  237. } else {
  238. timeoutStr = fmt.Sprintf("%d分%d秒", minutes, seconds)
  239. }
  240. return timeoutStr
  241. }
  242. // FormatMarkdown 格式化Markdown
  243. // 主要修复ChatGPT返回多行代码块,钉钉会将代码块中的#当作Markdown语法里的标题来处理,这里进行下转义
  244. func FormatMarkdown(md string) string {
  245. lines := strings.Split(md, "\n")
  246. codeblock := false
  247. for i, line := range lines {
  248. if strings.HasPrefix(line, "```") {
  249. codeblock = !codeblock
  250. } else if codeblock && strings.HasPrefix(line, "#") {
  251. lines[i] = "\\" + lines[i]
  252. }
  253. }
  254. return strings.Join(lines, "\n")
  255. }
  256. // CheckRequestTimes 分析处理请求逻辑
  257. // 主要提供单日请求限额的功能
  258. func CheckRequestTimes(rmsg *dingbot.ReceiveMsg) bool {
  259. if public.Config.MaxRequest == 0 {
  260. return true
  261. }
  262. count := public.UserService.GetUseRequestCount(rmsg.GetSenderIdentifier())
  263. // 用户是管理员或VIP用户,不判断访问次数是否超过限制
  264. if public.JudgeAdminUsers(rmsg.SenderStaffId) || public.JudgeVipUsers(rmsg.SenderStaffId) {
  265. return true
  266. } else {
  267. // 用户不是管理员和VIP用户,判断访问次数是否超过限制
  268. if count >= public.Config.MaxRequest {
  269. logger.Info(fmt.Sprintf("亲爱的: %s,您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!", rmsg.SenderNick))
  270. _, err := rmsg.ReplyToDingtalk(string(dingbot.MARKDOWN), fmt.Sprintf("[Staple] **一个好的问题,胜过十个好的答案!** \n\n亲爱的%s:\n\n您今日请求次数已达上限,请明天再来,交互发问资源有限,请务必斟酌您的问题,给您带来不便,敬请谅解!\n\n如有需要,可联系管理员升级为VIP用户。", rmsg.SenderNick))
  271. if err != nil {
  272. logger.Warning(fmt.Errorf("send message error: %v", err))
  273. }
  274. return false
  275. }
  276. }
  277. // 访问次数未超过限制,将计数加1
  278. public.UserService.SetUseRequestCount(rmsg.GetSenderIdentifier(), count+1)
  279. return true
  280. }