Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
*.dylib
.idea/
.vscode/
wechatbot
storage.json

# Test binary, built with `go test -c`
*.test
Expand All @@ -16,5 +14,6 @@ storage.json
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
/config.json
config.json
storage.json
vendor
142 changes: 47 additions & 95 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,57 +1,48 @@
# wechatbot
# chatgpt_wechat_robot
个人微信接入ChatGPT,实现和GPT机器人互动聊天。支持私聊回复和群聊艾特回复。

> 本项目是 fork 他人的项目来进行学习和使用,请勿商用,可以下载下来做自定义的功能
> 最近ChatGPT异常火爆,本项目可以将个人微信化身GPT机器人,
> 项目基于[openwechat](https://github.com/eatmoreapple/openwechat) 开发。

> `友链:`[chatgpt-dingtalk](https://github.com/eryajf/chatgpt-dingtalk) 本项目可以将GPT机器人集成到钉钉群聊中。


### 目前实现了以下功能
### 实现功能

* GPT机器人模型热度可配置
* 提问增加上下文
* 指令清空上下文(指令:根据配置)
* 机器人群聊@回复
* 指令清空上下文
* 机器人私聊回复
* 机器人群聊@回复
* 私聊回复前缀设置
* 好友添加自动通过可配置
* ~~增加每天工作的起始时间和结束时间,只有在该时间段才会对外提供 chatgpt 服务~~
* ~~增加 vip 用户在任意时段都可享受 chatgpt 服务,只需要在 \wechatbot\handlers\group_msg_handler.go 中 的 VipUserList 切片中,
加入具体的 vip 昵称~~

# 实现机制
目前机器人有两种实现方式
* 逆向功能,扒取官网API,通过抓取cookie获取GPT响应信息,`优点:`效果与官网一致,`缺点:`cookie会过期需要不定时更新。
* 基于openai官网提供的API,`优点`:模型以及各种参数可以自由配置,`缺点:`效果达不到官网智能,且API收费,新账号有18美元免费额度。
### 实现机制
基于openai官网提供的API,`优点`:模型以及各种参数可以自由配置,`缺点:`效果达不到官网智能,且API收费,新账号有18美元免费额度。

> 本项目基于第二种方式实现,模型之间具体差异可以参考[官方文档](https://beta.openai.com/docs/models/overview), 详细[参数示例](https://beta.openai.com/examples) 。
> 模型之间具体差异可以参考[官方文档](https://beta.openai.com/docs/models/overview), 详细[参数示例](https://beta.openai.com/examples) 。

# 常见问题
* 如无法登录 login error: write storage.json: bad file descriptor 删除掉storage.json文件重新登录。
* 如无法登录 login error: wechat network error: Get "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage": 301 response missing Location header 一般是微信登录权限问题,先确保PC端能否正常登录。
* 其他无法登录问题,依然尝试删除掉storage.json文件,结束进程(linux一般是kill -9 进程id)之后重启程序,重新扫码登录,(如为docket部署,Supervisord进程管理工具会自动重启程序)。
* ~~机器人无法正常回复,检查ApiKey能否正常使用,控制台日志中有详细错误信息~~ 新版本会机器人会直接输出,因为被问得好烦了。
* linux中二维码无法扫描,缩小命令行功能,让二维码像素尽可能清晰。(无法从代码层面解决)
* 机器人一直答非所问,可能因为上下文累积过多。切换不同问题时,发送指令:启动时配置的`session_clear_token`字段。会清空上下文
### 常见问题
> 如无法登录`login error: write storage.json: bad file descriptor`
删除掉storage.json文件重新登录。

# 使用前提
> 如无法登录`login error: wechat network error: Get "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage": 301 response missing Location header`
一般是微信登录权限问题,先确保PC端能否正常登录。

> * ~~目前只支持在windows上运行因为需要弹窗扫码登录微信,后续会支持linux~~ 已支持
> * 有openai账号,并且创建好api_key,注册事项可以参考[此文章](https://juejin.cn/post/7173447848292253704) 。
> * 应用可以参考这篇文章 [此文章](https://juejin.cn/post/7176813187705077816) 。
> * 微信必须实名认证。
> 其他无法登录问题
尝试删除掉storage.json文件,结束进程(linux一般是kill -9 进程id)之后重启程序,重新扫码登录。
如果为docket部署,Supervisord进程管理工具会自动重启程序。

# 注意事项
> 机器人一直答非所问
可能因为上下文累积过多。切换不同问题时,发送指令:启动时配置的`session_clear_token`字段。会清空上下文

> * 项目仅供娱乐,滥用可能有微信封禁的风险,请勿用于商业用途。
> * 请注意收发敏感信息,本项目不做信息过滤。
### 使用前提
* 有openai账号,并且创建好api_key,注册事项可以参考[此文章](https://juejin.cn/post/7173447848292253704) 。
* 应用可以参考这篇文章 [此文章](https://juejin.cn/post/7176813187705077816) 。
* 微信必须实名认证。

# 使用docker运行
### 注意事项
* 项目仅供娱乐,滥用可能有微信封禁的风险,请勿用于商业用途。
* 请注意收发敏感信息,本项目不做信息过滤。

### docker运行
你可以使用docker快速运行本项目。

`第一种:基于环境变量运行`
#### 1. 基于环境变量运行

```sh
# 运行项目,环境变量参考下方配置说明
Expand All @@ -73,7 +64,7 @@ $ tail -f -n 50 /app/run.log

运行命令中映射的配置文件参考下边的配置文件说明。

`第二种:基于配置文件挂载运行`
#### 2. 基于配置文件挂载运行

```sh
# 复制配置文件,根据自己实际情况,调整配置里的内容
Expand All @@ -89,78 +80,39 @@ $ tail -f -n 50 /app/run.log

其中配置文件参考下边的配置文件说明。

# 快速开始

`第一种:直接下载二进制(适合对编程不了解的同学)`

> 非技术人员请直接下载release中的[压缩包](https://github.com/869413421/wechatbot/releases) ,请根据自己系统以及架构选择合适的压缩包,下载之后直接解压运行。

下载之后,在本地解压,即可看到可执行程序,与配置文件:

```
# windows
1.下载压缩包解压
2.复制文件中config.dev.json更改为config.json
3.将config.json中的api_key替换为自己的
4.双击exe,扫码登录

# linux
$ tar xf wechatbot-v0.0.2-darwin-arm64.tar.gz
$ cd wechatbot-v0.0.2-darwin-arm64
$ cp config.dev.json # 根据情况调整配置文件内容
$ ./wechatbot # 直接运行

# 如果要守护在后台运行
$ nohup ./wechatbot &> run.log &
$ tail -f run.log
```

`第二种:基于源码运行(适合了解go语言编程的同学)`
### 源码运行
适合了解go语言编程的同学

````
# 获取项目
$ git clone https://github.com/869413421/wechatbot.git
$ git clone https://github.com/ZYallers/chatgpt_wechat_robot.git

# 进入项目目录
$ cd wechatbot
$ cd chatgpt_wechat_robot

# 复制配置文件
$ copy config.dev.json config.json
$ cp config.dev.json config.json

# 启动项目
$ go run main.go
````

# 配置文件说明
### 配置说明

````
```json
{
"api_key": "your api key",
"auto_pass": true,
"session_timeout": 60,
"max_tokens": 1024,
"model": "text-davinci-003",
"temperature": 1,
"reply_prefix": "来自机器人回复:",
"session_clear_token": "清空会话"
"api_key": "your api key", # openai账号里设置的api_key
"auto_pass": true, # 是否自动通过好友添加
"session_timeout": 60, # 会话超时时间,默认60秒,单位秒,在会话时间内所有发送给机器人的信息会作为上下文
"max_tokens": 1024, # GPT响应字符数,最大2048,默认值512。会影响接口响应速度,字符越大响应越慢
"model": "text-davinci-003", # GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
"temperature": 1, # GPT热度,0到1,默认0.9,数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
"reply_prefix": "来自机器人回复:", # 私聊回复前缀
"session_clear_token": "清空会话" # 会话清空口令,默认`下一个问题`
}
```

api_key:openai api_key
auto_pass:是否自动通过好友添加
session_timeout:会话超时时间,默认60秒,单位秒,在会话时间内所有发送给机器人的信息会作为上下文。
max_tokens: GPT响应字符数,最大2048,默认值512。max_tokens会影响接口响应速度,字符越大响应越慢。
model: GPT选用模型,默认text-davinci-003,具体选项参考官网训练场
temperature: GPT热度,0到1,默认0.9。数字越大创造力越强,但更偏离训练事实,越低越接近训练事实
reply_prefix: 私聊回复前缀
session_clear_token: 会话清空口令,默认`下一个问题`
````

# 使用示例
### 私聊

<img width="300px" src="https://raw.githubusercontent.com/869413421/study/master/static/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221208153022.jpg"/>

### 群聊@回复

<img width="300px" src="https://raw.githubusercontent.com/869413421/study/master/static/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20221208153015.jpg"/>

### 友情提示
本项目是 fork 他人的项目来进行学习和使用,请勿商用,可以下载下来做自定义的功能。
项目基于[openwechat](https://github.com/eatmoreapple/openwechat) 开发。
24 changes: 17 additions & 7 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package bootstrap

import (
"fmt"
"github.com/eatmoreapple/openwechat"
"github.com/qingconglaixueit/wechatbot/handlers"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
"github.com/eatmoreapple/openwechat"
"os"
)

func Run() {
Expand All @@ -14,23 +15,32 @@ func Run() {
// 注册消息处理函数
handler, err := handlers.NewHandler()
if err != nil {
logger.Danger("register error: %v", err)
logger.Danger(fmt.Sprintf("handlers.NewHandler error: %v", err))
return
}
bot.MessageHandler = handler

// 注册登陆二维码回调
bot.UUIDCallback = handlers.QrCodeCallBack
bot.UUIDCallback = openwechat.PrintlnQrcodeUrl

// 创建热存储容器对象
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")

// 执行热登录
err = bot.HotLogin(reloadStorage, true)
err = bot.HotLogin(reloadStorage)
if err != nil {
logger.Warning(fmt.Sprintf("login error: %v ", err))
return
if err := os.Remove("storage.json"); err != nil {
logger.Warning(fmt.Sprintf("os.Remove storage.json error: %v", err))
return
}
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
err = bot.HotLogin(reloadStorage)
if err != nil {
logger.Warning(fmt.Sprintf("bot.HotLogin error: %v", err))
return
}
}

// 阻塞主goroutine, 直到发生异常或者用户主动退出
bot.Block()
_ = bot.Block()
}
18 changes: 9 additions & 9 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package config
import (
"encoding/json"
"fmt"
"github.com/qingconglaixueit/wechatbot/pkg/logger"
"log"
"os"
"strconv"
"sync"
"time"

"github.com/qingconglaixueit/wechatbot/pkg/logger"
)

// Configuration 项目配置
Expand Down Expand Up @@ -44,22 +44,22 @@ func LoadConfig() *Configuration {
MaxTokens: 512,
Model: "text-davinci-003",
Temperature: 0.9,
SessionClearToken: "下一个问题",
SessionClearToken: "下个问题",
}

// 判断配置文件是否存在,存在直接JSON读取
_, err := os.Stat("config.json")
if err == nil {
f, err := os.Open("config.json")
if err != nil {
log.Fatalf("open config err: %v", err)
logger.Danger(fmt.Sprintf("open config error: %v", err))
return
}
defer f.Close()
encoder := json.NewDecoder(f)
err = encoder.Decode(config)
if err != nil {
log.Fatalf("decode config err: %v", err)
logger.Danger(fmt.Sprintf("decode config error: %v", err))
return
}
}
Expand All @@ -81,7 +81,7 @@ func LoadConfig() *Configuration {
if SessionTimeout != "" {
duration, err := time.ParseDuration(SessionTimeout)
if err != nil {
logger.Danger(fmt.Sprintf("config session timeout err: %v ,get is %v", err, SessionTimeout))
logger.Danger(fmt.Sprintf("config session timeout error: %v, get is %v", err, SessionTimeout))
return
}
config.SessionTimeout = duration
Expand All @@ -92,15 +92,15 @@ func LoadConfig() *Configuration {
if MaxTokens != "" {
max, err := strconv.Atoi(MaxTokens)
if err != nil {
logger.Danger(fmt.Sprintf("config MaxTokens err: %v ,get is %v", err, MaxTokens))
logger.Danger(fmt.Sprintf("config max tokens error: %v ,get is %v", err, MaxTokens))
return
}
config.MaxTokens = uint(max)
}
if Temperature != "" {
temp, err := strconv.ParseFloat(Temperature, 64)
if err != nil {
logger.Danger(fmt.Sprintf("config Temperature err: %v ,get is %v", err, Temperature))
logger.Danger(fmt.Sprintf("config temperature error: %v, get is %v", err, Temperature))
return
}
config.Temperature = temp
Expand All @@ -114,7 +114,7 @@ func LoadConfig() *Configuration {

})
if config.ApiKey == "" {
logger.Danger("config err: api key required")
logger.Danger("config error: api key required")
}

return config
Expand Down
Loading