mamoe / mirai

高效率 QQ 机器人支持库
https://mirai.mamoe.net
GNU Affero General Public License v3.0
14.41k stars 2.54k forks source link

希望mirai能支持对json消息的ark签名 #2279

Closed Star-Vk closed 1 year ago

Star-Vk commented 2 years ago

关于mirai发送json不显示的问题&分析

关于发送json消息需要进行ark签名后获取token值得问题

114377ab72876a2f7d28902975b78504


一、问题复现

代码编写采用的是mirai-js以及TypeScript语言,实现代码如下:

import { Bot, Message } from "mirai-js";

(async ()=>{

    //创建bot实例
    let bot = new Bot();

    //与mah建立连接
    await bot.open({
        baseUrl:"http://localhost:8080",
        verifyKey:"Mykey",
        qq:3054689042
    })

    //要发送的json文本
    let json = `{"app":"com.tencent.bot.task.deblock","desc":"","view":"index","ver":"2.0.4.0","prompt":"XKBot6.0 Menu","appID":"","sourceName":"","actionData":"","actionData_A":"","sourceUrl":"","meta":{"detail":{"appID":"","battleDesc":"","botName":"XKbot","cmdList":[{"cmd":" ","cmdDesc":"欢迎您的使用!!!","cmdTitle":"QQ"}],"cmdTitle":"","content":"121212","guildID":"","iconLeft":[{"num":"10"}],"iconRight":[],"receiverName":"","subGuildID":"SUBGUILDID#","title":"","titleColor":""}}}`;

    //发送json消息,只能addApp如果换成addJson的话会报错
    await bot.sendMessage({
        group:1169617978,
        message:new Message().addApp(json)
    })

})()

e99bdf0546bef4026a0cd9545f36288e

发送完的卡片消息如下,但是手机端无法显示(不截图了)


二、问题分析

经过多方求助,根据oicq某某大佬提供,发出去的卡片需要带上token值,而这个token值是和整个token文本相呼应的,整个文本改了token值也要跟着变化,否则就会出现只能发不能收的现象,获取token这个一过程叫做ark签名(小栗子框架是这么定义的)

现在已经知晓的方法除了小栗子框架以外,开源的机器人框架可以使用小程序分享来获取签名后的json,由于我不熟悉Java和Kotlin没办法使用mirai的代码去实现一个签名的函数作为参考,因此采用oicq的core来签名上述的json如下:

Result {
  code: 1,
  msg: '签名成功!',
  data: '{"actionData":"","actionData_A":"","app":"com.tencent.bot.task.deblock","appID":"","config":{"ctime":1665848025,"token":"e0e8a49720c42903dc476783aced2d77"},"desc":"","meta":{"detail":{"appID":"","battleDesc":"","botName":"XKbot","cmdList":[{"cmd":" ","cmdDesc":"欢迎您的使用!!!","cmdTitle":"QQ"}],"cmdTitle":"","content":"121212","guildID":"","iconLeft":[{"num":"10"}],"iconRight":[],"receiverName":"","subGuildID":"SUBGUILDID#","title":"","titleColor":""}},"prompt":"XKBot6.0 Menu","sourceName":"","sourceUrl":"","ver":"2.0.4.0","view":"index"}'
}

可以看到签名后已经拿到了对应的token值“e0e8a49720c42903dc476783aced2d77”,在使用mirai发送试试

fd7f7cb81e833cb892b72bc166b3e749 915976d06b417c13ebe8993fbb338597

发送成功!!!!


三、总结与期盼

根据大佬提供的思路,只要支持音乐分享的框架,仅仅只需要把原来的type类型改成10就是小程序分享,具体代码如何实现的我不清楚,不是我发现的这一签名方法,希望mirai能支持ark签名的功能,让json消息不再是多余的存在!

Nambers commented 1 year ago

看起来可能好像和 ArkAppMessage, MessageForArkApp , resIDForLongMsg有关系

Star-Vk commented 1 year ago

是的是的,可惜我比较菜,希望mirai能支持,这样就能填补mirai不能发送视频消息的空缺了

Xunop commented 1 year ago

最近我也遇到了这个问题,也在想办法能不能制作token出来,但是能力有限根本不知道怎么计算,不知道你是怎么通过oicq的core去获取token的呢?

Star-Vk commented 1 year ago

最近我也遇到了这个问题,也在想办法能不能制作token出来,但是能力有限根本不知道怎么计算,不知道你是怎么通过oicq的core去获取token的呢?

在第一条帖子中我已经说明了获取token的方法,这个token值并不是oicq进行计算的,我也试图找到他的算法,但最终以失败告终,通过oicq的底层代码可知,原本的json的文本内容,通过了增加了一些新的东西进去之后进行了两次编码(or 加密?)通过发送消息的方式发送回了腾讯服务器,然后本地监听了message事件,等待腾讯服务器返回签名好的json,然后在返回给ark签名的函数结果。关于oicq实现的代码不方便展现,这一功能实现原理在第一条已经提到过了,可以通过原来音乐分享的入口将type改为10通过小程序的方式”申请“签名好的token值,oicq的ark签名方法无法整个移植到mirai来,以为他需要发送消息的协议。

LaoLittle commented 1 year ago

这个是从服务器动态拉取js代码来计算的吧?我之前找的,可能忘了

Star-Vk commented 1 year ago

这个是从服务器动态拉取js代码来计算的吧?我之前找的,可能忘了

emmmmm,反正token值不是本地计算的,是要把json文本发送回腾讯服务器然后腾讯服务器计算完,返回带有签名的json回来,然后QQ机器人发出去

LaoLittle commented 1 year ago

emmmmm,反正token值不是本地计算的,是要把json文本发送回腾讯服务器然后腾讯服务器计算完,返回带有签名的json回来,然后QQ机器人发出去

新版macqq查询打开的文件就能看到arkapp的js缓存文件

Star-Vk commented 1 year ago

emmmmm,反正token值不是本地计算的,是要把json文本发送回腾讯服务器然后腾讯服务器计算完,返回带有签名的json回来,然后QQ机器人发出去

新版macqq查询打开的文件就能看到arkapp的js缓存文件

这样子嘛?有能找到相关的token的算法嘛?望提供下,谢谢!

LaoLittle commented 1 year ago

这样子嘛?有能找到相关的token的算法嘛?望提供下,谢谢!

我还没仔细看过,这玩意可能是算法,可能是界面,而且是从服务器拉取的

LaoLittle commented 1 year ago

this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

直接贴链接不好么

Star-Vk commented 1 year ago

this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

直接贴链接不好么

这个方法是根据oicq单独写出来,链接只有oicq通过OidbSvc.0xb77_9分享音乐的方法

Star-Vk commented 1 year ago

this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

直接贴链接不好么

好吧

Xunop commented 1 year ago

谢谢!我昨天在好几个地方看见你的id在问这个问题,但是都没人回答,谢谢你的代码!

Star-Vk commented 1 year ago

谢谢!我昨天在好几个地方看见你的id在问这个问题,但是都没人回答,谢谢你的代码!

好的

Nambers commented 1 year ago

我根据这个测了下,不知道为什么都是Time Out :( 以及 oicq 的包格式和 mirai 里的有一些差别,我这里以 oicq 为主改了下 mirai 里的包结构 https://github.com/Nambers/mirai/blob/6e09aab2d7c6dfa298e0dc992bddcd088e4d5fed/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SignArkPacket.kt 测试代码:

val app = """
        {"app":"com.tencent.bot.task.deblock","desc":"","view":"index","ver":"2.0.4.0","prompt":"XKBot6.0 Menu","appID":"","sourceName":"","actionData":"","actionData_A":"","sourceUrl":"","meta":{"detail":{"appID":"1109937557","battleDesc":"","botName":"XKbot","cmdList":[{"cmd":" ","cmdDesc":"欢迎您的使用!!!","cmdTitle":"QQ"}],"cmdTitle":"","content":"121212","guildID":"","iconLeft":[{"num":"10"}],"iconRight":[],"receiverName":"","subGuildID":"SUBGUILDID#","title":"","titleColor":""}}}
    """.trimIndent()
    bot.asQQAndroidBot().network.sendAndExpect(
        SignArkPacket(
            bot.client,
            app
        )
    ).response.printStructure()
Nekoer commented 1 year ago

以上的json只有手机端能显示吗

Nekoer commented 1 year ago

@Nambers 如果你用的最新的miria源代码的话,应该是QQ版本不支持该OidbSvc,可能需要你重新抓

Nambers commented 1 year ago

@Nambers 如果你用的最新的miria源代码的话,应该是QQ版本不支持该OidbSvc,可能需要你重新抓

qs 不过我对着 qq 8.8.95里面看了下,结构体和mirai一样的。我晚点可以试一下能不能抓到这个包

Nambers commented 1 year ago

@Nambers 如果你用的最新的miria源代码的话,应该是QQ版本不支持该OidbSvc,可能需要你重新抓

我看了下,我没抓到 0xb77 这个包, 但是有另外两个可能有关系的包 LightAppSvc.mini_app_share.AdaptShareInfo 和 ~LightAppSvc.mini_app_usr_time.ReportShare~

Nambers commented 1 year ago

@Nambers 如果你用的最新的miria源代码的话,应该是QQ版本不支持该OidbSvc,可能需要你重新抓

我看了下,我没抓到 0xb77 这个包, 但是有另外两个可能有关系的包 LightAppSvc.mini_app_share.AdaptShareInfo 和 ~LightAppSvc.mini_app_usr_time.ReportShare~

我看了下AdaptShareInfo, 我觉得他比较有可能取到ark, 他的发送包和返回包:

@Serializable
internal class StAdaptShareInfoReq(
    @JvmField @ProtoNumber(1) val extInfo: StCommonExt? = null,
    @JvmField @ProtoNumber(2) val appid: String = "",
    @JvmField @ProtoNumber(3) val title: String = "",
    @JvmField @ProtoNumber(4) val desc: String = "",
    @JvmField @ProtoNumber(5) val time: Int = 0,
    @JvmField @ProtoNumber(6) val scene: Int /* enum */ = 0,
    @JvmField @ProtoNumber(7) val templetType: Int /* enum */ = 0,
    @JvmField @ProtoNumber(8) val businessType: Int /* enum */ = 0,
    @JvmField @ProtoNumber(9) val picUrl: String = "",
    @JvmField @ProtoNumber(10) val vidUrl: String = "",
    @JvmField @ProtoNumber(11) val jumpUrl: String = "",
    @JvmField @ProtoNumber(12) val iconUrl: String = "",
    @JvmField @ProtoNumber(13) val verType: Int = 0,
    @JvmField @ProtoNumber(14) val shareType: Int = 0,
    @JvmField @ProtoNumber(15) val versionId: String = "",
    @JvmField @ProtoNumber(16) val withShareTicket: Int = 0,
    @JvmField @ProtoNumber(17) val webURL: String = "",
    @JvmField @ProtoNumber(18) val appidRich: String = "",
    @JvmField @ProtoNumber(19) val template: StTemplateInfo? = null,
    @JvmField @ProtoNumber(20) val rcvOpenId: String = ""
) : ProtoBuf
@Serializable
internal class StAdaptShareInfoRsp(
    @JvmField @ProtoNumber(1) val extInfo: StCommonExt? = null,
    @JvmField @ProtoNumber(2) val jsonData: String = ""
) : ProtoBuf

但是因为我模拟器上的qq不知道为什么打开不了任何小程序了(比如腾讯文档/qq音乐), 唯一抓到的包还是坏的, 然后我手上没有真机来抓 :( 你们谁要是有时间有兴趣可以去看看这个包,他发送部分是不带 token 的,返回部分的 jsonData 是带 token 的. PS: extInfo 是设备信息

ha1c9on commented 1 year ago

结果大佬许可,下面是oicq对ark签名的代码实现

/**
 * 使用须知
 * 1.代码语言为:TypeScript
 * 2.基于的框架为:oicq
 * 3.使用函数ArkSign前,请先执行npm i oicq
 */
import { Client } from "oicq";

/**
 * 通过小程序对json消息进行ark签名 基于oicq的core
 * @param json 要签名的json
 * @param Bot 已经创建好的机器人实例
 * @param core oicq的核心
 * @returns 
 */
 async function ArkSign(json:any,Bot:Client,core:any){
  return new Promise((resolve, reject) => {

        class Result{
            code:number = -1;
            [key:string]:any
        }

      let result = new Result();
      let json_data = null;
      try{
          json_data = JSON.parse(json);
      }catch(err){}

      if(!json_data){
          result.code = -1;
          result.msg = '签名失败,不是有效的json!';
          resolve(result);
          return;
      }
      delete json_data['extra'];

      //style 改成 10表示小程序发送
      let appid = 100951776, style = 10, appname = 'tv.danmaku.bili', appsign = '7194d531cbe7960a22007b9f6bdaa38b';
      let send_type = 0, recv_uin = Bot.uin, recv_guild_id = 0;

      let time = new Date().getTime();

        function random(min:number,max:number){
            const range  = max - min;
            const random = Math.random();
            const result = min + Math.round(random * range);
            return result;
        }
      let msg_seq = BigInt(`${time}${random(100,999)}`);

      //拼凑一个发送包
      let body = {
          1: appid,
          2: 1,
          3: style,
          5: {
              1: 1,
              2: "0.0.0",
              3: appname,
              4: appsign,
          },
          7: {
              15: msg_seq
          },
          10: send_type,
          11: recv_uin,
          18: {
              1: 1109937557,
              2: {
                  14: 'pages',
              },
              3: 'url',
              4: 'text',
              5: 'text',
              6: 'text',
              10: JSON.stringify(json_data),
          }
      };

      //接收到签名好的json消息的处理函数
      let json_handle = function(e:any){
          if(Bot.uin == e.user_id && e?.message[0]?.type == 'json'){
              let json_str = e.message[0].data;
              let json = null;
              let extra = null;
              try{
                  json = JSON.parse(json_str);
                  extra = typeof(json.extra) == 'object' ? json.extra : JSON.parse(json.extra);
              }catch(err){}

              if(extra && extra.msg_seq == msg_seq){
                  Bot.off('message',json_handle);
                  clearTimeout(timer);
                  delete json['extra'];
                  result.code = 1;
                  result.msg = '签名成功!';
                  result.data = JSON.stringify(json);
                  resolve(result);

              }

          }
      }

      let timer = setTimeout(function(){
          Bot.off('message',json_handle);
          result.code = -1;
          result.msg = '签名失败,请稍后再试!';
          resolve(result);
      },3000);

      //监听TX服务器返回签名好的json文本
      Bot.on('message',json_handle);

      //发送数据包
      Bot.sendOidb("OidbSvc.0xb77_9", core.pb.encode(body));
  });
}

export {ArkSign};

最终把消息进行core.pb.encode(body)得封装后传递进sendOidb方法,继续单步跟踪如下

/** dont use it if not clear the usage */
sendOidb(cmd: string, body: Uint8Array, timeout = 5) {
      const sp = cmd //OidbSvc.0x568_22
          .replace("OidbSvc.", "")
          .replace("oidb_", "")
          .split("_")
      const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1])
      body = pb.encode({
          1: type1,
          2: isNaN(type2) ? 1 : type2,
          3: 0,
          4: body,
          6: "android " + this.apk.ver,
      })
      return this.sendUni(cmd, body, timeout)
}

除去其他信息以外,主要是对发送消息就行了两次encode方法

this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

您好 请问如何正确调用此函数 本人不太熟悉nodejs 望回复 谢谢!

Star-Vk commented 1 year ago

结果大佬许可,下面是oicq对ark签名的代码实现

/**
 * 使用须知
 * 1.代码语言为:TypeScript
 * 2.基于的框架为:oicq
 * 3.使用函数ArkSign前,请先执行npm i oicq
 */
import { Client } from "oicq";

/**
 * 通过小程序对json消息进行ark签名 基于oicq的core
 * @param json 要签名的json
 * @param Bot 已经创建好的机器人实例
 * @param core oicq的核心
 * @returns 
 */
 async function ArkSign(json:any,Bot:Client,core:any){
    return new Promise((resolve, reject) => {

        class Result{
            code:number = -1;
            [key:string]:any
        }

        let result = new Result();
        let json_data = null;
        try{
            json_data = JSON.parse(json);
        }catch(err){}

        if(!json_data){
            result.code = -1;
            result.msg = '签名失败,不是有效的json!';
            resolve(result);
            return;
        }
        delete json_data['extra'];

        //style 改成 10表示小程序发送
        let appid = 100951776, style = 10, appname = 'tv.danmaku.bili', appsign = '7194d531cbe7960a22007b9f6bdaa38b';
        let send_type = 0, recv_uin = Bot.uin, recv_guild_id = 0;

        let time = new Date().getTime();

        function random(min:number,max:number){
            const range  = max - min;
            const random = Math.random();
            const result = min + Math.round(random * range);
            return result;
        }
        let msg_seq = BigInt(`${time}${random(100,999)}`);

        //拼凑一个发送包
        let body = {
            1: appid,
            2: 1,
            3: style,
            5: {
                1: 1,
                2: "0.0.0",
                3: appname,
                4: appsign,
            },
            7: {
                15: msg_seq
            },
            10: send_type,
            11: recv_uin,
            18: {
                1: 1109937557,
                2: {
                    14: 'pages',
                },
                3: 'url',
                4: 'text',
                5: 'text',
                6: 'text',
                10: JSON.stringify(json_data),
            }
        };

        //接收到签名好的json消息的处理函数
        let json_handle = function(e:any){
            if(Bot.uin == e.user_id && e?.message[0]?.type == 'json'){
                let json_str = e.message[0].data;
                let json = null;
                let extra = null;
                try{
                    json = JSON.parse(json_str);
                    extra = typeof(json.extra) == 'object' ? json.extra : JSON.parse(json.extra);
                }catch(err){}

                if(extra && extra.msg_seq == msg_seq){
                    Bot.off('message',json_handle);
                    clearTimeout(timer);
                    delete json['extra'];
                    result.code = 1;
                    result.msg = '签名成功!';
                    result.data = JSON.stringify(json);
                    resolve(result);

                }

            }
        }

        let timer = setTimeout(function(){
            Bot.off('message',json_handle);
            result.code = -1;
            result.msg = '签名失败,请稍后再试!';
            resolve(result);
        },3000);

        //监听TX服务器返回签名好的json文本
        Bot.on('message',json_handle);

        //发送数据包
        Bot.sendOidb("OidbSvc.0xb77_9", core.pb.encode(body));
    });
}

export {ArkSign};

最终把消息进行core.pb.encode(body)得封装后传递进sendOidb方法,继续单步跟踪如下

/** dont use it if not clear the usage */
sendOidb(cmd: string, body: Uint8Array, timeout = 5) {
    const sp = cmd //OidbSvc.0x568_22
        .replace("OidbSvc.", "")
        .replace("oidb_", "")
        .split("_")
    const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1])
    body = pb.encode({
        1: type1,
        2: isNaN(type2) ? 1 : type2,
        3: 0,
        4: body,
        6: "android " + this.apk.ver,
    })
    return this.sendUni(cmd, body, timeout)
}

除去其他信息以外,主要是对发送消息就行了两次encode方法 this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

您好 请问如何正确调用此函数 本人不太熟悉nodejs 望回复 谢谢!

我很抱歉,如果你不是oicq的开发者无法使用这个函数,以及有人写出了mirai支持的方法,详情可参考https://github.com/Mrs4s/MiraiGo/pull/307,我的疑惑也就此解开,也提醒各位请勿滥用ark签名去签名违法、违规的标签,json卡片最终会因为ark签名滥用而导致和谐,祝您早日替换成图片替换成卡片,再次关闭本issue

ha1c9on commented 1 year ago

结果大佬许可,下面是oicq对ark签名的代码实现

/**
 * 使用须知
 * 1.代码语言为:TypeScript
 * 2.基于的框架为:oicq
 * 3.使用函数ArkSign前,请先执行npm i oicq
 */
import { Client } from "oicq";

/**
 * 通过小程序对json消息进行ark签名 基于oicq的core
 * @param json 要签名的json
 * @param Bot 已经创建好的机器人实例
 * @param core oicq的核心
 * @returns 
 */
 async function ArkSign(json:any,Bot:Client,core:any){
  return new Promise((resolve, reject) => {

        class Result{
            code:number = -1;
            [key:string]:any
        }

      let result = new Result();
      let json_data = null;
      try{
          json_data = JSON.parse(json);
      }catch(err){}

      if(!json_data){
          result.code = -1;
          result.msg = '签名失败,不是有效的json!';
          resolve(result);
          return;
      }
      delete json_data['extra'];

      //style 改成 10表示小程序发送
      let appid = 100951776, style = 10, appname = 'tv.danmaku.bili', appsign = '7194d531cbe7960a22007b9f6bdaa38b';
      let send_type = 0, recv_uin = Bot.uin, recv_guild_id = 0;

      let time = new Date().getTime();

        function random(min:number,max:number){
            const range  = max - min;
            const random = Math.random();
            const result = min + Math.round(random * range);
            return result;
        }
      let msg_seq = BigInt(`${time}${random(100,999)}`);

      //拼凑一个发送包
      let body = {
          1: appid,
          2: 1,
          3: style,
          5: {
              1: 1,
              2: "0.0.0",
              3: appname,
              4: appsign,
          },
          7: {
              15: msg_seq
          },
          10: send_type,
          11: recv_uin,
          18: {
              1: 1109937557,
              2: {
                  14: 'pages',
              },
              3: 'url',
              4: 'text',
              5: 'text',
              6: 'text',
              10: JSON.stringify(json_data),
          }
      };

      //接收到签名好的json消息的处理函数
      let json_handle = function(e:any){
          if(Bot.uin == e.user_id && e?.message[0]?.type == 'json'){
              let json_str = e.message[0].data;
              let json = null;
              let extra = null;
              try{
                  json = JSON.parse(json_str);
                  extra = typeof(json.extra) == 'object' ? json.extra : JSON.parse(json.extra);
              }catch(err){}

              if(extra && extra.msg_seq == msg_seq){
                  Bot.off('message',json_handle);
                  clearTimeout(timer);
                  delete json['extra'];
                  result.code = 1;
                  result.msg = '签名成功!';
                  result.data = JSON.stringify(json);
                  resolve(result);

              }

          }
      }

      let timer = setTimeout(function(){
          Bot.off('message',json_handle);
          result.code = -1;
          result.msg = '签名失败,请稍后再试!';
          resolve(result);
      },3000);

      //监听TX服务器返回签名好的json文本
      Bot.on('message',json_handle);

      //发送数据包
      Bot.sendOidb("OidbSvc.0xb77_9", core.pb.encode(body));
  });
}

export {ArkSign};

最终把消息进行core.pb.encode(body)得封装后传递进sendOidb方法,继续单步跟踪如下

/** dont use it if not clear the usage */
sendOidb(cmd: string, body: Uint8Array, timeout = 5) {
      const sp = cmd //OidbSvc.0x568_22
          .replace("OidbSvc.", "")
          .replace("oidb_", "")
          .split("_")
      const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1])
      body = pb.encode({
          1: type1,
          2: isNaN(type2) ? 1 : type2,
          3: 0,
          4: body,
          6: "android " + this.apk.ver,
      })
      return this.sendUni(cmd, body, timeout)
}

除去其他信息以外,主要是对发送消息就行了两次encode方法 this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

您好 请问如何正确调用此函数 本人不太熟悉nodejs 望回复 谢谢!

我很抱歉,如果你不是oicq的开发者无法使用这个函数,以及有人写出了mirai支持的方法,详情可参考https://github.com/Mrs4s/MiraiGo/pull/307,我的疑惑也就此解开,也提醒各位请勿滥用ark签名去签名违法、违规的标签,json卡片最终会因为ark签名滥用而导致和谐,祝您早日替换成图片替换成卡片,再次关闭本issue

了解 因为我是Java开发者 所以对于这两种语言不太熟悉 感谢您的回复 我再学习下

Star-Vk commented 1 year ago

结果大佬许可,下面是oicq对ark签名的代码实现

/**
 * 使用须知
 * 1.代码语言为:TypeScript
 * 2.基于的框架为:oicq
 * 3.使用函数ArkSign前,请先执行npm i oicq
 */
import { Client } from "oicq";

/**
 * 通过小程序对json消息进行ark签名 基于oicq的core
 * @param json 要签名的json
 * @param Bot 已经创建好的机器人实例
 * @param core oicq的核心
 * @returns 
 */
 async function ArkSign(json:any,Bot:Client,core:any){
    return new Promise((resolve, reject) => {

        class Result{
            code:number = -1;
            [key:string]:any
        }

        let result = new Result();
        let json_data = null;
        try{
            json_data = JSON.parse(json);
        }catch(err){}

        if(!json_data){
            result.code = -1;
            result.msg = '签名失败,不是有效的json!';
            resolve(result);
            return;
        }
        delete json_data['extra'];

        //style 改成 10表示小程序发送
        let appid = 100951776, style = 10, appname = 'tv.danmaku.bili', appsign = '7194d531cbe7960a22007b9f6bdaa38b';
        let send_type = 0, recv_uin = Bot.uin, recv_guild_id = 0;

        let time = new Date().getTime();

        function random(min:number,max:number){
            const range  = max - min;
            const random = Math.random();
            const result = min + Math.round(random * range);
            return result;
        }
        let msg_seq = BigInt(`${time}${random(100,999)}`);

        //拼凑一个发送包
        let body = {
            1: appid,
            2: 1,
            3: style,
            5: {
                1: 1,
                2: "0.0.0",
                3: appname,
                4: appsign,
            },
            7: {
                15: msg_seq
            },
            10: send_type,
            11: recv_uin,
            18: {
                1: 1109937557,
                2: {
                    14: 'pages',
                },
                3: 'url',
                4: 'text',
                5: 'text',
                6: 'text',
                10: JSON.stringify(json_data),
            }
        };

        //接收到签名好的json消息的处理函数
        let json_handle = function(e:any){
            if(Bot.uin == e.user_id && e?.message[0]?.type == 'json'){
                let json_str = e.message[0].data;
                let json = null;
                let extra = null;
                try{
                    json = JSON.parse(json_str);
                    extra = typeof(json.extra) == 'object' ? json.extra : JSON.parse(json.extra);
                }catch(err){}

                if(extra && extra.msg_seq == msg_seq){
                    Bot.off('message',json_handle);
                    clearTimeout(timer);
                    delete json['extra'];
                    result.code = 1;
                    result.msg = '签名成功!';
                    result.data = JSON.stringify(json);
                    resolve(result);

                }

            }
        }

        let timer = setTimeout(function(){
            Bot.off('message',json_handle);
            result.code = -1;
            result.msg = '签名失败,请稍后再试!';
            resolve(result);
        },3000);

        //监听TX服务器返回签名好的json文本
        Bot.on('message',json_handle);

        //发送数据包
        Bot.sendOidb("OidbSvc.0xb77_9", core.pb.encode(body));
    });
}

export {ArkSign};

最终把消息进行core.pb.encode(body)得封装后传递进sendOidb方法,继续单步跟踪如下

/** dont use it if not clear the usage */
sendOidb(cmd: string, body: Uint8Array, timeout = 5) {
    const sp = cmd //OidbSvc.0x568_22
        .replace("OidbSvc.", "")
        .replace("oidb_", "")
        .split("_")
    const type1 = parseInt(sp[0], 16), type2 = parseInt(sp[1])
    body = pb.encode({
        1: type1,
        2: isNaN(type2) ? 1 : type2,
        3: 0,
        4: body,
        6: "android " + this.apk.ver,
    })
    return this.sendUni(cmd, body, timeout)
}

除去其他信息以外,主要是对发送消息就行了两次encode方法 this.sendUni是oicq发送”数据包“(应该是这么叫)的方法,再往下追踪就是涉及到收发消息的协议了,同时oicq发送普通消息也是通过this.sendUni实现的。

您好 请问如何正确调用此函数 本人不太熟悉nodejs 望回复 谢谢!

我很抱歉,如果你不是oicq的开发者无法使用这个函数,以及有人写出了mirai支持的方法,详情可参考https://github.com/Mrs4s/MiraiGo/pull/307,我的疑惑也就此解开,也提醒各位请勿滥用ark签名去签名违法、违规的标签,json卡片最终会因为ark签名滥用而导致和谐,祝您早日替换成图片替换成卡片,再次关闭本issue

了解 因为我是Java开发者 所以对于这两种语言不太熟悉 感谢您的回复 我再学习下

好的好的