Underc0de

Programación Scripting => Python => Mensaje iniciado por: ANTRAX en Febrero 22, 2017, 02:01:39 PM

Título: Bot Whatsapp en Python
Publicado por: ANTRAX en Febrero 22, 2017, 02:01:39 PM
(http://blog.elfsystems.pro/wp-content/uploads/2017/02/bot-kirill__m-shutterstock-930x930-300x300.jpg)

Buenas, quería compartir un proyecto que tengo hace un tiempo con respecto a whatsapp, últimamente se esta poniendo muy difícil debido a los bloqueos, las actualizaciones que saca whatsapp para combatir esto, ademas que no tiene una api, las que se crearon o existen de forma no oficial están teniendo problemas.

Ya hace algún tiempo en github salieron apis para whatsapp, una de las primeras que use fue whatsapi después llamada chatapi (https://github.com/mgp25/Chat-API), esta en php, si no me equivoco es la primera que salio, en base a esta se creo su equivalente en python llamada yowsup (https://github.com/tgalal/yowsup).

Para utilizar esta api primero hay que instalarla, después hacemos uso de sus clases en python.

El código que voy a compartir es básico, responde de acuerdo a algunas palabras claves, también cuenta con algunos scrapers de paginas, una de ellas es el inpres (http://www.inpres.gov.ar/), también cuenta con un monitor de inpres que tiene definido ciertos parámetros para enviar alertas,en el nuevo trabajo que estoy haciendo estoy tratando de implementar chatterbot (https://github.com/gunthercox/ChatterBot) como motor de respuestas.

Empecemos, primero instalar la api.

Código (bash) [Seleccionar]
git clone https://github.com/tgalal/yowsup.git

ir a la carpeta env y editar el archivo env_s40.py (https://github.com/tgalal/yowsup/commit/70fbe97de5d97b1ebf41fc4153e46463cdb432fe#diff-e66c7dbef9b5bd86772b5fbc02ebe8cb) en github todavía no esta actualizado, no se por que pero no tiene los valores correctos y jode por el token, por lo que si no lo editan no podrán utilizar la api, error (https://github.com/tgalal/yowsup/issues/1952).

Aquí están los tokens correctos

https://coderus.openrepos.net/whitesoft/whatsapp_scratch

Editar en ese archivo las variables

Código (text) [Seleccionar]
_VERSION = "2.16.9"

_TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1471306425030{phone}"


Por

Código (text) [Seleccionar]
_VERSION = "2.16.11"

_TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1478194306452{phone}"


Y con esto debería funcionar, ahora falta compilar de nuevo.

Código (bash) [Seleccionar]
sudo python setup.py build

Instalar

Código (bash) [Seleccionar]
sudo python setup.py install

Recomiendo comprar un chip descartable o prepago para registrar en whatsapp y utilizar la api, por los bloqueos, no queremos perder nuestra linea en whatsapp.

Procedemos a registrar nuestra linea, todo eso desde yowsup cli 2.0 (https://github.com/tgalal/yowsup/wiki/yowsup-cli-2.0), el objetivo es registrar y obtener la clave que esta codificada en base64

Solicitamos el OTP

Código (bash) [Seleccionar]
yowsup-cli registration --requestcode sms --phone 549XXXXXXXXXX --cc 54 --mcc 722 --mnc XX

mmc y mnc por pais

https://es.wikipedia.org/wiki/MCC/MNC

Ejemplo

Código (bash) [Seleccionar]
yowsup-cli registration --requestcode sms --phone 5493804651130 --cc 54 --mcc 722 --mnc 341

Debería salir en status : sent
mmc 722 corresponde a ARG
mnc 341 corresponde a PERSONAL

Antes de solicitar la OTP ponemos el chip en cualquier celular, solo nos interesa el sms con el código de activación
Una vez que nos llego

Código (bash) [Seleccionar]
yowsup-cli registration --register XXXXX --phone 549XXXXXXXXXX --cc 54 -d

Ejemplo:

Código (bash) [Seleccionar]
yowsup-cli registration --register 542301 --phone 5493804651130 --cc 54 -d

Si todo sale bien nos devolverá la clave.
ahora creamos un archivo con la configuración o credenciales.

Código (bash) [Seleccionar]
nano 5493804651130.cfg

Dentro del mismo

cc=54
phone=549XXXXXXX
id=549XXXXXXX
password=b64pw

Ejemplo

cc=54
phone=5493804651130
id=5493804651130
password=m+S232dtFUfghljjKS12325pZmNELasdas=

Probamos todo en el cli.

Código (bash) [Seleccionar]
yowsup-cli demos -c 5493804651130.cfg -e

-c = archivo de configuración creado anteriormente
-e = modo eco.

El modo eco, responde a todo lo que le enviamos.

Ahora probamos enviar un whatsapp que diga hola mundo a ese numero debería responder con hola mundo.

Si todo sale bien, estamos listos para utilizar el script.

DESCARGAR CODIGO: http://elfsystems.pro/files/MOT_IMPRES-WA.zip

Editar el array CREDENTIALS con sus datos en run.py

Código (python) [Seleccionar]
#!/usr/bin/python
# -*- coding: utf-8 -*-
from yowsup.stacks                             import YowStackBuilder
from yowsup.common                             import YowConstants
from yowsup.layers                             import YowLayerEvent
from layer                                     import EchoLayer
from yowsup.layers.auth                        import YowAuthenticationProtocolLayer
from yowsup.layers.coder                       import YowCoderLayer
from yowsup.layers.network                     import YowNetworkLayer
from yowsup.env                                import YowsupEnv
from scrapers.inpres                           import SCinpres
import threading, os, subprocess, time, sys

CREDENTIALS = ("549XXXXXXXXX", "pwB64")
laye = EchoLayer()

def workerinpress():
inpre = SCinpres()
print "Monitor inpres start\n"
while (True):
al = inpre.monitor()
if (al and al.strip() and al !='Error en consulta'):
print al
laye.envialertainpres(al)
time.sleep(1)

def workerwhatsapp():
stackBuilder = YowStackBuilder()
stack = stackBuilder.pushDefaultLayers(True).push(laye).build()
stack.setCredentials(CREDENTIALS)
stack.broadcastEvent(YowLayerEvent(YowNetworkLayer.EVENT_STATE_CONNECT))
stack.setProp(YowNetworkLayer.PROP_ENDPOINT, YowConstants.ENDPOINTS[0])
stack.setProp(YowCoderLayer.PROP_DOMAIN, YowConstants.DOMAIN)
stack.setProp(YowCoderLayer.PROP_RESOURCE, YowsupEnv.getCurrent().getResource())
stack.loop( timeout = 0.5, discrete = 0.5 )

if __name__==  "__main__":
threads = list()
moninp = threading.Thread(target=workerinpress)
threads.append(moninp)
wasa = threading.Thread(target=workerwhatsapp)
threads.append(wasa)
moninp.start()
wasa.start()


En layer.py editar

permitidos=['5493834621141′,'5493834654414']   #Agregar los números a los cuales solo quieren responder

name = "Bort" #nombre del usuario
ALERTAN = ("[email protected]")  #numero al cual queremos enviar las alertas del impres

Código (python) [Seleccionar]
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, subprocess, time, sys
from sys import exit
from yowsup.layers.interface                           import YowInterfaceLayer,ProtocolEntityCallback     
import mimetypes
from yowsup.layers.protocol_messages.protocolentities  import TextMessageProtocolEntity 
from yowsup.layers.protocol_presence.protocolentities  import AvailablePresenceProtocolEntity   
from yowsup.layers.protocol_presence.protocolentities  import UnavailablePresenceProtocolEntity
from yowsup.layers.protocol_presence.protocolentities  import PresenceProtocolEntity           
from yowsup.layers.protocol_chatstate.protocolentities import OutgoingChatstateProtocolEntity   
from yowsup.common.tools                               import Jid
from yowsup.layers.protocol_media.mediadownloader      import MediaDownloader
from yowsup.layers.protocol_media.protocolentities     import *
from respuesta                                         import Respuesta                             
import sys, shutil

permitidos=['5493834621141']
ac = set(permitidos)

name = "Bort"
ALERTAN = ("[email protected]")

class EchoLayer(YowInterfaceLayer):

@ProtocolEntityCallback("message")
def envialertainpres(self, al):
self.toLower(AvailablePresenceProtocolEntity())
time.sleep(0.5)
self.toLower(PresenceProtocolEntity(name = name))#Set presencia
time.sleep(0.5)
textmsg = TextMessageProtocolEntity
for tel in ALERTAN:
self.toLower(textmsg(al, to = tel))
time.sleep(0.4)
time.sleep(1)
self.toLower(UnavailablePresenceProtocolEntity())#Setoffline

@ProtocolEntityCallback("message")
def onMessage(self, messageProtocolEntity):
if messageProtocolEntity.getType() == 'text':
time.sleep(0.5)
self.toLower(messageProtocolEntity.ack())#Set visto
time.sleep(0.5)
self.toLower(AvailablePresenceProtocolEntity())
time.sleep(0.5)
self.toLower(PresenceProtocolEntity(name = name))#Set presencia
time.sleep(0.5)
self.toLower(messageProtocolEntity.ack(True))#Set doble visto
time.sleep(0.5)
self.toLower(OutgoingChatstateProtocolEntity(OutgoingChatstateProtocolEntity.STATE_TYPING, Jid.normalize(messageProtocolEntity.getFrom(False))))#Set esta escribiendo
time.sleep(1)
self.onTextMessage(messageProtocolEntity)#envia la respuesta
time.sleep(0.5)
self.toLower(OutgoingChatstateProtocolEntity(OutgoingChatstateProtocolEntity.STATE_PAUSED, Jid.normalize(messageProtocolEntity.getFrom(False))))#Set pausa
time.sleep(1)
self.toLower(UnavailablePresenceProtocolEntity())#Setoffline
elif messageProtocolEntity.getType() == 'media':
time.sleep(0.5)
self.toLower(AvailablePresenceProtocolEntity())
time.sleep(0.5)
self.toLower(PresenceProtocolEntity(name = name))#Set presencia
#self.onMediaMessage(messageProtocolEntity)
self.toLower(messageProtocolEntity.ack())
self.toLower(messageProtocolEntity.ack(True))
time.sleep(1)
self.toLower(UnavailablePresenceProtocolEntity())#Set offline
else:
pass

@ProtocolEntityCallback("receipt")
def onReceipt(self, entity):
print entity.ack()
self.toLower(entity.ack())

def onTextMessage(self,messageProtocolEntity):
namemitt   = messageProtocolEntity.getNotify()
message    = messageProtocolEntity.getBody()
recipient  = messageProtocolEntity.getFrom()
textmsg    = TextMessageProtocolEntity
if messageProtocolEntity.getFrom(False) in ac:
resp = Respuesta()
answer = resp.obtener(message,namemitt,recipient)
if answer and answer.strip():
if recipient.lower().find("-") == -1:
self.toLower(textmsg(answer, to = recipient ))
print answer+"\n Personal"
else:
self.toLower(textmsg(answer, to = recipient ))
print answer+"\n Grupo"
else:
answer = "Hola "+namemitt+", no estas en la lista de respuesta\n"
print recipient + "\n"
print message + "\n"
if recipient.lower().find("-") == -1:
time.sleep(1)
self.toLower(textmsg(answer, to = recipient))
print answer
else:
print answer

def onMediaMessage(self, messageProtocolEntity):
if messageProtocolEntity.getFrom(False) in ac:
if messageProtocolEntity.getMediaType() == "image":
url = messageProtocolEntity.url
self.extension = self.getExtension(messageProtocolEntity.getMimeType())
return self.downloadMedia(url)

def downloadMedia(self, url):
print("Descargando %s" % url)
downloader = MediaDownloader(self.onSuccess, self.onError, self.onProgress)
downloader.download(url)

def onError(self):
print "Error al descargar archivo"

def onSuccess(self, path):
outPath = "/home/yowsup/imgs/%s%s" % (os.path.basename(path), self.extension)
shutil.copyfile(path, outPath)
print("\nImagen descargada en %s" % outPath)

def onProgress(self, progress):
sys.stdout.write("Progreso descarga => %d%% \r" % progress)
sys.stdout.flush()

def getExtension(self, mimetype):
type = mimetypes.guess_extension(mimetype.split(';')[0])
if type is None:
raise Exception("Unsupported/unrecognized mimetype: "+mimetype);
return type


Por ultimo dentro de scrapers en inpres.py editar a gusto

provincias = ['CATAMARCA','LA RIOJA','CORDOBA']#Las provincias que importan
mgtmax= 4.3 #valor igual o máximo del temblor para que envié la alerta al numero definido

Código (python) [Seleccionar]
#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib2
import datetime
import time
from bs4 import BeautifulSoup
from controladores import Cinpresdb

class SCinpres:

    def __init__(self):
        pass

    def extraer(self):
url = 'http://www.inpres.gov.ar/desktop/'
formateado = ''
try:
respuesta = urllib2.urlopen(url)
datos = respuesta.read().decode('utf-8')
scrapsismo = BeautifulSoup(datos, "lxml")
tabla = scrapsismo.find("table", {"id" : "sismos"})
formateado = ''
registros = tabla.find_all('tr')
cantidadr = len(registros)
i=0
for reg in registros:
if (i>1 and i<cantidadr-3):
sctd = BeautifulSoup(str(reg), "lxml")
tds = sctd.find_all('td')
fecha = tds[1].text
hora = tds[2].text
mntud = tds[4].text
lugar = tds[7].text
formateado = formateado+"Fecha: "+fecha+" H: "+hora+" magnitud: "+mntud+" lugar: "+lugar+ "\n"
i += 1
except:
formateado = 'Error en consulta'
return formateado

    def monitor(self):
url = 'http://www.inpres.gov.ar/desktop/'
provincias = ['CATAMARCA','LA RIOJA','CORDOBA']
mgtmax= 4.3
hoyhora = datetime.datetime.now()
controlador = Cinpresdb()
i=0
tdin=0
ale=False
formateado = ''
try:
respuesta = urllib2.urlopen(url)
datos = respuesta.read().decode('utf-8')
scrapsismo = BeautifulSoup(datos, "lxml")
tabla = scrapsismo.find("table", {"id" : "sismos"})
registros = tabla.find_all('tr')
cantidadr = len(registros)
for reg in registros:
if (i>1 and i<cantidadr-3):
sctd = BeautifulSoup(str(reg), "lxml")
tds = sctd.find_all('td')
fechat = tds[1].text
horat = tds[2].text
proft = tds[3].text
mntud = float(tds[4].text)
magnitudt = tds[4].text
latt = tds[5].text
longt = tds[6].text
lugart = tds[7].text
h = datetime.datetime.strptime(fechat.strip()+" "+horat.strip(), '%d/%m/%Y %H:%M:%S')
ds_ts = time.mktime(hoyhora.timetuple())
dr_ts = time.mktime(h.timetuple())
diff = int(ds_ts-dr_ts) / 60
ck = controlador.checkdb(h,lugart)
if (lugart in provincias and ck==False):
controlador.inserta(h,proft,magnitudt,latt,longt,lugart)
if (mntud>mgtmax):
tdin = i
ale=True
formateado = "Alerta Temblor!!\nHace "+str(diff)+" min\n"
i += 1
if (ale==True):
formateado = formateado + registros[tdin].text
except:
formateado = 'Error en consulta'
return formateado


También almacena en una bd los valores que va recolectando del impres, de las provincias que vamos definiendo, esto es opcional y lo pueden sacar.

En fin, en el zip esta el .sql con la estructura de la tabla.

Si quisieran utilizarlo, en el archivo controladores.py que esta dentro de la carpeta de scrapers esta para que editen la conexión a la base de datos.

Si no quieren almacenar nada, editar el inpres.py que esta dentro de la carpeta de scrapers con este código

Código (python) [Seleccionar]
#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib2
import datetime
import time
from bs4 import BeautifulSoup

class SCinpres:

    def __init__(self):
        pass

    def extraer(self):
url = 'http://www.inpres.gov.ar/desktop/'
formateado = ''
try:
respuesta = urllib2.urlopen(url)
datos = respuesta.read().decode('utf-8')
scrapsismo = BeautifulSoup(datos, "lxml")
tabla = scrapsismo.find("table", {"id" : "sismos"})
formateado = ''
registros = tabla.find_all('tr')
cantidadr = len(registros)
i=0
for reg in registros:
if (i>1 and i<cantidadr-3):
sctd = BeautifulSoup(str(reg), "lxml")
tds = sctd.find_all('td')
fecha = tds[1].text
hora = tds[2].text
mntud = tds[4].text
lugar = tds[7].text
formateado = formateado+"Fecha: "+fecha+" H: "+hora+" magnitud: "+mntud+" lugar: "+lugar+ "\n"
i += 1
except:
formateado = 'Error en consulta'
return formateado

    def monitor(self):
url = 'http://www.inpres.gov.ar/desktop/'
provincias = ['CATAMARCA','LA RIOJA','CORDOBA']
mgtmax= 4.3
hoyhora = datetime.datetime.now()
i=0
tdin=0
ale=False
formateado = ''
try:
respuesta = urllib2.urlopen(url)
datos = respuesta.read().decode('utf-8')
scrapsismo = BeautifulSoup(datos, "lxml")
tabla = scrapsismo.find("table", {"id" : "sismos"})
registros = tabla.find_all('tr')
cantidadr = len(registros)
for reg in registros:
if (i>1 and i<cantidadr-3):
sctd = BeautifulSoup(str(reg), "lxml")
tds = sctd.find_all('td')
fechat = tds[1].text
horat = tds[2].text
proft = tds[3].text
mntud = float(tds[4].text)
magnitudt = tds[4].text
latt = tds[5].text
longt = tds[6].text
lugart = tds[7].text
h = datetime.datetime.strptime(fechat.strip()+" "+horat.strip(), '%d/%m/%Y %H:%M:%S')
ds_ts = time.mktime(hoyhora.timetuple())
dr_ts = time.mktime(h.timetuple())
diff = int(ds_ts-dr_ts) / 60
if (lugart in provincias):
if (mntud>mgtmax):
tdin = i
ale=True
formateado = "Alerta Temblor!!\nHace "+str(diff)+" min\n"
i += 1
if (ale==True):
formateado = formateado + registros[tdin].text
except:
formateado = 'Error en consulta'
return formateado


Por ultimo correr el script

Código (bash) [Seleccionar]
python run.py &

Si tienen algún vps o servidor dedicado pueden usar tmux, para que siga su ejecución después de cerrar la sesion ssh.

Código (bash) [Seleccionar]
tmux
python run.py &


ctrl+b y luego presionar d
para volver a la sesión tmux

Código (bash) [Seleccionar]
tmux attach

para salir nuevamente ctrl+b y luego presionar d
Para ver la lista de comandos del bot enviar la palabra:

ayuda

(http://blog.elfsystems.pro/wp-content/uploads/2017/02/Screenshot_2017-02-21-14-34-21-180x300.png)(http://blog.elfsystems.pro/wp-content/uploads/2017/02/Screenshot_2017-02-21-14-34-34-180x300.png)(http://blog.elfsystems.pro/wp-content/uploads/2017/02/Screenshot_2017-02-21-14-35-05.png)

El codigo puede fallar a la primera ya que tiene algunas dependencias que no vienen instaladas con python por defecto. Los scrapers utilizan BeautifulSoup, MySQLdb y otros, que deberán ir instalando con pip install

Código (bash) [Seleccionar]
pip install MySQLdb
pip install beautifulsoup


SI falla deberían parar el script con ctrl + z , ya que utiliza hilos.
Como conclusión, se pueden automatizar consultas procesos, responder consultas para empresas, todo usando de gateway whatsapp.




Actualización Fix 22/02/2017

Esta dando problemas al recibir contactos y audios la api, por lo que el script se detiene.

Reemplazar el código de  /usr/lib/python2.7/site-packages/yowsup2-2.5.0-py2.7.egg/yowsup/layers/axolotl/layer_receive.py

Código (python) [Seleccionar]
from .layer_base import AxolotlBaseLayer

from yowsup.layers.protocol_receipts.protocolentities import OutgoingReceiptProtocolEntity
from yowsup.layers.protocol_messages.proto.wa_pb2 import *
from yowsup.layers.axolotl.protocolentities import *
from yowsup.structs import ProtocolTreeNode
from yowsup.layers.axolotl.props import PROP_IDENTITY_AUTOTRUST

from axolotl.protocol.prekeywhispermessage import PreKeyWhisperMessage
from axolotl.protocol.whispermessage import WhisperMessage
from axolotl.sessioncipher import SessionCipher
from axolotl.groups.groupcipher import GroupCipher
from axolotl.invalidmessageexception import InvalidMessageException
from axolotl.duplicatemessagexception import DuplicateMessageException
from axolotl.invalidkeyidexception import InvalidKeyIdException
from axolotl.nosessionexception import NoSessionException
from axolotl.untrustedidentityexception import UntrustedIdentityException
from axolotl.axolotladdress import AxolotlAddress
from axolotl.groups.senderkeyname import SenderKeyName
from axolotl.groups.groupsessionbuilder import GroupSessionBuilder
from axolotl.protocol.senderkeydistributionmessage import SenderKeyDistributionMessage

import logging
import copy
logger = logging.getLogger(__name__)
#/usr/lib/python2.7/site-packages/yowsup2-2.5.0-py2.7.egg/yowsup/layers/axolotl path fix
class AxolotlReceivelayer(AxolotlBaseLayer):
    def __init__(self):
        super(AxolotlReceivelayer, self).__init__()
        self.v2Jids = [] #people we're going to send v2 enc messages
        self.sessionCiphers = {}
        self.groupCiphers = {}
        self.pendingIncomingMessages = {} #(jid, participantJid?) => message

    def receive(self, protocolTreeNode):
        """
        :type protocolTreeNode: ProtocolTreeNode
        """
        if not self.processIqRegistry(protocolTreeNode):
            if protocolTreeNode.tag == "message":
                self.onMessage(protocolTreeNode)
            elif not protocolTreeNode.tag == "receipt":
                #receipts will be handled by send layer
                self.toUpper(protocolTreeNode)

            # elif protocolTreeNode.tag == "iq":
            #     if protocolTreeNode.getChild("encr_media"):
            #         protocolTreeNode.addChild("media", {
            #             "url": protocolTreeNode["url"],
            #             "ip": protocolTreeNode["ip"],
            #         })
            #         self.toUpper(protocolTreeNode)
            #         return

    ######

    def onEncrMediaResult(self, resultNode):
        pass


    def processPendingIncomingMessages(self, jid, participantJid = None):
        conversationIdentifier = (jid, participantJid)
        if conversationIdentifier in self.pendingIncomingMessages:
            for messageNode in self.pendingIncomingMessages[conversationIdentifier]:
                self.onMessage(messageNode)

            del self.pendingIncomingMessages[conversationIdentifier]

    ##### handling received data #####

    def onMessage(self, protocolTreeNode):
        encNode = protocolTreeNode.getChild("enc")
        if encNode:
            self.handleEncMessage(protocolTreeNode)
        else:
            self.toUpper(protocolTreeNode)

    def handleEncMessage(self, node):
        encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node)
        isGroup =  node["participant"] is not None
        senderJid = node["participant"] if isGroup else node["from"]
        if node.getChild("enc")["v"] == "2" and node["from"] not in self.v2Jids:
            self.v2Jids.append(node["from"])
        try:
            if encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_PKMSG):
                self.handlePreKeyWhisperMessage(node)
            elif encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_MSG):
                self.handleWhisperMessage(node)
            if encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_SKMSG):
                self.handleSenderKeyMessage(node)
        except (InvalidMessageException, InvalidKeyIdException) as e:
            logger.warning("InvalidMessage or KeyId for %s, going to send a retry", encMessageProtocolEntity.getAuthor(False))
            retry = RetryOutgoingReceiptProtocolEntity.fromMessageNode(node, self.store.getLocalRegistrationId())
            self.toLower(retry.toProtocolTreeNode())
        except NoSessionException as e:
            logger.warning("No session for %s, getting their keys now", encMessageProtocolEntity.getAuthor(False))

            conversationIdentifier = (node["from"], node["participant"])

            if conversationIdentifier not in self.pendingIncomingMessages:
                self.pendingIncomingMessages[conversationIdentifier] = []
            self.pendingIncomingMessages[conversationIdentifier].append(node)

            successFn = lambda successJids, b: self.processPendingIncomingMessages(*conversationIdentifier) if len(successJids) else None

            self.getKeysFor([senderJid], successFn)

        except DuplicateMessageException as e:
            logger.warning("Received a message that we've previously decrypted, goint to send the delivery receipt myself")
            self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode())

        except UntrustedIdentityException as e:
            if self.getProp(PROP_IDENTITY_AUTOTRUST, False):
                logger.warning("Autotrusting identity for %s", e.getName())
                self.store.saveIdentity(e.getName(), e.getIdentityKey())
                return self.handleEncMessage(node)
            else:
                logger.error("Ignoring message with untrusted identity")

    def handlePreKeyWhisperMessage(self, node):
        pkMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node)
        enc = pkMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_PKMSG)
        preKeyWhisperMessage = PreKeyWhisperMessage(serialized=enc.getData())
        sessionCipher = self.getSessionCipher(pkMessageProtocolEntity.getAuthor(False))
        plaintext = sessionCipher.decryptPkmsg(preKeyWhisperMessage)
        if enc.getVersion() == 2:
            paddingByte = plaintext[-1] if type(plaintext[-1]) is int else ord(plaintext[-1])
            padding = paddingByte & 0xFF
            self.parseAndHandleMessageProto(pkMessageProtocolEntity, plaintext[:-padding])
        else:
            self.handleConversationMessage(node, plaintext)

    def handleWhisperMessage(self, node):
        encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node)

        enc = encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_MSG)
        whisperMessage = WhisperMessage(serialized=enc.getData())
        sessionCipher = self.getSessionCipher(encMessageProtocolEntity.getAuthor(False))
        plaintext = sessionCipher.decryptMsg(whisperMessage)

        if enc.getVersion() == 2:
            paddingByte = plaintext[-1] if type(plaintext[-1]) is int else ord(plaintext[-1])
            padding = paddingByte & 0xFF
            self.parseAndHandleMessageProto(encMessageProtocolEntity, plaintext[:-padding])
        else:
            self.handleConversationMessage(encMessageProtocolEntity.toProtocolTreeNode(), plaintext)

    def handleSenderKeyMessage(self, node):
        encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node)
        enc = encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_SKMSG)

        senderKeyName = SenderKeyName(encMessageProtocolEntity.getFrom(True), AxolotlAddress(encMessageProtocolEntity.getParticipant(False), 0))
        groupCipher = GroupCipher(self.store, senderKeyName)
        try:
            plaintext = groupCipher.decrypt(enc.getData())
            padding = ord(plaintext[-1]) & 0xFF
            plaintext = plaintext[:-padding]
            plaintext = plaintext.encode() if sys.version_info >= (3, 0) else plaintext
            self.parseAndHandleMessageProto(encMessageProtocolEntity, plaintext)

        except NoSessionException as e:
            logger.warning("No session for %s, going to send a retry", encMessageProtocolEntity.getAuthor(False))
            retry = RetryOutgoingReceiptProtocolEntity.fromMessageNode(node, self.store.getLocalRegistrationId())
            self.toLower(retry.toProtocolTreeNode())

    def parseAndHandleMessageProto(self, encMessageProtocolEntity, serializedData):
        node = encMessageProtocolEntity.toProtocolTreeNode()
        m = Message()
        handled = False
        try:
            m.ParseFromString(serializedData)
        except:
            print("DUMP:")
            print(serializedData)
            print([s for s in serializedData])
            print([ord(s) for s in serializedData])
            raise
        if not m or not serializedData:
            raise ValueError("Empty message")

        if m.HasField("sender_key_distribution_message"):
            handled = True
            axolotlAddress = AxolotlAddress(encMessageProtocolEntity.getParticipant(False), 0)
            self.handleSenderKeyDistributionMessage(m.sender_key_distribution_message, axolotlAddress)

        if m.HasField("conversation"):
            handled = True
            self.handleConversationMessage(node, m.conversation)
        elif m.HasField("contact_message"):
            handled = True
            self.handleContactMessage(node, m.contact_message)
        elif m.HasField("url_message"):
            handled = True
            self.handleUrlMessage(node, m.url_message)
        elif m.HasField("location_message"):
            handled = True
            #self.handleLocationMessage(node, m.location_message)
        elif m.HasField("image_message"):
            handled = True
            self.handleImageMessage(node, m.image_message)

        if not handled:
            print(m)
            #raise ValueError("Unhandled")

    def handleSenderKeyDistributionMessage(self, senderKeyDistributionMessage, axolotlAddress):
        groupId = senderKeyDistributionMessage.groupId
        axolotlSenderKeyDistributionMessage = SenderKeyDistributionMessage(serialized=senderKeyDistributionMessage.axolotl_sender_key_distribution_message)
        groupSessionBuilder = GroupSessionBuilder(self.store)
        senderKeyName = SenderKeyName(groupId, axolotlAddress)
        groupSessionBuilder.process(senderKeyName, axolotlSenderKeyDistributionMessage)

    def handleConversationMessage(self, originalEncNode, text):
        messageNode = copy.deepcopy(originalEncNode)
        messageNode.children = []
        messageNode.addChild(ProtocolTreeNode("body", data = text))
        self.toUpper(messageNode)

    def handleImageMessage(self, originalEncNode, imageMessage):
        messageNode = copy.deepcopy(originalEncNode)
        messageNode["type"] = "media"
        mediaNode = ProtocolTreeNode("media", {
            "type": "image",
            "filehash": imageMessage.file_sha256,
            "size": str(imageMessage.file_length),
            "url": imageMessage.url,
            "mimetype": imageMessage.mime_type,
            "width": imageMessage.width,
            "height": imageMessage.height,
            "caption": imageMessage.caption,
            "encoding": "raw",
            "file": "enc",
            "ip": "0"
        }, data = imageMessage.jpeg_thumbnail)
        messageNode.addChild(mediaNode)

        self.toUpper(messageNode)

    def handleUrlMessage(self, originalEncNode, urlMessage):
        #convert to ??
        pass

    def handleDocumentMessage(self, originalEncNode, documentMessage):
        #convert to ??
        pass

    def handleLocationMessage(self, originalEncNode, locationMessage):
        messageNode = copy.deepcopy(originalEncNode)
        messageNode["type"] = "media"
        mediaNode = ProtocolTreeNode("media", {
            "latitude": locationMessage.degrees_latitude,
            "longitude": locationMessage.degress_longitude,
            "name": "%s %s" % (locationMessage.name, locationMessage.address),
            "url": locationMessage.url,
            "encoding": "raw",
            "type": "location"
        }, data=locationMessage.jpeg_thumbnail)
        messageNode.addChild(mediaNode)
        self.toUpper(messageNode)

    def handleContactMessage(self, originalEncNode, contactMessage):
        messageNode = copy.deepcopy(originalEncNode)
        messageNode["type"] = "media"
        mediaNode = ProtocolTreeNode("media", {
            "type": "vcard"
        }, [
            ProtocolTreeNode("vcard", {"name": contactMessage.display_name}, data = contactMessage.vcard)
        ] )
        messageNode.addChild(mediaNode)
        self.toUpper(messageNode)

    def getSessionCipher(self, recipientId):
        if recipientId in self.sessionCiphers:
            sessionCipher = self.sessionCiphers[recipientId]
        else:
            sessionCipher = SessionCipher(self.store, self.store, self.store, self.store, recipientId, 1)
            self.sessionCiphers[recipientId] = sessionCipher

        return sessionCipher

    def getGroupCipher(self, groupId, senderId):
        senderKeyName = SenderKeyName(groupId, AxolotlAddress(senderId, 1))
        if senderKeyName in self.groupCiphers:
            groupCipher = self.groupCiphers[senderKeyName]
        else:
            groupCipher = GroupCipher(self.store, senderKeyName)
            self.groupCiphers[senderKeyName] = groupCipher
        return groupCipher




Autor: Jorge T
Fuente: http://blog.elfsystems.pro/2017/02/bot-whatsapp-en-python/



Espero que les sirva!
ANTRAX
Título: Re:Bot Whatsapp en Python
Publicado por: VinC90 en Febrero 23, 2017, 09:55:24 AM
Enhorabuena! Gran Trabajo!
Título: Re:Bot Whatsapp en Python
Publicado por: Leo_Al en Febrero 26, 2017, 01:02:58 AM
Hola, en los comandos que dice "sudo", desde que programa lo tendría que hacer en Windows?
Título: Re:Bot Whatsapp en Python
Publicado por: kid_goth en Febrero 26, 2017, 01:15:19 AM
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Hola, en los comandos que dice "sudo", desde que programa lo tendría que hacer en Windows?

no tienes que... y si te jode por permisos, abre ese cmd con clic derecho "Abrir como administrador"
Título: Re:Bot Whatsapp en Python
Publicado por: Leo_Al en Febrero 26, 2017, 01:22:03 AM
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Hola, en los comandos que dice "sudo", desde que programa lo tendría que hacer en Windows?

no tienes que... y si te jode por permisos, abre ese cmd con clic derecho "Abrir como administrador"

Listo, puse "setup.py build" y funcionó, gracias!
Título: Re:Bot Whatsapp en Python
Publicado por: Leo_Al en Marzo 02, 2017, 09:01:42 AM
Hola, tengo un problema, es que al poner el codigo
Código (bash) [Seleccionar]
yowsup-cli registration --requestcode sms --phone 549XXXXXXXXXX --cc 54 --mcc 722 --mnc XX me dice que el mensaje se está enviando pero nunca llega, ya intenté con otro número y pasa lo mismo
Título: Re:Bot Whatsapp en Python
Publicado por: ANTRAX en Marzo 02, 2017, 12:08:30 PM
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Hola, tengo un problema, es que al poner el codigo
Código (bash) [Seleccionar]
yowsup-cli registration --requestcode sms --phone 549XXXXXXXXXX --cc 54 --mcc 722 --mnc XX me dice que el mensaje se está enviando pero nunca llega, ya intenté con otro número y pasa lo mismo

Bueno, estuve hablando con el autor del post sobre tu problema. Al parecer a partir del 28 de Febrero cambiaron las politicas de WhatsApp y bannean los numeros que intentan ser bots.
En el github del script varios comentan que les ha pasado lo mismo.

Lo que habría que hacer, es probar un numero que ya use whatsapp desde hace rato para ver que pasa... Pero bueno, corres el riesgo de que lo banneen tambien.

Por el momento lo mas seguro es hacer bots para Telegram. Pronto subiré un post sobre como hacerlos.

Saludos,
ANTRAX
Título: Re:Bot Whatsapp en Python
Publicado por: Leo_Al en Marzo 02, 2017, 01:16:29 PM
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Hola, tengo un problema, es que al poner el codigo
Código (bash) [Seleccionar]
yowsup-cli registration --requestcode sms --phone 549XXXXXXXXXX --cc 54 --mcc 722 --mnc XX me dice que el mensaje se está enviando pero nunca llega, ya intenté con otro número y pasa lo mismo

Bueno, estuve hablando con el autor del post sobre tu problema. Al parecer a partir del 28 de Febrero cambiaron las politicas de WhatsApp y bannean los numeros que intentan ser bots.
En el github del script varios comentan que les ha pasado lo mismo.

Lo que habría que hacer, es probar un numero que ya use whatsapp desde hace rato para ver que pasa... Pero bueno, corres el riesgo de que lo banneen tambien.

Por el momento lo mas seguro es hacer bots para Telegram. Pronto subiré un post sobre como hacerlos.

Saludos,
ANTRAX
Ah ok, muchas gracias por responder!
Título: Re:Bot Whatsapp en Python
Publicado por: kendex en Septiembre 13, 2017, 05:19:15 PM
Hola a todos y muchas gracias por el contenido para el bot,

Ahora bien, tengo una duda, tendrán los tokens correspondientes a la versión 2.17.52 de whatsapp?, ya que pruebo los expuestos en el sitio, pero me arroja lo siguiente:


yowsup-cli  v2.0.15
yowsup      v2.5.2

Copyright (c) 2012-2016 Tarek Galal
http://www.openwhatsapp.org

This software is provided free of charge. Copying and redistribution is
encouraged.

If you appreciate this software and you would like to support future
development please consider donating:
http://openwhatsapp.org/yowsup/donate


INFO:yowsup.common.http.warequest:b'{"login":"XXXXXXXXXX","status":"fail","reason":"bad_token"}\n'
status: b'fail'
reason: b'bad_token'
login: b'XXXXXXXXXX'



Saludos.
Título: Re:Bot Whatsapp en Python
Publicado por: RickHunterEc en Septiembre 16, 2017, 02:34:08 PM
 :) buen trabajo
Título: Re:Bot Whatsapp en Python
Publicado por: leomontilla en Febrero 19, 2018, 09:34:54 PM
Excelente trabajo, me parece excepcional el código. Me parece muy bien que la gente se interese por innovar en la tecnología.
Título: Re:Bot Whatsapp en Python
Publicado por: Fabio en Julio 26, 2018, 07:37:09 PM
Saludos, me puede decir alguien si aun funciona el Bot Whatsapp en Python ???¡
Título: Re:Bot Whatsapp en Python
Publicado por: Prxm3t30 en Noviembre 12, 2019, 06:50:43 AM
Buenas Tardes,
Podría re-subir el código fuente, el link me está dando error.
Un Saludo.