基于网页版微信实现的微信SDK(Kotlin版)

跟朋友合作开发一个小项目,需要实现一个微信个人号机器人。他的首选语言是Java,基于一个码农的懒惰,我就选择了Kotlin作为我的开发语言(我们各自负责不同模块)。本身我对实现一个微信个人号SDK也是蛮有兴趣的,就没有使用第三方的轮子,着手使用Kotlin重新实现了一个网页版微信的SDK。

网页版微信消息处理流程

首先是分析微信网页版的消息处理流程:

  • 获取UUID
  • 下载二维码,等待扫码
  • 获取登录认证信息
  • 初始化微信应用
  • 同步消息和获取对话列表
  • 发送消息

二维码扫描问题

因为程序要放到服务器环境上面运行,而服务器上面并没有图形界面。二维码的处理就较为繁琐,为此参考kdepp/tty_qr.py 实现了Kotlin版本的二维码Terminal输出工具。

const val BLOCK = "MM"  
const val BLACK = "  "

/**
 * 二维码输出工具
 * */
class QRPrinter(private val img: BufferedImage) {  
    constructor(input: InputStream) : this(ImageIO.read(input))

    private fun qrString(size: Int = 37, padding: Int = 3): String {
        val builder = StringBuffer()
        val times = img.width / (size + padding * 2)
        (0..50).forEach {
            builder.append(BLACK)
        }
        builder.append("\n")
        builder.append("\n")
        val sp = padding + 1
        for (y in 0..size) {
            builder.append(BLOCK)
            for (x in 0..size) {
                val v = img.getRGB(((x + sp) * times), (y + sp) * times)
                val rgb = RGB(v)
                builder.append(if (rgb.r > 127) BLOCK else BLACK)
            }
            builder.append("\n")
        }
        (0..size + 2).forEach {
            builder.append(BLACK)
        }
        builder.append("\n")
        return builder.toString()
    }

    fun print(writer: PrintStream, size: Int = 37, padding: Int = 3) {
        writer.println(qrString(size = size, padding = padding))
        writer.flush()
    }

    private class RGB(value: Int) {
        val r = (value shr 16) and 0xff
        val g = (value shr 8) and 0xff
        val b = value and 0xff
    }
}

二维码输出效果如下: 控制台输出二维码效果图

网页版微信SDK

重点问题解决之后,封装了网页版微信的SDKirealing/wxbot。通过简单的操作即可实现有趣的功能:)(也许是有趣吧)

示例:消息发送

import cn.fuser.tool.net.QRPrinter  
import java.io.File

fun main(args: Array<String>) {  
    // 登录校验,参数为二维码处理函数,示例为输出二维码到标准错误流
    val auth = AuthValidator({ QRPrinter(it).print(System.err) }).validate()
    // 初始化 WXBot 对象
    val bot = WXBot(auth)
    // 心跳,登录状态维持,接收消息等
    bot.heartbeat()
    // @@ 开头的userName 系微信群
    // 
    val members = bot.contact().list.filter { it.userName.startsWith("@@") }
    members.forEach({
        // 发送文本消息    
        bot.sendText(it.userName, it.userName)
        // 发送图片消息
        bot.sendImg(it.userName, File("****"))
    })
}

示例: 转发群消息给某用户

package cn.fuser.vx.wxbot

import cn.fuser.tool.net.Log  
import cn.fuser.tool.net.QRPrinter

fun main(args: Array<String>) {  
    val auth = AuthValidator({ QRPrinter(it).print(System.err) }).validate()
    val bot = WXBot(auth)
    bot.heartbeat()
    // 根据昵称查找用户
    val members = bot.contact().list.find { it.nickName.startsWith("***") }
    bot.registerHandler(SimpleMessageHandler {
        Log.info("message[%d] %s: %s", it.msgType, it.fromUserName, it.content)
        if (it.msgType != 1) return@SimpleMessageHandler
        // userName以@@开头则为微信群消息
        val content = if (it.fromUserName.startsWith("@@")) it.content.substringAfter(":<br/>") else it.content
        bot.sendText(members!!.userName, content)
    })
}

最后

项目地址为irealing/wxbot,欢迎关注和交流 :)