From 0dac7b7b81a08e216bd6dc28f2d5ed177dc819f7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 27 Sep 2017 21:20:20 +0200 Subject: [PATCH 01/11] Version up, thanks to @ekohilas --- fbchat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 642281f..e3f21a7 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -17,7 +17,7 @@ from .client import * __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) -__version__ = '1.0.20' +__version__ = '1.0.21' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __email__ = 'carpedm20@gmail.com' From b8fdcda2fbbcf8fb47ef8673ee2a1db8f808cef6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 30 Sep 2017 01:15:41 +0200 Subject: [PATCH 02/11] Properly uploading requirements (pip requires changed version number) --- fbchat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index e3f21a7..43e0132 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -17,7 +17,7 @@ from .client import * __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) -__version__ = '1.0.21' +__version__ = '1.0.22' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __email__ = 'carpedm20@gmail.com' From 9d5f06b810bca48adac58dbdcec49e9d145aa39e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sat, 30 Sep 2017 19:17:40 +0200 Subject: [PATCH 03/11] Fixed pip setup --- fbchat/__init__.py | 2 +- setup.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 43e0132..dbd642c 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -17,7 +17,7 @@ from .client import * __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) -__version__ = '1.0.22' +__version__ = '1.0.23' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __email__ = 'carpedm20@gmail.com' diff --git a/setup.py b/setup.py index 3ce1c77..2b8560a 100644 --- a/setup.py +++ b/setup.py @@ -16,10 +16,12 @@ except ImportError: with open('README.rst') as f: readme_content = f.read().strip() -try: - requirements = [line.rstrip('\n') for line in open(os.path.join('fbchat.egg-info', 'requires.txt'))] -except IOError: - requirements = [line.rstrip('\n') for line in open('requirements.txt')] +requirements = [ + 'requests', + 'lxml', + 'beautifulsoup4', + "enum34; python_version == '2.7'" +] version = None author = None From 2d027af71a01b3bee6039d01e8bc132d1b86ceeb Mon Sep 17 00:00:00 2001 From: Tang Date: Tue, 3 Oct 2017 01:14:00 +0800 Subject: [PATCH 04/11] Enable fetching pending/archived threads Add "type" parameter to fetchThreadList(). type can be 'inbox', 'pending' or 'archived' If set to 'pending', it can fetch messages from unknown users. It is quite useful to build a service accepting requests from anyone. For example, in doOneListen(), fetch pending messages once for a while to handle the messages from strangers. --- fbchat/client.py | 49 ++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 778849a..a8f9c86 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -748,11 +748,12 @@ class Client(object): return list(reversed([graphql_to_message(message) for message in j['message_thread']['messages']['nodes']])) - def fetchThreadList(self, offset=0, limit=20): + def fetchThreadList(self, offset=0, limit=20, type='inbox'): """Get thread list of your facebook account :param offset: The offset, from where in the list to recieve threads from :param limit: Max. number of threads to retrieve. Capped at 20 + :param type: (optional) "inbox", "pending", "archived" :type offset: int :type limit: int :return: :class:`models.Thread` objects @@ -763,10 +764,16 @@ class Client(object): if limit > 20 or limit < 1: raise FBchatUserError('`limit` should be between 1 and 20') + if type in ['inbox', 'pending', 'archived']: + if type == 'archived': + type = 'action:archived' + else: + raise ValueError('thread_type must be "inbox", "pending" or "archived"') + data = { 'client' : self.client, - 'inbox[offset]' : offset, - 'inbox[limit]' : limit, + type + '[offset]' : offset, + type + '[limit]' : limit, } j = self._post(self.req_url.THREADS, data, fix_request=True, as_json=True) @@ -774,25 +781,27 @@ class Client(object): raise FBchatException('Missing payload: {}, with data: {}'.format(j, data)) participants = {} - for p in j['payload']['participants']: - if p['type'] == 'page': - participants[p['fbid']] = Page(p['fbid'], url=p['href'], photo=p['image_src'], name=p['name']) - elif p['type'] == 'user': - participants[p['fbid']] = User(p['fbid'], url=p['href'], first_name=p['short_name'], is_friend=p['is_friend'], gender=GENDERS[p['gender']], photo=p['image_src'], name=p['name']) - else: - raise FBchatException('A participant had an unknown type {}: {}'.format(p['type'], p)) + if 'participants' in j['payload']: + for p in j['payload']['participants']: + if p['type'] == 'page': + participants[p['fbid']] = Page(p['fbid'], url=p['href'], photo=p['image_src'], name=p['name']) + elif p['type'] == 'user': + participants[p['fbid']] = User(p['fbid'], url=p['href'], first_name=p['short_name'], is_friend=p['is_friend'], gender=GENDERS[p['gender']], photo=p['image_src'], name=p['name']) + else: + raise FBchatException('A participant had an unknown type {}: {}'.format(p['type'], p)) entries = [] - for k in j['payload']['threads']: - if k['thread_type'] == 1: - if k['other_user_fbid'] not in participants: - raise FBchatException('The thread {} was not in participants: {}'.format(k, j['payload'])) - participants[k['other_user_fbid']].message_count = k['message_count'] - entries.append(participants[k['other_user_fbid']]) - elif k['thread_type'] == 2: - entries.append(Group(k['thread_fbid'], participants=set([p.strip('fbid:') for p in k['participants']]), photo=k['image_src'], name=k['name'], message_count=k['message_count'])) - else: - raise FBchatException('A thread had an unknown thread type: {}'.format(k)) + if 'threads' in j['payload']: + for k in j['payload']['threads']: + if k['thread_type'] == 1: + if k['other_user_fbid'] not in participants: + raise FBchatException('The thread {} was not in participants: {}'.format(k, j['payload'])) + participants[k['other_user_fbid']].message_count = k['message_count'] + entries.append(participants[k['other_user_fbid']]) + elif k['thread_type'] == 2: + entries.append(Group(k['thread_fbid'], participants=set([p.strip('fbid:') for p in k['participants']]), photo=k['image_src'], name=k['name'], message_count=k['message_count'])) + else: + raise FBchatException('A thread had an unknown thread type: {}'.format(k)) return entries From ee81620c148bef26baf0ed821fbe4e06c30a5d70 Mon Sep 17 00:00:00 2001 From: Tang Date: Tue, 3 Oct 2017 00:50:27 +0800 Subject: [PATCH 05/11] Add send GIF images support When uploading and sending GIF images, the keys are explicitly changed to "gif_id" or "gif_ids" rather than "image_id" or "image_ids". --- fbchat/client.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 778849a..24b4941 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -938,7 +938,7 @@ class Client(object): return self._doSendRequest(data) - def _uploadImage(self, image_path, data, mimetype): + def _uploadImage(self, image_path, data, mimetype, is_gif=False): """Upload an image and get the image_id for sending in a message""" j = self._postFile(self.req_url.UPLOAD, { @@ -949,9 +949,12 @@ class Client(object): ) }, fix_request=True, as_json=True) # Return the image_id - return j['payload']['metadata'][0]['image_id'] + if not is_gif: + return j['payload']['metadata'][0]['image_id'] + else: + return j['payload']['metadata'][0]['gif_id'] - def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER): + def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): """ Sends an already uploaded image to a thread. (Used by :func:`Client.sendRemoteImage` and :func:`Client.sendLocalImage`) @@ -972,11 +975,14 @@ class Client(object): data['specific_to_list[0]'] = 'fbid:' + str(thread_id) data['specific_to_list[1]'] = 'fbid:' + str(self.uid) - data['image_ids[0]'] = image_id + if not is_gif: + data['image_ids[0]'] = image_id + else: + data['gif_ids[0]'] = image_id return self._doSendRequest(data) - def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER): + def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): """ Sends an image from a URL to a thread @@ -991,10 +997,10 @@ class Client(object): thread_id, thread_type = self._getThread(thread_id, thread_type) mimetype = guess_type(image_url)[0] remote_image = requests.get(image_url).content - image_id = self._uploadImage(image_url, remote_image, mimetype) - return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type) + image_id = self._uploadImage(image_url, remote_image, mimetype, is_gif) + return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type, is_gif=is_gif) - def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER): + def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): """ Sends a local image to a thread @@ -1008,8 +1014,8 @@ class Client(object): """ thread_id, thread_type = self._getThread(thread_id, thread_type) mimetype = guess_type(image_path)[0] - image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype) - return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type) + image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype, is_gif) + return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type, is_gif=is_gif) def addUsersToGroup(self, user_ids, thread_id=None): """ From e0aedd617b75512304d59a07b42a713e3f7b24b9 Mon Sep 17 00:00:00 2001 From: Tang Date: Tue, 3 Oct 2017 01:30:03 +0800 Subject: [PATCH 06/11] add param is_gif to doc of functions --- fbchat/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fbchat/client.py b/fbchat/client.py index 24b4941..6040244 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -962,6 +962,7 @@ class Client(object): :param message: Additional message :param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_type: See :ref:`intro_threads` + :param is_gif: if sending GIF, True, else False :type thread_type: models.ThreadType :return: :ref:`Message ID ` of the sent image :raises: FBchatException if request failed @@ -990,6 +991,7 @@ class Client(object): :param message: Additional message :param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_type: See :ref:`intro_threads` + :param is_gif: if sending GIF, True, else False :type thread_type: models.ThreadType :return: :ref:`Message ID ` of the sent image :raises: FBchatException if request failed @@ -1008,6 +1010,7 @@ class Client(object): :param message: Additional message :param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_type: See :ref:`intro_threads` + :param is_gif: if sending GIF, True, else False :type thread_type: models.ThreadType :return: :ref:`Message ID ` of the sent image :raises: FBchatException if request failed From 8eb6b83411438fc87772559e1bd295ff0da2b620 Mon Sep 17 00:00:00 2001 From: Tang Date: Tue, 3 Oct 2017 03:05:08 +0800 Subject: [PATCH 07/11] Update for feedback by @madsmtm 1. Add ThreadLocation Enum in models. 2. avoid using build-in name "type" as parameter name 3. replace ValueError with FBchatUserError thanks to @madsmtm --- fbchat/client.py | 15 +++++++-------- fbchat/models.py | 7 +++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index a8f9c86..3e1bbab 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -748,12 +748,12 @@ class Client(object): return list(reversed([graphql_to_message(message) for message in j['message_thread']['messages']['nodes']])) - def fetchThreadList(self, offset=0, limit=20, type='inbox'): + def fetchThreadList(self, offset=0, limit=20, thread_location=ThreadLocation.INBOX): """Get thread list of your facebook account :param offset: The offset, from where in the list to recieve threads from :param limit: Max. number of threads to retrieve. Capped at 20 - :param type: (optional) "inbox", "pending", "archived" + :param thread_location: models.ThreadLocation: INBOX, PENDING, ARCHIVED or OTHER :type offset: int :type limit: int :return: :class:`models.Thread` objects @@ -764,16 +764,15 @@ class Client(object): if limit > 20 or limit < 1: raise FBchatUserError('`limit` should be between 1 and 20') - if type in ['inbox', 'pending', 'archived']: - if type == 'archived': - type = 'action:archived' + if thread_location in ThreadLocation: + loc_str = thread_location.value else: - raise ValueError('thread_type must be "inbox", "pending" or "archived"') + raise FBchatUserError('"thread_location" must be a value of ThreadLocation') data = { 'client' : self.client, - type + '[offset]' : offset, - type + '[limit]' : limit, + loc_str + '[offset]' : offset, + loc_str + '[limit]' : limit, } j = self._post(self.req_url.THREADS, data, fix_request=True, as_json=True) diff --git a/fbchat/models.py b/fbchat/models.py index c61ceb1..4894cd2 100644 --- a/fbchat/models.py +++ b/fbchat/models.py @@ -193,6 +193,13 @@ class ThreadType(Enum): GROUP = 2 PAGE = 3 +class ThreadLocation(Enum): + """Used to specify where a thread is located (inbox, pending, archived, other).""" + INBOX = 'inbox' + PENDING = 'pending' + ARCHIVED = 'action:archived' + OTHER = 'other' + class TypingStatus(Enum): """Used to specify whether the user is typing or has stopped typing""" STOPPED = 0 From 707df4f941911f72ed396517acebe2c7b12172cf Mon Sep 17 00:00:00 2001 From: Tang Date: Tue, 3 Oct 2017 03:15:48 +0800 Subject: [PATCH 08/11] use mimetype to see if it's a GIF thanks to @madsmtm's good idea --- fbchat/client.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 6040244..277fed9 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -938,7 +938,7 @@ class Client(object): return self._doSendRequest(data) - def _uploadImage(self, image_path, data, mimetype, is_gif=False): + def _uploadImage(self, image_path, data, mimetype): """Upload an image and get the image_id for sending in a message""" j = self._postFile(self.req_url.UPLOAD, { @@ -949,7 +949,7 @@ class Client(object): ) }, fix_request=True, as_json=True) # Return the image_id - if not is_gif: + if not mimetype == 'image/gif': return j['payload']['metadata'][0]['image_id'] else: return j['payload']['metadata'][0]['gif_id'] @@ -983,7 +983,7 @@ class Client(object): return self._doSendRequest(data) - def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): + def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER): """ Sends an image from a URL to a thread @@ -991,18 +991,18 @@ class Client(object): :param message: Additional message :param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_type: See :ref:`intro_threads` - :param is_gif: if sending GIF, True, else False :type thread_type: models.ThreadType :return: :ref:`Message ID ` of the sent image :raises: FBchatException if request failed """ thread_id, thread_type = self._getThread(thread_id, thread_type) mimetype = guess_type(image_url)[0] + is_gif = (mimetype == 'image/gif') remote_image = requests.get(image_url).content - image_id = self._uploadImage(image_url, remote_image, mimetype, is_gif) + image_id = self._uploadImage(image_url, remote_image, mimetype) return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type, is_gif=is_gif) - def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): + def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER): """ Sends a local image to a thread @@ -1010,14 +1010,14 @@ class Client(object): :param message: Additional message :param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_type: See :ref:`intro_threads` - :param is_gif: if sending GIF, True, else False :type thread_type: models.ThreadType :return: :ref:`Message ID ` of the sent image :raises: FBchatException if request failed """ thread_id, thread_type = self._getThread(thread_id, thread_type) mimetype = guess_type(image_path)[0] - image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype, is_gif) + is_gif = (mimetype == 'image/gif') + image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype) return self.sendImage(image_id=image_id, message=message, thread_id=thread_id, thread_type=thread_type, is_gif=is_gif) def addUsersToGroup(self, user_ids, thread_id=None): From 57954816b24fcd03d8d7cb9e694616f1500d484c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Oct 2017 08:29:34 +0200 Subject: [PATCH 09/11] Version up, thanks to @WeiTang114 --- fbchat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index dbd642c..81e42ae 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -17,7 +17,7 @@ from .client import * __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) -__version__ = '1.0.23' +__version__ = '1.0.24' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __email__ = 'carpedm20@gmail.com' From f0271e17b0849cdd702cae580267ee1c69747dc3 Mon Sep 17 00:00:00 2001 From: ekohilas Date: Wed, 4 Oct 2017 01:57:02 +1100 Subject: [PATCH 10/11] updated for older setuptools --- requirements.txt | 2 +- setup.py | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2f7a7df..22069fb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ requests lxml beautifulsoup4 -enum34; python_version == '2.7' +enum34; python_version < '3.4' diff --git a/setup.py b/setup.py index 3ce1c77..a1800fa 100644 --- a/setup.py +++ b/setup.py @@ -4,22 +4,24 @@ """ Setup script for fbchat """ - - import os try: from setuptools import setup except ImportError: from distutils.core import setup - with open('README.rst') as f: readme_content = f.read().strip() -try: - requirements = [line.rstrip('\n') for line in open(os.path.join('fbchat.egg-info', 'requires.txt'))] -except IOError: - requirements = [line.rstrip('\n') for line in open('requirements.txt')] +requirements = [ + 'requests', + 'lxml', + 'beautifulsoup4', +] + +extras_requirements = { + ':python_version < "3.4"': ['enum34'] +} version = None author = None @@ -75,6 +77,7 @@ setup( include_package_data=True, packages=['fbchat'], install_requires=requirements, + extras_require=extras_requirements, url=source, version=version, zip_safe=True, From 023fd58f056bb178e4be17a40ee985a31836b5d5 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 3 Oct 2017 22:46:04 +0200 Subject: [PATCH 11/11] Version up, thanks to @ekohilas --- fbchat/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 81e42ae..16f16ef 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -17,7 +17,7 @@ from .client import * __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) -__version__ = '1.0.24' +__version__ = '1.0.25' __license__ = 'BSD' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __email__ = 'carpedm20@gmail.com'