diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 5316c78..6c73aee 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -15,7 +15,7 @@ from .client import * __copyright__ = 'Copyright 2015 by Taehoon Kim' -__version__ = '0.3.1' +__version__ = '0.4.0' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan' __email__ = 'carpedm20@gmail.com' diff --git a/fbchat/client.py b/fbchat/client.py index 200386e..f47057d 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -6,15 +6,18 @@ Facebook Chat (Messenger) for Python - :copyright: (c) 2015 by Taehoon Kim. + :copyright: (c) 2015 by Taehoon Kim. + :copyright: (c) 2015-2016 by PidgeyL. :license: BSD, see LICENSE for more details. """ import requests +import json from uuid import uuid1 from random import random, choice from datetime import datetime from bs4 import BeautifulSoup as bs +from mimetypes import guess_type from .utils import * from .models import * @@ -34,6 +37,8 @@ BaseURL ="https://www.facebook.com" MobileURL ="https://m.facebook.com/" StickyURL ="https://0-edge-chat.facebook.com/pull" PingURL ="https://0-channel-proxy-06-ash2.facebook.com/active_ping" +UploadURL ="https://upload.facebook.com/ajax/mercury/upload.php" + class Client(object): """A client for the Facebook Chat (Messenger). @@ -117,7 +122,12 @@ class Client(object): def _cleanPost(self, url, query=None, timeout=30): self.req_counter += 1 return self._session.post(url, headers=self._header, data=query, timeout=timeout) - + + def _postFile(self, url, files=None, timeout=30): + payload=self._generatePayload(None) + return self._session.post(url, data=payload, timeout=timeout, files=files) + + def login(self): if not (self.email and self.password): raise Exception("id and password or config is needed") @@ -195,21 +205,30 @@ class Client(object): users.append(User(entry)) return users # have bug TypeError: __repr__ returned non-string (type bytes) - def send(self, thread_id, message=None, like=None): + def send(self, recipient_id, message=None, message_type='user', like=None, image_id=None): """Send a message with given thread id - :param thread_id: a thread id that you want to send a message + :param recipient_id: the user id or thread id that you want to send a message to :param message: a text that you want to send + :param message_type: determines if the recipient_id is for user or thread :param like: size of the like sticker you want to send + :param image_id: id for the image to send, gotten from the UploadURL """ + if message_type.lower() == 'group': + thread_id = recipient_id + user_id = None + else: + thread_id = None + user_id = recipient_id + timestamp = now() date = datetime.now() data = { 'client' : self.client, 'message_batch[0][action_type]' : 'ma-type:user-generated-message', 'message_batch[0][author]' : 'fbid:' + str(self.uid), - 'message_batch[0][specific_to_list][0]' : 'fbid:' + str(thread_id), + 'message_batch[0][specific_to_list][0]' : 'fbid:' + str(recipient_id), 'message_batch[0][specific_to_list][1]' : 'fbid:' + str(self.uid), 'message_batch[0][timestamp]' : timestamp, 'message_batch[0][timestamp_absolute]' : 'Today', @@ -229,8 +248,12 @@ class Client(object): 'message_batch[0][message_id]' : generateMessageID(self.client_id), 'message_batch[0][manual_retry_cnt]' : '0', 'message_batch[0][thread_fbid]' : thread_id, - 'message_batch[0][has_attachment]' : False + 'message_batch[0][has_attachment]' : image_id != None, + 'message_batch[0][other_user_fbid]' : user_id } + + if image_id: + data['message_batch[0][image_ids][0]'] = image_id if like: try: @@ -243,7 +266,40 @@ class Client(object): r = self._post(SendURL, data) return r.ok - + def sendRemoteImage(self, recipient_id, message=None, message_type='user', image=''): + """Send an image from a URL + + :param recipient_id: the user id or thread id that you want to send a message to + :param message: a text that you want to send + :param message_type: determines if the recipient_id is for user or thread + :param image: URL for an image to download and send + """ + mimetype = guess_type(image)[0] + remote_image = requests.get(image).content + image_id = self.uploadImage({'file': (image, remote_image, mimetype)}) + return self.send(recipient_id, message, message_type, None, image_id) + + def sendLocalImage(self, recipient_id, message=None, message_type='user', image=''): + """Send an image from a file path + + :param recipient_id: the user id or thread id that you want to send a message to + :param message: a text that you want to send + :param message_type: determines if the recipient_id is for user or thread + :param image: path to a local image to send + """ + mimetype = guess_type(image)[0] + image_id = self.uploadImage({'file': (image, open(image), mimetype)}) + return self.send(recipient_id, message, message_type, None, image_id) + + def uploadImage(self, image): + """Upload an image and get the image_id for sending in a message + + :param image: a tuple of (file name, data, mime type) to upload to facebook + """ + r = self._postFile(UploadURL, image) + # Strip the start and parse out the returned image_id + return json.loads(r._content[9:])['payload']['metadata'][0]['image_id'] + def getThreadInfo(self, userID, start, end=None): """Get the info of one Thread @@ -428,21 +484,29 @@ class Client(object): name = m['message']['sender_name'] self.on_message(mid, fbid, name, message, m) elif m['type'] in ['typ']: - self.on_typing(m["from"]) + self.on_typing(m.get("from")) elif m['type'] in ['m_read_receipt']: - self.on_read(m['realtime_viewer_fbid'], m['reader'], m['time']) + self.on_read(m.get('realtime_viewer_fbid'), m.get('reader'), m.get('time')) elif m['type'] in ['inbox']: - viewer = m['realtime_viewer_fbid'] - unseen = m['unseen'] - unread = m['unread'] - other_unseen = m['other_unseen'] - other_unread = m['other_unread'] - timestamp = m['seen_timestamp'] + viewer = m.get('realtime_viewer_fbid') + unseen = m.get('unseen') + unread = m.get('unread') + other_unseen = m.get('other_unseen') + other_unread = m.get('other_unread') + timestamp = m.get('seen_timestamp') self.on_inbox(viewer, unseen, unread, other_unseen, other_unread, timestamp) elif m['type'] in ['qprimer']: - self.on_qprimer(m['made']) + self.on_qprimer(m.get('made')) + elif m['type'] in ['delta']: + if 'messageMetadata' in m['delta']: + mid = m['delta']['messageMetadata']['messageId'] + message = m['delta']['body'] + fbid = m['delta']['messageMetadata']['threadKey']['otherUserFbId'] + name = None + self.on_message(mid, fbid, name, message, m) else: - print(m) + if self.debug: + print(m) except Exception as e: self.on_message_error(e, m)