M

message-service

Simple web service for multi-format queue-and-retry messaging across many platforms.

Multimensajería

version 0.6

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' (nombre=@data.file, key=@key.file)

donde "nombre" será el nombre utilizado para el archivo. 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
  • Bajo 'dest' se envía la dirección de destino, el formato puede variar dependiendo del servicio y dicho formato será documentado
  • Bajo 'type' se envía un json { archivo : tipo }, donde a cada archivo (a través del nombre del paso anterior) se le asigna un tipo válido para el servicio
  • Bajo 'info' se envía un json { parámetro : valor }, donde se asignan parámetros específicos del servicio
  • Bajo 'conf' se envía true o false, dependiendo si se requiere confirmación del mensaje o no. El no envío del parámetro es sinónimo de false. Aunque no se cuente con confirmación, se puede consultar, el único estado no chequeable es 'delivered'

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 nada fue enviado. 'partially delivered' que no todo fue enviado (no fue probado). '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'

SMS - 'sms'

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 en validate 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 a Services y expandir serviceFactory(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 en la base de datos, este debería ser irrelevante a la operación
  • 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 'info' se encuentran los parámetros específicos del servicio, su obligatoriedad depende del servicio
  • En 'conf' se especifica si se requiere confimación, no es relevante para la operación
  • 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 validatetype(self,type):
    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.

El método

@abstractmethod
  def validateinfo(self,info):
    pass

toma un JSON con los parámetros del servicio y retorna un booleano correspondiente a si son válidos

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.

Para testear se debe tener una carpeta llamada 'testfolder' donde poder tirar los archivos para testeo.

Mejoras no implementadas

  1. Posibilidad de usar un emisor de mensajes diferente. (hecho parcialmente)