Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #4 from stv0g/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
dazzzl committed Feb 10, 2016
2 parents cd44b01 + 7236571 commit bb49001
Show file tree
Hide file tree
Showing 14 changed files with 1,026 additions and 413 deletions.
5 changes: 4 additions & 1 deletion INSTALL.md
Expand Up @@ -63,6 +63,9 @@ Create a new file `/etc/spectrum2/transports/whatsapp.cfg` with the following co
[logging]
config = /etc/spectrum2/logging.cfg
backend_config = /etc/spectrum2/backend-logging.cfg

[database]
type = sqlite3

## transWhat

Expand All @@ -74,7 +77,7 @@ Checkout the latest version of transWhat from GitHub:

Install required dependencies:

$ pip install --pre e4u protobuf python-dateutil yowsup
$ pip install --pre e4u protobuf python-dateutil yowsup2

- **e4u**: is a simple emoji4unicode python bindings
- [**yowsup**](https://github.com/tgalal/yowsup): is a python library that enables you build application which use WhatsApp service.
21 changes: 18 additions & 3 deletions Spectrum2/backend.py
Expand Up @@ -222,13 +222,29 @@ def handleFTData(self, ftID, data):
message = WRAP(d.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_FT_DATA);
self.send(message)

def handleBackendConfig(self, section, key, value):
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()
c.config = "[%s]\n%s = %s\n" % (section, key, value)
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):
Expand All @@ -252,7 +268,6 @@ def handleStatusChangedPayload(self, data):

def handleConvMessagePayload(self, data):
payload = protocol_pb2.ConversationMessage()
self.logger.error("handleConvMessagePayload")
if (payload.ParseFromString(data) == False):
#TODO: ERROR
return
Expand Down
1 change: 0 additions & 1 deletion USAGE.md
Expand Up @@ -28,7 +28,6 @@ The bot is one of the contacts every user has in its contact list. It offers you
| ------------ | --------------- |
| `\help` | show this message |
| `\prune` | clear your buddylist |
| `\sync` | sync your imported contacts with WhatsApp |
| `\lastseen` | request last online timestamp from buddy |
| `\leave` | permanently leave group chat |
| `\groups` | print all attended groups |
Expand Down
18 changes: 2 additions & 16 deletions bot.py
Expand Up @@ -37,13 +37,12 @@ def __init__(self, session, name = "Bot"):
self.commands = {
"help": self._help,
"prune": self._prune,
"sync": self._sync,
"groups": self._groups,
"getgroups": self._getgroups
}

def parse(self, message):
args = message.split(" ")
args = message.strip().split(" ")
cmd = args.pop(0)

if cmd[0] == '\\':
Expand All @@ -57,7 +56,7 @@ def parse(self, message):
self.send("a valid command starts with a backslash")

def call(self, cmd, args = []):
func = self.commands[cmd]
func = self.commands[cmd.lower()]
spec = inspect.getargspec(func)
maxs = len(spec.args) - 1
reqs = maxs - len(spec.defaults or [])
Expand All @@ -71,23 +70,10 @@ def send(self, message):
self.session.backend.handleMessage(self.session.user, self.name, message)

# commands
def _sync(self):
user = self.session.legacyName
password = self.session.password

count = self.session.buddies.sync(user, password)
self.session.updateRoster()

if count:
self.send("sync complete, %d buddies are using WhatsApp" % count)
else:
self.send("sync failed, sorry something went wrong")

def _help(self):
self.send("""following bot commands are available:
\\help show this message
\\prune clear your buddylist
\\sync sync your imported contacts with WhatsApp
following user commands are available:
\\lastseen request last online timestamp from buddy
Expand Down
139 changes: 107 additions & 32 deletions buddy.py
Expand Up @@ -24,6 +24,12 @@
from Spectrum2 import protocol_pb2

import logging
import time
import utils
import base64

import deferred
from deferred import call


class Buddy():
Expand All @@ -33,11 +39,10 @@ def __init__(self, owner, number, nick, statusMsg, groups, image_hash):
self.number = number
self.groups = groups
self.image_hash = image_hash if image_hash is not None else ""
self.statusMsg = ""
self.statusMsg = u""
self.lastseen = 0
self.presence = 0


def update(self, nick, groups, image_hash):
self.nick = nick
self.groups = groups
Expand All @@ -55,49 +60,58 @@ def __init__(self, owner, backend, user, session):
self.session = session
self.user = user
self.logger = logging.getLogger(self.__class__.__name__)
self.synced = False

def _load(self, buddies):
for buddy in buddies:
number = buddy.buddyName
nick = buddy.alias
statusMsg = buddy.statusMessage
statusMsg = buddy.statusMessage.decode('utf-8')
groups = [g for g in buddy.group]
image_hash = buddy.iconHash
self[number] = Buddy(self.owner, number, nick, statusMsg,
groups, image_hash)

self.logger.debug("Update roster")

# old = self.buddies.keys()
# self.buddies.load()
# new = self.buddies.keys()
# contacts = new
contacts = self.keys()
contacts.remove('bot')

if self.synced == False:
self.session.sendSync(contacts, delta = False, interactive = True)
self.synced = True

# add = set(new) - set(old)
# remove = set(old) - set(new)
self.session.sendSync(contacts, delta=False, interactive=True,
success=self.onSync)

# self.logger.debug("Roster remove: %s", str(list(remove)))
self.logger.debug("Roster add: %s", str(list(contacts)))

# for number in remove:
# self.backend.handleBuddyChanged(self.user, number, "", [],
# protocol_pb2.STATUS_NONE)
# self.backend.handleBuddyRemoved(self.user, number)
# self.unsubscribePresence(number)
#
for number in contacts:
buddy = self[number]
if number != 'bot':
self.backend.handleBuddyChanged(self.user, number, buddy.nick,
buddy.groups, protocol_pb2.STATUS_NONE,
iconHash = buddy.image_hash if buddy.image_hash is not None else "")
self.session.subscribePresence(number)
self.updateSpectrum(buddy)

def onSync(self, existing, nonexisting, invalid):
"""We should only presence subscribe to existing numbers"""

for number in existing:
self.session.subscribePresence(number)
self.logger.debug("%s is requesting statuses of: %s", self.user, existing)
self.session.requestStatuses(existing, success = self.onStatus)

self.logger.debug("Removing nonexisting buddies %s", nonexisting)
for number in nonexisting:
self.remove(number)
del self[number]

self.logger.debug("Removing invalid buddies %s", invalid)
for number in invalid:
self.remove(number)
del self[number]

def onStatus(self, contacts):
self.logger.debug("%s received statuses of: %s", self.user, contacts)
for number, (status, time) in contacts.iteritems():
buddy = self[number]
if status is None:
buddy.statusMsg = ""
else:
buddy.statusMsg = utils.softToUni(status)
self.updateSpectrum(buddy)


def load(self, buddies):
Expand All @@ -111,23 +125,38 @@ def update(self, number, nick, groups, image_hash):
buddy = self[number]
buddy.update(nick, groups, image_hash)
else:
self.session.sendSync([number], delta = True, interactive = True)
self.session.subscribePresence(number)
buddy = Buddy(self.owner, number, nick, "", groups, image_hash)
self[number] = buddy
self.logger.debug("Roster add: %s", buddy)
self.session.sendSync([number], delta = True, interactive = True)
self.session.subscribePresence(number)
self.session.requestStatuses([number], success = self.onStatus)
if image_hash == "" or image_hash is None:
self.requestVCard(number)
self.updateSpectrum(buddy)
return buddy

def updateSpectrum(self, buddy):
if buddy.presence == 0:
status = protocol_pb2.STATUS_NONE
elif buddy.presence == 'unavailable':
status = protocol_pb2.STATUS_AWAY
else:
status = protocol_pb2.STATUS_ONLINE
self.backend.handleBuddyChanged(self.user, number, buddy.nick,
buddy.groups, status,
iconHash = buddy.image_hash if buddy.image_hash is not None else "")

return buddy
statusmsg = buddy.statusMsg
if buddy.lastseen != 0:
timestamp = time.localtime(buddy.lastseen)
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 ""

self.logger.debug("Updating buddy %s (%s) in %s, image_hash = %s",
buddy.nick, buddy.number, buddy.groups, iconHash)
self.logger.debug("Status Message: %s", statusmsg)
self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick,
buddy.groups, status, statusMessage=statusmsg, iconHash=iconHash)


def remove(self, number):
try:
Expand All @@ -141,3 +170,49 @@ def remove(self, number):
return buddy
except KeyError:
return None

def requestVCard(self, buddy, ID=None):
if buddy == self.user or buddy == self.user.split('@')[0]:
buddy = self.session.legacyName

# Get profile picture
self.logger.debug('Requesting profile picture of %s', buddy)
response = deferred.Deferred()
# Error probably means image doesn't exist
error = deferred.Deferred()
self.session.requestProfilePicture(buddy, onSuccess=response.run,
onFailure=error.run)
response = response.arg(0)

pictureData = response.pictureData()
# Send VCard
if ID != None:
call(self.logger.debug, 'Sending VCard (%s) with image id %s: %s',
ID, response.pictureId(), pictureData.then(base64.b64encode))
call(self.backend.handleVCard, self.user, ID, buddy, "", "",
pictureData)
# If error
error.when(self.logger.debug, 'Sending VCard (%s) without image', ID)
error.when(self.backend.handleVCard, self.user, ID, buddy, "", "", "")

# Send image hash
if not buddy == self.session.legacyName:
try:
obuddy = self[buddy]
nick = obuddy.nick
groups = obuddy.groups
except KeyError:
nick = ""
groups = []
image_hash = pictureData.then(utils.sha1hash)
call(self.logger.debug, 'Image hash is %s', image_hash)
call(self.update, buddy, nick, groups, image_hash)
# No image
error.when(self.logger.debug, 'No image')
error.when(self.update, buddy, nick, groups, '')

def refresh(self, number):
self.session.unsubscribePresence(number)
self.session.subscribePresence(number)
self.requestVCard(number)
self.session.requestStatuses([number], success = self.onStatus)

0 comments on commit bb49001

Please sign in to comment.