微信插件之护妻宝(二)

1,039 阅读4分钟

前介

花了将近 2 天的时间,总算是把所有功能写完了,说实话,当时认为很简单,应该话费不了多长时间,但是最后发现越写逻辑越多。不过总算是完成了,看看最终的效果吧!

代码使用了,我开发的 Andorid 资源优化插件,非常还用了,大家可以试试 安卓资源瘦身丶混淆丶去重插件

代码仓库

确定目标

  1. 集成图灵机器人
  2. 集成第四范式客服机器人
  3. 增加设置界面
  4. 增加定时向 娇妻 推送消息 早安问候丶天气丶土味情话丶自定义 消息

开始干活

当时定完目标,感觉难点都一一攻破了,没想到开发过程中这点逻辑还挺复杂的,接下来听我一一道来。

集成机器人

集成机器人方面,都是看阅读官方文档即可。
图灵机器人 没啥好说的就是一个简单的 Http 请求,我是使用 Retrofit + OkHttp3 + Kotlin协程 完成 Http 网络请求的(Retrofit 2.6.0 内置支持协程啦,可以和 RxJava 说拜拜了)。

/**
 * 图灵机器人API
 */
object TuLingMsgRetrofit : BaseRtrofit("http://www.tuling123.com") {
    val api = retrofit.create(TuLingMsgApi::class.java)

}

data class TuLingMsg(val key: String, val info: String)


interface TuLingMsgApi {
    @POST("/openapi/api")
    suspend fun getMsg(@Body msg: TuLingMsg): TuLingResult
}

第四范式客服机器人图灵机器人 的实现方式不一样,它提供的方式本质是 WebSocket。但是它设计上存在一个缺陷,就是机器反馈每条消息与我发送的消息没有一个一一对应关系。

举个例子,A 用户和 B用户同时和我对话,我肯定只连接一个 WebSocketA你好 ,同时 B在吗 ,机器人回馈的 2 条结果,没法区分是给 A 还是 B

回头想想,可能 第四范式客服机器人 设计的 1 个连接,只能对应一个客户吧。

最终我也没有找到比较完美的解决办法,只能通过一个队列来管理消息回馈了(或多或少还是存在问题)。

/**
 * 第四范式自动回复机器人
 */
object PDbootManager : BotLibClient.MessageListener, BotLibClient.ConnectionListener {
    var classLoader: ClassLoader? = null
    /**
     * 存放消息的队列
     */
    val msgTask = ConcurrentLinkedQueue<String>()

    override fun onConnectionStateChanged(state: Int) {
        when (state) {
            BotKitClient.ConnectionIdel -> log("连接断开")
            BotKitClient.ConnectionConnecting -> log("正在连接...")
            BotKitClient.ConnectionConnectedRobot, BotKitClient.ConnectionConnectedRobot -> log("连接成功  ${BotLibClient.getInstance().robotName}")  // 显示机器人名字
            else -> log("连接失败")
        }
    }

    override fun onAppendMessage(message: Message) {

        if (message.direction == 1) {
            // 接收到消息
            try {
                // 去除回馈id
                val wxid = msgTask.poll()
                // 判断逻辑
                if (wxid?.isNotBlank() == true && classLoader != null && message.contentType == Message.ContentTypeText) {
                    val contentText = message.content as MessageContentText
                    Core.receiverMsg(classLoader!!, contentText.text, wxid)
                }
            } catch (e: Exception) {
            }


        }
    }

    override fun onReceivedSuggestion(suggestions: ArrayList<MenuItem>?) {

    }

    fun initConfig(context: Context) {
        this.classLoader = context.classLoader

        if (SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY?.isNotBlank() == true) {
            log("初始化${SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY}")
            BotKitClient.getInstance().init(context, SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY);

            BotKitClient.getInstance().connect()
        }
    }

    init {
        BotKitClient.getInstance().setMessageListener(this)
        BotKitClient.getInstance().setConnectionListener(this)
    }


    fun sendMsg(msg: String, talker: String) {
        // 加入回馈队列        
        msgTask.add(talker)

        BotKitClient.getInstance().askQuestion(msg)
    }    
}

来看看 第四范式客服机器人 效果!

哈哈,这回复有点作死啊!!也不能怪机器人,毕竟 第四范式客服机器人 宣传的是 客服。大家可以首选图灵机器人。

设置界面

说到设置界面,这还不简单吗?不就是一个布局的事情吗?其实不然,我们要记住我们开发的是一款 Xposed 插件,根据 Xposed 的原理,资源是无法注入的(当然其实技术方案还是有的,可以参考换肤的原理)。

其实动态加载资源也是可以的。但是为了这一个布局,增加一个资源文件,你还需要考虑资源文件怎么获取。其实这个问题一直困扰我,主要原因是我懒。

最终我的方案,就是自定义View,所有的布局都靠代码构建(想一想都恶心)。由于我使用的是 Kotlin 开发,存在一种 DSL 的写法,再加上 Kotlin 提供的 Anko 库,可以很方便的通过代码编写一个布局(有兴趣可以去学习下此库)。

定时任务

难点都攻破了,为何不在做一些有趣的事情呢?回想下,你追求的女孩每天定时定点,能收到一条天气状况和温馨提醒丶每天一条 鸡汤话丶每天一条 情话

定时任务,最先想到的就是 AlarmManager,它是系统服务提供的一种方案 (虽然存在一些误差)。但是最后发现这个 API 在分身大师X版本既然无效(分身BUG),真是处处碰坎坷啊。

最后实在没办了,只能开开启个线程循环判断了。

/**
 * 定时检查任务 15秒一次
 */
object CoreAlarmManager {
    var handler: Handler
    var classLoader: ClassLoader? = null

    init {
        val handlerThread = HandlerThread("TASK_WORK")
        handlerThread.start()
        handler = Handler(handlerThread.looper) {
            if (it.what == 1) {
                classLoader?.let { loader ->
                    TaskManager.invokeTask(loader)
                }

                postMessage()

            }
            true
        }
    }

    private fun postMessage(delayMillis:Long=15*1000L){
        handler.removeMessages(1)

        handler.sendMessageDelayed(Message.obtain().apply {
            what = 1
        },delayMillis)
    }

    /**
     * 判断定时任务
     *
     * 15秒扫描一次
     */
    @Synchronized
    fun init(context: Context) {
        this.classLoader = context.classLoader

        postMessage(0)
    }
}

当然还有 每日一句土味情话自定义情话丶 的设置和 今日天气 的流程一样,大家可以自行尝试。

补充

由于大部分功能我已将完成了,所以我已经将微信版本做了兼容,已经支持微信 7.0.9 版本。

我是用我公司的 360分身大师X版 做的免 Root 使用 Xposed(微信版本是7.0.7哦)。

代码仓库