From 0250539db7a91c3df33154b101dc25657c0569c0 Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Sat, 8 Jul 2017 06:47:31 +0000 Subject: [PATCH] Update Twython to 3.5.0. --- src/twython/__init__.py | 2 +- src/twython/api.py | 81 +++++++++++++++++++++++++++------------- src/twython/endpoints.py | 61 ++++++++++++++++++++++-------- 3 files changed, 103 insertions(+), 41 deletions(-) diff --git a/src/twython/__init__.py b/src/twython/__init__.py index 3525f3c7..7404b288 100644 --- a/src/twython/__init__.py +++ b/src/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.3.0' +__version__ = '3.5.0' from .api import Twython from .streaming import TwythonStreamer diff --git a/src/twython/api.py b/src/twython/api.py index fab4a4e8..f806cea9 100644 --- a/src/twython/api.py +++ b/src/twython/api.py @@ -140,11 +140,8 @@ class Twython(EndpointsMixin, object): params = params or {} func = getattr(self.client, method) - if type(params) is dict: - params, files = _transparent_params(params) - else: - params = params - files = list() + params, files = _transparent_params(params) + requests_args = {} for k, v in self.client_args.items(): # Maybe this should be set as a class variable and only done once? @@ -202,10 +199,8 @@ class Twython(EndpointsMixin, object): else: content = response.json() except ValueError: - # Send the response as is for working with /media/metadata/create.json. - content = response.content -# raise TwythonError('Response was not valid JSON. \ -# Unable to decode.') + raise TwythonError('Response was not valid JSON. \ + Unable to decode.') return content @@ -258,8 +253,10 @@ class Twython(EndpointsMixin, object): url = endpoint else: url = '%s/%s.json' % (self.api_url % version, endpoint) + content = self._request(url, method=method, params=params, api_call=url) + return content def get(self, endpoint, params=None, version='1.1'): @@ -531,7 +528,7 @@ class Twython(EndpointsMixin, object): @staticmethod def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False, expand_quoted_status=False): - """Return HTML for a tweet (urls, mentions, hashtags replaced with links) + """Return HTML for a tweet (urls, mentions, hashtags, symbols replaced with links) :param tweet: Tweet object from received from Twitter API :param use_display_url: Use display URL to represent link @@ -547,8 +544,18 @@ class Twython(EndpointsMixin, object): if 'retweeted_status' in tweet: tweet = tweet['retweeted_status'] + if 'extended_tweet' in tweet: + tweet = tweet['extended_tweet'] + + orig_tweet_text = tweet.get('full_text') or tweet['text'] + + display_text_range = tweet.get('display_text_range') or [0, len(orig_tweet_text)] + display_text_start, display_text_end = display_text_range[0], display_text_range[1] + display_text = orig_tweet_text[display_text_start:display_text_end] + prefix_text = orig_tweet_text[0:display_text_start] + suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)] + if 'entities' in tweet: - text = tweet['text'] entities = tweet['entities'] # Mentions @@ -556,9 +563,13 @@ class Twython(EndpointsMixin, object): key=lambda mention: len(mention['screen_name']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] - mention_html = '@%(screen_name)s' - text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', - mention_html % {'screen_name': entity['screen_name']}, text) + mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} + sub_expr = r'(?)' + orig_tweet_text[start:end] + '(?!)' + if display_text_start <= start <= display_text_end: + display_text = re.sub(sub_expr, mention_html, display_text) + else: + prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags for entity in sorted(entities['hashtags'], @@ -566,8 +577,17 @@ class Twython(EndpointsMixin, object): start, end = entity['indices'][0], entity['indices'][1] hashtag_html = '#%(hashtag)s' - text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', - hashtag_html % {'hashtag': entity['text']}, text) + display_text = re.sub(r'(?)' + orig_tweet_text[start:end] + '(?!)', + hashtag_html % {'hashtag': entity['text']}, display_text) + + # Symbols + for entity in sorted(entities['symbols'], + key=lambda symbol: len(symbol['text']), reverse=True): + start, end = entity['indices'][0], entity['indices'][1] + + symbol_html = '$%(symbol)s' + display_text = re.sub(r'(?)' + re.escape(orig_tweet_text[start:end]) + r'\b(?!)', + symbol_html % {'symbol': entity['text']}, display_text) # Urls for entity in entities['urls']: @@ -580,9 +600,11 @@ class Twython(EndpointsMixin, object): else: shown_url = entity['url'] - url_html = '%s' - text = text.replace(tweet['text'][start:end], - url_html % (entity['url'], shown_url)) + url_html = '%s' % (entity['url'], shown_url) + if display_text_start <= start <= display_text_end: + display_text = display_text.replace(orig_tweet_text[start:end], url_html) + else: + suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) # Media if 'media' in entities: @@ -596,13 +618,17 @@ class Twython(EndpointsMixin, object): else: shown_url = entity['url'] - url_html = '%s' - text = text.replace(tweet['text'][start:end], - url_html % (entity['url'], shown_url)) + url_html = '%s' % (entity['url'], shown_url) + if display_text_start <= start <= display_text_end: + # for compatibility with pre-extended tweets + display_text = display_text.replace(orig_tweet_text[start:end], url_html) + else: + suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) - if expand_quoted_status and tweet.get('is_quote_status'): + quote_text = '' + if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): quoted_status = tweet['quoted_status'] - text += '
%(quote)s' \ + quote_text += '
%(quote)s' \ '%(quote_user_name)s' \ '@%(quote_user_screen_name)s' \ '
' % \ @@ -612,4 +638,9 @@ class Twython(EndpointsMixin, object): 'quote_user_name': quoted_status['user']['name'], 'quote_user_screen_name': quoted_status['user']['screen_name']} - return text + return '%(prefix)s%(display)s%(suffix)s%(quote)s' % { + 'prefix': '%s' % prefix_text if prefix_text else '', + 'display': display_text, + 'suffix': '%s' % suffix_text if suffix_text else '', + 'quote': quote_text + } diff --git a/src/twython/endpoints.py b/src/twython/endpoints.py index 7691bfd5..95e33d29 100644 --- a/src/twython/endpoints.py +++ b/src/twython/endpoints.py @@ -14,13 +14,14 @@ This map is organized the order functions are documented at: https://dev.twitter.com/docs/api/1.1 """ -import json import os import warnings -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +from io import BytesIO +from time import sleep +#try: + #from StringIO import StringIO +#except ImportError: + #from io import StringIO from .advisory import TwythonDeprecationWarning @@ -143,15 +144,13 @@ class EndpointsMixin(object): Docs: https://dev.twitter.com/rest/reference/post/media/upload """ + # https://dev.twitter.com/rest/reference/get/media/upload-status + if params and params.get('command', '') == 'STATUS': + return self.get('https://upload.twitter.com/1.1/media/upload.json', params=params) + return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def set_description(self, **params): - """ Adds a description to an image.""" - # This method only accepts strings, no dictionaries. - params = json.dumps(params) - return self.post("media/metadata/create", params=params) - - def upload_video(self, media, media_type, size=None): + def upload_video(self, media, media_type, media_category=None, size=None, check_progress=False): """Uploads video file to Twitter servers in chunks. The file will be available to be attached to a status for 60 minutes. To attach to a update, pass a list of returned media ids to the 'update_status' method using the 'media_ids' param. @@ -176,7 +175,8 @@ class EndpointsMixin(object): params = { 'command': 'INIT', 'media_type': media_type, - 'total_bytes': size + 'total_bytes': size, + 'media_category': media_category } response_init = self.post(upload_url, params=params) media_id = response_init['media_id'] @@ -187,7 +187,7 @@ class EndpointsMixin(object): data = media.read(1*1024*1024) if not data: break - media_chunk = StringIO() + media_chunk = BytesIO() media_chunk.write(data) media_chunk.seek(0) @@ -205,7 +205,38 @@ class EndpointsMixin(object): 'command': 'FINALIZE', 'media_id': media_id } - return self.post(upload_url, params=params) + + response = self.post(upload_url, params=params) + + # Only get the status if explicity asked to + # Default to False + if check_progress: + + # Stage 4: STATUS call if still processing + params = { + 'command': 'STATUS', + 'media_id': media_id + } + + # added code to handle if media_category is NOT set and check_progress=True + # the API will return a NoneType object in this case + try: + processing_state = response.get('processing_info').get('state') + except AttributeError: + return response + + if processing_state: + while (processing_state == 'pending' or processing_state == 'in_progress') : + # get the secs to wait + check_after_secs = response.get('processing_info').get('check_after_secs') + + if check_after_secs: + sleep(check_after_secs) + response = self.get(upload_url, params=params) + # get new state after waiting + processing_state = response.get('processing_info').get('state') + + return response def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded