实现MUD游戏和QQ消息互通教程

提示:本教程针对fluffos v2019驱动的MUD。

前言

现在MUD游戏中的玩家聊天越来越少,很多游戏也都有QQ交流群,为了保持更活跃的交流氛围,打通MUD和QQ群游戏是比较好的。比如这种:

file

有很多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

手动安装

  1. 安装 Java 运行时(版本必须 >= 11)
  2. Releases 下载最新版本的MCL
  3. 解压到某处
  4. 在命令行中启动MCL
    • Windows: 运行mcl.cmd
    • linuxmac: 运行./mcl

安装mirai-api-http

注意:目前最新版是2.0,本教程使用的是1.10版。

使用 Mirai Console Loader 安装mirai-api-http

  • MCL 支持自动更新插件,支持设置插件更新频道等功能

./mcl --update-package net.mamoe:mirai-api-http --channel stable --type plugin

手动安装mirai-api-http

  1. 运行 Mirai Console 生成plugins文件夹
  2. Releases 下载jar并将其放入plugins文件夹中

启动QQ API服务

  1. 编辑config/MiraiApiHttp/setting.yml配置文件 (没有则自行创建)
  2. 启动 Mirai Console
  3. 记录日志中出现的authKey

setting.yml模板

## 该配置为全局配置,对所有Session有效

# 可选,默认值为0.0.0.0
host: '0.0.0.0'

# 可选,默认值为8080
port: 8080          

# 可选,默认由插件第一次启动时随机生成,建议手动指定
authKey: 1234567890  

# 可选,缓存大小,默认4096.缓存过小会导致引用回复与撤回消息失败
cacheSize: 4096

# 可选,是否开启websocket,默认关闭,建议通过Session范围的配置设置
enableWebsocket: true

# 可选,配置CORS跨域,默认为*,即允许所有域名
cors: 
  - '*'

## 消息上报
report:
# 功能总开关
  enable: false
  # 群消息上报
  groupMessage:
    report: false
  # 好友消息上报
  friendMessage:
    report: false
  # 临时消息上报
  tempMessage:
    report: false
  # 事件上报
  eventMessage:
    report: false
  # 上报URL
  destinations: []
  # 上报时的额外Header
  extraHeaders: {}

## 心跳
heartbeat:
  # 功能总开关
  enable: false
  # 启动延迟
  delay: 1000
  # 心跳间隔
  period: 15000
  # 心跳上报URL
  destinations: []
  # 上报时的额外信息
  extraBody: {}
  # 上报时的额外头
  extraHeaders: {}

启动 Mirai Console后,使用login指令登录你的消息同步QQ即可。需要注意的是,现在QQ登录安全验证比较严,解决方法如下:

  1. 下载MiraiAndroid
  2. 下载之后安装到你的手机上,并且完成登录;登录方法在主屏幕右上角的自动登录里,登录过程中需要验证请在通知栏内点击通知完成验证
  3. 登录成功后到左边菜单内找到高级功能,选择你得账号之后导出设备文件(device.json)发送到电脑,并覆盖电脑版文件,再次login QQ 密码即可成功登陆

获取QQ消息到MUD

先看看API文档:https://project-mirai.github.io/mirai-api-http/API.html

从文档可知除了交易相关功能不支持外,其它所有QQ和群消息都可以支持,我们可以通过API实现所有你想实现的功能。

我们MUD中只需要实现QQ群的消息自动同步到MUD和在MUD中发消息同步到QQ群。群消息同步到MUD这里直接使用API的websocket模式,简单好实现。

这里功能实现主要是使用了socket,如果你对socket编程不熟悉,请先看这里补充知识点:socket 介绍及游戏开发实战

先上示例代码:

/** QQ_D QQ群消息转发机器人
 *
 * 机器人服务端使用mirai开源框架,需要自己配置QQ API服务器
 * 服务器配置文档:https://project-mirai.github.io/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_authKey = "QQ7300637-6427";
nosave string mirai_qq = "21791131";
// 游戏消息转发到指定的QQ群
nosave string group = "289906259";
nosave mapping status = ([]);
nosave string session;

private void verify();
private void websocket();

private void http(int fd)
{
    socket_write(fd, status[fd]["http"]);
}

private void receive_auth(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 开始认证!");
        verify();
    }
}

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));
        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(result);
    if (n == 4 && strsrch(result, "}}}") > 0) // 判断返回的是不是完整的json
    {
        mixed json;
        mapping sender, messageChain;
        string type;
        res = trim(result[n..]);
        json = json_decode(res);
        debug_message(sprintf("%O", json));
        sender = json["sender"];
        type = json["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("info", 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);
        }
    }
}

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 = "/message?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 verify()
{
    int fd;
    int ret;
    string path = "/verify";
    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_verify", "http");
    if (ret != EESUCCESS)
    {
        debug_message("激活服务器连接失败。\n");
        socket_close(fd);
    }
}
// 获取session
private void auth()
{
    int fd;
    int ret;
    string path = "/auth";
    string body = "{\"authKey\":\"" + mirai_authKey + "\"}";

    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_auth", "http");
    if (ret != EESUCCESS)
    {
        debug_message("认证服务器连接失败。\n");
        socket_close(fd);
    }
}

void create()
{
    auth();
}

以上代码实现了自动验证,并通过websocket获取QQ群的消息并同步到MUD。这里示例的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)方法即可。

京ICP备13031296号-4