Multimensajería
Este repositorio es un servicio web de Flask en Python3 cuyo propósito es mandar mensajes de todo tipo de formatos a todo tipo de plataformas que tengan una API para ese propósito. Implementa una base de datos de mensajes a enviar, usa la MAC address del host que pide mandar mensajes para diferenciar las consultas y permitir la posibilidad de que el mensaje venga encriptado.
Instalación
Los requerimientos del programa son tener Python3 y pip3, para luego instalar los módulos de Flask, sqlite3 y python-arptable. Si no quiere hacerlo manualmente hay un archivo install.sh (desactualizado).
Preparación
Para correr el servicio en el servidor simplemente hay que escribir en la consola
python3 deploy.py
Uso
Para enviar mensajes se puede usar dos formas, la forma encriptada o la forma sin encripción, ambas son similares.
myserver.com/key
Llame a esta ruta para conseguir la llave pública del servidor en caso de querer encriptar la data. Este paso es opcional.
myserver.com/data
Este link es usado para subir los datos a enviar, pero obligatoriamente debe estar en formato de archivo. Opcionalmente puede estar encriptado, en cuyo caso debe mandar una llave en formato de archivo, el procedimiento es el siguiente:
- Crear una llave simétrica AES
- Encriptar los archivos con la llave simétrica
- Encriptar la llave simétrica con la llave pública dada por el servidor
- Enviar los archivos encriptados con cualquier nombre y la llave encriptada en formato de archivo con el nombre 'key' (somename=@data.file, key=@key.file)
Si no se envía una llave se presume que el archivo no está encriptado.
/data devuelve un id que corresponde al mensaje, se usa en el siguiente paso.
Aclaración: el Content-Type debe ser multipart/form-data
myserver.com/msg
Llamando a este link se envían los parámetros de envío del mensaje pasado en data, y se procede a poner en la cola el mensaje. (POST)
- Bajo 'id' se envía el id devuelto en el paso anterior
- Bajo 'serv' se envía el servicio a usar, por ahora solo hay 'wpp1' correspondiente a WhatsApp
- Bajo 'dest' se envía el número de destino, el formato puede variar dependiendo del servicio y dicho formato será documentado, el de 'wpp1' es el formato internacional sin el '+' y sin espacios ni guiones
- Bajo 'type' se envía un json { archivo : tipo }, donde a cada archivo se le asigna un tipo
En caso de que no exista el servicio o este no admita el tipo, o que el id no exista, se devolverá un mensaje de error con esa información. Caso contrario, se devolverá el string 'queued' desmotrando que todo salió bien y que el mensaje está en cola.
myserver.com/cons
Por este link se consulta el estado de los mensajes que se quieren enviar, mandando el id bajo 'id' (POST). Se devuelve 'preprocess', 'queued' o 'delivered'. Si fue enviado el mensaje se archiva y no puede ser consultado nuevamente. 'preprocess' significa que el mensaje no recibió parámetros. 'queued' que no todo fue enviado. 'delivered' que fueron enviados todos los archivos.
A saber
Los mensajes a los que no se les provea parámetros serán eliminados después de 24 hs. Lo mismo para mensajes que no sean consultados por 24 hs.
Servicios
WhatsApp 1 - 'wpp1'
Mail - 'mail'
Los tipos que aceptan y la forma de usarlos se encuentran en los links.
Contribuir
Para contribuir con el proyecto, mayormente, se deberá popular el archivo services.py
.
La forma de hacer esto consta de lo siguiente:
- Para servicios existentes se pueden agregar tipos de datos, en cuyo caso deberán ser incluidos en la clase
Datatypes
con su respectivo chequeo de validez envalidate
y se deberá agregar, si se necesita, una nueva restricción de formato (o funcionalidad) para todos los servicios. - Para agregar nuevos servicios se debe hacer una nueva clase que derive de
ServiceBase
e implementar las funciones de dicha clase abstracta. Luego agregar el servicio aServices
y expandirserviceFactory(serv)
para que pueda devolver una instancia de la nueva clase.
En el método
@abstractmethod
def send(self,data):
pass
data es un dictionario que contiene los datos del pedido guardados en la base de datos
- En 'id' está el id del pedido, que coincide con el nombre del archivo a mandar
- En 'path' está el nombre del directorio bajo el cual se encuentran los archivos a enviar
- En 'dest' se encuentra el destinatario
- En 'type' se encuentra el formato de lo que queremos mandar
- En 'state' se encuentra el estado del mensaje, que siempre será 'queued' y es completamente irrelevante a la operación
La función retorna un booleano del resultado.
El método
@abstractmethod
def validate(self,datatype):
pass
toma un string correspondiente al formato de los datos y retorna un booleano correspondiente a si el servicio soporta o no el tipo de dato. Es una buena práctica usar Datatypes.validate()
para chequear que el formato existe.
Archivos
En deploy.py
se encuentra el servicio Flask, esta parte se tocará muy poco. En process.py
se encuentran las aplicaciones de base de datos orientadas a lo que el servicio web necesita. En database.py
se encuentran las interacciones genéricas con la base de datos. En services.py
se encuentran las clases que representan los servicios de mensajería. En enums.py
se encuentran las clases estáticas que facilitan la estructura que maneja el servicio.
Errores
Si se envía un servicio inexistente, llegará un mensaje de error y el los parámetros serán cancelados. Lo mismo si, dado un servicio, los tipos de los archivos no están soportados o no existen.
Si se envía un tipo de un archivo que no existe se cancelará todo y se retornará el mensaje de error. Lo mismo con el id, aunque sin eso nada se puede hacer, puesto que no se pueden localizar los archivos.
Si en una consulta se envía un id incorrecto se responderá apropiadamente, pero no se va a romper nada.
Errores menos comunes incluyen:
- Intentar usar una tabla de base de datos que no existe
- En DBconnection.update(), usar un comparador no válido
- La data falla los chequeos de validez
Tests
Hay un archivo que hace los tests automáticamente si se corre con python3
. Tarda un minuto para permitir que se envíen todos los mensajes, hay que tener cuidado de que si hace falta que tarde más hay que cambiar la constante de tiempo.
Para agregar más tests, por cada uno debe existir una función y agregar un thread en main.
Mejoras no implementadas
- Añadir mas información a los mensajes de mail.
- Test de mensajes de error.
- Añadir estado "partially delivered" para mensajes enviados a medias.
- Cambiar id de /data a string (posiblemente el nombre de la carpeta)*.
- Posibilidad de usar un emisor de mensajes diferente.
*Esto, si bien es un golpe duro en performance, porque la búsqueda O(LogN) en base de datos se vuelve O(N), es un incremento sustancial en seguridad, puesto que nadie accidentalmente puede enviar parámetros a mensajes que no son suyos o consultar el estado de un mensaje enviado ajeno y borrarlo, volviéndolo inconsultable.
Soluciones que pensé
- Cambiar la tabla "type" a "info" (o simplemente agregar como tabla separada) y que el json acepte parámetros de todo tipo específicos de cada servicio.
- Remover por completo la noción de id numérico y que el comparador para los updates se vuelva el path
- Agregar una tbla "sender" y que tenga "default" o algún otro que se prefiera.