提示:本教程针对fluffos v2019驱动的MUD。
前言
现在MUD游戏中的玩家聊天越来越少,很多游戏也都有QQ交流群,为了保持更活跃的交流氛围,打通MUD和QQ群游戏是比较好的。比如这种:

有很多MUD已经实现了QQ消息和MUD消息互通,本教程是针对fluffos v2019驱动的MUD的一种更好的解决方案。
QQ官方是没有提供消息接口的,要实现消息互通,我们需要使用第三方API,本教程使用的是一个非常好用工具:mirai 。这是一款开源的工具,而且提供的有HTTP和websocket接口,我们自己下载后在服务器上配置后,在MUD中通过websocket读取消息到MUD,通过http发送游戏消息到QQ。
QQ 消息服务配置
Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人框架。我们需要通过Mirai HTTP API (console) plugin来在MUD中通信。
安装Mirai Console Loader
iTX Technologies Mirai Console Loader(下简称MCL)采用模块化设计,包含以下几个基础模块:
Script脚本执行模块,用于加载和执行脚本,MCL的主要功能均由脚本实现。脚本执行有各个阶段,详见注释。Config配置文件模块,用于配置的持久化。Downloader下载器模块,用于下载文件,并实时返回进度。Logger日志模块,用于向控制台输出日志。
一键安装
iTXTech MCL Installer 能在所有操作系统上一键安装 iTXTech MCL。
手动安装
- 安装 Java 运行时(版本必须 >= 11)
- 从 Releases 下载最新版本的
MCL - 解压到某处
- 在命令行中启动
MCLWindows: 运行mcl.cmdlinux和mac: 运行./mcl
安装mirai-api-http
注意:mirai-api-http分1.x版本和2.x版本,这里分别做说明。
使用 Mirai Console Loader 安装mirai-api-http
MCL支持自动更新插件,支持设置插件更新频道等功能
./mcl --update-package net.mamoe:mirai-api-http --channel stable --type plugin
目前此方式只能安装1.x版本,暂不推荐使用这种方式。
手动安装mirai-api-http
- 运行 Mirai Console 生成plugins文件夹
- 从 Releases 下载
jar并将其放入plugins文件夹中
启动QQ API服务
- 编辑
config/MiraiApiHttp/setting.yml配置文件 (没有则自行创建) - 需要注意的是mirai-api-http的1.x版本和2.x版本配置不同,本教程只提供2.x版
2.x版本setting.yml模板
## 配置文件中的值,全为默认值
## 启用的 adapter, 内置有 http, ws, reverse-ws, webhook
adapters:
- http
- ws
## 是否开启认证流程, 若为 true 则建立连接时需要验证 verifyKey
## 建议公网连接时开启
enableVerify: true
verifyKey: 1234567890
## 开启一些调式信息
debug: false
## 是否开启单 session 模式, 若为 true,则自动创建 session 绑定 console 中登录的 bot
## 开启后,接口中任何 sessionKey 不需要传递参数
## 若 console 中有多个 bot 登录,则行为未定义
## 确保 console 中只有一个 bot 登陆时启用
singleMode: false
## 历史消息的缓存大小
## 同时,也是 http adapter 的消息队列容量
cacheSize: 4096
## adapter 的单独配置,键名与 adapters 项配置相同
adapterSettings:
## 详情看 http adapter 使用说明 配置
http:
host: localhost
port: 8080
cors: [*]
## 详情看 websocket adapter 使用说明 配置
ws:
host: localhost
port: 8080
reservedSyncId: -1
启动 Mirai Console后,使用login指令登录你的消息同步QQ即可。需要注意的是,现在QQ登录安全验证比较严,解决方法如下:
- 下载MiraiAndroid
- 下载之后安装到你的手机上,并且完成登录;登录方法在主屏幕右上角的自动登录里,登录过程中需要验证请在通知栏内点击通知完成验证
- 登录成功后到左边菜单内找到高级功能,选择你得账号之后导出设备文件(
device.json)发送到电脑,并覆盖电脑版文件,再次login QQ 密码即可成功登陆
获取QQ消息到MUD
先看看API文档
从文档可知除了交易相关功能不支持外,其它所有QQ和群消息都可以支持,我们可以通过API实现所有你想实现的功能。
我们MUD中只需要实现QQ群的消息自动同步到MUD和在MUD中发消息同步到QQ群。群消息同步到MUD这里直接使用API的websocket模式,简单好实现。
这里功能实现主要是使用了socket,如果你对socket编程不熟悉,请先看这里补充知识点:socket 介绍及游戏开发实战
先上示例,2.x版本示例代码:
/** QQ_D QQ群消息转发机器人
*
* 机器人服务端使用mirai开源框架,需要自己配置QQ API服务器
* 服务器配置文档:https://github.com/project-mirai/mirai-api-http
*
*/
#include <ansi.h>
#define STREAM 1
#define EESUCCESS 1
// QQ消息API服务器配置
nosave string host = "mud.ren";
nosave string addr = "118.190.104.241 8006";
nosave string mirai_verifyKey = "QQ7300637-6427";
nosave string mirai_qq = "21791131";
// 游戏消息转发到指定的QQ群
nosave string group = "285533476";
nosave mapping status = ([]);
nosave string session;
private void bind();
private void websocket();
private void http(int fd)
{
socket_write(fd, status[fd]["http"]);
}
private void receive_verify(int fd, mixed result)
{
int n = strsrch(result, "{");
// debug_message(result);
if (n > 0)
{
mixed json;
json = json_decode(trim(result[n..]));
// debug_message(sprintf("%O", json));
session = json["session"];
}
if (stringp(session))
{
socket_close(fd);
debug_message("QQ_D 开始认证!");
bind();
}
}
private void receive_bind(int fd, mixed result)
{
int n = strsrch(result, "{");
// debug_message(result);
if (n > 0)
{
mixed json;
json = json_decode(trim(result[n..]));
// debug_message(sprintf("%O", json));
if (!json["code"])
{
socket_close(fd);
debug_message("QQ_D 认证完成!");
websocket();
}
}
}
private void receive_msg(int fd, mixed result)
{
// debug_message(sprintf("QQ_D receive_msg(fd = %d, result = %O)", fd, result));
socket_close(fd);
}
private void receive_data(int fd, mixed result)
{
string res;
int n = strsrch(result, "{");
// debug_message("n = " + n + "\n" + result);
if (n == 4 && strsrch(result, "}}}") > 0)
{
mixed json;
mapping sender, messageChain;
string type;
res = trim(result[n..]);
json = json_decode(res);
debug_message(sprintf("%O", json));
json = json["data"];
sender = json["sender"];
type = json["type"];
// 这里只做最傻瓜的处理
messageChain = json["messageChain"][1];
if (type == "GroupMessage")
{
string msg ="[其它类型消息]";
if (messageChain["type"] == "Plain")
{
msg = messageChain["text"];
}
else if (messageChain["type"] == "Face")
{
msg = "[表情]" + messageChain["name"];
}
message("chat", HIG"【QQ群】"HIC + sender["memberName"] + "@" + sender["group"]["name"] + ":" + msg, users());
}
}
}
private void receive_callback(int fd, mixed result)
{
// debug_message(sprintf("QQ_D receive_callback(fd = %d, result = %O)", fd, result));
}
private void socket_shutdown(int fd)
{
// debug_message(sprintf("QQ_D socket_shutdown(fd = %d)", fd));
socket_close(fd);
}
/* 游戏消息转发QQ群调用此方法 */
void msg(string msg)
{
int fd;
int ret;
string path = "/sendGroupMessage";
// string body = "{\"sessionKey\":\"" + session + "\",\"target\":" + group + ",\"messageChain\":[{\"type\":\"Plain\",\"text\":\"" + msg + "\"}]}";
// 美化格式,不用转义
string body = @RAW
{
"sessionKey": "%^session%^",
"target": %^group%^,
"messageChain": [
{
"type": "Plain",
"text": "%^msg%^"
}
]
}
RAW;
body = terminal_colour(body, ([
"session":session,
"group":group,
"msg":msg,
]));
fd = socket_create(STREAM, "receive_callback", "socket_shutdown");
status[fd] = ([]);
status[fd]["http"] = "POST " + path + " HTTP/1.1\nHost: " + host + "\nContent-Type: application/json\nContent-Length: " + sizeof(string_encode(body, "utf-8")) + "\r\n\r\n" + body;
debug_message(status[fd]["http"]);
ret = socket_connect(fd, addr, "receive_msg", "http");
if (ret != EESUCCESS)
{
debug_message("消息服务器连接失败。\n");
socket_close(fd);
}
}
// 连接websocket
private void websocket()
{
int fd;
int ret;
string path = "/all?verifyKey=" + mirai_verifyKey + "&sessionKey=" + session;
fd = socket_create(STREAM, "receive_callback", "socket_shutdown");
status[fd] = ([]);
status[fd]["http"] = "GET " + path + " HTTP/1.1\nHost: " + host + "\nUpgrade: websocket\nConnection: Upgrade\r\n\r\n";
ret = socket_connect(fd, addr, "receive_data", "http");
if (ret != EESUCCESS)
{
debug_message("WebSocket服务器连接失败。\n");
socket_close(fd);
}
}
// 绑定session到QQ
private void bind()
{
int fd;
int ret;
string path = "/bind";
string body = "{\"sessionKey\":\"" + session + "\",\"qq\":\"" + mirai_qq + "\"}";
fd = socket_create(STREAM, "receive_callback", "socket_shutdown");
status[fd] = ([]);
status[fd]["http"] = "POST " + path + " HTTP/1.1\nHost: " + host + "\nContent-Type: application/json\nContent-Length: " + strlen(body) + "\r\n\r\n" + body;
// debug_message(status[fd]["http"]);
ret = socket_connect(fd, addr, "receive_bind", "http");
if (ret != EESUCCESS)
{
debug_message("激活服务器连接失败。\n");
socket_close(fd);
}
}
// 认证获取session
private void verify()
{
int fd;
int ret;
string path = "/verify";
string body = "{\"verifyKey\":\"" + mirai_verifyKey + "\"}";
fd = socket_create(STREAM, "receive_callback", "socket_shutdown");
status[fd] = ([]);
status[fd]["http"] = "POST " + path + " HTTP/1.1\nHost: " + host + "\nContent-Type: application/json\nContent-Length: " + strlen(body) + "\r\n\r\n" + body;
// debug_message(status[fd]["http"]);
ret = socket_connect(fd, addr, "receive_verify", "http");
if (ret != EESUCCESS)
{
debug_message("认证服务器连接失败。\n");
socket_close(fd);
}
}
void create()
{
verify();
}
以上代码实现了自动验证,并通过websocket获取QQ群的消息并同步到MUD。2.x版可以取消验证,并直接开启singleMode,代码会减少很多,不过教程保持和1.x版本一致要求验证。
这里示例的API服务器是我自己配置的,如果你要在你的LIB中使用,需要自己配置自己的服务器,并登录你的QQ,QQ推荐新注册一个机器人消息专用的,可以减少不必要的代码判断。
代码中的private void receive_data(int fd, mixed result)方法负责接收QQ消息并发送到MUD中,这个方法可以根据自己的需求改造。
代码中void msg(string msg)方法负责转发MUD消息到指定的QQ群,如果你要把MUD消息同步到QQ群,在MUD的CHANNEL_D中调用msg(string msg)方法即可。
mudcore框架调用
以上示例代码是完全从零实现所有http通信,代码比较复杂,如果你的项目中集成了mudcore框架,可以用很少的代码实现自己的需求。需要注意的是,框架对代码做了优化,和以上示例并不一至,主要是msg()方法为接受QQ消息,且可根据自己的需求覆盖,而发送消息改为send()方法。
新建一个/adm/daemons/qq_d.c守护进程,代码示例如下:
/** QQ_D QQ群消息转发机器人
*
* 机器人服务端使用mirai开源框架,需要自己配置QQ API服务器
* 服务器配置文档:https://project-mirai.github.io/mirai-api-http/
*
*/
inherit CORE_DIR "system/daemons/http/qq_d";
#include <ansi.h>
protected void msg(mapping data)
{
mapping sender, messageChain;
string type;
if (!data)
{
return;
}
sender = data["sender"];
type = data["type"];
if (type == "GroupMessage")
{
string msg = "";
foreach(messageChain in data["messageChain"])
{
if (messageChain["type"] == "Source")
{
continue;
}
else if (messageChain["type"] == "Plain")
{
msg += messageChain["text"];
}
else if (messageChain["type"] == "Face")
{
msg += "[" + messageChain["name"] + "]";
}
else
{
msg += "[" + messageChain["type"] + " 类型消息]";
}
}
// 发送消息到MUD
message("QQ", HIG "【QQ群】" HIC + sender["memberName"] + "@" + sender["group"]["name"] + ":" + msg + NOR "\n", users());
// 转发消息到其它MUD
if (sender["group"]["id"] == Group)
"/adm/daemons/network/services/gchannel.c"->send_msg("ic", sender["group"]["name"], sender["memberName"], msg);
}
}
void create()
{
if (env("MIRAI_HOST"))
{
// Debug = 1;
verify();
}
}
通过inherit CORE_DIR "system/daemons/http/qq_d";继承框架提供的QQ_D守护进程,在create()方法中调用verify()方法完成认证初始化,需要在/data/.env环境配置文件中增加以下配置:
# QQ群机器人mirai配置(请修改为自己的配置)
MIRAI_HOST : http://mud.ren:8006
MIRAI_KEY : QQ7300637-****
MIRAI_QQ : 21791131
MIRAI_GROUP : 9783836
通过重写protected void msg(mapping data)自己处理收到的QQ消息。
通过调用/adm/daemons/qq_d->send("消息")发送消息到QQ群,send()方法可以接受第2个参数发消息到非.env配置的群,函数原型为varargs void send(string msg, int qun)。
答记者问
- 不用域名,只用IP怎么配置?
如果QQAPI服务和MUD是在同一台服务器上,直接把配置文件和代码中的host配置为
127.0.0.1,代码中的addr改为127.0.0.1 端口