抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

好久没写博客了(同时前几篇也太水了),之前在折腾博客基建,这段时间也搞了一堆东西,虽然有记录,但是没时间整理一下然后写出来,刚好最近没有什么

事,本来想实践一下微信小程序和微信公众号的开发,奈何没有成年,申请不了,于是被打回来写qqbot,本着好玩的目的,但是实践的过程中还是遇到了一些困难,由于缺乏基本认识而踩了一堆坑

QQ 协议端搭建

没错,这个东西叫协议端!

目前比较常见的有:

(来自知乎)

我这里采用go-cqhttp,采用其他的基本也相同,最重要的是要基于onebot标准

关于如何搭建可以看文档

首先编写config.hjson文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
go-cqhttp 默认配置文件
*/

{
// QQ号
uin: 0
// QQ密码
password: ""
// 是否启用密码加密
encrypt_password: false
// 加密后的密码, 如未启用密码加密将为空, 请勿随意修改.
password_encrypted: ""
// 是否启用内置数据库
// 启用将会增加10-20MB的内存占用和一定的磁盘空间
// 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
enable_db: true
// 访问密钥, 强烈推荐在公网的服务器设置
access_token: ""
// 重连设置
relogin: {
// 是否启用自动重连
// 如不启用掉线后将不会自动重连
enabled: true
// 重连延迟, 单位秒
relogin_delay: 3
// 最大重连次数, 0为无限制
max_relogin_times: 0
}
// API限速设置
// 该设置为全局生效
// 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
// 目前该限速设置为令牌桶算法, 请参考:
// https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
_rate_limit: {
// 是否启用限速
enabled: false
// 令牌回复频率, 单位秒
frequency: 1
// 令牌桶大小
bucket_size: 1
}
// 是否忽略无效的CQ码
// 如果为假将原样发送
ignore_invalid_cqcode: false
// 是否强制分片发送消息
// 分片发送将会带来更快的速度
// 但是兼容性会有些问题
force_fragmented: false
// 心跳频率, 单位秒
// -1 为关闭心跳
heartbeat_interval: 0
// HTTP设置
http_config: {
// 是否启用正向HTTP服务器
enabled: true
// 服务端监听地址
host: 127.0.0.1
// 服务端监听端口
port: 22333
// 反向HTTP超时时间, 单位秒
// 最小值为5,小于5将会忽略本项设置
timeout: 0
// 反向HTTP POST地址列表
// 格式:
// {
// 地址: secret
// }
post_urls:{}
}
// 正向WS设置
ws_config: {
// 是否启用正向WS服务器
enabled: false
// 正向WS服务器监听地址
host: 127.0.0.1
// 正向WS服务器监听端口
port: 6700
}
// 反向WS设置
ws_reverse_servers: [
// 可以添加多个反向WS推送
{
// 是否启用该推送
enabled: true
// 反向WS Universal 地址
// 注意 设置了此项地址后下面两项将会被忽略
// 留空请使用 ""
reverse_url: ws://127.0.0.1:22334/
// 反向WS API 地址
reverse_api_url: ws://you_websocket_api.server
// 反向WS Event 地址
reverse_event_url: ws://you_websocket_event.server
// 重连间隔 单位毫秒
reverse_reconnect_interval: 3000
}
]
// 上报数据类型
// 可选: string array
post_message_format: string
// 是否使用服务器下发的新地址进行重连
// 注意, 此设置可能导致在海外服务器上连接情况更差
use_sso_address: false
// 是否启用 DEBUG
debug: false
// 日志等级 trace,debug,info,warn,error
log_level: "info"
// WebUi 设置
web_ui: {
// 是否启用 WebUi
enabled: false
// 监听地址
host: 127.0.0.1
// 监听端口
web_ui_port: 9999
// 是否接收来自web的输入
web_input: false
}
}

./go-cqhttp运行后需要进行滑块认证,在浏览器抓取cap_union_new_verify 的返回值,然后把ticket的值输入就行了。

此时协议端搭建完毕

注:记得iptable开放相应端口

后端搭建

一、使用机器人框架

不得不说python的nonebot和nonebot2实在是太方便了,文档也十分详细。

如果你只是想要一个qqbot,推荐使用以上两个框架,大概几个小时就能实现一个bot

如果你想用go来写,也可以用ZeroBot来作为你的框架

二、写后端来调用协议

虽然框架很好用(单指python的库)

但由于觉得好玩,我还是头铁地使用了go来开发

找不到真正合适的go开发框架,于是自己写websocket来对接

go websocket的使用请自行学习,这里简单描述搭建服务的过程,细节可以参考我的代码部分https://github.com/RyaoChengfeng/rinqqbot/blob/main/controller/ws.go

  • http监听ws反向端口
    1
    2
    3
    4
    5
    6
    controller.StartWebsocket()  
    err:= http.ListenAndServe(config.Addr+`:`+config.WsPort,nil)
    if err != nil {
    fmt.Println(err)
    os.Exit(1)
    }
  • 将http升级为ws并启动ws
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    type wsConnection struct {  
    wsSocket *websocket.Conn // 底层websocket
    inChan chan *wsMessage // 读队列
    outChan chan *wsMessage // 写队列

    mutex sync.Mutex // 避免重复关闭管道,加锁处理
    isClosed bool
    closeChan chan byte // 关闭通知
    id int64
    }

    var upgrader = websocket.Upgrader{
    ReadBufferSize: 1024,
    WriteBufferSize: 1024,
    // 允许所有的CORS 跨域请求,正式环境可以关闭
    CheckOrigin: func(r *http.Request) bool {
    return true
    },
    }

    func StartWebsocket() {
    WsConnAll = make(map[int64]*wsConnection)
    http.HandleFunc("/", wsHandler)
    }

    func wsHandler(rsp http.ResponseWriter, req *http.Request) {
    // 应答客户端告知升级连接为websocket
    wsSocket, err := upgrader.Upgrade(rsp, req, nil)
    if err != nil {
    log.Logger.Error("升级为websocket失败", err.Error())
    } maxConnId++

    // 连接数保持一定数量,超过的部分不提供服务
    // 如果要控制连接数可以计算WsConnAll长度 len(WsConnAll) wsConn := &wsConnection{
    wsSocket: wsSocket,
    inChan: make(chan *wsMessage, 1000),
    outChan: make(chan *wsMessage, 1000),
    closeChan: make(chan byte),
    isClosed: false,
    id: maxConnId,
    }

    WsConnAll[maxConnId] = wsConn
    // 处理器,发送定时信息,避免意外关闭
    go processLoop(wsConn)
    // 读协程
    go wsReadLoop(wsConn)
    // 写协程
    go wsWriteLoop(wsConn)

    }

  • 在多线程中处理协议端发来的message,并提取信息进行调用协议端api
    1
    2
    3
    4
    5
    6
    err = json.Unmarshal(msg.data, &msgData)  
    if err != nil {
    log.Logger.Error("json信息解析错误", err.Error())
    }
    //log.Logger.Debug("收到消息:",msgData)
    HandleWsMsg(msgData)
  • HandleWsMsg中进行bot功能实现
    message结构请参考对应的协议文档,以及基于onebot标准的协议的onebot标准,具体参考下方链接

参考

评论