风控解决方案,使用 autoxjs 发送消息,免root

koishi-plugin-autoxjs-sender

npm

使用 autoxjs 发送消息

demo

原理

创建一个 websocket 服务,监听 send 事件,把消息内容通过 websocket 发送给 autoxjs 的 websocket 客户端,autoxjs 调用无障碍 Android Intent + 控件操作发送消息。

目前只支持发送文本消息和图片消息。

要求

  • 一台闲置的安卓手机
    或者
  • 安卓模拟器

使用方法

  • 安装 autoxjs

  • 修改 client.js 脚本的 websocket 地址

client.js
/*
 * @Author: initialencounter
 * @Date: 2023-07-01 22:04:50
 * @LastEditors: initialencounter
 * @LastEditTime: 2023-07-01 22:04:50
 * @FilePath: D:\dev\koishi-hmr\external\autoxjs-server\src\client.js
 * @Description: 请使用 AutoX.js 运行此脚本
 *
 * Copyright (c) 2023 by initialencounter, All Rights Reserved.
 */

// 图片保存的路径
const IMG_PATH = "/sdcard/Pictures/";
const ENDPOINT = "ws://127.0.0.1:32327";

importPackage(Packages["okhttp3"]); // 导入包
const client = new OkHttpClient.Builder().retryOnConnectionFailure(true).build();

let request = new Request.Builder().url(ENDPOINT).build(); // ws 地址,
client.dispatcher().cancelAll();// 清理一次
const heartbeatInterval = 5000; // 心跳间隔时间,单位:毫秒
let heartbeatTimer = null;
let heartbeatSign = 1;
let retryInterval = 10000;
// WebSocket监听器
const myListener = {
    onOpen: function (webSocket, response) {
        console.log("WebSocket连接已打开");
        heartbeatTimer = setInterval(() => {
            webSocket.send('heartbeat'); // 发送心跳包
        }, heartbeatInterval);
    },
    onMessage: function (webSocket, text) {
        if (text == "heartbeat") {
            heartbeatSign++;
            console.log(getTime() + text);
        } else {
            sendMsg(text);
        }
    },
    onClosing: function (webSocket, code, reason) {
        if(reason=="被动关闭"){
            heartbeatSign = 0
        }
        console.log("WebSocket连接即将关闭" + "\ncode:\n" + code + "\nreason:\n" + reason);
    },
    onFailure: function (webSocket, t, response) {
        console.log("WebSocket连接失败" + "\nreason:\n" + reason);
    }
};

// 创建WebSocket连接
let webSocket = client.newWebSocket(request, new WebSocketListener(myListener));

// 重连函数
function reconnect() {
    try {
        sleep(heartbeatInterval);
        console.log("正在尝试重新连接...");
        if (heartbeatTimer) {
            clearInterval(heartbeatTimer);
        }
        heartbeatTimer = null;
        webSocket.cancel(); // 取消当前WebSocket连接
        request = new Request.Builder().url(ENDPOINT).build(); // ws 地址
        client.dispatcher().cancelAll();//清理一次
        webSocket = client.newWebSocket(request, new WebSocketListener(myListener));
    } catch (e) {
        console.log('未知错误' + e);
    }

}
// 定时器,防止主线程退出
let mainproc = setInterval(function () {
    if (heartbeatSign == 0) {
        console.log('跳过');
        clearInterval(mainproc);
    } else {
        const heartbeatTmp = heartbeatSign;
        sleep(retryInterval);
        if (heartbeatTmp == heartbeatSign) {
            console.log('主动重连')
            reconnect();
        }
    }

}, retryInterval);

// 发送消息
function sendMsg(msg) {
    try {
        let { content, guildId, id } = JSON.parse(msg);
        if (guildId == 0) {
            id = id.slice(8,);
        }
        device.wakeUp();
        sleep(200);
        const id1 = content.indexOf('<image url="') + 12;
        if (id1 > 11) {
            sendImage(saveImage(getUrl(id1, content)), id)
        } else {
            // 发送文本
            sendText(id, content);
        }
        back();
    }
    catch (e) {
        console.log("W:\n", e);
    }
}
// 选择联系人并发送消息,然后返回
function selectTarget(qid) {
    id('ik5').text('搜索').findOne(4000).click();
    id('ik5').text('搜索').findOne(3000).setText(qid);
    id('j64').text(`(${qid})`).findOne(2000).parent().click();
    id('dialogRightBtn').text('发送').findOne(2000).click();
    back();
    back();
}
// 发送图片
function sendImage(imgUrl, qid) {
    const intent = app.intent({
        action: "SEND",
        packageName: "com.tencent.mobileqq",
        className: "com.tencent.mobileqq.activity.JumpActivity",
    })
    intent.setType("image/*");
    intent.putExtra(Intent.EXTRA_STREAM, imgUrl);
    app.startActivity(intent);
    selectTarget(qid);
}

// 保存图片
function saveImage(url) {
    console.log('img-url:', url);
    const img = images.load(url);
    const filePath = IMG_PATH + getTime() + ".jpg";
    images.save(img, filePath, "jpg", 100);
    img.recycle();
    console.log("img-path", filePath);
    return filePath;
}

// 获取图片 url
function getUrl(id1, msg) {
    const id2 = msg.indexOf('"/>');
    const url = msg.slice(id1, id2);
    return url;
}

// 获取时间
function getTime() {
    const date = new Date();
    const date_str = date.toISOString();
    return date_str.replace(/:/g, '-').slice(0, 19);
}


// 发送文本
function sendText(qid, msg) {
    console.log(qid, ': ', msg);
    const intent = app.intent({
        action: "SEND",
        packageName: "com.tencent.mobileqq",
        className: "com.tencent.mobileqq.activity.JumpActivity",
    })
    intent.putExtra(Intent.EXTRA_TEXT, msg);
    intent.setType("text/plain");
    app.startActivity(intent);
    selectTarget(qid);
}
  • 启用本插件

  • 为 Autoxjs 开启无障碍,授予 root 权限

  • 运行 client.js

5 个赞

好耶 可惜我不满足要求

1 个赞

大佬能详细说一下怎么操作吗?小白实在看不懂,谢谢啦

1 个赞

晚点录个视频

1 个赞

看来我之前的想法确实没问题,用机器人控制QQ。而不是直接讲QQ变为机器人

1 个赞

视频来了

1 个赞


大佬,我这个回复会卡到这一步,是什么问题吗?怎么解决啊

1 个赞

没有这个联系人,发不了

2 个赞

我是按步骤来的,在群里发消息就会卡到这一步,他这个输入的号码每次都是错的,不管是群聊还是私聊,会多两位

1 个赞

好的,更新一下脚本,要填写两个坐标,即61行和113行的 click(250, 270);,改成你发的这个搜索框的坐标,(一般情况是不需要改的,除非手机屏幕像素相差过大)

1 个赞


02
我的这个脚本61和113没有click,是58行和101这两行的click是吗?
还有这个我用模拟器设置的分辨率,怎么去定位坐标呢?

1 个赞

有两种方法

  1. 设置 → 开发人员选项 → 指针位置
  2. autox悬浮窗 → 指针位置(需要root权限)
1 个赞

autoxjs-sender/src/client.js · initencunter/mykoishi - 码云 - 开源中国 (gitee.com)

1 个赞

更新日志

  • v1.4.0
    • 更新脚本,新增断线自动重连,更改选择联系人的方法,更稳定了,回复速度更快了,不再需要 root 权限了
  • v1.3.0
    • 新增自动备份 koishi.yml package.json
1 个赞

那个,我不知道IP地址怎么填,因为我是手机挂koishi,手机虚拟机安装autox.js

2 个赞