Compare commits

..

No commits in common. "yowsup-3" and "yowsup-2" have entirely different histories.

23 changed files with 5234 additions and 2395 deletions

View file

@ -31,7 +31,7 @@ Configuration
The only important thing for us is the configuration of a XMPP component The only important thing for us is the configuration of a XMPP component
(Spectrum 2 in our case). See http://prosody.im/doc/components. (Spectrum 2 in our case). See http://prosody.im/doc/components.
Append the following at the end of ``/etc/prosody/conf.d/transwhat.cfg.lua`` Append the following at the end of ``/etc/prosody/prosody.cfg.lua``
:: ::
@ -53,7 +53,7 @@ http://spectrum.im/documentation/installation/from\_source\_code.html.
Configuration Configuration
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
Create a new file ``/etc/spectrum2/transports/transwhat.cfg`` with the Create a new file ``/etc/spectrum2/transports/whatsapp.cfg`` with the
following content: following content:
:: ::
@ -104,12 +104,14 @@ Install required dependencies:
:: ::
$ pip install --pre protobuf python-dateutil yowsup2 $ pip install --pre e4u protobuf python-dateutil yowsup2
- e4u_: is a simple emoji4unicode python bindings
- yowsup_: is a python library that enables you build application - yowsup_: is a python library that enables you build application
which use WhatsApp service. which use WhatsApp service.
.. _Spectrum 2: http://www.spectrum.im .. _Spectrum 2: http://www.spectrum.im
.. _Yowsup 3: https://github.com/tgalal/yowsup .. _Yowsup 2: https://github.com/tgalal/yowsup
.. _Github: https://github.com/hanzz/libtransport .. _Github: https://github.com/hanzz/libtransport
.. _yowsup: https://github.com/tgalal/yowsup .. _yowsup: https://github.com/tgalal/yowsup
.. _e4u: https://pypi.python.org/pypi/e4u

1
MANIFEST.in Normal file
View file

@ -0,0 +1 @@
include *.md

View file

@ -1,6 +1,7 @@
transpub transWhat
========= =========
transWhat is a WhatsApp XMPP Gateway based on `Spectrum 2`_ and `Yowsup 2`_.
Support Support
------- -------
@ -10,11 +11,11 @@ For support and discussions please join the XMPP MUC: **transwhat@conference.0l.
Features Features
-------- --------
- notifications - Typing notifications
- Receive data on pubsub extension xep - Receive images, audio & video
- Set/get online status - Set/get online status
- Set status message - Set status message
- Groupchats
Installation Installation
------------ ------------
@ -27,9 +28,31 @@ Users find details on the `Usage`_ page.
Branches Branches
-------- --------
- `yowsup-3`_ Update to @tgalals new Yowsup 3 - `yowsup-1`_ My original version which is based on @tgalal first
Yowsup version (**deprecated** and broken).
- `yowsup-2`_ Major rewrite from @moyamo for @tgalals new Yowsup 2
(**recommended**). (**recommended**).
For production, please use the ``yowsup-2`` branch.
Contributors
------------
Pull requests, bug reports etc. are welcome. Help us to provide a open
implementation of the WhatsApp protocol.
The following persons have contributed major parts of this code:
- @stv0g (Steffen Vogel): Idea and initial implementation based on
Yowsup 1
- @moyamo (Mohammed Yaseen Mowzer): Port to Yowsup 2
- @DaZZZl: Improvements to group chats, media & message receipts
License
-------
transWhat is licensed under the GPLv3_ license.
Links Links
----- -----
@ -37,6 +60,11 @@ Links
- An *outdated* writeup of this project is also availabe at my `blog`_. - An *outdated* writeup of this project is also availabe at my `blog`_.
.. _Spectrum 2: http://www.spectrum.im .. _Spectrum 2: http://www.spectrum.im
.. _Yowsup 2: https://github.com/tgalal/yowsup
.. _yowsup-1: http://github.com/stv0g/transwhat/tree/yowsup-1
.. _yowsup-2: http://github.com/stv0g/transwhat/tree/yowsup-2
.. _Installation: INSTALL.rst .. _Installation: INSTALL.rst
.. _Usage: USAGE.rst .. _Usage: USAGE.rst
.. _GPLv3: COPYING.rst .. _GPLv3: COPYING.rst
.. _here: https://dev.0l.de/wiki/projects/transwhat/
.. _blog: http://www.steffenvogel.de/2013/06/29/transwhat/

0
Spectrum2/__init__.py Normal file
View file

659
Spectrum2/backend.py Normal file
View file

@ -0,0 +1,659 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import protocol_pb2
import socket
import struct
import sys
import os
import logging
import google.protobuf
import resource
def WRAP(MESSAGE, TYPE):
wrap = protocol_pb2.WrapperMessage()
wrap.type = TYPE
wrap.payload = bytes(MESSAGE)
return wrap.SerializeToString()
class SpectrumBackend:
"""
Creates new NetworkPlugin and connects the Spectrum2 NetworkPluginServer.
@param loop: Event loop.
@param host: Host where Spectrum2 NetworkPluginServer runs.
@param port: Port.
"""
def __init__(self):
self.m_pingReceived = False
self.m_data = bytes("")
self.m_init_res = 0
self.logger = logging.getLogger(self.__class__.__name__)
def handleMessage(self, user, legacyName, msg, nickname = "", xhtml = "", timestamp = ""):
m = protocol_pb2.ConversationMessage()
m.userName = user
m.buddyName = legacyName
m.message = msg
m.nickname = nickname
m.xhtml = xhtml
m.timestamp = str(timestamp)
message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE)
self.send(message)
def handleMessageAck(self, user, legacyName, ID):
m = protocol_pb2.ConversationMessage()
m.userName = user
m.buddyName = legacyName
m.message = ""
m.id = ID
message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK)
self.send(message)
def handleAttention(self, user, buddyName, msg):
m = protocol_pb2.ConversationMessage()
m.userName = user
m.buddyName = buddyName
m.message = msg
message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_ATTENTION)
self.send(message)
def handleVCard(self, user, ID, legacyName, fullName, nickname, photo):
vcard = protocol_pb2.VCard()
vcard.userName = user
vcard.buddyName = legacyName
vcard.id = ID
vcard.fullname = fullName
vcard.nickname = nickname
vcard.photo = bytes(photo)
message = WRAP(vcard.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_VCARD)
self.send(message)
def handleSubject(self, user, legacyName, msg, nickname = ""):
m = protocol_pb2.ConversationMessage()
m.userName = user
m.buddyName = legacyName
m.message = msg
m.nickname = nickname
message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_ROOM_SUBJECT_CHANGED)
self.send(message)
def handleBuddyChanged(self, user, buddyName, alias, groups, status, statusMessage = "", iconHash = "", blocked = False):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
buddy.alias = alias
buddy.group.extend(groups)
buddy.status = status
buddy.statusMessage = statusMessage
buddy.iconHash = iconHash
buddy.blocked = blocked
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BUDDY_CHANGED)
self.send(message)
def handleBuddyRemoved(self, user, buddyName):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BUDDY_REMOVED)
self.send(message);
def handleBuddyTyping(self, user, buddyName):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPING)
self.send(message);
def handleBuddyTyped(self, user, buddyName):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPED)
self.send(message);
def handleBuddyStoppedTyping(self, user, buddyName):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BUDDY_STOPPED_TYPING)
self.send(message)
def handleAuthorization(self, user, buddyName):
buddy = protocol_pb2.Buddy()
buddy.userName = user
buddy.buddyName = buddyName
message = WRAP(buddy.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_AUTH_REQUEST)
self.send(message)
def handleConnected(self, user):
d = protocol_pb2.Connected()
d.user = user
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONNECTED)
self.send(message);
def handleDisconnected(self, user, error = 0, msg = ""):
d = protocol_pb2.Disconnected()
d.user = user
d.error = error
d.message = msg
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_DISCONNECTED)
self.send(message);
def handleParticipantChanged(self, user, nickname, room, flags, status, statusMessage = "", newname = "", iconHash = ""):
d = protocol_pb2.Participant()
d.userName = user
d.nickname = nickname
d.room = room
d.flag = flags
d.newname = newname
d.iconHash = iconHash
d.status = status
d.statusMessage = statusMessage
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_PARTICIPANT_CHANGED)
self.send(message);
def handleRoomNicknameChanged(self, user, r, nickname):
room = protocol_pb2.Room()
room.userName = user
room.nickname = nickname
room.room = r
room.password = ""
message = WRAP(room.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_ROOM_NICKNAME_CHANGED)
self.send(message);
def handleRoomList(self, rooms):
roomList = protocol_pb2.RoomList()
for room in rooms:
roomList.room.append(room[0])
roomList.name.append(room[1])
message = WRAP(roomList.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_ROOM_LIST)
self.send(message);
def handleFTStart(self, user, buddyName, fileName, size):
room = protocol_pb2.File()
room.userName = user
room.buddyName = buddyName
room.fileName = fileName
room.size = size
message = WRAP(room.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_START)
self.send(message);
def handleFTFinish(self, user, buddyName, fileName, size, ftid):
room = protocol_pb2.File()
room.userName = user
room.buddyName = buddyName
room.fileName = fileName
room.size = size
# Check later
if ftid != 0:
room.ftID = ftid
message = WRAP(room.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_FINISH)
self.send(message)
def handleFTData(self, ftID, data):
d = protocol_pb2.FileTransferData()
d.ftid = ftID
d.data = bytes(data)
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_DATA);
self.send(message)
def handleBackendConfig(self, data):
"""
data is a dictionary, whose keys are sections and values are a list of
tuples of configuration key and configuration value.
"""
c = protocol_pb2.BackendConfig()
config = []
for section, rest in data.items():
config.append('[%s]' % section)
for key, value in rest:
config.append('%s = %s' % (key, value))
c.config = '\n'.join(config)
message = WRAP(c.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_BACKEND_CONFIG);
self.send(message)
def handleQuery(self, command):
c = protocol_pb2.BackendConfig()
c.config = command
message = WRAP(c.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_QUERY);
self.send(message)
def handleLoginPayload(self, data):
payload = protocol_pb2.Login()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleLoginRequest(payload.user, payload.legacyName, payload.password, payload.extraFields)
def handleLogoutPayload(self, data):
payload = protocol_pb2.Logout()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleLogoutRequest(payload.user, payload.legacyName)
def handleStatusChangedPayload(self, data):
payload = protocol_pb2.Status()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleStatusChangeRequest(payload.userName, payload.status, payload.statusMessage)
def handleConvMessagePayload(self, data):
payload = protocol_pb2.ConversationMessage()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleMessageSendRequest(payload.userName, payload.buddyName, payload.message, payload.xhtml, payload.id)
def handleConvMessageAckPayload(self, data):
payload = protocol_pb2.ConversationMessage()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleMessageAckRequest(payload.userName, payload.buddyName, payload.id)
def handleAttentionPayload(self, data):
payload = protocol_pb2.ConversationMessage()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleAttentionRequest(payload.userName, payload.buddyName, payload.message)
def handleFTStartPayload(self, data):
payload = protocol_pb2.File()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleFTStartRequest(payload.userName, payload.buddyName, payload.fileName, payload.size, payload.ftID);
def handleFTFinishPayload(self, data):
payload = protocol_pb2.File()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleFTFinishRequest(payload.userName, payload.buddyName, payload.fileName, payload.size, payload.ftID)
def handleFTPausePayload(self, data):
payload = protocol_pb2.FileTransferData()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleFTPauseRequest(payload.ftID)
def handleFTContinuePayload(self, data):
payload = protocol_pb2.FileTransferData()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleFTContinueRequest(payload.ftID)
def handleJoinRoomPayload(self, data):
payload = protocol_pb2.Room()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleJoinRoomRequest(payload.userName, payload.room, payload.nickname, payload.password)
def handleLeaveRoomPayload(self, data):
payload = protocol_pb2.Room()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleLeaveRoomRequest(payload.userName, payload.room)
def handleVCardPayload(self, data):
payload = protocol_pb2.VCard()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
if payload.HasField('photo'):
self.handleVCardUpdatedRequest(payload.userName, payload.photo, payload.nickname)
elif len(payload.buddyName) > 0:
self.handleVCardRequest(payload.userName, payload.buddyName, payload.id)
def handleBuddyChangedPayload(self, data):
payload = protocol_pb2.Buddy()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
if payload.HasField('blocked'):
self.handleBuddyBlockToggled(payload.userName, payload.buddyName, payload.blocked)
else:
groups = [g for g in payload.group]
self.handleBuddyUpdatedRequest(payload.userName, payload.buddyName, payload.alias, groups);
def handleBuddyRemovedPayload(self, data):
payload = protocol_pb2.Buddy()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
groups = [g for g in payload.group]
self.handleBuddyRemovedRequest(payload.userName, payload.buddyName, groups);
def handleBuddiesPayload(self, data):
payload = protocol_pb2.Buddies()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
self.handleBuddies(payload);
def handleChatStatePayload(self, data, msgType):
payload = protocol_pb2.Buddy()
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
if msgType == protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPING:
self.handleTypingRequest(payload.userName, payload.buddyName)
elif msgType == protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPED:
self.handleTypedRequest(payload.userName, payload.buddyName)
elif msgType == protocol_pb2.WrapperMessage.TYPE_BUDDY_STOPPED_TYPING:
self.handleStoppedTypingRequest(payload.userName, payload.buddyName)
def handleDataRead(self, data):
self.m_data += data
while len(self.m_data) != 0:
expected_size = 0
if (len(self.m_data) >= 4):
expected_size = struct.unpack('!I', self.m_data[0:4])[0]
if (len(self.m_data) - 4 < expected_size):
self.logger.debug("Data packet incomplete")
return
else:
self.logger.debug("Data packet incomplete")
return
packet = self.m_data[4:4+expected_size]
wrapper = protocol_pb2.WrapperMessage()
try:
parseFromString = wrapper.ParseFromString(packet)
except:
self.m_data = self.m_data[expected_size+4:]
self.logger.error("Parse from String exception. Skipping packet.")
return
if parseFromString == False:
self.m_data = self.m_data[expected_size+4:]
self.logger.error("Parse from String failed. Skipping packet.")
return
self.m_data = self.m_data[4+expected_size:]
if wrapper.type == protocol_pb2.WrapperMessage.TYPE_LOGIN:
self.handleLoginPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_LOGOUT:
self.handleLogoutPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_PING:
self.sendPong()
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE:
self.handleConvMessagePayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_JOIN_ROOM:
self.handleJoinRoomPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_LEAVE_ROOM:
self.handleLeaveRoomPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_VCARD:
self.handleVCardPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDY_CHANGED:
self.handleBuddyChangedPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDY_REMOVED:
self.handleBuddyRemovedPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_STATUS_CHANGED:
self.handleStatusChangedPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPING:
self.handleChatStatePayload(wrapper.payload, protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPING)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPED:
self.handleChatStatePayload(wrapper.payload, protocol_pb2.WrapperMessage.TYPE_BUDDY_TYPED)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDY_STOPPED_TYPING:
self.handleChatStatePayload(wrapper.payload, protocol_pb2.WrapperMessage.TYPE_BUDDY_STOPPED_TYPING)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_ATTENTION:
self.handleAttentionPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_FT_START:
self.handleFTStartPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_FT_FINISH:
self.handleFTFinishPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_FT_PAUSE:
self.handleFTPausePayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_FT_CONTINUE:
self.handleFTContinuePayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_EXIT:
self.handleExitRequest()
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK:
self.handleConvMessageAckPayload(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_RAW_XML:
self.handleRawXmlRequest(wrapper.payload)
elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDIES:
self.handleBuddiesPayload(wrapper.payload)
def send(self, data):
header = struct.pack('!I',len(data))
self.sendData(header + data)
def checkPing(self):
if (self.m_pingReceived == False):
self.handleExitRequest()
self.m_pingReceived = False
def sendPong(self):
self.m_pingReceived = True
wrap = protocol_pb2.WrapperMessage()
wrap.type = protocol_pb2.WrapperMessage.TYPE_PONG
message = wrap.SerializeToString()
self.send(message)
self.sendMemoryUsage()
def sendMemoryUsage(self):
stats = protocol_pb2.Stats()
stats.init_res = self.m_init_res
res = 0
shared = 0
e_res, e_shared = self.handleMemoryUsage()
stats.res = res + e_res
stats.shared = shared + e_shared
stats.id = str(os.getpid())
message = WRAP(stats.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_STATS)
self.send(message)
def handleLoginRequest(self, user, legacyName, password, extra):
"""
Called when XMPP user wants to connect legacy network.
You should connect him to legacy network and call handleConnected or handleDisconnected function later.
@param user: XMPP JID of user for which this event occurs.
@param legacyName: Legacy network name of this user used for login.
@param password: Legacy network password of this user.
"""
#\msc
#NetworkPlugin,YourNetworkPlugin,LegacyNetwork;
#NetworkPlugin->YourNetworkPlugin [label="handleLoginRequest(...)", URL="\ref NetworkPlugin::handleLoginRequest()"];
#YourNetworkPlugin->LegacyNetwork [label="connect the legacy network"];
#--- [label="If password was valid and user is connected and logged in"];
#YourNetworkPlugin<-LegacyNetwork [label="connected"];
#YourNetworkPlugin->NetworkPlugin [label="handleConnected()", URL="\ref NetworkPlugin::handleConnected()"];
#--- [label="else"];
#YourNetworkPlugin<-LegacyNetwork [label="disconnected"];
#YourNetworkPlugin->NetworkPlugin [label="handleDisconnected()", URL="\ref NetworkPlugin::handleDisconnected()"];
#\endmsc
raise NotImplementedError, "Implement me"
def handleBuddies(self, buddies):
pass
def handleLogoutRequest(self, user, legacyName):
"""
Called when XMPP user wants to disconnect legacy network.
You should disconnect him from legacy network.
@param user: XMPP JID of user for which this event occurs.
@param legacyName: Legacy network name of this user used for login.
"""
raise NotImplementedError, "Implement me"
def handleMessageSendRequest(self, user, legacyName, message, xhtml = "", ID = 0):
"""
Called when XMPP user sends message to legacy network.
@param user: XMPP JID of user for which this event occurs.
@param legacyName: Legacy network name of buddy or room.
@param message: Plain text message.
@param xhtml: XHTML message.
@param ID: message ID
"""
raise NotImplementedError, "Implement me"
def handleMessageAckRequest(self, user, legacyName, ID = 0):
"""
Called when XMPP user sends message to legacy network.
@param user: XMPP JID of user for which this event occurs.
@param legacyName: Legacy network name of buddy or room.
@param ID: message ID
"""
# raise NotImplementedError, "Implement me"
pass
def handleVCardRequest(self, user, legacyName, ID):
""" Called when XMPP user requests VCard of buddy.
@param user: XMPP JID of user for which this event occurs.
@param legacyName: Legacy network name of buddy whose VCard is requested.
@param ID: ID which is associated with this request. You have to pass it to handleVCard function when you receive VCard."""
#\msc
#NetworkPlugin,YourNetworkPlugin,LegacyNetwork;
#NetworkPlugin->YourNetworkPlugin [label="handleVCardRequest(...)", URL="\ref NetworkPlugin::handleVCardRequest()"];
#YourNetworkPlugin->LegacyNetwork [label="start VCard fetching"];
#YourNetworkPlugin<-LegacyNetwork [label="VCard fetched"];
#YourNetworkPlugin->NetworkPlugin [label="handleVCard()", URL="\ref NetworkPlugin::handleVCard()"];
#\endmsc
pass
def handleVCardUpdatedRequest(self, user, photo, nickname):
"""
Called when XMPP user updates his own VCard.
You should update the VCard in legacy network too.
@param user: XMPP JID of user for which this event occurs.
@param photo: Raw photo data.
"""
pass
def handleJoinRoomRequest(self, user, room, nickname, pasword):
pass
def handleLeaveRoomRequest(self, user, room):
pass
def handleStatusChangeRequest(self, user, status, statusMessage):
pass
def handleBuddyUpdatedRequest(self, user, buddyName, alias, groups):
pass
def handleBuddyRemovedRequest(self, user, buddyName, groups):
pass
def handleBuddyBlockToggled(self, user, buddyName, blocked):
pass
def handleTypingRequest(self, user, buddyName):
pass
def handleTypedRequest(self, user, buddyName):
pass
def handleStoppedTypingRequest(self, user, buddyName):
pass
def handleAttentionRequest(self, user, buddyName, message):
pass
def handleFTStartRequest(self, user, buddyName, fileName, size, ftID):
pass
def handleFTFinishRequest(self, user, buddyName, fileName, size, ftID):
pass
def handleFTPauseRequest(self, ftID):
pass
def handleFTContinueRequest(self, ftID):
pass
def handleMemoryUsage(self):
return (resource.getrusage(resource.RUSAGE_SELF).ru_maxrss, 0)
def handleExitRequest(self):
sys.exit(1)
def handleRawXmlRequest(self, xml):
pass
def sendData(self, data):
pass

141
Spectrum2/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

69
Spectrum2/iochannel.py Normal file
View file

@ -0,0 +1,69 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import asyncore, socket
import logging
import sys
class IOChannel(asyncore.dispatcher):
def __init__(self, host, port, callback, closeCallback):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
self.logger = logging.getLogger(self.__class__.__name__)
self.callback = callback
self.closeCallback = closeCallback
self.buffer = bytes("")
def sendData(self, data):
self.buffer += data
def handle_connect(self):
pass
def handle_close(self):
self.close()
def handle_read(self):
data = self.recv(65536)
self.callback(data)
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
def handle_close(self):
self.logger.info('Connection to backend closed, terminating.')
self.close()
self.closeCallback()
def writable(self):
return (len(self.buffer) > 0)
def readable(self):
return True

203
Spectrum2/protocol.proto Normal file
View file

@ -0,0 +1,203 @@
package pbnetwork;
enum ConnectionError {
CONNECTION_ERROR_NETWORK_ERROR = 0;
CONNECTION_ERROR_INVALID_USERNAME = 1;
CONNECTION_ERROR_AUTHENTICATION_FAILED = 2;
CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE = 3;
CONNECTION_ERROR_NO_SSL_SUPPORT = 4;
CONNECTION_ERROR_ENCRYPTION_ERROR = 5;
CONNECTION_ERROR_NAME_IN_USE = 6;
CONNECTION_ERROR_INVALID_SETTINGS = 7;
CONNECTION_ERROR_CERT_NOT_PROVIDED = 8;
CONNECTION_ERROR_CERT_UNTRUSTED = 9;
CONNECTION_ERROR_CERT_EXPIRED = 10;
CONNECTION_ERROR_CERT_NOT_ACTIVATED = 11;
CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH = 12;
CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH = 13;
CONNECTION_ERROR_CERT_SELF_SIGNED = 14;
CONNECTION_ERROR_CERT_OTHER_ERROR = 15;
CONNECTION_ERROR_OTHER_ERROR = 16;
}
enum StatusType {
STATUS_ONLINE = 0;
STATUS_AWAY = 1;
STATUS_FFC = 2;
STATUS_XA = 3;
STATUS_DND = 4;
STATUS_NONE = 5;
STATUS_INVISIBLE = 6;
}
message Connected {
required string user = 1;
}
message Disconnected {
required string user = 1;
required int32 error = 2;
optional string message = 3;
}
message Login {
required string user = 1;
required string legacyName = 2;
required string password = 3;
repeated string extraFields = 4;
}
message Logout {
required string user = 1;
required string legacyName = 2;
}
message Buddy {
required string userName = 1;
required string buddyName = 2;
optional string alias = 3;
repeated string group = 4;
optional StatusType status = 5;
optional string statusMessage = 6;
optional string iconHash = 7;
optional bool blocked = 8;
}
message Buddies {
repeated Buddy buddy = 1;
}
message ConversationMessage {
required string userName = 1;
required string buddyName = 2;
required string message = 3;
optional string nickname = 4;
optional string xhtml = 5;
optional string timestamp = 6;
optional bool headline = 7;
optional string id = 8;
optional bool pm = 9;
}
message Room {
required string userName = 1;
required string nickname = 2;
required string room = 3;
optional string password = 4;
}
message RoomList {
repeated string room = 1;
repeated string name = 2;
optional string user = 3;
}
enum ParticipantFlag {
PARTICIPANT_FLAG_NONE = 0;
PARTICIPANT_FLAG_MODERATOR = 1;
PARTICIPANT_FLAG_CONFLICT = 2;
PARTICIPANT_FLAG_BANNED = 4;
PARTICIPANT_FLAG_NOT_AUTHORIZED = 8;
PARTICIPANT_FLAG_ME = 16;
PARTICIPANT_FLAG_KICKED = 32;
PARTICIPANT_FLAG_ROOM_NOT_FOUND = 64;
}
message Participant {
required string userName = 1;
required string room = 2;
required string nickname = 3;
required int32 flag = 4;
required StatusType status = 5;
optional string statusMessage = 6;
optional string newname = 7;
optional string iconHash = 8;
optional string alias = 9;
}
message VCard {
required string userName = 1;
required string buddyName = 2;
required int32 id = 3;
optional string fullname = 4;
optional string nickname = 5;
optional bytes photo = 6;
}
message Status {
required string userName = 1;
required StatusType status = 3;
optional string statusMessage = 4;
}
message Stats {
required int32 res = 1;
required int32 init_res = 2;
required int32 shared = 3;
required string id = 4;
}
message File {
required string userName = 1;
required string buddyName = 2;
required string fileName = 3;
required int32 size = 4;
optional int32 ftID = 5;
}
message FileTransferData {
required int32 ftID = 1;
required bytes data = 2;
}
message BackendConfig {
required string config = 1;
}
message APIVersion {
required int32 version = 1;
}
message WrapperMessage {
enum Type {
TYPE_CONNECTED = 1;
TYPE_DISCONNECTED = 2;
TYPE_LOGIN = 3;
TYPE_LOGOUT = 4;
TYPE_BUDDY_CHANGED = 6;
TYPE_BUDDY_REMOVED = 7;
TYPE_CONV_MESSAGE = 8;
TYPE_PING = 9;
TYPE_PONG = 10;
TYPE_JOIN_ROOM = 11;
TYPE_LEAVE_ROOM = 12;
TYPE_PARTICIPANT_CHANGED = 13;
TYPE_ROOM_NICKNAME_CHANGED = 14;
TYPE_ROOM_SUBJECT_CHANGED = 15;
TYPE_VCARD = 16;
TYPE_STATUS_CHANGED = 17;
TYPE_BUDDY_TYPING = 18;
TYPE_BUDDY_STOPPED_TYPING = 19;
TYPE_BUDDY_TYPED = 20;
TYPE_AUTH_REQUEST = 21;
TYPE_ATTENTION = 22;
TYPE_STATS = 23;
TYPE_FT_START = 24;
TYPE_FT_FINISH = 25;
TYPE_FT_DATA = 26;
TYPE_FT_PAUSE = 27;
TYPE_FT_CONTINUE = 28;
TYPE_EXIT = 29;
TYPE_BACKEND_CONFIG = 30;
TYPE_QUERY = 31;
TYPE_ROOM_LIST = 32;
TYPE_CONV_MESSAGE_ACK = 33;
TYPE_RAW_XML = 34;
TYPE_BUDDIES = 35;
TYPE_API_VERSION = 36;
}
required Type type = 1;
optional bytes payload = 2;
}
;

1452
Spectrum2/protocol_pb2.py Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,4 +0,0 @@
Component "whatsapp.0l.de"
component_secret = "whatsappsucks"
component_ports = { 5221 }
component_interface = "127.0.0.1"

View file

@ -1,30 +0,0 @@
[service]
user = spectrum
group = spectrum
jid = whatsapp.0l.de
server = localhost
password = whatsappsucks
port = 5221
backend_host = localhost
backend = /usr/bin/transwhat
users_per_backend = 10
more_resources = 1
admin_jid = your@jid.example
[identity]
name = transWhat
type = xmpp
category = gateway
[logging]
config = /etc/spectrum2/logging.cfg
backend_config = /etc/spectrum2/backend-logging.cfg
[database]
type = sqlite3

View file

@ -4,46 +4,50 @@ import os
import codecs import codecs
from setuptools import setup from setuptools import setup
def read_file(filename, encoding='utf8'): def read_file(filename, encoding='utf8'):
"""Read unicode from given file.""" """Read unicode from given file."""
with codecs.open(filename, encoding=encoding) as fd: with codecs.open(filename, encoding=encoding) as fd:
return fd.read() return fd.read()
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst')) readme = read_file(os.path.join(here, 'README.rst'))
setup(name='transwhat', setup(name='transwhat',
version='0.2.2', version='0.2.2',
description='A gateway between the XMPP and the WhatsApp IM networks', description='A gateway between the XMPP and the WhatsApp IM networks',
long_description=readme, long_description=readme,
keywords='whatsapp xmpp im gateway transport yowsup', keywords='whatsapp xmpp im gateway transport yowsup',
url='https://github.com/stv0g/transwhat', url='https://github.com/stv0g/transwhat',
author='Steffen Vogel', author='Steffen Vogel',
author_email='stv0g@0l.de', author_email='stv0g@0l.de',
python_requires='>=3.5', classifiers=[
classifiers=[ 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 'Development Status :: 4 - Beta',
'Development Status :: 4 - Beta', 'Environment :: Plugins',
'Environment :: Plugins', 'Operating System :: POSIX',
'Operating System :: POSIX', 'Topic :: Communications :: Chat'
'Topic :: Communications :: Chat' ],
], license='GPL-3+',
license='GPL-3+', packages=[
packages=[ 'transWhat',
'transWhat' 'Spectrum2'
], ],
scripts=[ scripts=[
'transWhat/transwhat.py' 'transWhat/transwhat.py'
], ],
install_requires=[ install_requires=[
'protobuf', 'protobuf',
'yowsup', 'yowsup2',
'pyspectrum2', 'e4u',
'python-dateutil', 'Pillow',
], 'python-dateutil'
entry_points={ ],
'console_scripts': ['transwhat=transWhat.transwhat:main'], entry_points={
}, 'console_scripts': ['transwhat=transWhat.transwhat:main'],
zip_safe=False, },
include_package_data=True zip_safe=False,
include_package_data=True
) )

View file

@ -1,73 +1,100 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import threading import threading
import inspect import inspect
import re import re
import urllib import urllib
import time import time
import os import os
import utils
class Bot(): class Bot():
def __init__(self, session, name = "Bot"): def __init__(self, session, name = "Bot"):
self.session = session self.session = session
self.name = name self.name = name
self.commands = { self.commands = {
"help": self._help, "help": self._help,
"groups": self._groups, "groups": self._groups,
"getgroups": self._getgroups "getgroups": self._getgroups
} }
def parse(self, message): def parse(self, message):
args = message.strip().split(" ") args = message.strip().split(" ")
cmd = args.pop(0) cmd = args.pop(0)
if len(cmd) > 0 and cmd[0] == '\\': if len(cmd) > 0 and cmd[0] == '\\':
try: try:
self.call(cmd[1:], args) self.call(cmd[1:], args)
except KeyError: except KeyError:
self.send("invalid command") self.send("invalid command")
except TypeError: except TypeError:
self.send("invalid syntax") self.send("invalid syntax")
else: else:
self.send("a valid command starts with a backslash") self.send("a valid command starts with a backslash")
def call(self, cmd, args = []): def call(self, cmd, args = []):
func = self.commands[cmd.lower()] func = self.commands[cmd.lower()]
spec = inspect.getargspec(func) spec = inspect.getargspec(func)
maxs = len(spec.args) - 1 maxs = len(spec.args) - 1
reqs = maxs - len(spec.defaults or []) reqs = maxs - len(spec.defaults or [])
if (reqs > len(args)) or (len(args) > maxs): if (reqs > len(args)) or (len(args) > maxs):
raise TypeError() raise TypeError()
thread = threading.Thread(target=func, args=tuple(args)) thread = threading.Thread(target=func, args=tuple(args))
thread.start() thread.start()
def send(self, message): def send(self, message):
self.session.backend.handleMessage(self.session.user, self.name, message) self.session.backend.handleMessage(self.session.user, self.name, message)
# commands # commands
def _help(self): def _help(self):
self.send("""following bot commands are available: self.send("""following bot commands are available:
\\help show thi \\help show this message
following user commands are available: following user commands are available:
\\lastseen \\lastseen request last online timestamp from buddy
following group commands are available following group commands are available
\\leave permanentlhat \\leave permanently leave group chat
\\groups print all attended groups \\groups print all attended groups
\\getgroups get current groups from WA""") \\getgroups get current groups from WA""")
def _groups(self): def _groups(self):
for group in self.session.groups: for group in self.session.groups:
buddy = self.session.groups[group].owner buddy = self.session.groups[group].owner
try: try:
nick = self.session.buddies[buddy].nick nick = self.session.buddies[buddy].nick
except KeyError: except KeyError:
nick = buddy nick = buddy
self.send(self.session.groups[group].id + "@" + self.session.backend.spectrum_jid + " " + self.session.groups[group].subject + " Owner: " + nick ) self.send(self.session.groups[group].id + "@" + self.session.backend.spectrum_jid + " " + self.session.groups[group].subject + " Owner: " + nick )
def _getgroups(self): def _getgroups(self):
#self.session.call("group_getGroups", ("participating",)) #self.session.call("group_getGroups", ("participating",))
self.session.requestGroupsList(self.session._updateGroups) self.session.requestGroupsList(self.session._updateGroups)

View file

@ -1,213 +1,237 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
from Spectrum2 import protocol_pb2
import logging import logging
import time import time
import utils
import base64 import base64
import hashlib
import Spectrum2
from . import deferred import deferred
from deferred import call
class Buddy(): 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 = "%s" % 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""
self.lastseen = 0 self.lastseen = 0
self.presence = 0 self.presence = 0
def update(self, nick, groups, image_hash): def update(self, nick, groups, image_hash):
self.nick = nick self.nick = nick
self.groups = groups self.groups = groups
if image_hash is not None: if image_hash is not None:
self.image_hash = image_hash self.image_hash = image_hash
def __str__(self): def __str__(self):
# we must return str here # we must return str here
return str("%s (nick=%s)") % (self.number, self.nick) return str("%s (nick=%s)") % (self.number, self.nick)
class BuddyList(dict): class BuddyList(dict):
def __init__(self, owner, backend, user, session): def __init__(self, owner, backend, user, session):
self.owner = owner self.owner = owner
self.backend = backend self.backend = backend
self.session = session self.session = session
self.user = user self.user = user
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
def _load(self, buddies): def _load(self, buddies):
for buddy in buddies: for buddy in buddies:
number = buddy.buddyName number = buddy.buddyName
nick = buddy.alias nick = buddy.alias
statusMsg = buddy.statusMessage 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,
groups, image_hash) groups, image_hash)
self.logger.debug("Update roster") self.logger.debug("Update roster")
contacts = self.keys() contacts = self.keys()
contacts.remove('bot') contacts.remove('bot')
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" % list(contacts)) self.logger.debug("Roster add: %s" % list(contacts))
for number in contacts: for number in contacts:
buddy = self[number] buddy = self[number]
self.updateSpectrum(buddy) self.updateSpectrum(buddy)
def onSync(self, existing, nonexisting, invalid): def onSync(self, existing, nonexisting, invalid):
"""We should only presence subscribe to existing numbers""" """We should only presence subscribe to existing numbers"""
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)
try: del self[number] try: del self[number]
except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % 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)
try: del self[number] try: del self[number]
except KeyError: self.logger.warn("non-existing buddy really didn't exist: %s" % 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():
try: buddy = self[number] try: buddy = self[number]
except KeyError: self.logger.warn("received status of buddy not in list: %s" % 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:
buddy.statusMsg = status buddy.statusMsg = utils.softToUni(status)
self.updateSpectrum(buddy) self.updateSpectrum(buddy)
def load(self, buddies): def load(self, buddies):
if self.session.loggedIn: if self.session.loggedIn:
self._load(buddies) self._load(buddies)
else: else:
self.session.loginQueue.append(lambda: self._load(buddies)) self.session.loginQueue.append(lambda: self._load(buddies))
def update(self, number, nick, groups, image_hash): def update(self, number, nick, groups, image_hash):
if number in self: if number in self:
buddy = self[number] buddy = self[number]
buddy.update(nick, groups, image_hash) buddy.update(nick, groups, image_hash)
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)
if image_hash == "" or image_hash is None: if image_hash == "" or image_hash is None:
self.requestVCard(number) self.requestVCard(number)
self.updateSpectrum(buddy) self.updateSpectrum(buddy)
return buddy return buddy
def updateSpectrum(self, buddy): def updateSpectrum(self, buddy):
if buddy.presence == 0: if buddy.presence == 0:
status = Spectrum2.protocol_pb2.STATUS_NONE status = protocol_pb2.STATUS_NONE
elif buddy.presence == 'unavailable': elif buddy.presence == 'unavailable':
status = Spectrum2.protocol_pb2.STATUS_AWAY status = protocol_pb2.STATUS_AWAY
else: else:
status = Spectrum2.protocol_pb2.STATUS_ONLINE status = protocol_pb2.STATUS_ONLINE
statusmsg = buddy.statusMsg statusmsg = buddy.statusMsg
if buddy.lastseen != 0: if buddy.lastseen != 0:
timestamp = time.localtime(buddy.lastseen) timestamp = time.localtime(buddy.lastseen)
statusmsg += time.strftime("\n Last seen: %a, %d %b %Y %H:%M:%S", timestamp) statusmsg += time.strftime("\n Last seen: %a, %d %b %Y %H:%M:%S", timestamp)
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)
def remove(self, number): def remove(self, number):
try: try:
buddy = self[number] buddy = self[number]
del self[number] del self[number]
self.backend.handleBuddyChanged(self.user, number, "", [], self.backend.handleBuddyChanged(self.user, number, "", [],
Spectrum2.protocol_pb2.STATUS_NONE) protocol_pb2.STATUS_NONE)
self.backend.handleBuddyRemoved(self.user, number) self.backend.handleBuddyRemoved(self.user, number)
self.session.unsubscribePresence(number) self.session.unsubscribePresence(number)
# TODO Sync remove # TODO Sync remove
return buddy return buddy
except KeyError: except KeyError:
return None return None
def requestVCard(self, buddy, ID=None): def requestVCard(self, buddy, ID=None):
if "/" in buddy: if "/" in buddy:
room, nick = buddy.split("/") room, nick = buddy.split("/")
group = self.session.groups[room] group = self.session.groups[room]
buddynr = None buddynr = None
for othernumber, othernick in group.participants.iteritems(): for othernumber, othernick in group.participants.iteritems():
if othernick == nick: if othernick == nick:
buddynr = othernumber buddynr = othernumber
break break
if buddynr is None: if buddynr is None:
return return
else: else:
buddynr = buddy buddynr = buddy
if buddynr == self.user or buddynr == self.user.split('@')[0]: if buddynr == self.user or buddynr == self.user.split('@')[0]:
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()
self.session.requestProfilePicture(buddynr, onSuccess=response.run, self.session.requestProfilePicture(buddynr, onSuccess=response.run,
onFailure=error.run) onFailure=error.run)
response = response.arg(0) response = response.arg(0)
pictureData = response.pictureData() pictureData = response.pictureData()
# Send VCard # Send VCard
if ID != None: if ID != None:
deferred.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)))
deferred.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
if not buddynr == self.session.legacyName: if not buddynr == self.session.legacyName:
try: try:
obuddy = self[buddynr] obuddy = self[buddynr]
nick = obuddy.nick nick = obuddy.nick
groups = obuddy.groups groups = obuddy.groups
except KeyError: except KeyError:
nick = "" nick = ""
groups = [] groups = []
image_hash = pictureData.then(utils.sha1hash)
call(self.logger.debug, 'Image hash is %s' % image_hash)
call(self.update, buddynr, nick, groups, image_hash)
# No image
error.when(self.logger.debug, 'No image')
error.when(self.update, buddynr, nick, groups, '')
def sha1hash(data): def refresh(self, number):
hashlib.sha1(data).hexdigest() self.session.unsubscribePresence(number)
self.session.subscribePresence(number)
image_hash = pictureData.then(sha1hash) self.requestVCard(number)
self.session.requestStatuses([number], success = self.onStatus)
deferred.call(self.logger.debug, 'Image hash is %s' % image_hash)
deferred.call(self.update, buddynr, nick, groups, image_hash)
# No image
error.when(self.logger.debug, 'No image')
error.when(self.update, buddynr, nick, groups, '')
def refresh(self, number):
self.session.unsubscribePresence(number)
self.session.subscribePresence(number)
self.requestVCard(number)
self.session.requestStatuses([number], success = self.onStatus)

View file

@ -1,139 +1,165 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
from functools import partial from functools import partial
class Deferred(object): class Deferred(object):
""" """
Represents a delayed computation. This is a more elegant way to deal with Represents a delayed computation. This is a more elegant way to deal with
callbacks. callbacks.
A Deferred object can be thought of as a computation whose value is yet to A Deferred object can be thought of as a computation whose value is yet to
be determined. We can manipulate the Deferred as if it where a regular be determined. We can manipulate the Deferred as if it where a regular
value by using the then method. Computations dependent on the Deferred will value by using the then method. Computations dependent on the Deferred will
only proceed when the run method is called. only proceed when the run method is called.
Attributes of a Deferred can be accessed directly as methods. The result of Attributes of a Deferred can be accessed directly as methods. The result of
calling these functions will be Deferred. calling these functions will be Deferred.
Example: Example:
image = Deferred() image = Deferred()
getImageWithCallback(image.run) getImageWithCallback(image.run)
image.then(displayFunc) image.then(displayFunc)
colors = Deferred() colors = Deferred()
colors.append('blue') colors.append('blue')
colors.then(print) colors.then(print)
colors.run(['red', 'green']) #=> ['red', 'green', 'blue'] colors.run(['red', 'green']) #=> ['red', 'green', 'blue']
""" """
def __init__(self): def __init__(self):
self.subscribers = [] self.subscribers = []
self.computed = False self.computed = False
self.args = None self.args = None
self.kwargs = None self.kwargs = None
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
""" """
Give a value to the deferred. Calling this method more than once will Give a value to the deferred. Calling this method more than once will
result in a DeferredHasValue exception to be raised. result in a DeferredHasValue exception to be raised.
""" """
if self.computed: if self.computed:
raise DeferredHasValue("Deferred object already has a value.") raise DeferredHasValue("Deferred object already has a value.")
else: else:
self.args = args self.args = args
self.kwargs = kwargs self.kwargs = kwargs
for func, deferred in self.subscribers: for func, deferred in self.subscribers:
deferred.run(func(*args, **kwargs)) deferred.run(func(*args, **kwargs))
self.computed = True self.computed = True
def then(self, func): def then(self, func):
""" """
Apply func to Deferred value. Returns a Deferred whose value will be Apply func to Deferred value. Returns a Deferred whose value will be
the result of applying func. the result of applying func.
""" """
result = Deferred() result = Deferred()
if self.computed: if self.computed:
result.run(func(*self.args, **self.kwargs)) result.run(func(*self.args, **self.kwargs))
else: else:
self.subscribers.append((func, result)) self.subscribers.append((func, result))
return result return result
def arg(self, n): def arg(self, n):
""" """
Returns the nth positional argument of a deferred as a deferred Returns the nth positional argument of a deferred as a deferred
Args: Args:
n - the index of the positional argument n - the index of the positional argument
""" """
def helper(*args, **kwargs): def helper(*args, **kwargs):
return args[n] return args[n]
return self.then(helper) return self.then(helper)
def when(self, func, *args, **kwargs): def when(self, func, *args, **kwargs):
""" Calls when func(*args, **kwargs) when deferred gets a value """ """ Calls when func(*args, **kwargs) when deferred gets a value """
def helper(*args2, **kwargs2): def helper(*args2, **kwargs2):
func(*args, **kwargs) func(*args, **kwargs)
return self.then(helper) return self.then(helper)
def __getattr__(self, method_name): def __getattr__(self, method_name):
return getattr(Then(self), method_name) return getattr(Then(self), method_name)
class Then(object): class Then(object):
""" """
Allows you to call methods on a Deferred. Allows you to call methods on a Deferred.
Example: Example:
colors = Deferred() colors = Deferred()
Then(colors).append('blue') Then(colors).append('blue')
colors.run(['red', 'green']) colors.run(['red', 'green'])
colors.then(print) #=> ['red', 'green', 'blue'] colors.then(print) #=> ['red', 'green', 'blue']
""" """
def __init__(self, deferred): def __init__(self, deferred):
self.deferred = deferred self.deferred = deferred
def __getattr__(self, name): def __getattr__(self, name):
def tryCall(obj, *args, **kwargs): def tryCall(obj, *args, **kwargs):
if callable(obj): if callable(obj):
return obj(*args, **kwargs) return obj(*args, **kwargs)
else: else:
return obj return obj
def helper(*args, **kwargs): def helper(*args, **kwargs):
func = (lambda x: tryCall(getattr(x, name), *args, **kwargs)) func = (lambda x: tryCall(getattr(x, name), *args, **kwargs))
return self.deferred.then(func) return self.deferred.then(func)
return helper return helper
def call(func, *args, **kwargs): def call(func, *args, **kwargs):
""" """
Call a function with deferred arguments Call a function with deferred arguments
Example: Example:
colors = Deferred() colors = Deferred()
colors.append('blue') colors.append('blue')
colors.run(['red', 'green']) colors.run(['red', 'green'])
call(print, colors) #=> ['red', 'green', 'blue'] call(print, colors) #=> ['red', 'green', 'blue']
call(print, 'hi', colors) #=> hi ['red', 'green', 'blue'] call(print, 'hi', colors) #=> hi ['red', 'green', 'blue']
""" """
for i, c in enumerate(args): for i, c in enumerate(args):
if isinstance(c, Deferred): if isinstance(c, Deferred):
# Function without deferred arguments # Function without deferred arguments
normalfunc = partial(func, *args[:i]) normalfunc = partial(func, *args[:i])
# Function with deferred and possibly deferred arguments # Function with deferred and possibly deferred arguments
def restfunc(*arg2, **kwarg2): def restfunc(*arg2, **kwarg2):
apply_deferred = partial(normalfunc, *arg2, **kwarg2) apply_deferred = partial(normalfunc, *arg2, **kwarg2)
return call(apply_deferred, *args[i + 1:], **kwargs) return call(apply_deferred, *args[i + 1:], **kwargs)
return c.then(restfunc) return c.then(restfunc)
items = kwargs.items() items = kwargs.items()
for i, (k, v) in enumerate(items): for i, (k, v) in enumerate(items):
if isinstance(v, Deferred): if isinstance(v, Deferred):
# Function without deferred arguments # Function without deferred arguments
normalfunc = partial(func, *args, **dict(items[:i])) normalfunc = partial(func, *args, **dict(items[:i]))
# Function with deferred and possibly deferred arguments # Function with deferred and possibly deferred arguments
def restfunc2(*arg2, **kwarg2): def restfunc2(*arg2, **kwarg2):
apply_deferred = partial(normalfunc, *arg2, **kwarg2) apply_deferred = partial(normalfunc, *arg2, **kwarg2)
return call(apply_deferred, **dict(items[i + 1:])) return call(apply_deferred, **dict(items[i + 1:]))
return v.then(restfunc2) return v.then(restfunc2)
# No items deferred # No items deferred
return func(*args, **kwargs) return func(*args, **kwargs)
class DeferredHasValue(Exception): class DeferredHasValue(Exception):
def __init__(self, string): def __init__(self, string):
super(DeferredHasValue, self).__init__(string) super(DeferredHasValue, self).__init__(string)

View file

@ -1,84 +1,110 @@
import Spectrum2 # use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
from Spectrum2 import protocol_pb2
class Group(): class Group():
def __init__(self, id, owner, subject, subjectOwner, backend, user): def __init__(self, id, owner, subject, subjectOwner, backend, user):
self.id = id self.id = id
self.subject = subject self.subject = subject
self.subjectOwner = subjectOwner self.subjectOwner = subjectOwner
self.owner = owner self.owner = owner
self.joined = False self.joined = False
self.backend = backend self.backend = backend
self.user = user self.user = user
self.nick = "me" self.nick = "me"
# Participants is a number -> nickname dict # Participants is a number -> nickname dict
self.participants = {} self.participants = {}
def addParticipants(self, participants, buddies, yourNumber): def addParticipants(self, participants, buddies, yourNumber):
""" """
Adds participants to the group. Adds participants to the group.
Args: Args:
- participants: (Iterable) phone numbers of participants - participants: (Iterable) phone numbers of participants
- buddies: (dict) Used to get the nicknames of the participants - buddies: (dict) Used to get the nicknames of the participants
- yourNumber: The number you are using - yourNumber: The number you are using
""" """
for jid in participants: for jid in participants:
number = jid.split('@')[0] number = jid.split('@')[0]
try: try:
nick = buddies[number].nick nick = buddies[number].nick
except KeyError: except KeyError:
nick = number nick = number
if number == yourNumber: if number == yourNumber:
nick = self.nick nick = self.nick
if nick == "": if nick == "":
nick = number nick = number
self.participants[number] = nick self.participants[number] = nick
def sendParticipantsToSpectrum(self, yourNumber): def sendParticipantsToSpectrum(self, yourNumber):
for number, nick in self.participants.iteritems(): for number, nick in self.participants.iteritems():
if number == self.owner: if number == self.owner:
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR
else: else:
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE flags = protocol_pb2.PARTICIPANT_FLAG_NONE
if number == yourNumber: if number == yourNumber:
flags = flags | Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ME flags = flags | protocol_pb2.PARTICIPANT_FLAG_ME
try: try:
self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, self._updateParticipant(number, flags, protocol_pb2.STATUS_ONLINE,
self.backend.sessions[self.user].buddies[number].image_hash) self.backend.sessions[self.user].buddies[number].image_hash)
except KeyError: except KeyError:
self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE) self._updateParticipant(number, flags, protocol_pb2.STATUS_ONLINE)
def removeParticipants(self, participants): def removeParticipants(self, participants):
for jid in participants: for jid in participants:
number = jid.split('@')[0] number = jid.split('@')[0]
nick = self.participants[number] nick = self.participants[number]
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE flags = protocol_pb2.PARTICIPANT_FLAG_NONE
self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) self._updateParticipant(number, flags, protocol_pb2.STATUS_NONE)
del self.participants[number] del self.participants[number]
def leaveRoom(self): def leaveRoom(self):
for number in self.participants: for number in self.participants:
nick = self.participants[number] nick = self.participants[number]
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_ROOM_NOT_FOUND flags = protocol_pb2.PARTICIPANT_FLAG_ROOM_NOT_FOUND
self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_NONE) self._updateParticipant(number, flags, protocol_pb2.STATUS_NONE)
def changeNick(self, number, new_nick): def changeNick(self, number, new_nick):
if self.participants[number] == new_nick: if self.participants[number] == new_nick:
return return
if number == self.owner: if number == self.owner:
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_MODERATOR flags = protocol_pb2.PARTICIPANT_FLAG_MODERATOR
else: else:
flags = Spectrum2.protocol_pb2.PARTICIPANT_FLAG_NONE flags = protocol_pb2.PARTICIPANT_FLAG_NONE
self._updateParticipant(number, flags, Spectrum2.protocol_pb2.STATUS_ONLINE, new_nick) self._updateParticipant(number, flags, protocol_pb2.STATUS_ONLINE, new_nick)
self.participants[number] = new_nick self.participants[number] = new_nick
def _updateParticipant(self, number, flags, status, imageHash = "", newNick = ""): def _updateParticipant(self, number, flags, status, imageHash = "", newNick = ""):
nick = self.participants[number] nick = self.participants[number]
# Notice the status message is the buddy's number # Notice the status message is the buddy's number
if self.joined: if self.joined:
self.backend.handleParticipantChanged( self.backend.handleParticipantChanged(
self.user, nick, self.id, flags, self.user, nick, self.id, flags,
status, number, newname = newNick, iconHash = imageHash) status, number, newname = newNick, iconHash = imageHash)

View file

@ -1,149 +1,173 @@
import sys # use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
from Spectrum2 import protocol_pb2
from yowsupwrapper import YowsupApp
import logging import logging
import Spectrum2 import threadutils
import sys
from .yowsupwrapper import YowsupApp
from . import threadutils
class RegisterSession(YowsupApp): class RegisterSession(YowsupApp):
""" """
A dummy Session object that is used to register a user to whatsapp A dummy Session object that is used to register a user to whatsapp
""" """
WANT_CC = 0 WANT_CC = 0
WANT_SMS = 1 WANT_SMS = 1
def __init__(self, backend, user, legacyName, extra): def __init__(self, backend, user, legacyName, extra):
self.user = user self.user = user
self.number = legacyName self.number = legacyName
self.backend = backend self.backend = backend
self.countryCode = '' self.countryCode = ''
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
self.state = self.WANT_CC self.state = self.WANT_CC
def login(self, password=""): def login(self, password=""):
self.backend.handleConnected(self.user) self.backend.handleConnected(self.user)
self.backend.handleBuddyChanged(self.user, 'bot', 'bot', self.backend.handleBuddyChanged(self.user, 'bot', 'bot',
['Admin'], Spectrum2.protocol_pb2.STATUS_ONLINE) ['Admin'], protocol_pb2.STATUS_ONLINE)
self.backend.handleMessage(self.user, 'bot', self.backend.handleMessage(self.user, 'bot',
'Please enter your country code') 'Please enter your country code')
def sendMessageToWA(self, buddy, message, ID='', xhtml=''): def sendMessageToWA(self, buddy, message, ID='', xhtml=''):
if buddy == 'bot' and self.state == self.WANT_CC: if buddy == 'bot' and self.state == self.WANT_CC:
try: try:
country_code = int(message.strip()) country_code = int(message.strip())
except ValueError: except ValueError:
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 = "%s" % 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:
code = message.strip() code = message.strip()
if self._checkSMSFormat(code): if self._checkSMSFormat(code):
self._requestPassword(code) self._requestPassword(code)
else: else:
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.')
def _checkSMSFormat(self, sms): def _checkSMSFormat(self, sms):
splitting = sms.split('-') splitting = sms.split('-')
if len(splitting) != 2: if len(splitting) != 2:
return False return False
a, b = splitting a, b = splitting
if len(a) != 3 and len(b) != 3: if len(a) != 3 and len(b) != 3:
return False return False
try: try:
int(a) int(a)
int(b) int(b)
except ValueError: except ValueError:
return False return False
return True return True
def _requestSMSCodeNonBlock(self): def _requestSMSCodeNonBlock(self):
number = self.number[len(self.countryCode):] number = self.number[len(self.countryCode):]
threadFunc = lambda: self.requestSMSCode(self.countryCode, number) threadFunc = lambda: self.requestSMSCode(self.countryCode, number)
threadutils.runInThread(threadFunc, self._confirmation) threadutils.runInThread(threadFunc, self._confirmation)
self.backend.handleMessage(self.user, 'bot', 'SMS Code Sent') self.backend.handleMessage(self.user, 'bot', 'SMS Code Sent')
def _confirmation(self, result): def _confirmation(self, result):
self.state = self.WANT_SMS self.state = self.WANT_SMS
resultStr = self._resultToString(result) resultStr = self._resultToString(result)
self.backend.handleMessage(self.user, 'bot', 'Response:') self.backend.handleMessage(self.user, 'bot', 'Response:')
self.backend.handleMessage(self.user, 'bot', resultStr) self.backend.handleMessage(self.user, 'bot', resultStr)
self.backend.handleMessage(self.user, 'bot', 'Please enter SMS Code') self.backend.handleMessage(self.user, 'bot', 'Please enter SMS Code')
def _requestPassword(self, smsCode): def _requestPassword(self, smsCode):
cc = self.countryCode cc = self.countryCode
number = self.number[len(cc):] number = self.number[len(cc):]
threadFunc = lambda: self.requestPassword(cc, number, smsCode) threadFunc = lambda: self.requestPassword(cc, number, smsCode)
threadutils.runInThread(threadFunc, self._gotPassword) threadutils.runInThread(threadFunc, self._gotPassword)
self.backend.handleMessage(self.user, 'bot', 'Getting Password') self.backend.handleMessage(self.user, 'bot', 'Getting Password')
def _gotPassword(self, result): def _gotPassword(self, result):
resultStr = self._resultToString(result) resultStr = self._resultToString(result)
self.backend.handleMessage(self.user, 'bot', 'Response:') self.backend.handleMessage(self.user, 'bot', 'Response:')
self.backend.handleMessage(self.user, 'bot', resultStr) self.backend.handleMessage(self.user, 'bot', resultStr)
self.backend.handleMessage(self.user, 'bot', 'Logging you in') self.backend.handleMessage(self.user, 'bot', 'Logging you in')
password = result['pw'] password = result['pw']
self.backend.relogin(self.user, self.number, password, None) self.backend.relogin(self.user, self.number, password, None)
def _resultToString(self, result): def _resultToString(self, result):
unistr = str if sys.version_info >= (3, 0) else unicode unistr = str if sys.version_info >= (3, 0) else unicode
out = [] out = []
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)) out.append("%s: %s" % (k, v))
return "\n".join(out) return "\n".join(out)
# Dummy methods. Whatsapp backend might call these, but they should have no # Dummy methods. Whatsapp backend might call these, but they should have no
# effect # effect
def logout(self): def logout(self):
pass pass
def joinRoom(self, room, nickname): def joinRoom(self, room, nickname):
pass pass
def leaveRoom(self, room): def leaveRoom(self, room):
pass pass
def changeStatusMessage(self, statusMessage): def changeStatusMessage(self, statusMessage):
pass pass
def changeStatus(self, status): def changeStatus(self, status):
pass pass
def loadBuddies(self, buddies): def loadBuddies(self, buddies):
pass pass
def updateBuddy(self, buddies): def updateBuddy(self, buddies):
pass pass
def removeBuddy(self, buddies): def removeBuddy(self, buddies):
pass pass
def sendTypingStarted(self, buddy): def sendTypingStarted(self, buddy):
pass pass
def sendTypingStopped(self, buddy): def sendTypingStopped(self, buddy):
pass pass
def requestVCard(self, buddy, ID): def requestVCard(self, buddy, ID):
pass pass
def setProfilePicture(self, previewPicture, fullPicture = None): def setProfilePicture(self, previewPicture, fullPicture = None):
pass pass

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,45 @@
import queue # use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import Queue
import threading import threading
# This queue is for other threads that want to execute code in the main thread # This queue is for other threads that want to execute code in the main thread
eventQueue = queue.Queue() eventQueue = Queue.Queue()
def runInThread(threadFunc, callback): def runInThread(threadFunc, callback):
""" """
Executes threadFunc in a new thread. The result of threadFunc will be Executes threadFunc in a new thread. The result of threadFunc will be
pass as the first argument to callback. callback will be called in the main pass as the first argument to callback. callback will be called in the main
thread. thread.
""" """
def helper(): def helper():
# Execute threadfunc in new thread # Execute threadfunc in new thread
result = threadFunc() result = threadFunc()
# Queue callback to be call in main thread # Queue callback to be call in main thread
eventQueue.put(lambda: callback(result)) eventQueue.put(lambda: callback(result))
thread = threading.Thread(target=helper)
thread = threading.Thread(target=helper) thread.start()
thread.start()

View file

@ -1,19 +1,48 @@
#!/usr/bin/python #!/usr/bin/python
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import argparse import argparse
import traceback import traceback
import logging import logging
import asyncore import asyncore
import sys import sys, os
import queue import e4u
import Queue
import transWhat.threadutils
import Spectrum2 sys.path.insert(0, os.getcwd())
from Spectrum2.iochannel import IOChannel
from Spectrum2.config import SpectrumConfig
from transWhat.whatsappbackend import WhatsAppBackend
from yowsup.common import YowConstants from yowsup.common import YowConstants
from yowsup.stacks import YowStack from yowsup.stacks import YowStack
from .whatsappbackend import WhatsAppBackend
from . import threadutils
# Arguments # Arguments
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--debug', action='store_true') parser.add_argument('--debug', action='store_true')
@ -21,8 +50,8 @@ 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)
parser.add_argument('-j', type=str, metavar="JID", required=True)
parser.add_argument('config', type=str) parser.add_argument('config', type=str)
parser.add_argument('-j', type=str, metavar="JID", required=True)
args, unknown = parser.parse_known_args() args, unknown = parser.parse_known_args()
@ -39,7 +68,7 @@ logging.basicConfig(
) )
if args.config is not None: if args.config is not None:
specConf = Spectrum2.Config(args.config) specConf = SpectrumConfig(args.config)
else: else:
specConf = None specConf = None
@ -53,13 +82,15 @@ def handleTransportData(data):
logger = logging.getLogger('transwhat') logger = logging.getLogger('transwhat')
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
e4u.load()
closed = False closed = False
def connectionClosed(): def connectionClosed():
global closed global closed
closed = True closed = True
# Main # Main
io = Spectrum2.IOChannel(args.host, args.port, handleTransportData, connectionClosed) io = IOChannel(args.host, args.port, handleTransportData, connectionClosed)
plugin = WhatsAppBackend(io, args.j, specConf) plugin = WhatsAppBackend(io, args.j, specConf)
@ -75,20 +106,18 @@ def main():
try: try:
asyncore.loop(timeout=1.0, count=10, use_poll = True) asyncore.loop(timeout=1.0, count=10, use_poll = True)
try: try:
callback = YowStack._YowStack__detachedQueue.get(False) # doesn't block callback = YowStack._YowStack__detachedQueue.get(False) #doesn't block
callback() callback()
except queue.Empty: except Queue.Empty:
pass pass
else: else:
break break
if closed: if closed:
break break
while True: while True:
try: try:
callback = threadutils.eventQueue.get_nowait() callback = transWhat.threadutils.eventQueue.get_nowait()
except queue.Empty: except Queue.Empty:
break break
else: else:
callback() callback()

56
transWhat/utils.py Normal file
View file

@ -0,0 +1,56 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
import e4u
import base64
import hashlib
def ago(secs):
periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"]
lengths = [60, 60, 24, 7,4.35, 12, 10]
j = 0
diff = secs
while diff >= lengths[j]:
diff /= lengths[j]
diff = round(diff)
j += 1
period = periods[j]
if diff > 1: period += "s"
return "%d %s ago" % (diff, period)
def softToUni(message):
return e4u.translate(message.encode("utf-8"), reverse=False, **e4u.SOFTBANK_TRANSLATE_PROFILE)
def decodePassword(password):
return base64.b64decode(bytes(password))
def sha1hash(data):
return hashlib.sha1(data).hexdigest()

View file

@ -1,144 +1,173 @@
# use unicode encoding for all literals by default (for python2.x)
from __future__ import unicode_literals
__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/>.
"""
from Spectrum2.backend import SpectrumBackend
from Spectrum2 import protocol_pb2
from session import Session
from registersession import RegisterSession
import logging import logging
import Spectrum2
from .session import Session
from .registersession import RegisterSession
class WhatsAppBackend(Spectrum2.Backend): class WhatsAppBackend(SpectrumBackend):
def __init__(self, io, spectrum_jid, specConf): def __init__(self, io, spectrum_jid, specConf):
Spectrum2.Backend.__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.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
self.lastMsgId = {} self.lastMsgId = {}
self.logger.debug("Backend started") self.logger.debug("Backend started")
# 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:
self.sessions[user] = RegisterSession(self, user, legacyName, extra) self.sessions[user] = RegisterSession(self, user, legacyName, extra)
else: else:
if user not in self.sessions: if user not in self.sessions:
self.sessions[user] = Session(self, user, legacyName, extra) self.sessions[user] = Session(self, user, legacyName, extra)
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)" % self.logger.debug("handleMessageSendRequest(user=%s, buddy=%s, message=%s, xhtml=%s, ID=%s)" %
( user, buddy, message, xhtml, ID)) ( 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
# messages with the same id # messages with the same id
if ID == '': if ID == '':
self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml)
elif user not in self.lastMsgId or self.lastMsgId[user] != ID: elif user not in self.lastMsgId or self.lastMsgId[user] != ID:
self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml) self.sessions[user].sendMessageToWA(buddy, message, ID, xhtml)
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, 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, 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
if password == 'register': # This shouldn't happen, but just in case if password == 'register': # This shouldn't happen, but just in case
self.sessions[user] = RegisterSession(self, user, legacyName, extra) self.sessions[user] = RegisterSession(self, user, legacyName, extra)
else: else:
self.sessions[user] = Session(self, user, legacyName, extra) self.sessions[user] = Session(self, user, legacyName, extra)
self.sessions[user].login(password) self.sessions[user].login(password)
# TODO # TODO
def handleAttentionRequest(self, user, buddy, message): def handleAttentionRequest(self, user, buddy, message):
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
def handleFTPauseRequest(self, ftID): def handleFTPauseRequest(self, ftID):
pass pass
def handleFTContinueRequest(self, ftID): def handleFTContinueRequest(self, ftID):
pass pass
def handleRawXmlRequest(self, xml): def handleRawXmlRequest(self, xml):
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)

File diff suppressed because it is too large Load diff