前言

企业内部有较多系统支撑着公司的核心业务流程。通过安卓手机剪贴板监听便捷控制钉钉自定义机器人,可以将这些系统事件同步到钉钉的聊天群。而基于auto.js编写的apk,为轻量级应用,工程量小,应用价值大。

安卓实时监听剪贴板发送消息至钉钉群

1.编译工具:auto.js,vscode

    Android上支持Node.js的JavaScript自动化和编程软件。本次文章将基于auto.js编译环境,来实现主体业务逻辑和ui的部署。利用vscode作为桥接平台,进行代码编辑和本地调试。但将会移除自动化的无障碍的功能,给用户带来更好的体验。

2.实时数据转换:钉钉机器人API-Webhook

    具体的说,webhook 是应用给其它应用提供实时信息的一种方式。信息一产生,Webhook在数据产生时立即发送数据和把它发送给已经注册的应用这就意味着你能实时得到数据
钉钉自定义机器人对接开放文档:
https://open.dingtalk.com/document/robots/custom-robot-access

3.主体业务逻辑部署
1)通过auto.js实现后台剪贴板监听

    ①在Android 10(Q)之前的Android版本中,每个应用程序都可以读取剪贴板的内容。无需授予任何应用程序任何运行时权限即可完成此操作。而在Android 10(Q)之后的Android版本, 引入了权限“ READ_CLIPBOARD_IN_BACKGROUND”,也就是说AndroidQ增加了对剪贴板的访问控制,除非应用是默认输入法编辑器(IME)或具有焦点的应用程序,否则无法获取剪贴板内容。将后台剪贴板访问仅限于OEM应用程序。即针对剪贴板监听功能我们需要创建一个延时线程,进行循环监听,并定义虚拟的悬浮窗,给予他请求的焦点。

function clip_listener() {
    importClass(android.os.Build)
    var version = Build.VERSION.RELEASE
    log(version)
    if(version<10){                //安卓10以下版本【推荐,不存在焦点抢占的情况】
        while (true) {
            sleep(100)   
            getClipstr()
        }
    }else{                        //安卓10以上版本
        while (true) {
            sleep(4000)
            var w = floaty.window(<text />);
            ui.run(function () {
                w.requestFocus();
                setTimeout(() => {
                    getClipstr()
                    w.close();
                }, 1200);
            });
        }
    }
}

②由于单纯使用auto.js开发文档的getCilp()方法,来获取剪贴板内容,无法做到重复消息的前一次后一次的剪贴比对,所以在此采用安卓原生开发文档的ClipboardManager,来进行获取剪贴板消息和 getPrimaryClip() 实时的时间戳。
【ClipboardManager相关方法详情见链接】
https://developer.android.google.cn/reference/android/content/ClipboardManager?hl=zh-cn

importClass(android.content.ClipData.Item);
var clip_Timestamp = null
function getClipstr() {
      var clipborad = context.getSystemService(context.CLIPBOARD_SERVICE);
      var clip = clipborad.getPrimaryClip()
      try{
		      //时间戳
		      if (clip_Timestamp != clip.getDescription().getTimestamp()) {
		          if (clip_Timestamp == null) {
		              //首次存储时间戳,下次进行比对
		              clip_Timestamp = clip.getDescription().getTimestamp()
		          } else {
		              var item = clip.getItemAt(0);
		              clip_Timestamp = clip.getDescription().getTimestamp()
		              item = clip.getItemAt(0)
		              log(item.getText())     //获取剪贴板内容
		              //进行发送
		              send_msg(item.getText())
		          }
		       }
	   }catch(e){}
}
2)通过钉钉Webhook-API进行实时数据转发

①自定义机器人对接时,需要获取Webhook地址,以及确认安全设置,才被允许对接。此处采用加签的方式进行安全设置验证。
   请求频率限制:每个机器人每分钟最多发送20条消息到群里,如果超过20条,会限流10分钟。
   把timestamp+“\n”+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名sign(需要使用UTF-8字符集)。
请求拼接地址:https://oapi.dingtalk.com/robot/send?access_token=XXXXXX&timestamp=XXX&sign=XXX
   由于钉钉开放文档示例代码中只存在签名计算示例代码(Java / Python),所以需要自行写一份适配auto.js编译的js代码:

##导入java类
importClass(javax.crypto.Mac)
importClass(javax.crypto.spec.SecretKeySpec)
function  send_msg(send_content){
	var timestamp = new Date().getTime()
	var secret_str = "mysecret"
	var secret = new java.lang.String(secret_str)   //转java字符串对象,以便于调用.getBytes()
	var stringToSign = new java.lang.String(timestamp + "\n" + secret_str)
	var mac = Mac.getInstance("HmacSHA256");        //HmacSHA256算法计算签名
	mac.init(new SecretKeySpec(secret.getBytes("utf-8"), "HmacSHA256")); //mac加密初始化
	signData = mac.doFinal(stringToSign.getBytes("UTF-8"));           
	var sign = base64Encode(signData)   
	log(sign)    //得出加签
	let access_token= "access_token";
	let url = "https://oapi.dingtalk.com/robot/send?access_token=" + access_token+ "&timestamp=" + timestamp + "&sign=" + sign
	var temp = http.postJson(url, { msgtype: "text", text: { content: send_content } });
	log(temp.body.string());
}

function base64Encode(r) {
    var r = android.util.Base64.encodeToString(r, 0)
    return r
};

安全设置配置

【钉钉自定义机器人对接Webhook-API详情见链接】
https://open.dingtalk.com/document/robots/customize-robot-security-settings

3)主运行函数以及UI代码

①主运行代码,通过ui.xxx监听响应以及进行控件的重定义

ui.listener.on("click", () => {
    plusOne()
    setInterval(() => { }, 1000);
});

function plusOne() {
    // 获取文本
    let text = ui.listener.text()
    log(text)
    // 解析为数字
    if (text == "开启监听") {
        // 设置文本
        toastLog("已经为您打开监听")
        setTimeout(function () {
            ui.listener.setText('关闭监听');
        }, 300);
        threads.start(clip_listener)
        //保持脚本运行
        setInterval(() => { }, 1000);
        // 1秒后继续
    } else {
        // 设置文本
        toastLog("已经为您关闭监听")
        setTimeout(function () {
            ui.listener.setText('开启监听');
        }, 300);
        threads.shutDownAll()
        // 1秒后继续
    }

}

②ui代码:ui代码运用了安卓原生相关GradientDrawable方法,进行对控件视图的二次渲染,在此不过多解释。
【GradientDrawable相关方法详情见链接】
https://developer.android.google.cn/reference/android/graphics/drawable/GradientDrawable?hl=zh-cn

"ui";
importClass(android.graphics.Color);
importClass(android.graphics.drawable.GradientDrawable);
importClass(android.graphics.Rect);
ui.layout(
    <vertical id="sss" h="*">
        <frame bg="#404A56" h="*">
            <vertical h="*" align="center" margin="0 50" gravity="center">
                <card w="*" margin="10 5" h="150" cardCornerRadius="6dp" cardElevation="1dp" gravity="center_vertical" clickable="true">
                    <vertical>
                        <text text="钉钉剪贴转发" gravity="center" textColor="black" textStyle="bold" size="16" />
                        <text text="1.0.1" gravity="center" textColor="black" textStyle="bold" size="14" />
                        <text textSize="13sp" textColor="black" text="                 " />
                        <text textSize="13sp" textColor="black" text="                 " />
                        <vertical w="*" clickable="true" marginLeft="9" marginRight="9">
                            <button id="listener" w="*" layout_gravity="center" bg="#166BC6" text="开启监听" textSize="17dp" textColor="white" />
                        </vertical>
                    </vertical>
                </card>
            </vertical>
        </frame>
    </vertical>
);
ui.statusBarColor("#404A56");
let view;
view = ui.listener;
setBackgroundRoundGradientDottedRectanglebt(view);
function setBackgroundRoundGradientDottedRectanglebt(view) {
    gradientDrawable = new GradientDrawable();
    gradientDrawable.setShape(GradientDrawable.LINEAR_GRADIENT);
    gradientDrawable.setColor(colors.parseColor("#3369C1"));
    view.setBackground(gradientDrawable);
    gradientDrawable.setCornerRadius(14);
}
4.成果展示

本次测试机型:realmeX
Andriod版本:10
型号:RMX1901
最后:保证app能够流畅的运行,忽略电池优化,白名单,以及任务栏锁定建议都开启。

基于auto.js实现的安卓剪贴板监听并控制钉钉机器人演示视频

第一次发文章,哈哈哈哈,见谅各位,有什么问题大家可以一起探讨探讨~~
在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐