(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.
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
_VERSION = "2.16.9"
_TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1471306425030{phone}"
Por
_VERSION = "2.16.11"
_TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk1478194306452{phone}"
Y con esto debería funcionar, ahora falta compilar de nuevo.
sudo python setup.py build
Instalar
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
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
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
yowsup-cli registration --register XXXXX --phone 549XXXXXXXXXX --cc 54 -d
Ejemplo:
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.
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.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
#!/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
#!/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
#!/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
#!/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
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.
tmux
python run.py &
ctrl+b y luego presionar d
para volver a la sesión tmux
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
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/2017Esta 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.pyfrom .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
Hola, en los comandos que dice "sudo", desde que programa lo tendría que hacer en Windows?
No tienes permitido ver los links.
Registrarse o Entrar a mi cuenta
Hola, tengo un problema, es que al poner el codigo 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
Excelente trabajo, me parece excepcional el código. Me parece muy bien que la gente se interese por innovar en la tecnología.