From bc197fd6653400afeef8cf8a1738eea4a397ae0a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 23 Aug 2018 20:38:55 +0200 Subject: [PATCH] Changed `sendXFiles` to only needing file url / path --- fbchat/client.py | 168 +++++++++++------------------------------------ fbchat/utils.py | 42 ++++++++++++ 2 files changed, 79 insertions(+), 131 deletions(-) diff --git a/fbchat/client.py b/fbchat/client.py index 2b861cd..f08d8e1 100644 --- a/fbchat/client.py +++ b/fbchat/client.py @@ -1087,103 +1087,40 @@ class Client(object): data['specific_to_list[0]'] = "fbid:{}".format(thread_id) return self._doSendRequest(data) - def _upload(self, file_name, data, mimetype, type_="file"): - data = { - 'file': ( - file_name, - data, - mimetype - ) - } - j = self._postFile(self.req_url.UPLOAD, data, fix_request=True, as_json=True) + def _upload(self, files): + """ + Uploads files to Facebook + + `files` should be a list of files that requests can upload, see: + http://docs.python-requests.org/en/master/api/#requests.request + + Returns a list of tuples with a file's ID and mimetype + """ + file_dict = {'upload_{}'.format(i): f for i, f in enumerate(files)} + j = self._postFile(self.req_url.UPLOAD, files=file_dict, fix_request=True, as_json=True) + + if len(j['payload']['metadata']) != len(files): + raise FBchatException("Some files could not be uploaded: {}, {}".format(j, files)) + + return [(data[mimetype_to_key(data['filetype'])], data['filetype']) for data in j['payload']['metadata']] - return j['payload']['metadata'][0]['{}_id'.format(type_)] - def _sendFiles(self, files, message=None, thread_id=None, thread_type=ThreadType.USER): + """ + Sends files from file IDs to a thread + + `files` should be a list of tuples, with a file's ID and mimetype + """ thread_id, thread_type = self._getThread(thread_id, thread_type) data = self._getSendData(message=self._oldMessage(message), thread_id=thread_id, thread_type=thread_type) data['action_type'] = 'ma-type:user-generated-message' data['has_attachment'] = True - for type_ in files: - for i, file_id in enumerate(files[type_]): - data['{}_ids[{}]'.format(type_, i)] = file_id + for i, (file_id, mimetype) in enumerate(files): + data['{}s[{}]'.format(mimetype_to_key(mimetype), i)] = file_id return self._doSendRequest(data) - def sendFiles(self, file_ids, message=None, thread_id=None, thread_type=ThreadType.USER): - """ - Sends files from file IDs to a thread - - :param file_ids: ID of files to upload and send - :param message: Additional message - :param thread_id: User/Group ID to send to. See :ref:`intro_threads` - :param thread_type: See :ref:`intro_threads` - :type thread_type: models.ThreadType - :return: :ref:`Message ID ` of the sent files - :raises: FBchatException if request failed - """ - file_ids = require_list(file_ids) - return self._sendFiles(files={'file': file_ids}, message=None, thread_id=None, thread_type=ThreadType.USER) - - def sendAudios(self, audio_ids, message=None, thread_id=None, thread_type=ThreadType.USER): - """ - Sends audios from audio IDs to a thread - - :param audio_id: IDs of audios to upload and send - :param message: Additional message - :param thread_id: User/Group ID to send to. See :ref:`intro_threads` - :param thread_type: See :ref:`intro_threads` - :type thread_type: models.ThreadType - :return: :ref:`Message ID ` of the sent audios - :raises: FBchatException if request failed - """ - audio_ids = require_list(audio_ids) - return self._sendFiles(files={'audio': audio_ids}, message=None, thread_id=None, thread_type=ThreadType.USER) - - def sendImages(self, image_ids, message=None, thread_id=None, thread_type=ThreadType.USER): - """ - Sends images from image IDs to a thread - - :param image_ids: IDs of images to upload and send - :param message: Additional message - :param thread_id: User/Group ID to send to. See :ref:`intro_threads` - :param thread_type: See :ref:`intro_threads` - :type thread_type: models.ThreadType - :return: :ref:`Message ID ` of the sent images - :raises: FBchatException if request failed - """ - return self._sendFiles(files={'image': image_ids}, message=None, thread_id=None, thread_type=ThreadType.USER) - - def sendGifs(self, gif_ids, message=None, thread_id=None, thread_type=ThreadType.USER): - """ - Sends gifs from gif IDs to a thread - - :param gif_ids: IDs of gifs to upload and send - :param message: Additional message - :param thread_id: User/Group ID to send to. See :ref:`intro_threads` - :param thread_type: See :ref:`intro_threads` - :type thread_type: models.ThreadType - :return: :ref:`Message ID ` of the sent image - :raises: FBchatException if request failed - """ - return self._sendFiles(files={'gif': gif_ids}, message=None, thread_id=None, thread_type=ThreadType.USER) - - def sendVideos(self, video_ids, message=None, thread_id=None, thread_type=ThreadType.USER): - """ - Sends videos from video IDs to a thread - - :param video_ids: IDs of videos to upload and send - :param message: Additional message - :param thread_id: User/Group ID to send to. See :ref:`intro_threads` - :param thread_type: See :ref:`intro_threads` - :type thread_type: models.ThreadType - :return: :ref:`Message ID ` of the sent videos - :raises: FBchatException if request failed - """ - return self._sendFiles(files={'video': video_ids}, message=None, thread_id=None, thread_type=ThreadType.USER) - def sendRemoteFiles(self, file_urls, message=None, thread_id=None, thread_type=ThreadType.USER): """ Sends files from URLs to a thread @@ -1197,17 +1134,7 @@ class Client(object): :raises: FBchatException if request failed """ file_urls = require_list(file_urls) - file_ids = list() - files = {'image':[], 'gif':[], 'video':[], 'audio':[], 'file':[]} - - for file_url in file_urls: - mimetype = guess_type(file_url)[0] - type_ = mimetype.split('/')[0] - type_ = 'file' if files.get(type_) is None else 'gif' if mimetype == "image/gif" else type_ - remote_file = requests.get(file_url).content - file_id = self._upload(file_url, remote_file, mimetype, type_=type_) - files[type_].append(file_id) - + files = self._upload(get_files_from_urls(file_urls)) return self._sendFiles(files=files, message=message, thread_id=thread_id, thread_type=thread_type) def sendLocalFiles(self, file_paths, message=None, thread_id=None, thread_type=ThreadType.USER): @@ -1223,16 +1150,8 @@ class Client(object): :raises: FBchatException if request failed """ file_paths = require_list(file_paths) - file_ids = list() - files = {'image':[], 'gif':[], 'video':[], 'audio':[], 'file':[]} - - for file_path in file_paths: - mimetype = guess_type(file_path)[0] - type_ = mimetype.split('/')[0] - type_ = 'file' if files.get(type_) is None else 'gif' if mimetype == "image/gif" else type_ - file_id = self._upload(file_path, open(file_path, 'rb'), mimetype, type_=type_) - files[type_].append(file_id) - + with get_files_from_paths(file_paths) as x: + files = self._upload(x) return self._sendFiles(files=files, message=message, thread_id=thread_id, thread_type=thread_type) def sendImage(self, image_id, message=None, thread_id=None, thread_type=ThreadType.USER, is_gif=False): @@ -1240,21 +1159,21 @@ class Client(object): Deprecated. Use :func:`fbchat.Client.sendFiles` instead """ if is_gif: - return self.sendGifs(image_ids=image_id, message=message, thread_id=thread_id, thread_type=thread_type) + return self._sendFiles(files=[(image_id, "image/png")], message=message, thread_id=thread_id, thread_type=thread_type) else: - return self.sendImages(image_ids=image_id, message=message, thread_id=thread_id, thread_type=thread_type) + return self._sendFiles(files=[(image_id, "image/gif")], message=message, thread_id=thread_id, thread_type=thread_type) def sendRemoteImage(self, image_url, message=None, thread_id=None, thread_type=ThreadType.USER): """ Deprecated. Use :func:`fbchat.Client.sendRemoteFiles` instead """ - return self.sendRemoteFiles(file_urls=image_url, message=message, thread_id=thread_id, thread_type=thread_type) - + return self.sendRemoteFiles(file_urls=[image_url], message=message, thread_id=thread_id, thread_type=thread_type) + def sendLocalImage(self, image_path, message=None, thread_id=None, thread_type=ThreadType.USER): """ Deprecated. Use :func:`fbchat.Client.sendLocalFiles` instead """ - return self.sendLocalFiles(file_paths=image_path, message=message, thread_id=thread_id, thread_type=thread_type) + return self.sendLocalFiles(file_paths=[image_path], message=message, thread_id=thread_id, thread_type=thread_type) def createGroup(self, message, person_ids=None): """Creates a group with the given ids @@ -1439,15 +1358,8 @@ class Client(object): :raises: FBchatException if request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) - - if thread_type != ThreadType.GROUP: - raise FBchatUserError('Can only change the image of group threads') - - mimetype = guess_type(image_url)[0] - is_gif = (mimetype == 'image/gif') - remote_image = requests.get(image_url).content - image_id = self._upload(image_url, remote_image, mimetype, type_="gif" if is_gif else "image") + with get_files_from_urls([image_url]) as files: + (image_id, mimetype), = self._upload(files) self.changeThreadImage(image_id, thread_id, thread_type) @@ -1462,14 +1374,8 @@ class Client(object): :raises: FBchatException if request failed """ - thread_id, thread_type = self._getThread(thread_id, thread_type) - - if thread_type != ThreadType.GROUP: - raise FBchatUserError('Can only change the image of group threads') - - mimetype = guess_type(image_path)[0] - is_gif = (mimetype == 'image/gif') - image_id = self._upload(image_path, open(image_path, 'rb'), mimetype, type_="gif" if is_gif else "image") + with get_files_from_paths([image_path]) as files: + (image_id, mimetype), = self._upload(files) self.changeThreadImage(image_id, thread_id, thread_type) @@ -1672,7 +1578,7 @@ class Client(object): }] } } - + j = self._post(self.req_url.PLAN_PARTICIPATION, full_data, fix_request=True, as_json=True) def createPoll(self, poll, thread_id=None, thread_type=None): @@ -1771,7 +1677,7 @@ class Client(object): def _readStatus(self, read, thread_ids): thread_ids = require_list(thread_ids) - + data = { "watermarkTimestamp": now(), "shouldSendReadReceipt": 'true', diff --git a/fbchat/utils.py b/fbchat/utils.py index 16ed658..9405cfe 100644 --- a/fbchat/utils.py +++ b/fbchat/utils.py @@ -5,8 +5,12 @@ import re import json from time import time from random import random +from contextlib import contextmanager +from mimetypes import guess_type +from os.path import basename import warnings import logging +import requests from .models import * try: @@ -265,3 +269,41 @@ def require_list(list_): return set(list_) else: return set([list_]) + +def mimetype_to_key(mimetype): + if not mimetype: + return "file_id" + if mimetype == "image/gif": + return "gif_id" + x = mimetype.split("/") + if x[0] in ["video", "image", "audio"]: + return "%s_id" % x[0] + return "file_id" + + +def get_files_from_urls(file_urls): + files = [] + for file_url in file_urls: + r = requests.get(file_url) + # We could possibly use r.headers.get('Content-Disposition'), see + # https://stackoverflow.com/a/37060758 + files.append(( + basename(file_url), + r.content, + r.headers.get('Content-Type') or guess_type(file_url)[0], + )) + return files + + +@contextmanager +def get_files_from_paths(filenames): + files = [] + for filename in filenames: + files.append(( + basename(filename), + open(filename, 'rb'), + guess_type(filename)[0], + )) + yield files + for fn, fp, ft in files: + fp.close()