import base64 from cStringIO import StringIO from twisted.words.xish import domish from twisted.internet import defer, reactor from twisted.internet.threads import deferToThread from twisted.web.client import Agent from twisted.web.http_headers import Headers from twisted.web.iweb import IBodyProducer from zope.interface import implements from tornado.escape import utf8 #from txmongo import gridfs import Image from base import send_raw import bnw.core.base import bnw.core.bnw_mongo from bnw.core import bnw_objects as objs from bnw.core.base import config class StringProducer(object): implements(IBodyProducer) def __init__(self, body): self.body = body self.length = len(body) def startProducing(self, consumer): consumer.write(self.body) return defer.succeed(None) def pauseProducing(self): pass def stopProducing(self): pass agent = Agent(reactor) def get_and_resize_avatar(iq): mimetype = str(iq.vCard.PHOTO.TYPE) if mimetype not in ('image/png', 'image/jpeg', 'image/gif'): return filedata = iq.vCard.PHOTO.BINVAL if not filedata: return filedata = str(filedata) if len(filedata) > 102400: # XEP-0153 says what image SHOULD be 8KB max, # let's approve up to 100KB. return try: missing_padding = 4 - len(filedata) % 4 if missing_padding: filedata += '=' * missing_padding avatar = base64.b64decode(filedata) except TypeError: return try: im = Image.open(StringIO(avatar)) if im.size[0]*im.size[1] > 1024**2: # we won't generate thumbnails for avatars larger than 1024x1024 # because uhm...well... fuck you print 'Avatar from %s is too large.' % (iq['from'],), im.size return im.thumbnail((48, 48), Image.ANTIALIAS) thumb_f = StringIO() im.save(thumb_f, 'png') except IOError: return return avatar, mimetype, thumb_f.getvalue() @defer.inlineCallbacks def vcard(iq, iq_user): if (not iq.vCard) or iq['type'] != 'result': defer.returnValue(False) if not iq_user: # User which have been sent IQ not registered. defer.returnValue(True) v = iq.vCard # Update avatar info. av_info = iq_user.get('avatar') av_key = 'avatars/' + iq_user['name'] if v.PHOTO and config.blob_storage: res = yield deferToThread(get_and_resize_avatar, iq) if res: avatar, mimetype, thumb = res yield agent.request('PUT',utf8(config.blob_storage+'put/'+av_key), Headers({'Content-Type': [mimetype], 'Content-Length': [str(len(avatar))]}), StringProducer(avatar)) yield agent.request('PUT',utf8(config.blob_storage+'put/'+av_key+'/thumb'), Headers({'Content-Type': ['image/png'], 'Content-Length': [str(len(thumb))]}), StringProducer(thumb)) yield objs.User.mupdate( {'name': iq_user['name']}, {'$set': {'avatar': 'blobstorage'}}) elif av_info and config.blob_storage: yield agent.request('DELETE',utf8(config.blob_storage+'delete/'+av_key)) yield agent.request('DELETE',utf8(config.blob_storage+'delete/'+av_key+'/thumb')) yield objs.User.mupdate( {'name': iq_user['name']}, {'$unset': {'avatar': 1}}) # Update additional fields. vcard = {} if v.N and v.N.GIVEN and str(v.N.GIVEN) and v.N.FAMILY and str(v.N.FAMILY): vcard['fullname'] = '%s %s' % (v.N.GIVEN, v.N.FAMILY) if v.URL and str(v.URL): vcard['url'] = str(v.URL) if v.DESC and str(v.DESC): vcard['desc'] = str(v.DESC) yield objs.User.mupdate( {'name': iq_user['name']}, {'$set': {'vcard': vcard}}) defer.returnValue(True) VERSION_XMLNS = 'jabber:iq:version' def version(iq, iq_user): if iq.query and iq.query.uri == VERSION_XMLNS: reply = domish.Element((None, 'iq')) reply['type'] = 'result' if iq.getAttribute('id'): reply['id'] = iq['id'] reply.addElement('query', VERSION_XMLNS) reply.query.addElement('name', content='BnW') reply.query.addElement('version', content='0.1') reply.query.addElement('os', content='OS/360') send_raw(iq['from'], iq['to'], reply) return True DISCO_ITEMS_XMLNS = 'http://jabber.org/protocol/disco#items' def disco_items(iq, iq_user): if iq.query and iq.query.uri == DISCO_ITEMS_XMLNS: reply = domish.Element((None, 'iq')) reply['type'] = 'result' if iq.getAttribute('id'): reply['id'] = iq['id'] reply.addElement('query', DISCO_ITEMS_XMLNS) send_raw(iq['from'], iq['to'], reply) return True DISCO_INFO_XMLNS = 'http://jabber.org/protocol/disco#info' FEATURES = ('jabber:iq:version', 'http://jabber.org/protocol/chatstates', 'http://jabber.org/protocol/disco#info', 'http://jabber.org/protocol/disco#items', 'urn:xmpp:receipts', ) def disco_info(iq, iq_user): if iq.query and iq.query.uri == DISCO_INFO_XMLNS: reply = domish.Element((None, 'iq')) reply['type'] = 'result' if iq.getAttribute('id'): reply['id'] = iq['id'] reply.addElement('query', DISCO_INFO_XMLNS) reply.query.addElement('identity') reply.query.identity['category'] = 'client' # not pretty sure reply.query.identity['type'] = 'bot' reply.query.identity['name'] = 'BnW' for feature_name in FEATURES: feature = reply.query.addElement('feature') feature['var'] = feature_name send_raw(iq['from'], iq['to'], reply) return True handlers = [ vcard, version, disco_items, disco_info, ]