Construcción de una Aplicación de Mensajería Instantánea Integrada con WeChat en Windows

Visión Genarel de la Integración de IM

La idea de desarrollar una aplicación de mensajería instantánea (IM) capaz de interoperar con los mensajes de WeChat, y hacerlo con una cantidad mínima de código, resulta sumamente atractiva. Para lograr un desarrollo rápido y una estabilidad robusta, la estrategia más eficiente es aprovechar un SDK de IM de terceros que ofrezca generosos límites de uso gratuito. La integración con WeChat se facilita mediante herramientas específicas para la interceptación de mensajes. Este artículo explora cómo construir una aplicación de chat que se comunique fluidamente con WeChat.

Los resultados finales pueden incluir funcionalidades como:

  1. Mensajería automatizada.
  2. Conversaciones en tiempo real.
  3. Respuestas automáticas.

1. Principios de Implementación Técnica

El esquema general de la arquitectura se ilustra en el siguiente diagrama:

Diagrama de arquitectura técnica

2. Intercepción de Mensajes de WeChat

2.1 Herramientas Existentes para la Intercepción de Mensajes

En lugar de construir una solución de intercepción desde cero, este enfoque se basa en una herramienta de terceros ya disponible: pc-wechat-hook-http-api. La documentación detallada para esta herramienta se puede encontrar en https://www.apifox.cn/apidoc/project-1222856/doc-1012539.

Es importante destacar que esta herramienta está diseñada para funcionar con la versión 3.6.0.18 de WeChat. Se recomienda instalar esta versión directamente sobre la instalación existente para conservar el historial de chat.

2.2 Guía de Uso de la Herramienta de Intercepción

El primer paso es descargar el paquete de la herramienta. Una vez descargado, copie el archivo HPSocket4C.dll al directorio de instalación de WeChat (por ejemplo, E:\Tencent\WeChat\[3.6.0.18]).

2.2.1 Inyección del DLL

Ejecute la aplicación Daen注入器.exe. En la interfaz, deberá configurar:

  • Directorio del Archivo: La ruta de instalación de WeChat.
  • Ruta del DLL: La ruta completa del archivo DaenWxHook.dll.
  • Parámetros del Proceso: Generalmente se pueden usar los valores predeterminados. Los puertos que se muestran típicamente son: 8089 para el servidor HTTP local que recibe mensajes de WeChat en tiempo real, y 8055 para el servidor HTTP que el DLL inicia, al cual se enviarán las solicitudes POST para mandar mensajes.

Después de configurar, haga clic en "Inyectar y Abrir", y luego inicie sesión en WeChat. Esto completará la fase de intercepción de mensajes de WeChat.

2.2.2 Envío de Mensajes de WeChat

Para enviar un mensaje de WeChat, se realiza una solicitud HTTP POST al puerto especificado durante la inyección del DLL (por ejemplo, 8055). A continuación, se muestra un ejemplo en Node.js para realizar esta operación:

const http = require('http');

function enviarMensajeWechat(datos, callback) {
    const opcionesSolicitud = {
        hostname: '127.0.0.1',
        port: 8055,
        path: '/DaenWxHook/client/',
        method: 'POST',
        headers: {
            'User-Agent': 'api-client/1.0',
            'Content-Type': 'application/json'
        }
    };

    const solicitud = http.request(opcionesSolicitud, function (respuesta) {
        let cuerpoRespuesta = "";
        respuesta.setEncoding('utf8');
        respuesta.on('data', function (fragmento) {
            cuerpoRespuesta += fragmento;
        });
        respuesta.on('end', function () {
            try {
                const jsonRespuesta = JSON.parse(cuerpoRespuesta);
                callback(jsonRespuesta);
            } catch (e) {
                console.error('Error al parsear respuesta JSON:', e.message);
                callback({ error: 'JSON_PARSE_ERROR', details: e.message });
            }
        });
    });

    solicitud.on('error', function (e) {
        console.error('Problema con la solicitud HTTP:', e.message);
        callback({ error: 'HTTP_REQUEST_ERROR', details: e.message });
    });

    solicitud.write(JSON.stringify(datos));
    solicitud.end();
}

El parámetro datos es un objeto JSON, cuya estructura específica se puede consultar en la documentación oficial de la herramienta.

2.2.3 Recepción de Mensajes de WeChat

La recepción de mensajes de WeChat es igualmente sencilla. Basta con iniciar un servidor HTTP en el puerto que se configuró durante la inyección del DLL (por ejemplo, 8089). Un ejemplo de implementación en Node.js podría ser el siguiente:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const PUERTO_ESCUCHA = 8089;

app.use(bodyParser.json());

// Funciones de manejo de eventos para distintos tipos de mensaje
let onMensajePrivadoWX = null;
let onMensajeGrupoWX = null;
let onMensajeCuentaPublica = null;

// Establece los manejadores de eventos
function configurarManejadores(privado, grupo, cuentaPublica) {
    onMensajePrivadoWX = privado;
    onMensajeGrupoWX = grupo;
    onMensajeCuentaPublica = cuentaPublica;
}

app.post('/wechat/', function (req, res) {
    const datosRecibidos = req.body;
    const tipoMensaje = datosRecibidos['type'];

    if (tipoMensaje === 'D0003') { // Tipo de mensaje específico para datos de chat
        const datosChat = datosRecibidos['data'];
        const contenidoMensaje = datosChat['msg'];
        const tipoRemitente = datosChat['fromType']; // 1=privado, 2=grupo, 3=cuenta pública
        const idRemitente = datosChat['fromWxid'];
        const esRecibido = datosChat['msgSource'] === 0; // 0=otro envía, 1=uno mismo envía

        if (tipoRemitente === 2 && onMensajeGrupoWX) {
            onMensajeGrupoWX(contenidoMensaje, idRemitente, esRecibido);
        } else if (tipoRemitente === 3 && onMensajeCuentaPublica) {
            onMensajeCuentaPublica(contenidoMensaje, idRemitente, esRecibido);
        } else if (tipoRemitente === 1 && onMensajePrivadoWX) {
            onMensajePrivadoWX(contenidoMensaje, idRemitente, esRecibido);
        }
    }
    res.send(''); // Responder para confirmar la recepción
});

app.listen(PUERTO_ESCUCHA, function () {
    console.log(`Servidor de escucha de mensajes de WeChat activo en el puerto ${PUERTO_ESCUCHA}`);
});

// Ejemplo de cómo se usaría:
// configurarManejadores(
//     (msg, id, esRcv) => console.log(`Mensaje P2P: ${msg} de ${id}, recibido: ${esRcv}`),
//     (msg, id, esRcv) => console.log(`Mensaje de Grupo: ${msg} de ${id}, recibido: ${esRcv}`),
//     (msg, id, esRcv) => console.log(`Mensaje de Cuenta Pública: ${msg} de ${id}, recibido: ${esRcv}`)
// );

3. Integración de un SDK de IM de Terceros

Una vez que se ha establecido la capacidad de enviar y recibir mensajes de WeChat, el siguiente paso es reenviar estos mensajes en tiempo real a la aplicación móvil. Desarrollar un sistema de IM completo desde cero es una tarea de gran magnitud. Por ello, se recomienda la integración de un SDK de IM de terceros.

Existen diversas plataformas de IM de terceros; la elección dependerá de las necesidades específicas del proyecto. Los SDKs de IM modernos ofrecen soporte para todas las plataformas principales, incluyendo frameworks multiplataforma como Flutter y UniApp, lo que acelera el tiempo de lanzamiento. En cuanto a la moderación de mensajes, estas plataformas suelen integrar servicios de seguridad avanzados. Además de las funcionalidades básicas de chat uno a uno y chat grupal, muchos SDKs soportan salas de chat con alta concurrencia (incluso millones de usuarios) y funciones avanzadas como invitaciones a llamadas, cubriendo todas las necesidades de comunicación instantánea.

Para un desarrollador individual, el acceso a una generosa cuota gratuita es crucial. Muchas plataformas ofrecen planes gratuitos que son más que suficientes para proyectos personales y facilitan una rápida integración y escalabilidad si el proyecto crece en el futuro.

Para comenzar, es necesario registrarse en la consola del proveedor del SDK, crear una aplicación y obtener las credenciales como AppId y ServerSecret.

La documentación del SDK de IM elegido proporcionará detalles exhaustivos sobre su uso.

3.1 Reenvío de Mensajes de WeChat a través del IM SDK

Dado que la intercepción de mensajes se realiza en la versión de Windows de WeChat, el cliente de Windows también debe integrar el SDK de IM para reenviar los datos a la aplicación móvil. Aunque algunos SDKs pueden ofrecer una versión C++ para Windows, que podría ser menos eficiente para el desarrollo rápido, es posible adaptar la versión web del SDK (por ejemplo, de Zego IM) para que funcione en un entorno Node.js, agilizando así la construcción del servicio de reenvío.

La función principal de este SDK de IM en este contexto es actuar como un intermediario, permitiendo que la aplicación móvil reciba mensajes de WeChat en tiempo real. Un ejemplo de inicialización del SDK de IM en Node.js es el siguiente:

const { createZIM, login, renewToken } = require('./zego-im-sdk'); // Suponiendo un módulo de abstracción del SDK

let identificadorClienteGlobal = 'C123456';
let identificadorServidorGlobal = 'S123456';
let marcaTiempoInicio = 0; // Para filtrar mensajes offline

function generarToken(userId) {
    // Lógica para generar un token seguro (ej. JWT) con el ServerSecret
    // Esto debería ser una llamada a su backend o un método seguro
    return `token_para_usuario_${userId}`; // Placeholder
}

function inicializarSDK_IM(callbackError, callbackMensajeRecibido, idCliente = identificadorClienteGlobal, idServidor = identificadorServidorGlobal) {
    identificadorClienteGlobal = idCliente;
    let tokenActual = generarToken(idServidor);
    marcaTiempoInicio = new Date().getTime();

    function onManejarError(zimInstancia, error) {
        console.error("Error del SDK de IM:", error);
        callbackError(error);
    }

    function onMensajeP2PRecibido(zimInstancia, objetoMensaje) {
        const listaMensajes = objetoMensaje.messageList;
        const idConversacionOrigen = objetoMensaje.fromConversationID; 
        listaMensajes.forEach(function (mensaje) {
            if (mensaje.timestamp >= marcaTiempoInicio) { // Filtrar mensajes offline/antiguos
                console.log("Mensaje IM recibido:", mensaje);
                callbackMensajeRecibido(mensaje, idConversacionOrigen);
            }  
        });
    }

    function onTokenProximoExpirar(zimInstancia, segundosRestantes) {
        console.warn(`El token de IM expirará en ${segundosRestantes} segundos. Renovando...`);
        tokenActual = generarToken(idServidor);
        zimInstancia.renewToken(tokenActual);
    }

    const zim = createZIM(onManejarError, onMensajeP2PRecibido, onTokenProximoExpirar);

    login(zim, idServidor, tokenActual, function (exito, datos) {
        if (exito) {
            console.log("¡Inicio de sesión en IM exitoso!");
        } else {
            console.error("Fallo el inicio de sesión en IM:", datos);
        }
    });
    return zim;
}

Es fundamental que el AppId utilizado al crear el motor ZIM (o similar) se obtenga de la consola de gestión de la aplicación del proveedor del SDK de IM.

4. Desarrollo de la Aplicación Móvil

La aplicación móvil comprenderá principalmente tres módulos: Chat, Contactos y Configuración (que incluirá el chatbot y las respuestas automáticas). La aplicación debe integrar el SDK de IM para manejar la comunicación. La documentación oficial del SDK proporcionará la mejor guía para su integración.

4.1 Módulo de Chat

El módulo de chat gestiona el envío y la recepción de mensajes de WeChat, pero encapsulándolos dentro del formato de mensaje del SDK de IM. Se define un tipo de mensaje para cada evento, lo que permite su identificación. Cuando el SDK de IM en la aplicación recibe un mensaje del cliente de Windows, se procesa de la siguiente manera (ejemplo en Java):

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;

// Suponiendo que ZIMMessage, ZIMTextMessage y Msg son clases del SDK o definidas previamente
public class ChatManager {

    private ArrayList<MsgCenterListener> listeners = new ArrayList<>();
    private long startTime; // Marca de tiempo de inicio de sesión para filtrar mensajes

    // Interfaz para notificar a los suscriptores sobre nuevos mensajes
    public interface MsgCenterListener {
        void onMensajeRecibido(Msg mensaje);
    }

    public void addListener(MsgCenterListener listener) {
        listeners.add(listener);
    }

    public void removeListener(MsgCenterListener listener) {
        listeners.remove(listener);
    }

    // Método que procesa los mensajes entrantes del SDK de IM
    private void procesarMensajesRecibidos(ArrayList<ZIMMessage> listaMensajes, String idUsuarioOrigen) {
        if (listeners.isEmpty()) return;

        for (ZIMMessage mensajeZIM : listaMensajes) {
            if (mensajeZIM instanceof ZIMTextMessage) {
                ZIMTextMessage mensajeTextoZIM = (ZIMTextMessage) mensajeZIM;
                // Filtrar mensajes anteriores al inicio de sesión de la app si es necesario
                if (mensajeZIM.getTimestamp() < this.startTime) {
                    continue;
                }
                String idRemitente = mensajeTextoZIM.getSenderUserID();
                // Asegurarse de que el mensaje es para la conversación activa si se está filtrando
                // if (!idConversacionActual.equals(idRemitente)) continue;

                String contenidoJson = mensajeTextoZIM.message;
                try {
                    Msg mensajeParseado = Msg.parseMsg(contenidoJson);
                    for (MsgCenterListener listener : listeners) {
                        listener.onMensajeRecibido(mensajeParseado);
                    }
                } catch (Exception e) {
                    System.err.println("Error al parsear mensaje JSON: " + e.getMessage());
                }
            }
        }
    }
}

La definición de los datos de los mensajes de WeChat se puede encapsular en una clase como la siguiente:

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

// Suponiendo que Msg es una clase base para diferentes tipos de mensajes
public class MensajeP2P_WX extends Msg { 
    
    // Constante que indica el tipo de mensaje P2P de WeChat
    public static final int TIPO_MENSAJE_P2P = 101; 

    @SerializedName("w")
    public String idWechat; // ID de WeChat del remitente/destinatario
    
    @SerializedName("tm")
    public long marcaTiempo; // Marca de tiempo del mensaje
    
    @SerializedName("m")
    public String contenidoMensaje; // Contenido textual del mensaje
    
    @SerializedName("r")
    public boolean esRecibido; // Indica si el mensaje fue recibido (true) o enviado (false)
    
    @Expose(serialize = false, deserialize = false)
    public String aliasContacto; // Alias o nombre de contacto, no serializado
    
    public MensajeP2P_WX(String wxId, long tiempo, String mensajeContenido, boolean recibido) {
        super(TIPO_MENSAJE_P2P); // Llamada al constructor de la clase base
        this.idWechat = wxId;
        this.marcaTiempo = tiempo;
        this.contenidoMensaje = mensajeContenido;
        this.esRecibido = recibido;
    }

    // Otros métodos, getters, setters según sea necesario
}

Tanto en la aplicación móvil como en el cliente de Windows, se utiliza el formato JSON definido por MensajeP2P_WX. El campo esRecibido determina si el mensaje fue recibido o enviado. El cliente de Windows reenvía el mensaje basándose en el idWechat, mietnras que la aplicación móvil utiliza el idWechat y esRecibido para identificar la conversación y la dirección del mensaje.

4.2 Módulo de Contactos

La obtención de la lista de contactos es relativamente sencilla. Una vez que el cliente de Windows recupera la lista, la envía a la aplicación. Sin embargo, debido a que la cantidad de contactos puede ser muy grande, enviar toda la lista de una vez podría provocar que el cuerpo del mensaje sea demasiado grande y falle la transmisión. Por lo tanto, se recomienda enviar la información de los contactos en lotes (paginación):

const LISTA_AMIGOS_WECHAT = [/* Array de objetos de amigos de WeChat */];
const TIPO_LISTA_AMIGOS = 201; // Un ID para este tipo de mensaje

function enviarListaAmigosIM(configuracionPaginacion) {
    const numeroPagina = configuracionPaginacion.pno;
    const tamanoPagina = 10;
    const totalPaginas = Math.ceil(LISTA_AMIGOS_WECHAT.length * 1.0 / tamanoPagina);

    let amigosParaEnviar = [];
    let datosMensajeJson = {};

    if (numeroPagina < 0) { // Un número de página negativo podría indicar una solicitud de reinicio o configuración
        datosMensajeJson = {
            tipo: TIPO_LISTA_AMIGOS,
            amigos: [],
            totalContactos: LISTA_AMIGOS_WECHAT.length,
            paginaActual: numeroPagina
        };
        // Posiblemente enviar configuración adicional aquí
        // enviarConfiguracionGlobal(); 
    } else {
        const inicioSlice = numeroPagina * tamanoPagina;
        const finSlice = Math.min((numeroPagina + 1) * tamanoPagina, LISTA_AMIGOS_WECHAT.length);
        
        amigosParaEnviar = LISTA_AMIGOS_WECHAT.slice(inicioSlice, finSlice);

        datosMensajeJson = {
            tipo: TIPO_LISTA_AMIGOS,
            amigos: amigosParaEnviar,
            totalPaginas: totalPaginas,
            paginaActual: numeroPagina
        };
    }
    // Suponiendo una función para enviar mensajes a través del SDK de IM
    // enviarMensajeIM(JSON.stringify(datosMensajeJson)); 
    console.log("Enviando página de amigos:", datosMensajeJson);
}

4.3 Chatbot y Respuestas Automáticas

Chatbot

Para implementar un chatbot, se puede integrar una API de terceros, como la proporcionada por Qingyunke, que permite la conversación automática. Un ejemplo de implementación en Python sería:

import requests
import urllib.parse

def interactuar_con_chatbot(mensaje_usuario, nombre_bot=None):
    """
    Interactúa con un servicio de chatbot para obtener una respuesta.

    Args:
        mensaje_usuario (str): El mensaje del usuario.
        nombre_bot (str, optional): Nombre personalizado para el bot.
                                     Si se proporciona, reemplaza el nombre predeterminado '菲菲'.

    Returns:
        str: La respuesta generada por el chatbot.
    """
    # Codificar el mensaje del usuario para la URL
    mensaje_codificado = urllib.parse.quote(mensaje_usuario)
    url_api = f'http://api.qingyunke.com/api.php?key=free&appid=0&msg={mensaje_codificado}'

    try:
        respuesta_http = requests.get(url_api)
        respuesta_http.raise_for_status()  # Lanza una excepción para errores HTTP

        datos_json = respuesta_http.json()
        respuesta_chatbot = datos_json.get("content", "Lo siento, no pude procesar tu mensaje.")

        # Reemplazar marcadores de posición de salto de línea
        respuesta_chatbot = respuesta_chatbot.replace("{br}", "\n")

        # Personalizar el nombre del bot si se proporciona
        if nombre_bot:
            respuesta_chatbot = respuesta_chatbot.replace("菲菲", nombre_bot)
        
        return respuesta_chatbot

    except requests.exceptions.RequestException as e:
        print(f"Error de conexión con la API del chatbot: {e}")
        return "Lo siento, tengo problemas para conectarme con el servicio de chat."
    except ValueError as e:
        print(f"Error al procesar la respuesta del chatbot (JSON inválido): {e}")
        return "Lo siento, recibí una respuesta inesperada del servicio de chat."

# Ejemplo de uso:
# print(interactuar_con_chatbot("Hola", "MiBot"))
# print(interactuar_con_chatbot("Qué hora es?", "Asistente"))

Respuestas Automáticas y Envío Programado

Las funciones de respuesta automática y envío programado se basan en reglas configurables. La configuración definida en la aplicación móvil se envía al cliente de Windows, que se encarga de ejecutar las operaciones correspondientes. Por ejemplo, una respuesta automática enviaría un mensaje predefinido cada vez que se recibe un mensaje, mientras que el envío programado utilizaría temporizadores para ejecutar tareas de envío en momentos específicos.

Etiquetas: WeChat IM mensajería instantánea Windows Node.js

Publicado el 8-25 17:54