Compare commits

..

14 Commits

Author SHA1 Message Date
Mads Marquart
57954816b2 Version up, thanks to @WeiTang114 2017-10-03 08:29:34 +02:00
Mads Marquart
3e4e1f9bb9 Merge pull request #212 from WeiTang114/gif_support_2
Add Gif support to send(Local/Remote)Image
2017-10-03 08:26:10 +02:00
Mads Marquart
7340918209 Merge pull request #211 from WeiTang114/fetch_pending_thread_2
Enable fetching pending/archived threads
2017-10-03 08:25:58 +02:00
Tang
707df4f941 use mimetype to see if it's a GIF
thanks to @madsmtm's good idea
2017-10-03 03:29:15 +08:00
Tang
8eb6b83411 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
2017-10-03 03:05:08 +08:00
Tang
e0aedd617b add param is_gif to doc of functions 2017-10-03 01:40:19 +08:00
Tang
ee81620c14 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".
2017-10-03 01:39:20 +08:00
Tang
2d027af71a 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.
2017-10-03 01:37:25 +08:00
Mads Marquart
9d5f06b810 Fixed pip setup 2017-09-30 19:17:40 +02:00
Mads Marquart
b8fdcda2fb Properly uploading requirements (pip requires changed version number) 2017-09-30 01:15:41 +02:00
Mads Marquart
0dac7b7b81 Version up, thanks to @ekohilas 2017-09-27 21:20:20 +02:00
Mads Marquart
b750e753d6 Merge pull request #206 from ekohilas/master
Fixes 2FA bug and updates pip requirements
2017-09-27 21:19:14 +02:00
ekohilas
ee33e92bed added conditional enum34 2.7 requirement 2017-09-27 19:24:35 +10:00
ekohilas
7413a643f6 fixed 2FA bug 2017-09-27 19:23:58 +10:00
5 changed files with 61 additions and 34 deletions

View File

@@ -17,7 +17,7 @@ from .client import *
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
__version__ = '1.0.20' __version__ = '1.0.24'
__license__ = 'BSD' __license__ = 'BSD'
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
__email__ = 'carpedm20@gmail.com' __email__ = 'carpedm20@gmail.com'

View File

@@ -249,8 +249,9 @@ class Client(object):
r = self._cleanPost(self.req_url.LOGIN, data) r = self._cleanPost(self.req_url.LOGIN, data)
# Usually, 'Checkpoint' will refer to 2FA # Usually, 'Checkpoint' will refer to 2FA
if ('checkpoint' in r.url and if ('checkpoint' in r.url
('Enter Security Code to Continue' in r.text or 'Enter Login Code to Continue' in r.text)): and ('enter security code to continue' in r.text.lower()
or 'enter login code to continue' in r.text.lower())):
r = self._2FA(r) r = self._2FA(r)
# Sometimes Facebook tries to show the user a "Save Device" dialog # Sometimes Facebook tries to show the user a "Save Device" dialog
@@ -747,11 +748,12 @@ class Client(object):
return list(reversed([graphql_to_message(message) for message in j['message_thread']['messages']['nodes']])) 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, thread_location=ThreadLocation.INBOX):
"""Get thread list of your facebook account """Get thread list of your facebook account
:param offset: The offset, from where in the list to recieve threads from :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 limit: Max. number of threads to retrieve. Capped at 20
:param thread_location: models.ThreadLocation: INBOX, PENDING, ARCHIVED or OTHER
:type offset: int :type offset: int
:type limit: int :type limit: int
:return: :class:`models.Thread` objects :return: :class:`models.Thread` objects
@@ -762,10 +764,15 @@ class Client(object):
if limit > 20 or limit < 1: if limit > 20 or limit < 1:
raise FBchatUserError('`limit` should be between 1 and 20') raise FBchatUserError('`limit` should be between 1 and 20')
if thread_location in ThreadLocation:
loc_str = thread_location.value
else:
raise FBchatUserError('"thread_location" must be a value of ThreadLocation')
data = { data = {
'client' : self.client, 'client' : self.client,
'inbox[offset]' : offset, loc_str + '[offset]' : offset,
'inbox[limit]' : limit, loc_str + '[limit]' : limit,
} }
j = self._post(self.req_url.THREADS, data, fix_request=True, as_json=True) j = self._post(self.req_url.THREADS, data, fix_request=True, as_json=True)
@@ -773,6 +780,7 @@ class Client(object):
raise FBchatException('Missing payload: {}, with data: {}'.format(j, data)) raise FBchatException('Missing payload: {}, with data: {}'.format(j, data))
participants = {} participants = {}
if 'participants' in j['payload']:
for p in j['payload']['participants']: for p in j['payload']['participants']:
if p['type'] == 'page': if p['type'] == 'page':
participants[p['fbid']] = Page(p['fbid'], url=p['href'], photo=p['image_src'], name=p['name']) participants[p['fbid']] = Page(p['fbid'], url=p['href'], photo=p['image_src'], name=p['name'])
@@ -782,6 +790,7 @@ class Client(object):
raise FBchatException('A participant had an unknown type {}: {}'.format(p['type'], p)) raise FBchatException('A participant had an unknown type {}: {}'.format(p['type'], p))
entries = [] entries = []
if 'threads' in j['payload']:
for k in j['payload']['threads']: for k in j['payload']['threads']:
if k['thread_type'] == 1: if k['thread_type'] == 1:
if k['other_user_fbid'] not in participants: if k['other_user_fbid'] not in participants:
@@ -948,9 +957,12 @@ class Client(object):
) )
}, fix_request=True, as_json=True) }, fix_request=True, as_json=True)
# Return the image_id # Return the image_id
if not mimetype == 'image/gif':
return j['payload']['metadata'][0]['image_id'] 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`) Sends an already uploaded image to a thread. (Used by :func:`Client.sendRemoteImage` and :func:`Client.sendLocalImage`)
@@ -958,6 +970,7 @@ class Client(object):
:param message: Additional message :param message: Additional message
:param thread_id: User/Group ID to send to. See :ref:`intro_threads` :param thread_id: User/Group ID to send to. See :ref:`intro_threads`
:param thread_type: 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 :type thread_type: models.ThreadType
:return: :ref:`Message ID <intro_message_ids>` of the sent image :return: :ref:`Message ID <intro_message_ids>` of the sent image
:raises: FBchatException if request failed :raises: FBchatException if request failed
@@ -971,7 +984,10 @@ class Client(object):
data['specific_to_list[0]'] = 'fbid:' + str(thread_id) data['specific_to_list[0]'] = 'fbid:' + str(thread_id)
data['specific_to_list[1]'] = 'fbid:' + str(self.uid) data['specific_to_list[1]'] = 'fbid:' + str(self.uid)
if not is_gif:
data['image_ids[0]'] = image_id data['image_ids[0]'] = image_id
else:
data['gif_ids[0]'] = image_id
return self._doSendRequest(data) return self._doSendRequest(data)
@@ -989,9 +1005,10 @@ class Client(object):
""" """
thread_id, thread_type = self._getThread(thread_id, thread_type) thread_id, thread_type = self._getThread(thread_id, thread_type)
mimetype = guess_type(image_url)[0] mimetype = guess_type(image_url)[0]
is_gif = (mimetype == 'image/gif')
remote_image = requests.get(image_url).content remote_image = requests.get(image_url).content
image_id = self._uploadImage(image_url, remote_image, mimetype) 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) 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):
""" """
@@ -1007,8 +1024,9 @@ class Client(object):
""" """
thread_id, thread_type = self._getThread(thread_id, thread_type) thread_id, thread_type = self._getThread(thread_id, thread_type)
mimetype = guess_type(image_path)[0] mimetype = guess_type(image_path)[0]
is_gif = (mimetype == 'image/gif')
image_id = self._uploadImage(image_path, open(image_path, 'rb'), mimetype) 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) 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): def addUsersToGroup(self, user_ids, thread_id=None):
""" """
@@ -1541,7 +1559,7 @@ class Client(object):
def on2FACode(self): def on2FACode(self):
"""Called when a 2FA code is needed to progress""" """Called when a 2FA code is needed to progress"""
input('Please enter your 2FA code --> ') return input('Please enter your 2FA code --> ')
def onLoggedIn(self, email=None): def onLoggedIn(self, email=None):
""" """

View File

@@ -193,6 +193,13 @@ class ThreadType(Enum):
GROUP = 2 GROUP = 2
PAGE = 3 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): class TypingStatus(Enum):
"""Used to specify whether the user is typing or has stopped typing""" """Used to specify whether the user is typing or has stopped typing"""
STOPPED = 0 STOPPED = 0

View File

@@ -1,4 +1,4 @@
requests requests
lxml lxml
beautifulsoup4 beautifulsoup4
enum34 enum34; python_version == '2.7'

View File

@@ -16,10 +16,12 @@ except ImportError:
with open('README.rst') as f: with open('README.rst') as f:
readme_content = f.read().strip() readme_content = f.read().strip()
try: requirements = [
requirements = [line.rstrip('\n') for line in open(os.path.join('fbchat.egg-info', 'requires.txt'))] 'requests',
except IOError: 'lxml',
requirements = [line.rstrip('\n') for line in open('requirements.txt')] 'beautifulsoup4',
"enum34; python_version == '2.7'"
]
version = None version = None
author = None author = None