Merge branch 'groupchat-fix' of https://github.com/moyamo/transwhat into moyamo-groupchat-fix
Conflicts: Spectrum2/backend.py session.py yowsupwrapper.py
This commit is contained in:
commit
c5b42044b2
|
@ -9,7 +9,7 @@ transWhat is a WhatsApp XMPP Gateway based on [Spectrum 2](http://www.spectrum.i
|
||||||
pip install e4u protobuf mysql dateutil
|
pip install e4u protobuf mysql dateutil
|
||||||
|
|
||||||
- **e4u**: is a simple emoji4unicode python bindings
|
- **e4u**: is a simple emoji4unicode python bindings
|
||||||
- **yowsup**:
|
- [**yowsup**](https://github.com/tgalal/yowsup): is a python library that enables you build application which use WhatsApp service.
|
||||||
- **mysqldb**: MySQL client python bindings
|
- **mysqldb**: MySQL client python bindings
|
||||||
|
|
||||||
#### Spectrum 2
|
#### Spectrum 2
|
||||||
|
@ -28,6 +28,6 @@ The following persons have contributed major parts of this code:
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
A project wiki is available [here](http://dev.0l.de/projects/transwhat/start).
|
A project wiki is available [here](https://dev.0l.de/wiki/projects/transwhat/).
|
||||||
|
|
||||||
An *outdated* writeup of this project is also availabe at my [blog](http://www.steffenvogel.de/2013/06/29/transwhat/).
|
An *outdated* writeup of this project is also availabe at my [blog](http://www.steffenvogel.de/2013/06/29/transwhat/).
|
||||||
|
|
|
@ -25,7 +25,6 @@ class SpectrumBackend:
|
||||||
self.m_init_res = 0
|
self.m_init_res = 0
|
||||||
self.logger = logging.getLogger(self.__class__.__name__)
|
self.logger = logging.getLogger(self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""):
|
def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""):
|
||||||
m = protocol_pb2.ConversationMessage()
|
m = protocol_pb2.ConversationMessage()
|
||||||
m.userName = user
|
m.userName = user
|
||||||
|
@ -371,20 +370,19 @@ class SpectrumBackend:
|
||||||
self.logger.error("Data too small")
|
self.logger.error("Data too small")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
packet = self.m_data[4:4+expected_size]
|
||||||
wrapper = protocol_pb2.WrapperMessage()
|
wrapper = protocol_pb2.WrapperMessage()
|
||||||
try:
|
try:
|
||||||
parseFromString = wrapper.ParseFromString(self.m_data[4:])
|
parseFromString = wrapper.ParseFromString(packet)
|
||||||
except:
|
except:
|
||||||
parseFromString = True
|
|
||||||
self.logger.error("Parse from String exception")
|
|
||||||
|
|
||||||
|
|
||||||
if (parseFromString == False):
|
|
||||||
self.m_data = self.m_data[expected_size+4:]
|
self.m_data = self.m_data[expected_size+4:]
|
||||||
self.logger.error("Parse from String error")
|
self.logger.error("Parse from String exception")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if parseFromString == False:
|
||||||
|
self.m_data = self.m_data[expected_size+4:]
|
||||||
|
self.logger.error("Parse from String failed")
|
||||||
|
return
|
||||||
|
|
||||||
self.m_data = self.m_data[4+expected_size:]
|
self.m_data = self.m_data[4+expected_size:]
|
||||||
#self.logger.error("Data Type: %s",wrapper.type)
|
#self.logger.error("Data Type: %s",wrapper.type)
|
||||||
|
|
112
session.py
112
session.py
|
@ -73,6 +73,8 @@ class Session(YowsupApp):
|
||||||
self.statusMessage = ''
|
self.statusMessage = ''
|
||||||
|
|
||||||
self.groups = {}
|
self.groups = {}
|
||||||
|
self.gotGroupList = False
|
||||||
|
self.joinRoomQueue = []
|
||||||
self.presenceRequested = []
|
self.presenceRequested = []
|
||||||
self.offlineQueue = []
|
self.offlineQueue = []
|
||||||
self.msgIDs = { }
|
self.msgIDs = { }
|
||||||
|
@ -108,22 +110,27 @@ class Session(YowsupApp):
|
||||||
super(Session, self).login(self.legacyName, self.password)
|
super(Session, self).login(self.legacyName, self.password)
|
||||||
|
|
||||||
def _shortenGroupId(self, gid):
|
def _shortenGroupId(self, gid):
|
||||||
# FIXME: will have problems if number begins with 0
|
# FIXME: might have problems if number begins with 0
|
||||||
#return '-'.join(hex(int(s))[2:] for s in gid.split('-'))
|
|
||||||
return gid
|
return gid
|
||||||
|
# return '-'.join(hex(int(s))[2:] for s in gid.split('-'))
|
||||||
|
|
||||||
def _lengthenGroupId(self, gid):
|
def _lengthenGroupId(self, gid):
|
||||||
# FIXME: will have problems if number begins with 0
|
|
||||||
#return '-'.join(str(int(s, 16)) for s in gid.split('-'))
|
|
||||||
return gid
|
return gid
|
||||||
|
# FIXME: might have problems if number begins with 0
|
||||||
|
# return '-'.join(str(int(s, 16)) for s in gid.split('-'))
|
||||||
|
|
||||||
def updateRoomList(self):
|
def updateRoomList(self):
|
||||||
rooms = []
|
rooms = []
|
||||||
|
text = []
|
||||||
for room, group in self.groups.iteritems():
|
for room, group in self.groups.iteritems():
|
||||||
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)
|
||||||
|
|
||||||
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" +\
|
||||||
|
'\n'.join(text) + '\nIf you do not join them you will lose messages'
|
||||||
|
self.bot.send(message)
|
||||||
|
|
||||||
def updateRoster(self):
|
def updateRoster(self):
|
||||||
self.logger.debug("Update roster")
|
self.logger.debug("Update roster")
|
||||||
|
@ -175,8 +182,9 @@ class Session(YowsupApp):
|
||||||
else:
|
else:
|
||||||
self.groups[room] = Group(room, owner, subject, subjectOwner)
|
self.groups[room] = Group(room, owner, subject, subjectOwner)
|
||||||
# self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0])
|
# self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0])
|
||||||
|
self.groups[room].participants = group.getParticipants().keys()
|
||||||
|
|
||||||
self._addParticipantsToRoom(room, group.getParticipants())
|
#self._addParticipantsToRoom(room, group.getParticipants())
|
||||||
|
|
||||||
if room in self.groupOfflineQueue:
|
if room in self.groupOfflineQueue:
|
||||||
while self.groupOfflineQueue[room]:
|
while self.groupOfflineQueue[room]:
|
||||||
|
@ -185,14 +193,17 @@ class Session(YowsupApp):
|
||||||
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
|
||||||
|
for room, nick in self.joinRoomQueue:
|
||||||
|
self.joinRoom(room, nick)
|
||||||
|
self.joinRoomQueue = []
|
||||||
self.updateRoomList()
|
self.updateRoomList()
|
||||||
|
|
||||||
def joinRoom(self, room, nick):
|
def joinRoom(self, room, nick):
|
||||||
|
if not self.gotGroupList:
|
||||||
|
self.joinRoomQueue.append((room, nick))
|
||||||
|
return
|
||||||
room = self._lengthenGroupId(room)
|
room = self._lengthenGroupId(room)
|
||||||
if room not in self.groups:
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -204,11 +215,14 @@ class Session(YowsupApp):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
ownerNick = group.subjectOwner
|
ownerNick = group.subjectOwner
|
||||||
|
|
||||||
self.backend.handleSubject(self.user, room, group.subject,
|
|
||||||
ownerNick)
|
|
||||||
self.backend.handleRoomNicknameChanged(self.user, room,
|
|
||||||
group.subject)
|
|
||||||
self._refreshParticipants(room)
|
self._refreshParticipants(room)
|
||||||
|
self.backend.handleSubject(self.user, self._shortenGroupId(room),
|
||||||
|
group.subject, ownerNick)
|
||||||
|
self.logger.debug("Room subject: room=%s, subject=%s",
|
||||||
|
room, group.subject)
|
||||||
|
self.backend.handleRoomNicknameChanged(
|
||||||
|
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)
|
||||||
|
|
||||||
|
@ -224,7 +238,6 @@ class Session(YowsupApp):
|
||||||
if nick == "":
|
if nick == "":
|
||||||
nick = buddy
|
nick = buddy
|
||||||
|
|
||||||
buddyFull = buddy
|
|
||||||
if buddy == group.owner:
|
if buddy == group.owner:
|
||||||
flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR
|
flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR
|
||||||
else:
|
else:
|
||||||
|
@ -232,37 +245,9 @@ class Session(YowsupApp):
|
||||||
if buddy == self.legacyName:
|
if buddy == self.legacyName:
|
||||||
nick = group.nick
|
nick = group.nick
|
||||||
flags = flags | protocol_pb2.PARTICIPANT_FLAG_ME
|
flags = flags | protocol_pb2.PARTICIPANT_FLAG_ME
|
||||||
buddyFull = self.user
|
|
||||||
self.backend.handleParticipantChanged(
|
self.backend.handleParticipantChanged(
|
||||||
self.user, buddyFull, self._shortenGroupId(room), flags,
|
self.user, nick, self._shortenGroupId(room), flags,
|
||||||
protocol_pb2.STATUS_ONLINE, buddy, nick)
|
protocol_pb2.STATUS_ONLINE, buddy)
|
||||||
|
|
||||||
def _addParticipantsToRoom(self, room, participants):
|
|
||||||
group = self.groups[room]
|
|
||||||
group.participants = participants
|
|
||||||
group.nick = self.user.split("@")[0]
|
|
||||||
|
|
||||||
for jid, _type in participants.iteritems():
|
|
||||||
buddy = jid.split("@")[0]
|
|
||||||
buddyFull = buddy
|
|
||||||
self.logger.info("Added %s to room %s", buddy, room)
|
|
||||||
try:
|
|
||||||
nick = self.buddies[buddy].nick
|
|
||||||
except KeyError:
|
|
||||||
nick = buddy
|
|
||||||
buddyFull = buddy
|
|
||||||
if _type == 'admin':
|
|
||||||
flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR
|
|
||||||
else:
|
|
||||||
flags = protocol_pb2.PARTICIPANT_FLAG_NONE
|
|
||||||
if buddy == self.legacyName:
|
|
||||||
nick = group.nick
|
|
||||||
flags = flags | protocol_pb2.PARTICIPANT_FLAG_ME
|
|
||||||
buddyFull = self.user
|
|
||||||
|
|
||||||
self.backend.handleParticipantChanged(self.user, buddyFull,
|
|
||||||
self._shortenGroupId(room), flags, protocol_pb2.STATUS_ONLINE, buddy, nick)
|
|
||||||
|
|
||||||
|
|
||||||
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, str(seconds)))
|
||||||
|
@ -282,6 +267,7 @@ class Session(YowsupApp):
|
||||||
if self.initialized == False:
|
if self.initialized == False:
|
||||||
self.sendOfflineMessages()
|
self.sendOfflineMessages()
|
||||||
#self.bot.call("welcome")
|
#self.bot.call("welcome")
|
||||||
|
self.bot.call("welcome")
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
self.sendPresence(True)
|
self.sendPresence(True)
|
||||||
self.updateRoster()
|
self.updateRoster()
|
||||||
|
@ -341,6 +327,17 @@ class Session(YowsupApp):
|
||||||
buddy, self.legacyName, messageContent, timestamp)
|
buddy, self.legacyName, messageContent, timestamp)
|
||||||
if participant is not None: # Group message
|
if participant is not None: # Group message
|
||||||
partname = participant.split('@')[0]
|
partname = participant.split('@')[0]
|
||||||
|
try:
|
||||||
|
part = self.buddies[partname]
|
||||||
|
if part.nick == "":
|
||||||
|
part.nick = notify
|
||||||
|
self.backend.handleParticipantChanged(
|
||||||
|
self.user, partname, self._shortenGroupId(buddy),
|
||||||
|
protocol_pb2.PARTICIPANT_FLAG_NONE,
|
||||||
|
protocol_pb2.STATUS_ONLINE, "", part.nick
|
||||||
|
) # TODO
|
||||||
|
except KeyError:
|
||||||
|
self.updateBuddy(partname, notify, [])
|
||||||
self.sendGroupMessageToXMPP(buddy, partname, messageContent,
|
self.sendGroupMessageToXMPP(buddy, partname, messageContent,
|
||||||
timestamp)
|
timestamp)
|
||||||
else:
|
else:
|
||||||
|
@ -357,6 +354,8 @@ class Session(YowsupApp):
|
||||||
self.logger.debug('Received image message %s', str(image))
|
self.logger.debug('Received image message %s', str(image))
|
||||||
buddy = image._from.split('@')[0]
|
buddy = image._from.split('@')[0]
|
||||||
participant = image.participant
|
participant = image.participant
|
||||||
|
if image.caption is None:
|
||||||
|
image.caption = ''
|
||||||
message = image.url + ' ' + image.caption
|
message = image.url + ' ' + image.caption
|
||||||
if participant is not None: # Group message
|
if participant is not None: # Group message
|
||||||
partname = participant.split('@')[0]
|
partname = participant.split('@')[0]
|
||||||
|
@ -416,6 +415,7 @@ class Session(YowsupApp):
|
||||||
self.sendMessageToXMPP(buddy, 'geo:' + latitude + ',' + longitude,
|
self.sendMessageToXMPP(buddy, 'geo:' + latitude + ',' + longitude,
|
||||||
location.timestamp)
|
location.timestamp)
|
||||||
self.sendReceipt(location._id, location._from, None, location.participant)
|
self.sendReceipt(location._id, location._from, None, location.participant)
|
||||||
|
location.timestamp)
|
||||||
|
|
||||||
|
|
||||||
# Called by superclass
|
# Called by superclass
|
||||||
|
@ -461,6 +461,28 @@ class Session(YowsupApp):
|
||||||
self.timer = Timer(3, self.backend.handleBuddyStoppedTyping,
|
self.timer = Timer(3, self.backend.handleBuddyStoppedTyping,
|
||||||
(self.user, buddy)).start()
|
(self.user, buddy)).start()
|
||||||
|
|
||||||
|
# Called by superclass
|
||||||
|
def onAddedToGroup(self, group):
|
||||||
|
self.logger.debug("Added to group: %s", group)
|
||||||
|
room = group.getGroupId()
|
||||||
|
owner = group.getCreatorJid(full = False)
|
||||||
|
subjectOwner = group.getSubjectOwnerJid(full = False)
|
||||||
|
subject = utils.softToUni(group.getSubject())
|
||||||
|
|
||||||
|
self.groups[room] = Group(room, owner, subject, subjectOwner)
|
||||||
|
self.groups[room].participants = group.getParticipants().keys()
|
||||||
|
# self.joinRoom(self._shortenGroupId(room), self.user.split("@")[0])
|
||||||
|
|
||||||
|
#self._addParticipantsToRoom(room, group.getParticipants())
|
||||||
|
self.bot.send("You have been added to group: %s@%s (%s)"
|
||||||
|
% (self._shortenGroupId(room), subject, self.backend.spectrum_jid))
|
||||||
|
|
||||||
|
def onParticipantsAddedToGroup(self, group):
|
||||||
|
self.logger.debug("Participants added to group: %s", group)
|
||||||
|
room = group.getGroupId().split('@')[0]
|
||||||
|
self.groups[room].participants.extend(group.getParticipants())
|
||||||
|
self._refreshParticipants(room)
|
||||||
|
|
||||||
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]
|
||||||
|
@ -642,7 +664,7 @@ class Session(YowsupApp):
|
||||||
"", timestamp)
|
"", timestamp)
|
||||||
|
|
||||||
def sendGroupMessageToXMPP(self, room, buddy, messageContent, timestamp = ""):
|
def sendGroupMessageToXMPP(self, room, buddy, messageContent, timestamp = ""):
|
||||||
self._refreshParticipants(room)
|
# self._refreshParticipants(room)
|
||||||
try:
|
try:
|
||||||
nick = self.buddies[buddy].nick
|
nick = self.buddies[buddy].nick
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -78,7 +78,7 @@ def connectionClosed():
|
||||||
db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE)
|
db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE)
|
||||||
io = IOChannel(args.host, args.port, handleTransportData, connectionClosed)
|
io = IOChannel(args.host, args.port, handleTransportData, connectionClosed)
|
||||||
|
|
||||||
plugin = WhatsAppBackend(io, db)
|
plugin = WhatsAppBackend(io, db, args.j)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -30,12 +30,13 @@ from session import Session
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
class WhatsAppBackend(SpectrumBackend):
|
class WhatsAppBackend(SpectrumBackend):
|
||||||
def __init__(self, io, db):
|
def __init__(self, io, db, spectrum_jid):
|
||||||
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.db = db
|
self.db = db
|
||||||
self.sessions = { }
|
self.sessions = { }
|
||||||
|
self.spectrum_jid = spectrum_jid
|
||||||
# Used to prevent duplicate messages
|
# Used to prevent duplicate messages
|
||||||
self.lastMessage = {}
|
self.lastMessage = {}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from yowsup import env
|
from yowsup import env
|
||||||
from yowsup.stacks import YowStack
|
from yowsup.stacks import YowStack
|
||||||
from yowsup.common import YowConstants
|
from yowsup.common import YowConstants
|
||||||
|
@ -70,6 +72,7 @@ class YowsupApp(object):
|
||||||
YowStanzaRegulator,
|
YowStanzaRegulator,
|
||||||
YowNetworkLayer
|
YowNetworkLayer
|
||||||
)
|
)
|
||||||
|
self.logger = logging.getLogger(self.__class__.__name__)
|
||||||
self.stack = YowStack(layers)
|
self.stack = YowStack(layers)
|
||||||
self.stack.broadcastEvent(
|
self.stack.broadcastEvent(
|
||||||
YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self)
|
YowLayerEvent(YowsupAppLayer.EVENT_START, caller = self)
|
||||||
|
@ -481,6 +484,14 @@ class YowsupApp(object):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def onAddedToGroup(self, entity):
|
||||||
|
"""Called when the user has been added to a new group"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onParticipantsAddedToGroup(self, entity):
|
||||||
|
"""Called when participants have been added to a group"""
|
||||||
|
pass
|
||||||
|
|
||||||
def sendEntity(self, entity):
|
def sendEntity(self, entity):
|
||||||
"""Sends an entity down the stack (as if YowsupAppLayer called toLower)"""
|
"""Sends an entity down the stack (as if YowsupAppLayer called toLower)"""
|
||||||
self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT,
|
self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT,
|
||||||
|
@ -511,6 +522,7 @@ class YowsupAppLayer(YowInterfaceLayer):
|
||||||
# return True otherwise
|
# return True otherwise
|
||||||
if layerEvent.getName() == YowsupAppLayer.EVENT_START:
|
if layerEvent.getName() == YowsupAppLayer.EVENT_START:
|
||||||
self.caller = layerEvent.getArg('caller')
|
self.caller = layerEvent.getArg('caller')
|
||||||
|
self.logger = logging.getLogger(self.__class__.__name__)
|
||||||
return True
|
return True
|
||||||
elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED:
|
elif layerEvent.getName() == YowNetworkLayer.EVENT_STATE_DISCONNECTED:
|
||||||
self.caller.onDisconnect()
|
self.caller.onDisconnect()
|
||||||
|
@ -575,7 +587,12 @@ class YowsupAppLayer(YowInterfaceLayer):
|
||||||
"""
|
"""
|
||||||
Sends ack automatically
|
Sends ack automatically
|
||||||
"""
|
"""
|
||||||
|
self.logger.debug("Received notification: %s", entity)
|
||||||
self.toLower(entity.ack())
|
self.toLower(entity.ack())
|
||||||
|
if isinstance(entity, CreateGroupsNotificationProtocolEntity):
|
||||||
|
self.caller.onAddedToGroup(entity)
|
||||||
|
elif isinstance(entity, AddGroupsNotificationProtocolEntity):
|
||||||
|
self.caller.onParticipantsAddedToGroup(entity)
|
||||||
|
|
||||||
@ProtocolEntityCallback('message')
|
@ProtocolEntityCallback('message')
|
||||||
def onMessageReceived(self, entity):
|
def onMessageReceived(self, entity):
|
||||||
|
|
Loading…
Reference in a new issue