Merge branch 'yowsup-2' into setuptools

This commit is contained in:
Steffen Vogel 2017-02-11 16:15:04 -03:00
commit 766c40c790
15 changed files with 645 additions and 358 deletions

View file

@ -6,7 +6,6 @@ transWhat is a WhatsApp XMPP Gateway based on [Spectrum 2](http://www.spectrum.i
- [yowsup-1](http://github.com/stv0g/transwhat/tree/yowsup-1) My original version which is based on @tgalal first Yowsup version (**deprecated** and broken). - [yowsup-1](http://github.com/stv0g/transwhat/tree/yowsup-1) My original version which is based on @tgalal first Yowsup version (**deprecated** and broken).
- [yowsup-2](http://github.com/stv0g/transwhat/tree/yowsup-2) Major rewrite from @moyamo for @tgalal's new Yowsup 2 (**recommended**). - [yowsup-2](http://github.com/stv0g/transwhat/tree/yowsup-2) Major rewrite from @moyamo for @tgalal's new Yowsup 2 (**recommended**).
- [develop](http://github.com/stv0g/transwhat/tree/develop) A develop branch with the latest fixes and improvements. This branch is based on `yowsup-2`.
For production, please use the `yowsup-2` branch. For production, please use the `yowsup-2` branch.

View file

@ -1,3 +1,29 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import protocol_pb2 import protocol_pb2
import socket import socket
import struct import struct
@ -11,7 +37,7 @@ import resource
def WRAP(MESSAGE, TYPE): def WRAP(MESSAGE, TYPE):
wrap = protocol_pb2.WrapperMessage() wrap = protocol_pb2.WrapperMessage()
wrap.type = TYPE wrap.type = TYPE
wrap.payload = MESSAGE wrap.payload = bytes(MESSAGE)
return wrap.SerializeToString() return wrap.SerializeToString()
class SpectrumBackend: class SpectrumBackend:
@ -24,7 +50,7 @@ class SpectrumBackend:
def __init__(self): def __init__(self):
self.m_pingReceived = False self.m_pingReceived = False
self.m_data = "" self.m_data = bytes("")
self.m_init_res = 0 self.m_init_res = 0
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
@ -67,7 +93,7 @@ class SpectrumBackend:
vcard.id = ID vcard.id = ID
vcard.fullname = fullName vcard.fullname = fullName
vcard.nickname = nickname vcard.nickname = nickname
vcard.photo = photo vcard.photo = bytes(photo)
message = WRAP(vcard.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_VCARD) message = WRAP(vcard.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_VCARD)
self.send(message) self.send(message)
@ -220,7 +246,7 @@ class SpectrumBackend:
def handleFTData(self, ftID, data): def handleFTData(self, ftID, data):
d = protocol_pb2.FileTransferData() d = protocol_pb2.FileTransferData()
d.ftid = ftID d.ftid = ftID
d.data = data d.data = bytes(data)
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_DATA); message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_DATA);
self.send(message) self.send(message)
@ -283,8 +309,6 @@ class SpectrumBackend:
return return
self.handleMessageAckRequest(payload.userName, payload.buddyName, payload.id) self.handleMessageAckRequest(payload.userName, payload.buddyName, payload.id)
def handleAttentionPayload(self, data): def handleAttentionPayload(self, data):
payload = protocol_pb2.ConversationMessage() payload = protocol_pb2.ConversationMessage()
if (payload.ParseFromString(data) == False): if (payload.ParseFromString(data) == False):

View file

@ -1,3 +1,29 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import asyncore, socket import asyncore, socket
import logging import logging
import sys import sys
@ -12,7 +38,7 @@ class IOChannel(asyncore.dispatcher):
self.callback = callback self.callback = callback
self.closeCallback = closeCallback self.closeCallback = closeCallback
self.buffer = "" self.buffer = bytes("")
def sendData(self, data): def sendData(self, data):
self.buffer += data self.buffer += data

141
config.py Normal file
View file

@ -0,0 +1,141 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# I'm guessing this is the format of the spectrum config file in BNF
# <config_file> ::= <line>*
# <line> ::= <space>* <expr> <space>* <newline> | <space*>
# <expr> ::= <section> | <assignment>
# <section> ::= [<identifier>*]
# <assignment> ::= <identifier> <space>* = <space>* <value>
class SpectrumConfig:
"""
Represents spectrum2 configuration options.
"""
def __init__(self, path_to_config_file):
"""
Initialises configuration file.
Args:
path_to_config_file: The absolute path to the configuration file.
"""
self.config_path = path_to_config_file
self.options = self.loadConfig(self.config_path)
# Load backend_logging information
self.options.update(self.loadConfig(self['logging.backend_config']))
def loadConfig(self, file_name):
section = {'a': ""} # Current section heading,
# It's a dictionary because variables in python closures can't be
# assigned to.
options = dict()
# Recursive descent parser
def consume_spaces(line):
i = 0
for c in line:
if c != ' ':
break
i += 1
return line[i:]
def read_identifier(line):
i = 0
for c in line:
if c == ' ' or c==']' or c=='[' or c=='=':
break
i += 1
# no identifier
if i == 0:
return (None, 'No identifier')
return (line[:i], line[i:])
def parse_section(line):
if len(line) == 0 or line[0] != '[':
return (None, 'expected [')
line = line[1:]
identifier, line = read_identifier(line)
if len(line) == 0 or line[0] != ']' or identifier is None:
return (None, line)
return (identifier, line[1:])
def parse_assignment(line):
key, line = read_identifier(line)
if key is None:
return (None, None, line)
line = consume_spaces(line)
if len(line) == 0 or line[0] != '=':
return (None, None, 'Expected =')
line = consume_spaces(line[1:])
value = line[:-1]
return (key, value, '\n')
def expr(line):
sec, newline = parse_section(line)
if sec is not None:
section['a'] = sec
else:
key, value, newline = parse_assignment(line)
if key is not None:
if section['a'] != '':
options[section['a'] + '.' + key] = value
else:
options[key] = value
else:
return (None, newline)
return (newline, None)
def parse_line(line, line_number):
line = consume_spaces(line)
if line == '\n':
return
newline, error = expr(line)
if newline is None:
raise ConfigParseError(str(line_number) + ': ' + error + ': ' + repr(line))
newline = consume_spaces(newline)
if newline != '\n':
raise ConfigParseError(str(line_number) + ': Expected newline got ' + repr(newline))
def strip_comments(line):
i = 0
for c in line:
if c == '#' or c == '\n':
break
i += 1
return line[:i] + '\n'
with open(file_name, 'r') as f:
i = 1
while True:
line = f.readline()
if line == '':
break
parse_line(strip_comments(line), i)
i += 1
return options
def __getitem__(self, key):
return self.options[key]
class ConfigParseError(Exception):
pass

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,6 +21,9 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import threading import threading
import inspect import inspect
import re import re

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,6 +21,9 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
from Spectrum2 import protocol_pb2 from Spectrum2 import protocol_pb2
import logging import logging
@ -36,7 +39,7 @@ class Buddy():
def __init__(self, owner, number, nick, statusMsg, groups, image_hash): def __init__(self, owner, number, nick, statusMsg, groups, image_hash):
self.nick = nick self.nick = nick
self.owner = owner self.owner = owner
self.number = number self.number = "%s" % number
self.groups = groups self.groups = groups
self.image_hash = image_hash if image_hash is not None else "" self.image_hash = image_hash if image_hash is not None else ""
self.statusMsg = u"" self.statusMsg = u""
@ -50,7 +53,8 @@ class Buddy():
self.image_hash = image_hash self.image_hash = image_hash
def __str__(self): def __str__(self):
return "%s (nick=%s)" % (self.number, self.nick) # we must return str here
return str("%s (nick=%s)") % (self.number, self.nick)
class BuddyList(dict): class BuddyList(dict):
@ -65,7 +69,7 @@ class BuddyList(dict):
for buddy in buddies: for buddy in buddies:
number = buddy.buddyName number = buddy.buddyName
nick = buddy.alias nick = buddy.alias
statusMsg = buddy.statusMessage.decode('utf-8') statusMsg = buddy.statusMessage
groups = [g for g in buddy.group] groups = [g for g in buddy.group]
image_hash = buddy.iconHash image_hash = buddy.iconHash
self[number] = Buddy(self.owner, number, nick, statusMsg, self[number] = Buddy(self.owner, number, nick, statusMsg,
@ -79,7 +83,7 @@ class BuddyList(dict):
self.session.sendSync(contacts, delta=False, interactive=True, self.session.sendSync(contacts, delta=False, interactive=True,
success=self.onSync) success=self.onSync)
self.logger.debug("Roster add: %s", str(list(contacts))) self.logger.debug("Roster add: %s" % list(contacts))
for number in contacts: for number in contacts:
buddy = self[number] buddy = self[number]
@ -90,23 +94,27 @@ class BuddyList(dict):
for number in existing: for number in existing:
self.session.subscribePresence(number) self.session.subscribePresence(number)
self.logger.debug("%s is requesting statuses of: %s", self.user, existing) self.logger.debug("%s is requesting statuses of: %s" % (self.user, existing))
self.session.requestStatuses(existing, success = self.onStatus) self.session.requestStatuses(existing, success = self.onStatus)
self.logger.debug("Removing nonexisting buddies %s", nonexisting) self.logger.debug("Removing nonexisting buddies %s" % nonexisting)
for number in nonexisting: for number in nonexisting:
self.remove(number) self.remove(number)
del self[number] try: del self[number]
except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number)
self.logger.debug("Removing invalid buddies %s", invalid) self.logger.debug("Removing invalid buddies %s" % invalid)
for number in invalid: for number in invalid:
self.remove(number) self.remove(number)
del self[number] try: del self[number]
except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % number)
def onStatus(self, contacts): def onStatus(self, contacts):
self.logger.debug("%s received statuses of: %s", self.user, contacts) self.logger.debug("%s received statuses of: %s" % (self.user, contacts))
for number, (status, time) in contacts.iteritems(): for number, (status, time) in contacts.iteritems():
buddy = self[number] try: buddy = self[number]
except KeyError: self.logger.warn("received status of buddy not in list: %s" % number)
if status is None: if status is None:
buddy.statusMsg = "" buddy.statusMsg = ""
else: else:
@ -127,7 +135,7 @@ class BuddyList(dict):
else: else:
buddy = Buddy(self.owner, number, nick, "", groups, image_hash) buddy = Buddy(self.owner, number, nick, "", groups, image_hash)
self[number] = buddy self[number] = buddy
self.logger.debug("Roster add: %s", buddy) self.logger.debug("Roster add: %s" % buddy)
self.session.sendSync([number], delta = True, interactive = True) self.session.sendSync([number], delta = True, interactive = True)
self.session.subscribePresence(number) self.session.subscribePresence(number)
self.session.requestStatuses([number], success = self.onStatus) self.session.requestStatuses([number], success = self.onStatus)
@ -151,9 +159,9 @@ class BuddyList(dict):
iconHash = buddy.image_hash if buddy.image_hash is not None else "" iconHash = buddy.image_hash if buddy.image_hash is not None else ""
self.logger.debug("Updating buddy %s (%s) in %s, image_hash = %s", self.logger.debug("Updating buddy %s (%s) in %s, image_hash = %s" %
buddy.nick, buddy.number, buddy.groups, iconHash) (buddy.nick, buddy.number, buddy.groups, iconHash))
self.logger.debug("Status Message: %s", statusmsg) self.logger.debug("Status Message: %s" % statusmsg)
self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick, self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick,
buddy.groups, status, statusMessage=statusmsg, iconHash=iconHash) buddy.groups, status, statusMessage=statusmsg, iconHash=iconHash)
@ -190,7 +198,7 @@ class BuddyList(dict):
buddynr = self.session.legacyName buddynr = self.session.legacyName
# Get profile picture # Get profile picture
self.logger.debug('Requesting profile picture of %s', buddynr) self.logger.debug('Requesting profile picture of %s' % buddynr)
response = deferred.Deferred() response = deferred.Deferred()
# Error probably means image doesn't exist # Error probably means image doesn't exist
error = deferred.Deferred() error = deferred.Deferred()
@ -201,12 +209,12 @@ class BuddyList(dict):
pictureData = response.pictureData() pictureData = response.pictureData()
# Send VCard # Send VCard
if ID != None: if ID != None:
call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s', call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s' %
ID, response.pictureId(), pictureData.then(base64.b64encode)) (ID, response.pictureId(), pictureData.then(base64.b64encode)))
call(self.backend.handleVCard, self.user, ID, buddy, "", "", call(self.backend.handleVCard, self.user, ID, buddy, "", "",
pictureData) pictureData)
# If error # If error
error.when(self.logger.debug, 'Sending VCard (%s) without image', ID) error.when(self.logger.debug, 'Sending VCard (%s) without image' % ID)
error.when(self.backend.handleVCard, self.user, ID, buddy, "", "", "") error.when(self.backend.handleVCard, self.user, ID, buddy, "", "", "")
# Send image hash # Send image hash
@ -219,7 +227,7 @@ class BuddyList(dict):
nick = "" nick = ""
groups = [] groups = []
image_hash = pictureData.then(utils.sha1hash) image_hash = pictureData.then(utils.sha1hash)
call(self.logger.debug, 'Image hash is %s', image_hash) call(self.logger.debug, 'Image hash is %s' % image_hash)
call(self.update, buddynr, nick, groups, image_hash) call(self.update, buddynr, nick, groups, image_hash)
# No image # No image
error.when(self.logger.debug, 'No image') error.when(self.logger.debug, 'No image')

View file

@ -1,3 +1,29 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
from functools import partial from functools import partial
class Deferred(object): class Deferred(object):

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,6 +21,9 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
from Spectrum2 import protocol_pb2 from Spectrum2 import protocol_pb2
class Group(): class Group():

View file

@ -1,3 +1,28 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
from Spectrum2 import protocol_pb2 from Spectrum2 import protocol_pb2
from yowsupwrapper import YowsupApp from yowsupwrapper import YowsupApp
@ -34,13 +59,13 @@ class RegisterSession(YowsupApp):
self.backend.handleMessage(self.user, 'bot', self.backend.handleMessage(self.user, 'bot',
'Country code must be a number') 'Country code must be a number')
else: # Succeded in decoding country code else: # Succeded in decoding country code
country_code = str(country_code) country_code = "%s" % country_code
if country_code != self.number[:len(country_code)]: if country_code != self.number[:len(country_code)]:
self.backend.handleMessage(self.user, self.backend.handleMessage(self.user,
'bot', 'Number does not start with provided country code') 'bot', 'Number does not start with provided country code')
else: else:
self.backend.handleMessage(self.user, 'bot', 'Requesting sms code') self.backend.handleMessage(self.user, 'bot', 'Requesting sms code')
self.logger.debug('Requesting SMS code for %s', self.user) self.logger.debug('Requesting SMS code for %s' % self.user)
self.countryCode = country_code self.countryCode = country_code
self._requestSMSCodeNonBlock() self._requestSMSCodeNonBlock()
elif buddy == 'bot' and self.state == self.WANT_SMS: elif buddy == 'bot' and self.state == self.WANT_SMS:
@ -51,7 +76,7 @@ class RegisterSession(YowsupApp):
self.backend.handleMessage(self.user, self.backend.handleMessage(self.user,
'bot', 'Invalid code. Must be of the form XXX-XXX.') 'bot', 'Invalid code. Must be of the form XXX-XXX.')
else: else:
self.logger.warn('Unauthorised user (%s) attempting to send messages', self.logger.warn('Unauthorised user (%s) attempting to send messages' %
self.user) self.user)
self.backend.handleMessage(self.user, buddy, self.backend.handleMessage(self.user, buddy,
'You are not logged in yet. You can only send messages to bot.') 'You are not logged in yet. You can only send messages to bot.')
@ -104,7 +129,7 @@ class RegisterSession(YowsupApp):
for k, v in result.items(): for k, v in result.items():
if v is None: if v is None:
continue continue
out.append("%s: %s" %(k, v.encode("utf-8") if type(v) is unistr else v)) out.append("%s: %s" % (k, v))
return "\n".join(out) return "\n".join(out)

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,12 +21,15 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import utils import utils
import logging import logging
import urllib import urllib
import time import time
from PIL import Image # from PIL import Image
import sys import sys
import os import os
@ -50,16 +53,13 @@ class MsgIDs:
self.waId = waId self.waId = waId
self.cnt = 0 self.cnt = 0
class Session(YowsupApp): class Session(YowsupApp):
broadcast_prefix = u'\U0001F4E2 ' broadcast_prefix = '\U0001F4E2 '
def __init__(self, backend, user, legacyName, extra): def __init__(self, backend, user, legacyName, extra):
super(Session, self).__init__() super(Session, self).__init__()
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.logger.info("Created: %s", legacyName) self.logger.info("Created: %s" % legacyName)
self.backend = backend self.backend = backend
self.user = user self.user = user
@ -99,12 +99,12 @@ class Session(YowsupApp):
self.logout() self.logout()
def logout(self): def logout(self):
self.logger.info("%s logged out", self.user) self.logger.info("%s logged out" % self.user)
super(Session, self).logout() super(Session, self).logout()
self.loggedIn = False self.loggedIn = False
def login(self, password): def login(self, password):
self.logger.info("%s attempting login", self.user) self.logger.info("%s attempting login" % self.user)
self.password = password self.password = password
self.shouldBeConncted = True self.shouldBeConncted = True
super(Session, self).login(self.legacyName, self.password) super(Session, self).login(self.legacyName, self.password)
@ -126,14 +126,14 @@ class Session(YowsupApp):
rooms.append([self._shortenGroupId(room), group.subject]) rooms.append([self._shortenGroupId(room), group.subject])
text.append(self._shortenGroupId(room) + '@' + self.backend.spectrum_jid + ' :' + group.subject) text.append(self._shortenGroupId(room) + '@' + self.backend.spectrum_jid + ' :' + group.subject)
self.logger.debug("Got rooms: %s", rooms) self.logger.debug("Got rooms: %s" % rooms)
self.backend.handleRoomList(rooms) self.backend.handleRoomList(rooms)
message = "Note, you are a participant of the following groups:\n" +\ message = "Note, you are a participant of the following groups:\n" + \
'\n'.join(text) + '\nIf you do not join them you will lose messages' "\n".join(text) + "\nIf you do not join them you will lose messages"
#self.bot.send(message) #self.bot.send(message)
def _updateGroups(self, response, request): def _updateGroups(self, response, request):
self.logger.debug('Received groups list %s', response) self.logger.debug('Received groups list %s' % response)
groups = response.getGroups() groups = response.getGroups()
for group in groups: for group in groups:
room = group.getId() room = group.getId()
@ -159,8 +159,8 @@ class Session(YowsupApp):
msg = self.groupOfflineQueue[room].pop(0) msg = self.groupOfflineQueue[room].pop(0)
self.backend.handleMessage(self.user, room, msg[1], self.backend.handleMessage(self.user, room, msg[1],
msg[0], "", msg[2]) msg[0], "", msg[2])
self.logger.debug("Send queued group message to: %s %s %s", self.logger.debug("Send queued group message to: %s %s %s" %
msg[0],msg[1], msg[2]) (msg[0],msg[1], msg[2]))
self.gotGroupList = True self.gotGroupList = True
for room, nick in self.joinRoomQueue: for room, nick in self.joinRoomQueue:
self.joinRoom(room, nick) self.joinRoom(room, nick)
@ -173,8 +173,8 @@ class Session(YowsupApp):
return return
room = self._lengthenGroupId(room) room = self._lengthenGroupId(room)
if room in self.groups: if room in self.groups:
self.logger.info("Joining room: %s room=%s, nick=%s", self.logger.info("Joining room: %s room=%s, nick=%s" %
self.legacyName, room, nick) (self.legacyName, room, nick))
group = self.groups[room] group = self.groups[room]
group.joined = True group.joined = True
@ -188,39 +188,39 @@ class Session(YowsupApp):
group.sendParticipantsToSpectrum(self.legacyName) group.sendParticipantsToSpectrum(self.legacyName)
self.backend.handleSubject(self.user, self._shortenGroupId(room), self.backend.handleSubject(self.user, self._shortenGroupId(room),
group.subject, ownerNick) group.subject, ownerNick)
self.logger.debug("Room subject: room=%s, subject=%s", self.logger.debug("Room subject: room=%s, subject=%s" %
room, group.subject) (room, group.subject))
self.backend.handleRoomNicknameChanged( self.backend.handleRoomNicknameChanged(
self.user, self._shortenGroupId(room), group.subject self.user, self._shortenGroupId(room), group.subject
) )
else: else:
self.logger.warn("Room doesn't exist: %s", room) self.logger.warn("Room doesn't exist: %s" % room)
def leaveRoom(self, room): def leaveRoom(self, room):
if room in self.groups: if room in self.groups:
self.logger.info("Leaving room: %s room=%s", self.legacyName, room) self.logger.info("Leaving room: %s room=%s" % (self.legacyName, room))
group = self.groups[room] group = self.groups[room]
group.joined = False group.joined = False
else: else:
self.logger.warn("Room doesn't exist: %s. Unable to leave.", room) self.logger.warn("Room doesn't exist: %s. Unable to leave." % room)
def _lastSeen(self, number, seconds): def _lastSeen(self, number, seconds):
self.logger.debug("Last seen %s at %s seconds" % (number, str(seconds))) self.logger.debug("Last seen %s at %s seconds" % (number, seconds))
if seconds < 60: if seconds < 60:
self.onPresenceAvailable(number) self.onPresenceAvailable(number)
else: else:
self.onPresenceUnavailable(number) self.onPresenceUnavailable(number)
def sendReadReceipts(self, buddy): def sendReadReceipts(self, buddy):
for _id, _from, participant in self.recvMsgIDs: for _id, _from, participant, t in self.recvMsgIDs:
if _from.split('@')[0] == buddy: if _from.split('@')[0] == buddy:
self.sendReceipt(_id, _from, 'read', participant) self.sendReceipt(_id, _from, 'read', participant)
self.recvMsgIDs.remove((_id, _from, participant)) self.recvMsgIDs.remove((_id, _from, participant, t))
self.logger.debug("Send read receipt to %s (ID: %s)", _from, _id) self.logger.debug("Send read receipt to %s (ID: %s)", _from, _id)
# Called by superclass # Called by superclass
def onAuthSuccess(self, status, kind, creation, def onAuthSuccess(self, status, kind, creation,
expiration, props, nonce, t): expiration, props, nonce, t):
self.logger.info("Auth success: %s", self.user) self.logger.info("Auth success: %s" % self.user)
self.backend.handleConnected(self.user) self.backend.handleConnected(self.user)
self.backend.handleBuddyChanged(self.user, "bot", self.bot.name, self.backend.handleBuddyChanged(self.user, "bot", self.bot.name,
@ -249,7 +249,7 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onAuthFailed(self, reason): def onAuthFailed(self, reason):
self.logger.info("Auth failed: %s (%s)", self.user, reason) self.logger.info("Auth failed: %s (%s)" % (self.user, reason))
self.backend.handleDisconnected(self.user, 0, reason) self.backend.handleDisconnected(self.user, 0, reason)
self.password = None self.password = None
self.loggedIn = False self.loggedIn = False
@ -261,9 +261,8 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onReceipt(self, _id, _from, timestamp, type, participant, offline, items): def onReceipt(self, _id, _from, timestamp, type, participant, offline, items):
self.logger.debug("received receipt, sending ack: " + self.logger.debug("received receipt, sending ack: %s" %
' '.join(map(str, [_id, _from, timestamp, [ _id, _from, timestamp, type, participant, offline, items ]
type, participant, offline, items]))
) )
try: try:
number = _from.split('@')[0] number = _from.split('@')[0]
@ -272,29 +271,22 @@ class Session(YowsupApp):
if self.msgIDs[_id].cnt == 2: if self.msgIDs[_id].cnt == 2:
del self.msgIDs[_id] del self.msgIDs[_id]
except KeyError: except KeyError:
self.logger.error("Message %s not found. Unable to send ack", _id) self.logger.error("Message %s not found. Unable to send ack" % _id)
# Called by superclass # Called by superclass
def onAck(self, _id, _class, _from, timestamp): def onAck(self, _id, _class, _from, timestamp):
self.logger.debug('received ack ' + self.logger.debug('received ack: %s' % [ _id, _class, _from, timestamp ])
' '.join(map(str, [_id, _class, _from,timestamp,]))
)
# Called by superclass # Called by superclass
def onTextMessage(self, _id, _from, to, notify, timestamp, participant, def onTextMessage(self, _id, _from, to, notify, timestamp, participant,
offline, retry, body): offline, retry, body):
self.logger.debug('received TextMessage' +
' '.join(map(str, [
_id, _from, to, notify, timestamp,
participant, offline, retry, body
]))
)
buddy = _from.split('@')[0] buddy = _from.split('@')[0]
messageContent = utils.softToUni(body) messageContent = utils.softToUni(body)
self.sendReceipt(_id, _from, None, participant) self.sendReceipt(_id, _from, None, participant)
self.recvMsgIDs.append((_id, _from, participant)) self.recvMsgIDs.append((_id, _from, participant, timestamp))
self.logger.info("Message received from %s to %s: %s (at ts=%s)", self.logger.info("Message received from %s to %s: %s (at ts=%s)" %
buddy, self.legacyName, messageContent, timestamp) (buddy, self.legacyName, messageContent, timestamp))
if participant is not None: # Group message or broadcast if participant is not None: # Group message or broadcast
partname = participant.split('@')[0] partname = participant.split('@')[0]
if _from.split('@')[1] == 'broadcast': # Broadcast message if _from.split('@')[1] == 'broadcast': # Broadcast message
@ -310,30 +302,46 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onImage(self, image): def onImage(self, image):
self.logger.debug('Received image message %s', str(image)) self.logger.debug('Received image message: %s' % image)
buddy = image._from.split('@')[0] buddy = image._from.split('@')[0]
participant = image.participant participant = image.participant
if image.caption is None: if image.caption is None:
image.caption = '' image.caption = ''
if image.isEncrypted():
self.logger.debug('Received encrypted image message')
if self.backend.specConf is not None and self.backend.specConf.__getitem__("service.web_directory") is not None and self.backend.specConf.__getitem__("service.web_url") is not None :
ipath = "/" + str(image.timestamp) + image.getExtension()
with open(self.backend.specConf.__getitem__("service.web_directory") + ipath,"wb") as f:
f.write(image.getMediaContent())
url = self.backend.specConf.__getitem__("service.web_url") + ipath
else:
self.logger.warn('Received encrypted image: web storage not set in config!')
url = image.url
else:
url = image.url
if participant is not None: # Group message if participant is not None: # Group message
partname = participant.split('@')[0] partname = participant.split('@')[0]
if image._from.split('@')[1] == 'broadcast': # Broadcast message if image._from.split('@')[1] == 'broadcast': # Broadcast message
self.sendMessageToXMPP(partname, self.broadcast_prefix, image.timestamp) self.sendMessageToXMPP(partname, self.broadcast_prefix, image.timestamp)
self.sendMessageToXMPP(partname, image.url, image.timestamp) self.sendMessageToXMPP(partname, url, image.timestamp)
self.sendMessageToXMPP(partname, image.caption, image.timestamp) self.sendMessageToXMPP(partname, image.caption, image.timestamp)
else: # Group message else: # Group message
self.sendGroupMessageToXMPP(buddy, partname, image.url, image.timestamp) self.sendGroupMessageToXMPP(buddy, partname, url, image.timestamp)
self.sendGroupMessageToXMPP(buddy, partname, image.caption, image.timestamp) self.sendGroupMessageToXMPP(buddy, partname, image.caption, image.timestamp)
else: else:
self.sendMessageToXMPP(buddy, image.url, image.timestamp) self.sendMessageToXMPP(buddy, url, image.timestamp)
self.sendMessageToXMPP(buddy, image.caption, image.timestamp) self.sendMessageToXMPP(buddy, image.caption, image.timestamp)
self.sendReceipt(image._id, image._from, None, image.participant) self.sendReceipt(image._id, image._from, None, image.participant)
self.recvMsgIDs.append((image._id, image._from, image.participant)) self.recvMsgIDs.append((image._id, image._from, image.participant, image.timestamp))
# Called by superclass # Called by superclass
def onAudio(self, audio): def onAudio(self, audio):
self.logger.debug('Received audio message %s', str(audio)) self.logger.debug('Received audio message: %s' % audio)
buddy = audio._from.split('@')[0] buddy = audio._from.split('@')[0]
participant = audio.participant participant = audio.participant
message = audio.url message = audio.url
@ -347,12 +355,12 @@ class Session(YowsupApp):
else: else:
self.sendMessageToXMPP(buddy, message, audio.timestamp) self.sendMessageToXMPP(buddy, message, audio.timestamp)
self.sendReceipt(audio._id, audio._from, None, audio.participant) self.sendReceipt(audio._id, audio._from, None, audio.participant)
self.recvMsgIDs.append((audio._id, audio._from, audio.participant)) self.recvMsgIDs.append((audio._id, audio._from, audio.participant, audio.timestamp))
# Called by superclass # Called by superclass
def onVideo(self, video): def onVideo(self, video):
self.logger.debug('Received video message %s', str(video)) self.logger.debug('Received video message: %s' % video)
buddy = video._from.split('@')[0] buddy = video._from.split('@')[0]
participant = video.participant participant = video.participant
@ -367,7 +375,7 @@ class Session(YowsupApp):
else: else:
self.sendMessageToXMPP(buddy, message, video.timestamp) self.sendMessageToXMPP(buddy, message, video.timestamp)
self.sendReceipt(video._id, video._from, None, video.participant) self.sendReceipt(video._id, video._from, None, video.participant)
self.recvMsgIDs.append((video._id, video._from, video.participant)) self.recvMsgIDs.append((video._id, video._from, video.participant, video.timestamp))
def onLocation(self, location): def onLocation(self, location):
@ -378,9 +386,7 @@ class Session(YowsupApp):
participant = location.participant participant = location.participant
latlong = 'geo:' + latitude + ',' + longitude latlong = 'geo:' + latitude + ',' + longitude
self.logger.debug("Location received from %s: %s, %s", self.logger.debug("Location received from %s: %s, %s", (buddy, latitude, longitude))
buddy, latitude, longitude)
if participant is not None: # Group message if participant is not None: # Group message
partname = participant.split('@')[0] partname = participant.split('@')[0]
@ -398,16 +404,14 @@ class Session(YowsupApp):
self.sendMessageToXMPP(buddy, url, location.timestamp) self.sendMessageToXMPP(buddy, url, location.timestamp)
self.sendMessageToXMPP(buddy, latlong, location.timestamp) self.sendMessageToXMPP(buddy, latlong, location.timestamp)
self.sendReceipt(location._id, location._from, None, location.participant) self.sendReceipt(location._id, location._from, None, location.participant)
self.recvMsgIDs.append((loaction._id, location._from, location.participant)) self.recvMsgIDs.append((location._id, location._from, location.participant, location.timestamp))
# Called by superclass # Called by superclass
def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant): def onVCard(self, _id, _from, name, card_data, to, notify, timestamp, participant):
self.logger.debug('received VCard' + self.logger.debug('received VCard: %s' %
' '.join(map(str, [ [ _id, _from, name, card_data, to, notify, timestamp, participant ]
_id, _from, name, card_data, to, notify, timestamp, participant
]))
) )
message = "Received VCard (not implemented yet)" message = "Received VCard (not implemented yet)"
buddy = _from.split("@")[0] buddy = _from.split("@")[0]
@ -423,19 +427,19 @@ class Session(YowsupApp):
# self.sendMessageToXMPP(buddy, card_data) # self.sendMessageToXMPP(buddy, card_data)
#self.transferFile(buddy, str(name), card_data) #self.transferFile(buddy, str(name), card_data)
self.sendReceipt(_id, _from, None, participant) self.sendReceipt(_id, _from, None, participant)
self.recvMsgIDs.append((_id, _from, participant)) self.recvMsgIDs.append((_id, _from, participant, timestamp))
def transferFile(self, buddy, name, data): def transferFile(self, buddy, name, data):
# Not working # Not working
self.logger.debug('transfering file %s', name) self.logger.debug('transfering file: %s' % name)
self.backend.handleFTStart(self.user, buddy, name, len(data)) self.backend.handleFTStart(self.user, buddy, name, len(data))
self.backend.handleFTData(0, data) self.backend.handleFTData(0, data)
self.backend.handleFTFinish(self.user, buddy, name, len(data), 0) self.backend.handleFTFinish(self.user, buddy, name, len(data), 0)
# Called by superclass # Called by superclass
def onContactTyping(self, buddy): def onContactTyping(self, buddy):
self.logger.info("Started typing: %s", buddy) self.logger.info("Started typing: %s" % buddy)
if buddy != 'bot': if buddy != 'bot':
self.sendPresence(True) self.sendPresence(True)
self.backend.handleBuddyTyping(self.user, buddy) self.backend.handleBuddyTyping(self.user, buddy)
@ -445,7 +449,7 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onContactPaused(self, buddy): def onContactPaused(self, buddy):
self.logger.info("Paused typing: %s", buddy) self.logger.info("Paused typing: %s" % buddy)
if buddy != 'bot': if buddy != 'bot':
self.backend.handleBuddyTyped(self.user, buddy) self.backend.handleBuddyTyped(self.user, buddy)
self.timer = Timer(3, self.backend.handleBuddyStoppedTyping, self.timer = Timer(3, self.backend.handleBuddyStoppedTyping,
@ -453,20 +457,20 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onAddedToGroup(self, group): def onAddedToGroup(self, group):
self.logger.debug("Added to group: %s", group) self.logger.debug("Added to group: %s" % group)
room = group.getGroupId() room = group.getGroupId()
owner = group.getCreatorJid(full = False) owner = group.getCreatorJid(full = False)
subjectOwner = group.getSubjectOwnerJid(full = False) subjectOwner = group.getSubjectOwnerJid(full = False)
subject = utils.softToUni(group.getSubject()) subject = utils.softToUni(group.getSubject())
self.groups[room] = Group(room, owner, subject, subjectOwner, self.backend, self.user) self.groups[room] = Group(room, owner, subject, subjectOwner, self.backend, self.user)
self.groups[room].addParticipants(group.getParticipants, self.buddies, self.legacyName) self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName)
self.bot.send("You have been added to group: %s@%s (%s)" self.bot.send("You have been added to group: %s@%s (%s)"
% (self._shortenGroupId(room), subject, self.backend.spectrum_jid)) % (self._shortenGroupId(room), subject, self.backend.spectrum_jid))
# Called by superclass # Called by superclass
def onParticipantsAddedToGroup(self, group): def onParticipantsAddedToGroup(self, group):
self.logger.debug("Participants added to group: %s", group) self.logger.debug("Participants added to group: %s" % group)
room = group.getGroupId().split('@')[0] room = group.getGroupId().split('@')[0]
self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName) self.groups[room].addParticipants(group.getParticipants(), self.buddies, self.legacyName)
self.groups[room].sendParticipantsToSpectrum(self.legacyName) self.groups[room].sendParticipantsToSpectrum(self.legacyName)
@ -474,12 +478,13 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onSubjectChanged(self, room, subject, subjectOwner, timestamp): def onSubjectChanged(self, room, subject, subjectOwner, timestamp):
self.logger.debug( self.logger.debug(
"onSubjectChange(rrom=%s, subject=%s, subjectOwner=%s, timestamp=%s)", "onSubjectChange(rrom=%s, subject=%s, subjectOwner=%s, timestamp=%s)" %
room, subject, subjectOwner, timestamp) (room, subject, subjectOwner, timestamp)
)
try: try:
group = self.groups[room] group = self.groups[room]
except KeyError: except KeyError:
self.logger.error("Subject of non-existant group (%s) changed", group) self.logger.error("Subject of non-existant group (%s) changed" % group)
else: else:
group.subject = subject group.subject = subject
group.subjectOwner = subjectOwner group.subjectOwner = subjectOwner
@ -491,46 +496,46 @@ class Session(YowsupApp):
# Called by superclass # Called by superclass
def onParticipantsRemovedFromGroup(self, room, participants): def onParticipantsRemovedFromGroup(self, room, participants):
self.logger.debug("Participants removed from group: %s, %s", self.logger.debug("Participants removed from group: %s, %s" %
room, participants) (room, participants))
self.groups[room].removeParticipants(participants) self.groups[room].removeParticipants(participants)
# Called by superclass # Called by superclass
def onContactStatusChanged(self, number, status): def onContactStatusChanged(self, number, status):
self.logger.debug("%s changed their status to %s", number, status) self.logger.debug("%s changed their status to %s" % (number, status))
try: try:
buddy = self.buddies[number] buddy = self.buddies[number]
buddy.statusMsg = status buddy.statusMsg = status
self.buddies.updateSpectrum(buddy) self.buddies.updateSpectrum(buddy)
except KeyError: except KeyError:
self.logger.debug("%s not in buddy list", number) self.logger.debug("%s not in buddy list" % number)
# Called by superclass # Called by superclass
def onContactPictureChanged(self, number): def onContactPictureChanged(self, number):
self.logger.debug("%s changed their profile picture", number) self.logger.debug("%s changed their profile picture" % number)
self.buddies.requestVCard(number) self.buddies.requestVCard(number)
# Called by superclass # Called by superclass
def onContactAdded(self, number, nick): def onContactAdded(self, number, nick):
self.logger.debug("Adding new contact %s (%s)", nick, number) self.logger.debug("Adding new contact %s (%s)" % (nick, number))
self.updateBuddy(number, nick, []) self.updateBuddy(number, nick, [])
# Called by superclass # Called by superclass
def onContactRemoved(self, number): def onContactRemoved(self, number):
self.logger.debug("Removing contact %s", number) self.logger.debug("Removing contact %s" % number)
self.removeBuddy(number) self.removeBuddy(number)
def onContactUpdated(self, oldnumber, newnumber): def onContactUpdated(self, oldnumber, newnumber):
self.logger.debug("Contact has changed number from %s to %s", self.logger.debug("Contact has changed number from %s to %s" %
oldnumber, newnumber) (oldnumber, newnumber))
if newnumber in self.buddies: if newnumber in self.buddies:
self.logger.warn("Contact %s exists, just updating", newnumber) self.logger.warn("Contact %s exists, just updating" % newnumber)
self.buddies.refresh(newnumber) self.buddies.refresh(newnumber)
try: try:
buddy = self.buddies[oldnumber] buddy = self.buddies[oldnumber]
except KeyError: except KeyError:
self.logger.warn("Old contact (%s) not found. Adding new contact (%s)", self.logger.warn("Old contact (%s) not found. Adding new contact (%s)" %
oldnumber, newnumber) (oldnumber, newnumber))
nick = "" nick = ""
else: else:
self.removeBuddy(buddy.number) self.removeBuddy(buddy.number)
@ -538,17 +543,17 @@ class Session(YowsupApp):
self.updateBuddy(newnumber, nick, []) self.updateBuddy(newnumber, nick, [])
def onPresenceReceived(self, _type, name, jid, lastseen): def onPresenceReceived(self, _type, name, jid, lastseen):
self.logger.info("Presence received: %s %s %s %s", _type, name, jid, lastseen) self.logger.info("Presence received: %s %s %s %s" % (_type, name, jid, lastseen))
buddy = jid.split("@")[0] buddy = jid.split("@")[0]
try: try:
buddy = self.buddies[buddy] buddy = self.buddies[buddy]
except KeyError: except KeyError:
# Sometimes whatsapp send our own presence # Sometimes whatsapp send our own presence
if buddy != self.legacyName: if buddy != self.legacyName:
self.logger.error("Buddy not found: %s", buddy) self.logger.error("Buddy not found: %s" % buddy)
return return
if (lastseen == str(buddy.lastseen)) and (_type == buddy.presence): if (lastseen == buddy.lastseen) and (_type == buddy.presence):
return return
if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")): if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")):
@ -563,20 +568,18 @@ class Session(YowsupApp):
else: else:
self.onPresenceAvailable(buddy) self.onPresenceAvailable(buddy)
def onPresenceAvailable(self, buddy): def onPresenceAvailable(self, buddy):
self.logger.info("Is available: %s", buddy) self.logger.info("Is available: %s" % buddy)
self.buddies.updateSpectrum(buddy) self.buddies.updateSpectrum(buddy)
def onPresenceUnavailable(self, buddy): def onPresenceUnavailable(self, buddy):
self.logger.info("Is unavailable: %s", buddy) self.logger.info("Is unavailable: %s" % buddy)
self.buddies.updateSpectrum(buddy) self.buddies.updateSpectrum(buddy)
# spectrum RequestMethods # spectrum RequestMethods
def sendTypingStarted(self, buddy): def sendTypingStarted(self, buddy):
if buddy != "bot": if buddy != "bot":
self.logger.info("Started typing: %s to %s", self.legacyName, buddy) self.logger.info("Started typing: %s to %s" % (self.legacyName, buddy))
self.sendTyping(buddy, True) self.sendTyping(buddy, True)
self.sendReadReceipts(buddy) self.sendReadReceipts(buddy)
# If he is typing he is present # If he is typing he is present
@ -586,7 +589,7 @@ class Session(YowsupApp):
def sendTypingStopped(self, buddy): def sendTypingStopped(self, buddy):
if buddy != "bot": if buddy != "bot":
self.logger.info("Stopped typing: %s to %s", self.legacyName, buddy) self.logger.info("Stopped typing: %s to %s" % (self.legacyName, buddy))
self.sendTyping(buddy, False) self.sendTyping(buddy, False)
self.sendReadReceipts(buddy) self.sendReadReceipts(buddy)
@ -602,7 +605,7 @@ class Session(YowsupApp):
# Success # Success
path = success.arg(0) path = success.arg(0)
call(self.logger.info, "Success: Image downloaded to %s", path) call(self.logger.info, "Success: Image downloaded to %s" % path)
pathWithExt = path.then(lambda p: p + "." + imgType) pathWithExt = path.then(lambda p: p + "." + imgType)
call(os.rename, path, pathWithExt) call(os.rename, path, pathWithExt)
pathJpg = path.then(lambda p: p + ".jpg") pathJpg = path.then(lambda p: p + ".jpg")
@ -610,7 +613,7 @@ class Session(YowsupApp):
im = call(Image.open, pathWithExt) im = call(Image.open, pathWithExt)
call(im.save, pathJpg) call(im.save, pathJpg)
call(os.remove, pathWithExt) call(os.remove, pathWithExt)
call(self.logger.info, "Sending image to %s", to) call(self.logger.info, "Sending image to %s" % to)
waId = deferred.Deferred() waId = deferred.Deferred()
call(super(Session, self).sendImage, to, pathJpg, onSuccess = waId.run) call(super(Session, self).sendImage, to, pathJpg, onSuccess = waId.run)
call(self.setWaId, ID, waId) call(self.setWaId, ID, waId)
@ -626,10 +629,9 @@ class Session(YowsupApp):
self.msgIDs[waId] = MsgIDs(XmppId, waId) self.msgIDs[waId] = MsgIDs(XmppId, waId)
def sendMessageToWA(self, sender, message, ID, xhtml=""): def sendMessageToWA(self, sender, message, ID, xhtml=""):
self.logger.info("Message sent from %s to %s: %s (xhtml=%s)", self.logger.info("Message sent from %s to %s: %s (xhtml=%s)" %
self.legacyName, sender, message, xhtml) (self.legacyName, sender, message, xhtml))
message = message.encode("utf-8")
self.sendReadReceipts(sender) self.sendReadReceipts(sender)
if sender == "bot": if sender == "bot":
@ -644,27 +646,27 @@ class Session(YowsupApp):
number = othernumber number = othernumber
break break
if number is not None: if number is not None:
self.logger.debug("Private message sent from %s to %s", self.legacyName, number) self.logger.debug("Private message sent from %s to %s" % (self.legacyName, number))
waId = self.sendTextMessage(number + '@s.whatsapp.net', message) waId = self.sendTextMessage(number + '@s.whatsapp.net', message)
self.msgIDs[waId] = MsgIDs( ID, waId) self.msgIDs[waId] = MsgIDs( ID, waId)
else: else:
self.logger.error("Attempted to send private message to non-existent user") self.logger.error("Attempted to send private message to non-existent user")
self.logger.debug("%s to %s in %s", self.legacyName, nick, room) self.logger.debug("%s to %s in %s" % (self.legacyName, nick, room))
else: else:
room = sender room = sender
if message[0] == '\\' and message[:1] != '\\\\': if message[0] == '\\' and message[:1] != '\\\\':
self.logger.debug("Executing command %s in %s", message, room) self.logger.debug("Executing command %s in %s" % (message, room))
self.executeCommand(message, room) self.executeCommand(message, room)
else: else:
try: try:
group = self.groups[self._lengthenGroupId(room)] group = self.groups[self._lengthenGroupId(room)]
self.logger.debug("Group Message from %s to %s Groups: %s", self.logger.debug("Group Message from %s to %s Groups: %s" %
group.nick , group , self.groups) (group.nick , group , self.groups))
self.backend.handleMessage( self.backend.handleMessage(
self.user, room, message.decode('utf-8'), group.nick, xhtml=xhtml self.user, room, message, group.nick, xhtml=xhtml
) )
except KeyError: except KeyError:
self.logger.error('Group not found: %s', room) self.logger.error('Group not found: %s' % room)
if (".jpg" in message.lower()) or (".webp" in message.lower()): if (".jpg" in message.lower()) or (".webp" in message.lower()):
self.sendImage(message, ID, room + '@g.us') self.sendImage(message, ID, room + '@g.us')
@ -674,82 +676,68 @@ class Session(YowsupApp):
self.sendTextMessage(room + '@g.us', message) self.sendTextMessage(room + '@g.us', message)
else: # private msg else: # private msg
buddy = sender buddy = sender
# if message == "\\lastseen":
# self.call("presence_request", buddy = (buddy + "@s.whatsapp.net",))
# else:
if message.split(" ").pop(0) == "\\lastseen": if message.split(" ").pop(0) == "\\lastseen":
self.presenceRequested.append(buddy) self.presenceRequested.append(buddy)
#self.call("presence_request", (buddy + "@s.whatsapp.net",))
self._requestLastSeen(buddy) self._requestLastSeen(buddy)
elif message.split(" ").pop(0) == "\\gpp": elif message.split(" ").pop(0) == "\\gpp":
self.logger.info("Get Profile Picture! ")
self.sendMessageToXMPP(buddy, "Fetching Profile Picture") self.sendMessageToXMPP(buddy, "Fetching Profile Picture")
#self.call("contact_getProfilePicture", (buddy + "@s.whatsapp.net",))
self.requestVCard(buddy) self.requestVCard(buddy)
else: elif (".jpg" in message.lower()) or (".webp" in message.lower()):
if (".jpg" in message.lower()) or (".webp" in message.lower()):
self.sendImage(message, ID, buddy + "@s.whatsapp.net") self.sendImage(message, ID, buddy + "@s.whatsapp.net")
elif "geo:" in message.lower(): elif "geo:" in message.lower():
self._sendLocation(buddy + "@s.whatsapp.net", message, ID) self._sendLocation(buddy + "@s.whatsapp.net", message, ID)
else: else:
waId = self.sendTextMessage(sender + '@s.whatsapp.net', message) waId = self.sendTextMessage(sender + '@s.whatsapp.net', message)
self.msgIDs[waId] = MsgIDs( ID, waId) self.msgIDs[waId] = MsgIDs(ID, waId)
self.logger.info("WA Message send to %s with ID %s", buddy, waId) self.logger.info("WA Message send to %s with ID %s", buddy, waId)
#self.sendTextMessage(sender + '@s.whatsapp.net', message)
def executeCommand(self, command, room): def executeCommand(self, command, room):
if command == '\\leave': if command == '\\leave':
self.logger.debug("Leaving room %s", room) self.logger.debug("Leaving room %s", room)
# Leave group on whatsapp side self.leaveGroup(room) # Leave group on whatsapp side
self.leaveGroup(room)
# Delete Room on spectrum side
group = self.groups[room] group = self.groups[room]
group.leaveRoom() group.leaveRoom() # Delete Room on spectrum side
del self.groups[room] del self.groups[room]
def _requestLastSeen(self, buddy): def _requestLastSeen(self, buddy):
def onSuccess(buddy, lastseen): def onSuccess(buddy, lastseen):
timestamp = time.localtime(time.localtime()-lastseen) timestamp = time.localtime(time.localtime()-lastseen)
timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp) timestring = time.strftime("%a, %d %b %Y %H:%M:%S", timestamp)
self.sendMessageToXMPP(buddy, "%s (%s) %s" % (timestring, utils.ago(lastseen),str(lastseen))) self.sendMessageToXMPP(buddy, "%s (%s) %s" % (timestring, utils.ago(lastseen), str(lastseen)))
def onError(errorIqEntity, originalIqEntity): def onError(errorIqEntity, originalIqEntity):
self.sendMessageToXMPP(errorIqEntity.getFrom(), "LastSeen Error") self.sendMessageToXMPP(errorIqEntity.getFrom(), "LastSeen Error")
self.requestLastSeen(buddy, onSuccess, onError) self.requestLastSeen(buddy, onSuccess, onError)
def _sendLocation(self, buddy, message, ID): def _sendLocation(self, buddy, message, ID):
#with open('/opt/transwhat/map.jpg', 'rb') as imageFile:
# raw = base64.b64encode(imageFile.read())
latitude,longitude = message.split(':')[1].split(',') latitude,longitude = message.split(':')[1].split(',')
waId = self.sendLocation(buddy, float(latitude), float(longitude)) waId = self.sendLocation(buddy, float(latitude), float(longitude))
self.msgIDs[waId] = MsgIDs( ID, waId) self.msgIDs[waId] = MsgIDs(ID, waId)
self.logger.info("WA Location Message send to %s with ID %s", buddy, waId) self.logger.info("WA Location Message send to %s with ID %s", buddy, waId)
def sendMessageToXMPP(self, buddy, messageContent, timestamp = "", nickname = ""): def sendMessageToXMPP(self, buddy, messageContent, timestamp = "", nickname = ""):
if timestamp: if timestamp:
timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp))
if self.initialized == False: if self.initialized == False:
self.logger.debug("Message queued from %s to %s: %s", self.logger.debug("Message queued from %s to %s: %s" %
buddy, self.legacyName, messageContent) (buddy, self.legacyName, messageContent))
self.offlineQueue.append((buddy, messageContent, timestamp)) self.offlineQueue.append((buddy, messageContent, timestamp))
else: else:
self.logger.debug("Message sent from %s to %s: %s", buddy, self.logger.debug("Message sent from %s to %s: %s" % (
self.legacyName, messageContent) buddy, self.legacyName, messageContent))
self.backend.handleMessage(self.user, buddy, messageContent, "", self.backend.handleMessage(self.user, buddy, messageContent, "",
"", timestamp) "", timestamp)
def sendGroupMessageToXMPP(self, room, number, messageContent, timestamp = u"", defaultname = u""): def sendGroupMessageToXMPP(self, room, number, messageContent, timestamp = "", defaultname = ""):
if timestamp: if timestamp:
timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp)) timestamp = time.strftime("%Y%m%dT%H%M%S", time.gmtime(timestamp))
if self.initialized == False: if self.initialized == False:
self.logger.debug("Group message queued from %s to %s: %s", self.logger.debug("Group message queued from %s to %s: %s" %
number, room, messageContent) (number, room, messageContent))
if room not in self.groupOfflineQueue: if room not in self.groupOfflineQueue:
self.groupOfflineQueue[room] = [ ] self.groupOfflineQueue[room] = [ ]
@ -758,8 +746,8 @@ class Session(YowsupApp):
(number, messageContent, timestamp) (number, messageContent, timestamp)
) )
else: else:
self.logger.debug("Group message sent from %s to %s: %s", self.logger.debug("Group message sent from %s to %s: %s" %
number, room, messageContent) (number, room, messageContent))
try: try:
group = self.groups[room] group = self.groups[room]
# Update nickname # Update nickname
@ -787,7 +775,7 @@ class Session(YowsupApp):
def changeStatus(self, status): def changeStatus(self, status):
if status != self.status: if status != self.status:
self.logger.info("Status changed: %s", status) self.logger.info("Status changed: %s" % status)
self.status = status self.status = status
if status == protocol_pb2.STATUS_ONLINE \ if status == protocol_pb2.STATUS_ONLINE \
@ -799,8 +787,8 @@ class Session(YowsupApp):
def changeStatusMessage(self, statusMessage): def changeStatusMessage(self, statusMessage):
if (statusMessage != self.statusMessage) or (self.initialized == False): if (statusMessage != self.statusMessage) or (self.initialized == False):
self.statusMessage = statusMessage self.statusMessage = statusMessage
self.setStatus(statusMessage.encode('utf-8')) self.setStatus(statusMessage)
self.logger.info("Status message changed: %s", statusMessage) self.logger.info("Status message changed: %s" % statusMessage)
#if self.initialized == False: #if self.initialized == False:
# self.sendOfflineMessages() # self.sendOfflineMessages()
@ -824,7 +812,7 @@ class Session(YowsupApp):
def removeBuddy(self, buddy): def removeBuddy(self, buddy):
if buddy != "bot": if buddy != "bot":
self.logger.info("Buddy removed: %s", buddy) self.logger.info("Buddy removed: %s" % buddy)
self.buddies.remove(buddy) self.buddies.remove(buddy)
def requestVCard(self, buddy, ID=None): def requestVCard(self, buddy, ID=None):
@ -853,37 +841,38 @@ class Session(YowsupApp):
# Not used # Not used
def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast): def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast):
buddy = jid.split("@")[0] buddy = jid.split("@")[0]
self.logger.info("Location received from %s: %s, %s", buddy, latitude, longitude) self.logger.info("Location received from %s: %s, %s" % (buddy, latitude, longitude))
url = "http://maps.google.de?%s" % urllib.urlencode({ "q": "%s %s" % (latitude, longitude) }) url = "http://maps.google.de?%s" % urllib.urlencode({ "q": "%s %s" % (latitude, longitude) })
self.sendMessageToXMPP(buddy, utils.shorten(url)) self.sendMessageToXMPP(buddy, utils.shorten(url))
if receiptRequested: self.call("message_ack", (jid, messageId)) if receiptRequested:
self.call("message_ack", (jid, messageId))
def onGroupSubjectReceived(self, messageId, gjid, jid, subject, timestamp, receiptRequested): def onGroupSubjectReceived(self, messageId, gjid, jid, subject, timestamp, receiptRequested):
room = gjid.split("@")[0] room = gjid.split("@")[0]
buddy = jid.split("@")[0] buddy = jid.split("@")[0]
self.backend.handleSubject(self.user, room, subject, buddy) self.backend.handleSubject(self.user, room, subject, buddy)
if receiptRequested: self.call("subject_ack", (gjid, messageId)) if receiptRequested:
self.call("subject_ack", (gjid, messageId))
# Yowsup Notifications # Yowsup Notifications
def onGroupParticipantRemoved(self, gjid, jid, author, timestamp, messageId, receiptRequested): def onGroupParticipantRemoved(self, gjid, jid, author, timestamp, messageId, receiptRequested):
room = gjid.split("@")[0] room = gjid.split("@")[0]
buddy = jid.split("@")[0] buddy = jid.split("@")[0]
self.logger.info("Removed %s from room %s", buddy, room) self.logger.info("Removed %s from room %s" % (buddy, room))
self.backend.handleParticipantChanged(self.user, buddy, room, protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_NONE) # TODO self.backend.handleParticipantChanged(self.user, buddy, room, protocol_pb2.PARTICIPANT_FLAG_NONE, protocol_pb2.STATUS_NONE) # TODO
if receiptRequested: self.call("notification_ack", (gjid, messageId)) if receiptRequested: self.call("notification_ack", (gjid, messageId))
def onContactProfilePictureUpdated(self, jid, timestamp, messageId, pictureId, receiptRequested): def onContactProfilePictureUpdated(self, jid, timestamp, messageId, pictureId, receiptRequested):
# TODO # TODO
if receiptRequested: self.call("notification_ack", (jid, messageId)) if receiptRequested:
self.call("notification_ack", (jid, messageId))
def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested): def onGroupPictureUpdated(self, jid, author, timestamp, messageId, pictureId, receiptRequested):
# TODO # TODO
if receiptRequested: self.call("notification_ack", (jid, messageId)) if receiptRequested:
self.call("notification_ack", (jid, messageId))

View file

@ -1,3 +1,29 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import Queue import Queue
import threading import threading

View file

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -23,6 +23,9 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import argparse import argparse
import traceback import traceback
import logging import logging
@ -35,7 +38,7 @@ import threadutils
sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd())
from Spectrum2.iochannel import IOChannel from Spectrum2.iochannel import IOChannel
from config import SpectrumConfig
from whatsappbackend import WhatsAppBackend from whatsappbackend import WhatsAppBackend
from yowsup.common import YowConstants from yowsup.common import YowConstants
from yowsup.stacks import YowStack from yowsup.stacks import YowStack
@ -43,6 +46,7 @@ from yowsup.stacks import YowStack
# Arguments # Arguments
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true') parser.add_argument('--debug', action='store_true')
parser.add_argument('--log', type=str)
parser.add_argument('--host', type=str, required=True) parser.add_argument('--host', type=str, required=True)
parser.add_argument('--port', type=int, required=True) parser.add_argument('--port', type=int, required=True)
parser.add_argument('--service.backend_id', metavar="ID", type=int, required=True) parser.add_argument('--service.backend_id', metavar="ID", type=int, required=True)
@ -52,14 +56,22 @@ parser.add_argument('-j', type=str, metavar="JID", required=True)
args, unknown = parser.parse_known_args() args, unknown = parser.parse_known_args()
YowConstants.PATH_STORAGE='/var/lib/spectrum2/' + args.j YowConstants.PATH_STORAGE='/var/lib/spectrum2/' + args.j
loggingfile = '/var/log/spectrum2/' + args.j + '/backends/backend.log'
if args.log is None:
args.log = '/var/log/spectrum2/' + args.j + '/backends/backend.log'
# Logging # Logging
logging.basicConfig( \ logging.basicConfig(
filename=loggingfile,\ filename = args.log,
format = "%(asctime)-15s %(levelname)s %(name)s: %(message)s", \ format = "%(asctime)-15s %(levelname)s %(name)s: %(message)s",
level = logging.DEBUG if args.debug else logging.INFO \ level = logging.DEBUG if args.debug else logging.INFO
) )
if args.config is not None:
specConf = SpectrumConfig(args.config)
else:
specConf = None
# Handler # Handler
def handleTransportData(data): def handleTransportData(data):
try: try:
@ -80,7 +92,7 @@ def connectionClosed():
# Main # Main
io = IOChannel(args.host, args.port, handleTransportData, connectionClosed) io = IOChannel(args.host, args.port, handleTransportData, connectionClosed)
plugin = WhatsAppBackend(io, args.j) plugin = WhatsAppBackend(io, args.j, specConf)
plugin.handleBackendConfig({ plugin.handleBackendConfig({
'features': [ 'features': [

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,10 +21,14 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import e4u import e4u
import base64 import base64
import hashlib import hashlib
def ago(secs): def ago(secs):
periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"] periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"]
lengths = [60, 60, 24, 7,4.35, 12, 10] lengths = [60, 60, 24, 7,4.35, 12, 10]
@ -43,11 +47,10 @@ def ago(secs):
return "%d %s ago" % (diff, period) return "%d %s ago" % (diff, period)
def softToUni(message): def softToUni(message):
message = message.decode("utf-8") return e4u.translate(message.encode("utf-8"), reverse=False, **e4u.SOFTBANK_TRANSLATE_PROFILE)
return e4u.translate(message, reverse=False, **e4u.SOFTBANK_TRANSLATE_PROFILE)
def decodePassword(password): def decodePassword(password):
return base64.b64decode(bytes(password.encode("utf-8"))) return base64.b64decode(bytes(password))
def sha1hash(data): def sha1hash(data):
return hashlib.sha1(data).hexdigest() return hashlib.sha1(data).hexdigest()

View file

@ -1,5 +1,5 @@
__author__ = "Steffen Vogel" __author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015, Steffen Vogel" __copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3" __license__ = "GPLv3"
__maintainer__ = "Steffen Vogel" __maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de" __email__ = "post@steffenvogel.de"
@ -21,6 +21,9 @@ __email__ = "post@steffenvogel.de"
along with transWhat. If not, see <http://www.gnu.org/licenses/>. along with transWhat. If not, see <http://www.gnu.org/licenses/>.
""" """
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
from Spectrum2.backend import SpectrumBackend from Spectrum2.backend import SpectrumBackend
from Spectrum2 import protocol_pb2 from Spectrum2 import protocol_pb2
@ -29,11 +32,13 @@ from registersession import RegisterSession
import logging import logging
class WhatsAppBackend(SpectrumBackend): class WhatsAppBackend(SpectrumBackend):
def __init__(self, io, spectrum_jid): def __init__(self, io, spectrum_jid, specConf):
SpectrumBackend.__init__(self) SpectrumBackend.__init__(self)
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.io = io self.io = io
self.specConf = specConf
self.sessions = { } self.sessions = { }
self.spectrum_jid = spectrum_jid self.spectrum_jid = spectrum_jid
# Used to prevent duplicate messages # Used to prevent duplicate messages
@ -43,7 +48,7 @@ class WhatsAppBackend(SpectrumBackend):
# RequestsHandlers # RequestsHandlers
def handleLoginRequest(self, user, legacyName, password, extra): def handleLoginRequest(self, user, legacyName, password, extra):
self.logger.debug("handleLoginRequest(user=%s, legacyName=%s)", user, legacyName) self.logger.debug("handleLoginRequest(user=%s, legacyName=%s)" % (user, legacyName))
# Key word means we should register a new password # Key word means we should register a new password
if password == 'register': if password == 'register':
if user not in self.sessions: if user not in self.sessions:
@ -55,13 +60,14 @@ class WhatsAppBackend(SpectrumBackend):
self.sessions[user].login(password) self.sessions[user].login(password)
def handleLogoutRequest(self, user, legacyName): def handleLogoutRequest(self, user, legacyName):
self.logger.debug("handleLogoutRequest(user=%s, legacyName=%s)", user, legacyName) self.logger.debug("handleLogoutRequest(user=%s, legacyName=%s)" % (user, legacyName))
if user in self.sessions: if user in self.sessions:
self.sessions[user].logout() self.sessions[user].logout()
del self.sessions[user] del self.sessions[user]
def handleMessageSendRequest(self, user, buddy, message, xhtml="", ID=""): def handleMessageSendRequest(self, user, buddy, message, xhtml="", ID=""):
self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml=%s, ID=%s)", user, buddy, message, xhtml, ID) self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml=%s, ID=%s)" %
( user, buddy, message, xhtml, ID))
# For some reason spectrum occasionally sends to identical messages to # For some reason spectrum occasionally sends to identical messages to
# a buddy, one to the bare jid and one to the /bot resource. This # a buddy, one to the bare jid and one to the /bot resource. This
# causes duplicate messages. Thus we should not send consecutive # causes duplicate messages. Thus we should not send consecutive
@ -73,63 +79,63 @@ class WhatsAppBackend(SpectrumBackend):
self.lastMsgId[user] = ID self.lastMsgId[user] = ID
def handleJoinRoomRequest(self, user, room, nickname, pasword): def handleJoinRoomRequest(self, user, room, nickname, pasword):
self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)", user, room, nickname) self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)" % (user, room, nickname))
self.sessions[user].joinRoom(room, nickname) self.sessions[user].joinRoom(room, nickname)
def handleLeaveRoomRequest(self, user, room): def handleLeaveRoomRequest(self, user, room):
self.logger.debug("handleLeaveRoomRequest(user=%s, room=%s)", user, room) self.logger.debug("handleLeaveRoomRequest(user=%s, room=%s)" % (user, room))
self.sessions[user].leaveRoom(room) self.sessions[user].leaveRoom(room)
def handleStatusChangeRequest(self, user, status, statusMessage): def handleStatusChangeRequest(self, user, status, statusMessage):
self.logger.debug("handleStatusChangeRequest(user=%s, status=%d, statusMessage=%s)", user, status, statusMessage) self.logger.debug("handleStatusChangeRequest(user=%s, status=%d, statusMessage=%s)" % (user, status, statusMessage))
self.sessions[user].changeStatusMessage(statusMessage) self.sessions[user].changeStatusMessage(statusMessage)
self.sessions[user].changeStatus(status) self.sessions[user].changeStatus(status)
def handleBuddies(self, buddies): def handleBuddies(self, buddies):
"""Called when user logs in. Used to initialize roster.""" """Called when user logs in. Used to initialize roster."""
self.logger.debug("handleBuddies(buddies=%s)", buddies) self.logger.debug("handleBuddies(buddies=%s)" % buddies)
buddies = [b for b in buddies.buddy] buddies = [b for b in buddies.buddy]
if len(buddies) > 0: if len(buddies) > 0:
user = buddies[0].userName user = buddies[0].userName
self.sessions[user].loadBuddies(buddies) self.sessions[user].loadBuddies(buddies)
def handleBuddyUpdatedRequest(self, user, buddy, nick, groups): def handleBuddyUpdatedRequest(self, user, buddy, nick, groups):
self.logger.debug("handleBuddyUpdatedRequest(user=%s, buddy=%s, nick=%s, groups=%s)", user, buddy, nick, str(groups)) self.logger.debug("handleBuddyUpdatedRequest(user=%s, buddy=%s, nick=%s, groups=%s)" % (user, buddy, nick, groups))
self.sessions[user].updateBuddy(buddy, nick, groups) self.sessions[user].updateBuddy(buddy, nick, groups)
def handleBuddyRemovedRequest(self, user, buddy, groups): def handleBuddyRemovedRequest(self, user, buddy, groups):
self.logger.debug("handleBuddyRemovedRequest(user=%s, buddy=%s, groups=%s)", user, buddy, str(groups)) self.logger.debug("handleBuddyRemovedRequest(user=%s, buddy=%s, groups=%s)" % (user, buddy, groups))
self.sessions[user].removeBuddy(buddy) self.sessions[user].removeBuddy(buddy)
def handleTypingRequest(self, user, buddy): def handleTypingRequest(self, user, buddy):
self.logger.debug("handleTypingRequest(user=%s, buddy=%s)", user, buddy) self.logger.debug("handleTypingRequest(user=%s, buddy=%s)" % (user, buddy))
self.sessions[user].sendTypingStarted(buddy) self.sessions[user].sendTypingStarted(buddy)
def handleTypedRequest(self, user, buddy): def handleTypedRequest(self, user, buddy):
self.logger.debug("handleTypedRequest(user=%s, buddy=%s)", user, buddy) self.logger.debug("handleTypedRequest(user=%s, buddy=%s)" % (user, buddy))
self.sessions[user].sendTypingStopped(buddy) self.sessions[user].sendTypingStopped(buddy)
def handleStoppedTypingRequest(self, user, buddy): def handleStoppedTypingRequest(self, user, buddy):
self.logger.debug("handleStoppedTypingRequest(user=%s, buddy=%s)", user, buddy) self.logger.debug("handleStoppedTypingRequest(user=%s, buddy=%s)" % (user, buddy))
self.sessions[user].sendTypingStopped(buddy) self.sessions[user].sendTypingStopped(buddy)
def handleVCardRequest(self, user, buddy, ID): def handleVCardRequest(self, user, buddy, ID):
self.logger.debug("handleVCardRequest(user=%s, buddy=%s, ID=%s)", user, buddy, ID) self.logger.debug("handleVCardRequest(user=%s, buddy=%s, ID=%s)" % (user, buddy, ID))
self.sessions[user].requestVCard(buddy, ID) self.sessions[user].requestVCard(buddy, ID)
def handleVCardUpdatedRequest(self, user, photo, nickname): def handleVCardUpdatedRequest(self, user, photo, nickname):
self.logger.debug("handleVCardUpdatedRequest(user=%s, nickname=%s)", user, nickname) self.logger.debug("handleVCardUpdatedRequest(user=%s, nickname=%s)" % (user, nickname))
self.sessions[user].setProfilePicture(photo) self.sessions[user].setProfilePicture(photo)
def handleBuddyBlockToggled(self, user, buddy, blocked): def handleBuddyBlockToggled(self, user, buddy, blocked):
self.logger.debug("handleBuddyBlockedToggled(user=%s, buddy=%s, blocked=%s)", user, buddy, blocked) self.logger.debug("handleBuddyBlockedToggled(user=%s, buddy=%s, blocked=%s)" % (user, buddy, blocked))
def relogin(self, user, legacyName, password, extra): def relogin(self, user, legacyName, password, extra):
""" """
Used to re-initialize the session object. Used when finished with Used to re-initialize the session object. Used when finished with
registration session and the user needs to login properly registration session and the user needs to login properly
""" """
self.logger.debug("relogin(user=%s, legacyName=%s)", user, legacyName) self.logger.debug("relogin(user=%s, legacyName=%s)" % (user, legacyName))
# Change password in spectrum database # Change password in spectrum database
self.handleQuery('register %s %s %s' % (user, legacyName, password)) self.handleQuery('register %s %s %s' % (user, legacyName, password))
# Key word means we should register a new password # Key word means we should register a new password
@ -144,8 +150,8 @@ class WhatsAppBackend(SpectrumBackend):
pass pass
def handleFTStartRequest(self, user, buddy, fileName, size, ftID): def handleFTStartRequest(self, user, buddy, fileName, size, ftID):
self.logger.debug('File send request %s, for user %s, from %s, size: %s', self.logger.debug('File send request %s, for user %s, from %s, size: %s' %
fileName, user, buddy, size) (fileName, user, buddy, size))
def handleFTFinishRequest(self, user, buddy, fileName, size, ftID): def handleFTFinishRequest(self, user, buddy, fileName, size, ftID):
pass pass
@ -160,7 +166,7 @@ class WhatsAppBackend(SpectrumBackend):
pass pass
def handleMessageAckRequest(self, user, legacyName, ID = 0): def handleMessageAckRequest(self, user, legacyName, ID = 0):
self.logger.info("Meassage ACK request for %s !!",legacyName) self.logger.info("Meassage ACK request for %s !!" % legacyName)
def sendData(self, data): def sendData(self, data):
self.io.sendData(data) self.io.sendData(data)

View file

@ -1,13 +1,43 @@
__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2015-2017, Steffen Vogel"
__license__ = "GPLv3"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
"""
This file is part of transWhat
transWhat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
transwhat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with transWhat. If not, see <http://www.gnu.org/licenses/>.
"""
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
import logging import logging
from yowsup import env from yowsup import env
from yowsup.env import S40YowsupEnv
from yowsup.stacks import YowStack from yowsup.stacks import YowStack
from yowsup.common import YowConstants from yowsup.common import YowConstants
from yowsup.layers import YowLayerEvent, YowParallelLayer from yowsup.layers import YowLayerEvent, YowParallelLayer
from yowsup.layers.auth import AuthError from yowsup.layers.auth import AuthError
from yowsup.stacks import YowStack
from yowsup.stacks import YowStackBuilder
from yowsup.common import YowConstants
# Layers # Layers
from yowsup.layers.axolotl import YowAxolotlLayer from yowsup.layers.axolotl import AxolotlSendLayer, AxolotlControlLayer, AxolotlReceivelayer
from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer from yowsup.layers.auth import YowCryptLayer, YowAuthenticationProtocolLayer
from yowsup.layers.coder import YowCoderLayer from yowsup.layers.coder import YowCoderLayer
from yowsup.layers.logger import YowLoggerLayer from yowsup.layers.logger import YowLoggerLayer
@ -53,40 +83,9 @@ from yowsup.registration import WARegRequest
from functools import partial from functools import partial
#from session import MsgIDs
# Temporarily work around yowsup padding bugs with new protocol
class UpdatedYowAxolotlLayer(YowAxolotlLayer):
def decodeInt7bit(self, string):
idx = 0
while ord(string[idx]) >= 128:
idx += 1
consumedBytes = idx + 1
value = 0
while idx >= 0:
value <<= 7
value += ord(string[idx]) % 128
idx -= 1
return value, consumedBytes
def unpadV2Plaintext(self, v2plaintext):
end = -ord(v2plaintext[-1]) # length of the left padding
length,consumed = self.decodeInt7bit(v2plaintext[1:])
return v2plaintext[1+consumed:end]
# Temporary env until yowsup updates
class UpdatedS40YowsupEnv(env.S40YowsupEnv):
_VERSION = "2.13.39"
_OS_NAME= "S40"
_OS_VERSION = "14.26"
_DEVICE_NAME = "302"
_MANUFACTURER = "Nokia"
_TOKEN_STRING = "PdA2DJyKoUrwLw1Bg6EIhzh502dF9noR9uFCllGk{phone}"
_AXOLOTL = True
class YowsupApp(object): class YowsupApp(object):
def __init__(self): def __init__(self):
env.CURRENT_ENV = UpdatedS40YowsupEnv() env.CURRENT_ENV = env.AndroidYowsupEnv()
layers = (YowsupAppLayer, layers = (YowsupAppLayer,
YowParallelLayer((YowAuthenticationProtocolLayer, YowParallelLayer((YowAuthenticationProtocolLayer,
@ -104,14 +103,21 @@ class YowsupApp(object):
YowProfilesProtocolLayer, YowProfilesProtocolLayer,
YowGroupsProtocolLayer, YowGroupsProtocolLayer,
YowPresenceProtocolLayer)), YowPresenceProtocolLayer)),
UpdatedYowAxolotlLayer, AxolotlControlLayer,
YowParallelLayer((AxolotlSendLayer, AxolotlReceivelayer)),
YowCoderLayer, YowCoderLayer,
YowCryptLayer, YowCryptLayer,
YowStanzaRegulator, YowStanzaRegulator,
YowNetworkLayer YowNetworkLayer
) )
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.stack = YowStack(layers) stackBuilder = YowStackBuilder()
self.stack = stackBuilder \
.pushDefaultLayers(True) \
.push(YowsupAppLayer) \
.build()
self.stack.broadcastEvent( self.stack.broadcastEvent(
YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self) YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self)
) )
@ -129,12 +135,6 @@ class YowsupApp(object):
""" """
self.stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS, self.stack.setProp(YowAuthenticationProtocolLayer.PROP_CREDENTIALS,
(username, password)) (username, password))
self.stack.setProp(YowNetworkLayer.PROP_ENDPOINT,
YowConstants.ENDPOINTS[0])
self.stack.setProp(YowCoderLayer.PROP_DOMAIN,
YowConstants.DOMAIN)
self.stack.setProp(YowCoderLayer.PROP_RESOURCE,
env.CURRENT_ENV.getResource())
# self.stack.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 5) # self.stack.setProp(YowIqProtocolLayer.PROP_PING_INTERVAL, 5)
try: try:
@ -163,6 +163,7 @@ class YowsupApp(object):
- read: ('read' or None) None is just delivered, 'read' is read - read: ('read' or None) None is just delivered, 'read' is read
- participant - participant
""" """
self.logger.debug(u'Sending receipt to whatsapp: %s', [_id, _from, read, participant])
receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant) receipt = OutgoingReceiptProtocolEntity(_id, _from, read, participant)
self.sendEntity(receipt) self.sendEntity(receipt)
@ -178,13 +179,14 @@ class YowsupApp(object):
- to: (xxxxxxxxxx@s.whatsapp.net) who to send the message to - to: (xxxxxxxxxx@s.whatsapp.net) who to send the message to
- message: (str) the body of the message - message: (str) the body of the message
""" """
messageEntity = TextMessageProtocolEntity(message, to = to) messageEntity = TextMessageProtocolEntity(message.encode('utf-8'), to = to)
self.sendEntity(messageEntity) self.sendEntity(messageEntity)
return messageEntity.getId() return messageEntity.getId()
def sendLocation(self, to, latitude, longitude): def sendLocation(self, to, latitude, longitude):
messageEntity = LocationMediaMessageProtocolEntity(latitude,longitude, None, None, "raw", to = to) messageEntity = LocationMediaMessageProtocolEntity(latitude,longitude, None, None, "raw", to = to)
self.sendEntity(messageEntity) self.sendEntity(messageEntity)
return messageEntity.getId() return messageEntity.getId()
def sendImage(self, jid, path, caption = None, onSuccess = None, onFailure = None): def sendImage(self, jid, path, caption = None, onSuccess = None, onFailure = None):
@ -195,7 +197,6 @@ class YowsupApp(object):
self.sendIq(entity, successFn, errorFn) self.sendIq(entity, successFn, errorFn)
def onRequestUploadResult(self, jid, filePath, resultRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity, caption = None, onSuccess=None, onFailure=None): def onRequestUploadResult(self, jid, filePath, resultRequestUploadIqProtocolEntity, requestUploadIqProtocolEntity, caption = None, onSuccess=None, onFailure=None):
if requestUploadIqProtocolEntity.mediaType == RequestUploadIqProtocolEntity.MEDIA_TYPE_AUDIO: if requestUploadIqProtocolEntity.mediaType == RequestUploadIqProtocolEntity.MEDIA_TYPE_AUDIO:
doSendFn = self.doSendAudio doSendFn = self.doSendAudio
else: else:
@ -203,9 +204,9 @@ class YowsupApp(object):
if resultRequestUploadIqProtocolEntity.isDuplicate(): if resultRequestUploadIqProtocolEntity.isDuplicate():
doSendFn(filePath, resultRequestUploadIqProtocolEntity.getUrl(), jid, doSendFn(filePath, resultRequestUploadIqProtocolEntity.getUrl(), jid,
resultRequestUploadIqProtocolEntity.getIp(), caption) resultRequestUploadIqProtocolEntity.getIp(), caption, onSuccess, onFailure)
else: else:
successFn = lambda filePath, jid, url: doSendFn(filePath, url, jid, resultRequestUploadIqProtocolEntity.getIp(), caption, onSuccess, onFailure) successFn = lambda filePath, jid, url: doSendFn(filePath, url.encode('ascii','ignore'), jid, resultRequestUploadIqProtocolEntity.getIp(), caption, onSuccess, onFailure)
ownNumber = self.stack.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(full=False) ownNumber = self.stack.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(full=False)
mediaUploader = MediaUploader(jid, ownNumber, filePath, mediaUploader = MediaUploader(jid, ownNumber, filePath,
resultRequestUploadIqProtocolEntity.getUrl(), resultRequestUploadIqProtocolEntity.getUrl(),
@ -217,12 +218,10 @@ class YowsupApp(object):
self.logger.error("Request upload for file %s for %s failed" % (path, jid)) self.logger.error("Request upload for file %s for %s failed" % (path, jid))
def onUploadError(self, filePath, jid, url): def onUploadError(self, filePath, jid, url):
#logger.error("Upload file %s to %s for %s failed!" % (filePath, url, jid)) self.logger.error("Upload file %s to %s for %s failed!" % (filePath, url, jid))
self.logger.error("Upload Error!")
def onUploadProgress(self, filePath, jid, url, progress): def onUploadProgress(self, filePath, jid, url, progress):
#sys.stdout.write("%s => %s, %d%% \r" % (os.path.basename(filePath), jid, progress)) self.logger.info("%s => %s, %d%% \r" % (os.path.basename(filePath), jid, progress))
#sys.stdout.flush()
pass pass
def doSendImage(self, filePath, url, to, ip = None, caption = None, onSuccess = None, onFailure = None): def doSendImage(self, filePath, url, to, ip = None, caption = None, onSuccess = None, onFailure = None):
@ -233,7 +232,6 @@ class YowsupApp(object):
onSuccess(entity.getId()) onSuccess(entity.getId())
return entity.getId() return entity.getId()
def doSendAudio(self, filePath, url, to, ip = None, caption = None, onSuccess = None, onFailure = None): def doSendAudio(self, filePath, url, to, ip = None, caption = None, onSuccess = None, onFailure = None):
entity = AudioDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to) entity = AudioDownloadableMediaMessageProtocolEntity.fromFilePath(filePath, url, ip, to)
self.sendEntity(entity) self.sendEntity(entity)
@ -242,8 +240,6 @@ class YowsupApp(object):
onSuccess(entity.getId()) onSuccess(entity.getId())
return entity.getId() return entity.getId()
def sendPresence(self, available): def sendPresence(self, available):
""" """
Send presence to whatsapp Send presence to whatsapp
@ -264,7 +260,7 @@ class YowsupApp(object):
- phone_number: (str) The cellphone number of the person to - phone_number: (str) The cellphone number of the person to
subscribe to subscribe to
""" """
self.logger.debug("Subscribing to Presence updates from %s", (phone_number)) self.logger.debug("Subscribing to Presence updates from %s" % phone_number)
jid = phone_number + '@s.whatsapp.net' jid = phone_number + '@s.whatsapp.net'
entity = SubscribePresenceProtocolEntity(jid) entity = SubscribePresenceProtocolEntity(jid)
self.sendEntity(entity) self.sendEntity(entity)
@ -394,7 +390,7 @@ class YowsupApp(object):
iq = GetStatusesIqProtocolEntity([c + '@s.whatsapp.net' for c in contacts]) iq = GetStatusesIqProtocolEntity([c + '@s.whatsapp.net' for c in contacts])
def onSuccess(response, request): def onSuccess(response, request):
if success is not None: if success is not None:
self.logger.debug("Received Statuses %s", response) self.logger.debug("Received Statuses %s" % response)
s = {} s = {}
for k, v in response.statuses.iteritems(): for k, v in response.statuses.iteritems():
s[k.split('@')[0]] = v s[k.split('@')[0]] = v
@ -798,7 +794,7 @@ class YowsupAppLayer(YowInterfaceLayer):
""" """
Sends ack automatically Sends ack automatically
""" """
self.logger.debug("Received notification (%s): %s", type(entity), entity) self.logger.debug("Received notification (%s): %s" % (type(entity), entity))
self.toLower(entity.ack()) self.toLower(entity.ack())
if isinstance(entity, CreateGroupsNotificationProtocolEntity): if isinstance(entity, CreateGroupsNotificationProtocolEntity):
self.caller.onAddedToGroup(entity) self.caller.onAddedToGroup(entity)
@ -840,7 +836,7 @@ class YowsupAppLayer(YowInterfaceLayer):
@ProtocolEntityCallback('message') @ProtocolEntityCallback('message')
def onMessageReceived(self, entity): def onMessageReceived(self, entity):
self.logger.debug("Received Message: %s", entity) self.logger.debug("Received Message: %s" % unicode(entity))
if entity.getType() == MessageProtocolEntity.MESSAGE_TYPE_TEXT: if entity.getType() == MessageProtocolEntity.MESSAGE_TYPE_TEXT:
self.caller.onTextMessage( self.caller.onTextMessage(
entity._id, entity._id,