From 8eb8c2dd556bb0efed9cd8f0e77ccfa59e7c0d82 Mon Sep 17 00:00:00 2001 From: Derek Hu Date: Wed, 19 Nov 2014 01:09:02 -0500 Subject: [PATCH 01/80] Remove redundant checking for oauth_token & oauth_token_secret --- twython/api.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/twython/api.py b/twython/api.py index 0b421c3..e7e0046 100644 --- a/twython/api.py +++ b/twython/api.py @@ -102,15 +102,10 @@ class Twython(EndpointsMixin, object): auth = None if oauth_version == 1: # User Authentication is through OAuth 1 - if self.app_key is not None and self.app_secret is not None and \ - self.oauth_token is None and self.oauth_token_secret is None: - auth = OAuth1(self.app_key, self.app_secret) - - if self.app_key is not None and self.app_secret is not None and \ - self.oauth_token is not None and self.oauth_token_secret is \ - not None: + if self.app_key is not None and self.app_secret is not None: auth = OAuth1(self.app_key, self.app_secret, - self.oauth_token, self.oauth_token_secret) + self.oauth_token, self.oauth_token_secret) + elif oauth_version == 2 and self.access_token: # Application Authentication is through OAuth 2 token = {'token_type': token_type, From a875f270a83dd9003b577f6d9922cf32835e1514 Mon Sep 17 00:00:00 2001 From: Ethan Baer Date: Fri, 31 Jul 2015 15:03:26 -0500 Subject: [PATCH 02/80] fixed cursor function code to return next pages when funciton iter_mode is 'id' --- twython/api.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03..6046f13 100644 --- a/twython/api.py +++ b/twython/api.py @@ -18,6 +18,7 @@ from requests_oauthlib import OAuth1, OAuth2 from . import __version__ from .advisory import TwythonDeprecationWarning from .compat import json, urlencode, parse_qsl, quote_plus, str, is_py2 +from .compat import urlsplit from .endpoints import EndpointsMixin from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError from .helpers import _transparent_params @@ -498,19 +499,27 @@ class Twython(EndpointsMixin, object): try: if function.iter_mode == 'id': - if 'max_id' not in params: - # Add 1 to the id because since_id and - # max_id are inclusive - if hasattr(function, 'iter_metadata'): - since_id = content[function.iter_metadata].get('since_id_str') + # Set max_id in params to one less than lowest tweet id + if hasattr(function, 'iter_metadata'): + # Get supplied next max_id + metadata = content.get(function.iter_metadata) + if 'next_results' in metadata: + next_results = urlsplit(metadata['next_results']) + params = dict(parse_qsl(next_results.query)) else: - since_id = content[0]['id_str'] - params['since_id'] = (int(since_id) - 1) + # No more results + raise StopIteration + else: + # Twitter gives tweets in reverse chronological order: + params['max_id'] = str(int(content[-1]['id_str']) - 1) elif function.iter_mode == 'cursor': params['cursor'] = content['next_cursor_str'] except (TypeError, ValueError): # pragma: no cover raise TwythonError('Unable to generate next page of search \ results, `page` is not a number.') + except (KeyError, AttributeError): #pragma no cover + raise TwythonError('Unable to generate next page of search \ + results, content has unexpected structure.') @staticmethod def unicode2utf8(text): From 0b3df413d825af9026182a3fb96febde0a050a95 Mon Sep 17 00:00:00 2001 From: Ethan Baer Date: Fri, 31 Jul 2015 15:07:25 -0500 Subject: [PATCH 03/80] added urlsplit import for use in api.cursor function --- twython/compat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index c36b326..7c049b0 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -25,7 +25,7 @@ except ImportError: if is_py2: from urllib import urlencode, quote_plus - from urlparse import parse_qsl + from urlparse import parse_qsl, urlsplit str = unicode basestring = basestring @@ -33,7 +33,7 @@ if is_py2: elif is_py3: - from urllib.parse import urlencode, quote_plus, parse_qsl + from urllib.parse import urlencode, quote_plus, parse_qsl, urlsplit str = str basestring = (str, bytes) From 4a362a42aaf2eeee4a2ccac8524afc19c107db69 Mon Sep 17 00:00:00 2001 From: Martey Dodoo Date: Thu, 13 Aug 2015 21:52:53 -0400 Subject: [PATCH 04/80] Update Cory Dolphin's Github username in AUTHORS. Cory Dolphin changed Github usernames from "wcdolphin" to "corydolphin". Github does not automatically redirect profile links when you change usernames so his link in the AUTHORS file went to a 404. https://help.github.com/articles/what-happens-when-i-change-my-username/#changes-that-arent-automatic --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 739e11f..6b7d14e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -44,7 +44,7 @@ Patches and Suggestions - `Oleg Anashkin `_, streaming ``handlers`` functionality - `Luis Alberto Santana `_, Fixed issue where Twython was unnecessarily disabling compression -- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError +- `Cory Dolphin `_, Added retry_after attribute to TwythonRateLimitError - `Natan L `_, Fixed typo in documentation - `Cash Costello `_, Moved tests to use `responsoes`, fixed typos in documentation - `Joe Cabrera `_, PEP 8 contributions From 2ea45abb734af5a3de1e89a4ed2ea1a63e482160 Mon Sep 17 00:00:00 2001 From: ping Date: Sat, 15 Aug 2015 19:43:59 +0800 Subject: [PATCH 05/80] Fix html_for_tweet when a hashtag/mention is a substring of another --- twython/api.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03..d83c2da 100644 --- a/twython/api.py +++ b/twython/api.py @@ -10,6 +10,7 @@ dealing with the Twitter API """ import warnings +import re import requests from requests.auth import HTTPBasicAuth @@ -550,19 +551,22 @@ class Twython(EndpointsMixin, object): entities = tweet['entities'] # Mentions - for entity in entities['user_mentions']: + for entity in sorted(entities['user_mentions'], + key=lambda mention: len(mention['screen_name']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] mention_html = '@%(screen_name)s' - text = text.replace(tweet['text'][start:end], - mention_html % {'screen_name': entity['screen_name']}) + text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', + mention_html % {'screen_name': entity['screen_name']}, text) # Hashtags - for entity in entities['hashtags']: + for entity in sorted(entities['hashtags'], + key=lambda hashtag: len(hashtag['text']), reverse=True): start, end = entity['indices'][0], entity['indices'][1] hashtag_html = '#%(hashtag)s' - text = text.replace(tweet['text'][start:end], hashtag_html % {'hashtag': entity['text']}) + text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', + hashtag_html % {'hashtag': entity['text']}, text) # Urls for entity in entities['urls']: From 62d51c74314b7c95976bd83602092408a07580e9 Mon Sep 17 00:00:00 2001 From: ping Date: Mon, 17 Aug 2015 14:22:32 +0800 Subject: [PATCH 06/80] Add support for quoted_status in html_for_tweet() --- twython/api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index d2d5c03..68019f7 100644 --- a/twython/api.py +++ b/twython/api.py @@ -528,7 +528,7 @@ class Twython(EndpointsMixin, object): return str(text) @staticmethod - def html_for_tweet(tweet, use_display_url=True, use_expanded_url=False): + 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) :param tweet: Tweet object from received from Twitter API @@ -595,4 +595,16 @@ class Twython(EndpointsMixin, object): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) + if expand_quoted_status and tweet['is_quote_status']: + quoted_status = tweet['quoted_status'] + text += '
%(quote)s' \ + '%(quote_user_name)s' \ + '@%(quote_user_screen_name)s' \ + '
' % \ + {'quote': Twython.html_for_tweet(quoted_status, use_display_url, use_expanded_url, False), + 'quote_tweet_link': 'https://twitter.com/%s/status/%s' % + (quoted_status['user']['screen_name'], quoted_status['id_str']), + 'quote_user_name': quoted_status['user']['name'], + 'quote_user_screen_name': quoted_status['user']['screen_name']} + return text From 2cca89507992a760e65833ba88f0cec7be8d4cb8 Mon Sep 17 00:00:00 2001 From: Karambir Singh Nain Date: Wed, 23 Sep 2015 19:00:45 +0530 Subject: [PATCH 07/80] Add video upload endpoint --- docs/usage/advanced_usage.rst | 16 +++++++++ twython/api.py | 5 ++- twython/endpoints.py | 65 +++++++++++++++++++++++++++++++++-- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index d72b79f..fac35c2 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -35,6 +35,22 @@ Documentation: * https://dev.twitter.com/rest/reference/post/statuses/update * https://dev.twitter.com/rest/reference/post/media/upload +Updating Status with Video +-------------------------- + +This uploads a video as a media object and associates it with a status update. + +.. code-block:: python + + video = open('/path/to/file/video.mp4', 'rb') + response = twitter.upload_video(media=video, media_type='video/mp4') + twitter.update_status(status='Checkout this cool video!', media_ids=[response['media_id']]) + +Documentation: + +* https://dev.twitter.com/rest/reference/post/statuses/update +* https://dev.twitter.com/rest/reference/post/media/upload + Posting a Status with an Editing Image -------------------------------------- diff --git a/twython/api.py b/twython/api.py index 21b37da..3805e2f 100644 --- a/twython/api.py +++ b/twython/api.py @@ -194,7 +194,10 @@ class Twython(EndpointsMixin, object): retry_after=response.headers.get('X-Rate-Limit-Reset')) try: - content = response.json() + if response.status_code == 204: + content = response.content + else: + content = response.json() except ValueError: raise TwythonError('Response was not valid JSON. \ Unable to decode.') diff --git a/twython/endpoints.py b/twython/endpoints.py index 356cd8a..561ec45 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -14,7 +14,12 @@ This map is organized the order functions are documented at: https://dev.twitter.com/docs/api/1.1 """ +import os import warnings +try: + from StringIO import StringIO +except ImportError: + from io import StringIO from .advisory import TwythonDeprecationWarning @@ -139,6 +144,62 @@ class EndpointsMixin(object): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def upload_video(self, media, media_type, size=None): + """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. + + Upload happens in 3 stages: + - INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error. + - APPEND calls each with media chunk. This returns a 204(No Content) if chunk is received. + - FINALIZE call to complete media upload. This returns media_id to be used with status update. + + Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each. + + Docs: + https://dev.twitter.com/rest/public/uploading-media#chunkedupload + """ + upload_url = 'https://upload.twitter.com/1.1/media/upload.json' + if not size: + media.seek(0, os.SEEK_END) + size = media.tell() + media.seek(0) + + # Stage 1: INIT call + params = { + 'command': 'INIT', + 'media_type': media_type, + 'total_bytes': size + } + response_init = self.post(upload_url, params=params) + media_id = response_init['media_id'] + + # Stage 2: APPEND calls with 1mb chunks + segment_index = 0 + while True: + data = media.read(1*1024*1024) + if not data: + break + media_chunk = StringIO() + media_chunk.write(data) + media_chunk.seek(0) + + params = { + 'command': 'APPEND', + 'media_id': media_id, + 'segment_index': segment_index, + 'media': media_chunk, + } + self.post(upload_url, params=params) + segment_index += 1 + + # Stage 3: FINALIZE call to complete upload + params = { + 'command': 'FINALIZE', + 'media_id': media_id + } + return self.post(upload_url, params=params) + def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. @@ -546,7 +607,7 @@ class EndpointsMixin(object): list_mute_ids.iter_key = 'ids' def create_mute(self, **params): - """Mutes the specified user, preventing their tweets appearing + """Mutes the specified user, preventing their tweets appearing in the authenticating user's timeline. Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create @@ -555,7 +616,7 @@ class EndpointsMixin(object): return self.post('mutes/users/create', params=params) def destroy_mute(self, **params): - """Un-mutes the user specified in the user or ID parameter for + """Un-mutes the user specified in the user or ID parameter for the authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy From ff4655c314277d9c2c18bda56f07ac03d6972d76 Mon Sep 17 00:00:00 2001 From: ping Date: Thu, 8 Oct 2015 11:31:07 +0800 Subject: [PATCH 08/80] Fix html_for_tweet for pre-quote implementation tweet html --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index 3805e2f..2ed50bc 100644 --- a/twython/api.py +++ b/twython/api.py @@ -597,7 +597,7 @@ class Twython(EndpointsMixin, object): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) - if expand_quoted_status and tweet['is_quote_status']: + if expand_quoted_status and tweet.get('is_quote_status'): quoted_status = tweet['quoted_status'] text += '
%(quote)s' \ '%(quote_user_name)s' \ From 99e9cebdc38c6c0271148a6da70a1d74214f0e77 Mon Sep 17 00:00:00 2001 From: ping Date: Tue, 29 Mar 2016 10:44:04 +0800 Subject: [PATCH 09/80] Fix issue where is_quote_status is true but there is no quoted_status --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index 2ed50bc..1d514eb 100644 --- a/twython/api.py +++ b/twython/api.py @@ -597,7 +597,7 @@ class Twython(EndpointsMixin, object): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) - if expand_quoted_status and tweet.get('is_quote_status'): + if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): quoted_status = tweet['quoted_status'] text += '
%(quote)s' \ '%(quote_user_name)s' \ From a1640f4a170ad49a88b1b29a46a9dad474c33326 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Fri, 22 Apr 2016 14:22:32 +0100 Subject: [PATCH 10/80] Link $IBM-style symbols in tweets in html_for_tweet() Fixes #412 --- docs/usage/special_functions.rst | 5 +++-- tests/config.py | 2 ++ tests/test_core.py | 9 ++++++++- twython/api.py | 11 ++++++++++- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index c89da38..97e674c 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -72,11 +72,11 @@ The above code takes all the tweets from a specific users timeline, loops over t So: - http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 + http://t.co/FCmXyI6VHd is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71 will be replaced with: - google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71 + google.com is #cool, lol! @mikehelmick shd #checkitout. Love, @__twython__ $IBM github.com pic.twitter.com/N6InAO4B71 .. note:: When converting the string to HTML we add a class to each HTML tag so that you can maninpulate the DOM later on. @@ -84,6 +84,7 @@ will be replaced with: - For media urls that are replaced we add ``class="twython-media"`` to the anchor tag - For user mentions that are replaced we add ``class="twython-mention"`` to the anchor tag - For hashtags that are replaced we add ``class="twython-hashtag"`` to the anchor tag +- For symbols that are replaced we add ``class="twython-symbol"`` to the anchor tag This function accepts two parameters: ``use_display_url`` and ``use_expanded_url`` By default, ``use_display_url`` is ``True``. Meaning the link displayed in the tweet text will appear as (ex. google.com, github.com) diff --git a/tests/config.py b/tests/config.py index 21baa69..d520a7c 100644 --- a/tests/config.py +++ b/tests/config.py @@ -28,3 +28,5 @@ test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' + +test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} diff --git a/tests/test_core.py b/tests/test_core.py index 1aa0eea..a7c2758 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,7 @@ from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError from .config import ( - test_tweet_object, test_tweet_html, unittest + test_tweet_object, test_tweet_html, test_tweet_symbols_object, unittest ) import responses @@ -317,3 +317,10 @@ class TwythonAPITestCase(unittest.TestCase): # Make sure HTML doesn't contain the display OR expanded url self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) + + def test_html_for_tweet_symbols(self): + tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + # Should only link symbols listed in entities: + self.assertTrue('$AAPL' in tweet_text) + self.assertTrue('$ANOTHER' not in tweet_text) + diff --git a/twython/api.py b/twython/api.py index 2ed50bc..fc93283 100644 --- a/twython/api.py +++ b/twython/api.py @@ -528,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 @@ -566,6 +566,15 @@ class Twython(EndpointsMixin, object): text = re.sub(r'(?)' + tweet['text'][start:end] + '(?!)', hashtag_html % {'hashtag': entity['text']}, 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' + text = re.sub(r'(?)' + re.escape(tweet['text'][start:end]) + '(?!)', + symbol_html % {'symbol': entity['text']}, text) + # Urls for entity in entities['urls']: start, end = entity['indices'][0], entity['indices'][1] From 3f6700373f9b4bf6d0734912adf6386af4a22ff2 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 30 Apr 2016 05:22:26 -0400 Subject: [PATCH 11/80] 3.4.0 release, update version and history --- HISTORY.rst | 6 ++++++ docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 05f1f71..f8b0b31 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +3.4.0 (2016-30-04) +++++++++++++++++++ +- Added `upload_video` endpoint +- Fix quoted status checks in `html_for_tweet` +- Fix `html_for_tweet` method response when hashtag/mention is a substring of another + 3.3.0 (2015-18-07) ++++++++++++++++++ - Added support for muting users diff --git a/docs/conf.py b/docs/conf.py index 480a355..23044e3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.3.0' +version = '3.4.0' # The full version, including alpha/beta/rc tags. -release = '3.3.0' +release = '3.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 6d05baf..7b38b99 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.3.0' +__version__ = '3.4.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 3525f3c..1e57cee 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.3.0' +__version__ = '3.4.0' from .api import Twython from .streaming import TwythonStreamer From 86f878aad86bd76b6d7a09cbc709572b7ce4d4c9 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Sat, 30 Apr 2016 05:31:13 -0400 Subject: [PATCH 12/80] Fixes #417, update ReadTheDocs TLD in README --- README.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 6f12862..061d1b7 100644 --- a/README.rst +++ b/README.rst @@ -3,16 +3,16 @@ Twython .. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython + :target: https://pypi.python.org/pypi/twython .. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - + :target: https://pypi.python.org/pypi/twython + .. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square - :target: https://travis-ci.org/ryanmcgrath/twython + :target: https://travis-ci.org/ryanmcgrath/twython .. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square - :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master + :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master ``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! @@ -62,7 +62,7 @@ Or, if you want the code that is currently on GitHub Documentation ------------- -Documentation is available at https://twython.readthedocs.org/en/latest/ +Documentation is available at https://twython.readthedocs.io/en/latest/ Starting Out ------------ @@ -142,7 +142,7 @@ Once you have the final user tokens, store them in a database for later use:: OAUTH_TOKEN = final_step['oauth_token'] OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] -For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ +For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ Dynamic Function Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -174,7 +174,7 @@ Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline Updating Status ~~~~~~~~~~~~~~~ -This method makes use of dynamic arguments, `read more about them `_ +This method makes use of dynamic arguments, `read more about them `_ Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update @@ -195,8 +195,8 @@ Searching Advanced Usage -------------- -- `Advanced Twython Usage `_ -- `Streaming with Twython `_ +- `Advanced Twython Usage `_ +- `Streaming with Twython `_ Notes From f91da7cadf9d3ddeefd7e01b99622ad84fd14e88 Mon Sep 17 00:00:00 2001 From: Gage Coprivnicar Date: Sun, 12 Jun 2016 16:42:25 -0600 Subject: [PATCH 13/80] added get_direct_message example added a get_direct_message example hope this is helpful. I spent a lot of time trying to figure it out and figured I could help others with the same problem --- get_direct_message example | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 get_direct_message example diff --git a/get_direct_message example b/get_direct_message example new file mode 100644 index 0000000..5a27a06 --- /dev/null +++ b/get_direct_message example @@ -0,0 +1,17 @@ +from twython import Twython, TwythonError +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) + + +get_list = twitter.get_direct_messages() +#Returns All Twitter DM information which is a lot in a list format + +dm_dict = get_list[0] +#Sets get_list to a dictionary, the number in the list is the direct message retrieved +#That means that 0 is the most recent and n-1 is the last DM revieved. +#You can cycle through all the numbers and it will return the text and the sender id of each + +print dm_dict['text'] +#Gets the text from the dictionary + +print dm_dict['sender']['id'] +#Gets the ID of the sender From f7ddbcf414924ae31db4144bc5ffa16a000ca8ba Mon Sep 17 00:00:00 2001 From: ping Date: Wed, 22 Jun 2016 22:20:26 +0800 Subject: [PATCH 14/80] Use GET request if the media upload command is STATUS --- twython/endpoints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index 561ec45..1131d83 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -142,6 +142,10 @@ 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 upload_video(self, media, media_type, size=None): From 1b20f8f0f9eeeb398c89feb1787e541b21098741 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 17 Aug 2016 14:18:59 +0100 Subject: [PATCH 15/80] Fix upload_video "string argument detected, got 'bytes'" bug. Needed to use BytesIO instead of StringIO when uploading video to Twitter. Tested with python 2.6.9, 2.7.11, 3.3.6, 3.5.1. fixes #422 --- twython/endpoints.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 561ec45..9fe7734 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -16,10 +16,11 @@ https://dev.twitter.com/docs/api/1.1 import os import warnings -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +from io import BytesIO +#try: + #from StringIO import StringIO +#except ImportError: + #from io import StringIO from .advisory import TwythonDeprecationWarning @@ -180,7 +181,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) From 2c02b622a2cf8db5a2f060b3cb5672ff4e88d189 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Wed, 14 Sep 2016 15:35:51 -0700 Subject: [PATCH 16/80] added media_category and support for STATUS calls --- twython/endpoints.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 9fe7734..8814fee 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -199,7 +199,28 @@ class EndpointsMixin(object): 'command': 'FINALIZE', 'media_id': media_id } - return self.post(upload_url, params=params) + response_finalize = self.post(upload_url, params=params) + + # Stage 4: STATUS call if still processing + params = { + 'command': 'STATUS', + 'media_id': media_id + } + + processing_state = response_finalize['processing_info'].get('state', None) + + if processing_state is not None: + while (processing_state == "pending" or processing_state == "in_progress") : + # get the secs to wait + check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + + if check_after_secs is not None: + time.sleep(check_after_secs) + response_finalize = self.get(upload_url, params=params) + # get new state after waiting + processing_state = response_finalize['processing_info'].get('state') + + return response_finalize def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From c57c4bfc3488c0905de49dbcfdd06a121d55f4b8 Mon Sep 17 00:00:00 2001 From: ping Date: Tue, 20 Sep 2016 17:18:21 +0800 Subject: [PATCH 17/80] Update html_for_tweet to support extended tweets --- tests/config.py | 4 ++++ tests/test_core.py | 15 +++++++++++- twython/api.py | 57 +++++++++++++++++++++++++++++++++------------- 3 files changed, 59 insertions(+), 17 deletions(-) diff --git a/tests/config.py b/tests/config.py index d520a7c..607bdd8 100644 --- a/tests/config.py +++ b/tests/config.py @@ -30,3 +30,7 @@ test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} + +test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} +test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' diff --git a/tests/test_core.py b/tests/test_core.py index a7c2758..c7cf2a2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,7 +1,10 @@ +# -*- coding: utf-8 -*- from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, unittest + test_tweet_object, test_tweet_html, test_tweet_symbols_object, + test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, + unittest ) import responses @@ -324,3 +327,13 @@ class TwythonAPITestCase(unittest.TestCase): self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) + def test_html_for_tweet_compatmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + # link to compat web status link + self.assertTrue( + u'twitter.com/i/web/status/7…' in tweet_text) + + def test_html_for_tweet_extendedmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + # full tweet rendered with suffix + self.assertEqual(test_tweet_extended_html, tweet_text) diff --git a/twython/api.py b/twython/api.py index ce11a68..f806cea 100644 --- a/twython/api.py +++ b/twython/api.py @@ -544,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 @@ -553,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'], @@ -563,8 +577,8 @@ 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'], @@ -572,8 +586,8 @@ class Twython(EndpointsMixin, object): start, end = entity['indices'][0], entity['indices'][1] symbol_html = '$%(symbol)s' - text = re.sub(r'(?)' + re.escape(tweet['text'][start:end]) + '(?!)', - symbol_html % {'symbol': entity['text']}, text) + 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']: @@ -586,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: @@ -602,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) + 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' \ '
' % \ @@ -618,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 + } From 7401adfb640e50021cab7db2041f13a21ce6bad2 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Sun, 25 Sep 2016 18:03:26 -0700 Subject: [PATCH 18/80] - Double -> single quotes - Removed default value from .get() - Added a check_progress param to upload_video to allow users to decide when to check 'STATUS' calls --- twython/endpoints.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 8814fee..33d4b00 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -145,7 +145,7 @@ class EndpointsMixin(object): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def upload_video(self, media, media_type, size=None): + def upload_video(self, media, media_type, 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. @@ -199,28 +199,33 @@ class EndpointsMixin(object): 'command': 'FINALIZE', 'media_id': media_id } - response_finalize = self.post(upload_url, params=params) - # Stage 4: STATUS call if still processing - params = { - 'command': 'STATUS', - 'media_id': media_id - } - - processing_state = response_finalize['processing_info'].get('state', None) + # Only get the status if explicity asked to + # Default to False + if check_progress == True: - if processing_state is not None: - while (processing_state == "pending" or processing_state == "in_progress") : - # get the secs to wait - check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + response_finalize = self.post(upload_url, params=params) - if check_after_secs is not None: - time.sleep(check_after_secs) - response_finalize = self.get(upload_url, params=params) - # get new state after waiting - processing_state = response_finalize['processing_info'].get('state') + # Stage 4: STATUS call if still processing + params = { + 'command': 'STATUS', + 'media_id': media_id + } + + processing_state = response_finalize['processing_info'].get('state') - return response_finalize + if processing_state is not None: + while (processing_state == 'pending' or processing_state == 'in_progress') : + # get the secs to wait + check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + + if check_after_secs is not None: + time.sleep(check_after_secs) + response_finalize = self.get(upload_url, params=params) + # get new state after waiting + processing_state = response_finalize['processing_info'].get('state') + + return self.post(upload_url, param=params) def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From e76a290166884cebfffaca30bb847b00b1b2b29e Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 26 Sep 2016 12:51:05 -0700 Subject: [PATCH 19/80] fixed styling issues --- twython/endpoints.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 33d4b00..24ddcb9 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -200,11 +200,11 @@ class EndpointsMixin(object): 'media_id': media_id } + response = self.post(upload_url, params=params) + # Only get the status if explicity asked to # Default to False - if check_progress == True: - - response_finalize = self.post(upload_url, params=params) + if check_progress: # Stage 4: STATUS call if still processing params = { @@ -212,20 +212,20 @@ class EndpointsMixin(object): 'media_id': media_id } - processing_state = response_finalize['processing_info'].get('state') + processing_state = response['processing_info'].get('state') if processing_state is not None: while (processing_state == 'pending' or processing_state == 'in_progress') : # get the secs to wait - check_after_secs = response_finalize['processing_info'].get('check_after_secs', None) + check_after_secs = response['processing_info'].get('check_after_secs') if check_after_secs is not None: time.sleep(check_after_secs) - response_finalize = self.get(upload_url, params=params) + response = self.get(upload_url, params=params) # get new state after waiting - processing_state = response_finalize['processing_info'].get('state') + processing_state = response['processing_info'].get('state') - return self.post(upload_url, param=params) + return response def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded From 469432bcf84793db1137aaa6c5b3a4f70f386ad9 Mon Sep 17 00:00:00 2001 From: Tushar Bhushan Date: Mon, 26 Sep 2016 14:22:19 -0700 Subject: [PATCH 20/80] - added support for media_category param - added code to handle empty responses for STATUS calls --- twython/endpoints.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 24ddcb9..8919a71 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -17,6 +17,7 @@ https://dev.twitter.com/docs/api/1.1 import os import warnings from io import BytesIO +from time import sleep #try: #from StringIO import StringIO #except ImportError: @@ -145,7 +146,7 @@ class EndpointsMixin(object): """ return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) - def upload_video(self, media, media_type, size=None, check_progress=False): + 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. @@ -170,7 +171,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'] @@ -211,19 +213,24 @@ class EndpointsMixin(object): 'command': 'STATUS', 'media_id': media_id } - - processing_state = response['processing_info'].get('state') - if processing_state is not None: + # 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['processing_info'].get('check_after_secs') + check_after_secs = response.get('processing_info').get('check_after_secs') - if check_after_secs is not None: - time.sleep(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['processing_info'].get('state') + processing_state = response.get('processing_info').get('state') return response From 574483d87021e8cdd0272e79f9654f1040634153 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Tue, 6 Jun 2017 10:17:58 -0400 Subject: [PATCH 21/80] 3.5.0 Release --- HISTORY.rst | 6 ++++++ docs/conf.py | 4 ++-- setup.py | 2 +- twython/__init__.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index f8b0b31..4b0f752 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,12 @@ History ------- +3.5.0 (2017-06-06) +++++++++++++++++++ +- Added support for "symbols" in `Twython.html_for_tweet()` +- Added support for extended tweets in `Twython.html_for_tweet()` +- You can now check progress of video uploads to Twitter when using `Twython.upload_video()` + 3.4.0 (2016-30-04) ++++++++++++++++++ - Added `upload_video` endpoint diff --git a/docs/conf.py b/docs/conf.py index 23044e3..2a22092 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.4.0' +version = '3.5.0' # The full version, including alpha/beta/rc tags. -release = '3.4.0' +release = '3.5.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 7b38b99..b31d7af 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.4.0' +__version__ = '3.5.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 1e57cee..7404b28 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.4.0' +__version__ = '3.5.0' from .api import Twython from .streaming import TwythonStreamer From 5a87fc7d842994c48d32da10dc817fb1a9af16d7 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Fri, 16 Jun 2017 09:16:22 -0400 Subject: [PATCH 22/80] Fixes #446, add Python 3 classifier to setup.py --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b31d7af..1448fa8 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,12 @@ setup( 'License :: OSI Approved :: MIT License', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Communications :: Chat', - 'Topic :: Internet' + 'Topic :: Internet', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ] ) From b366ab55c3412e78c8dcf49ab73f04937fd31e67 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 22 Aug 2017 13:49:40 +0100 Subject: [PATCH 23/80] Improve replacing of entities with links in html_for_tweet() I've re-written the parts of `html_for_tweet()` so that it handles all the replacements of URLs, mentions, symbols and hashtags better. Mainly to fix #447 but it should be a little more robust generally. Shamelessly cribbed from https://stackoverflow.com/a/25514650/250962 Passes all tests, but I haven't checked it beyond that. Fixes #447 --- tests/config.py | 3 ++ tests/test_core.py | 7 +++ twython/api.py | 118 +++++++++++++++++++++++++++------------------ 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/tests/config.py b/tests/config.py index 607bdd8..9e0aa15 100644 --- a/tests/config.py +++ b/tests/config.py @@ -34,3 +34,6 @@ test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHE test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' + +test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} + diff --git a/tests/test_core.py b/tests/test_core.py index c7cf2a2..45d856e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,6 +4,7 @@ from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitErr from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, + test_tweet_identical_urls, unittest ) @@ -321,6 +322,12 @@ class TwythonAPITestCase(unittest.TestCase): self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) + def test_html_for_tweet_identical_urls(self): + """If the 'url's for different url entities are identical, they should link correctly.""" + tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + self.assertEqual(tweet_text, + u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') + def test_html_for_tweet_symbols(self): tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) # Should only link symbols listed in entities: diff --git a/twython/api.py b/twython/api.py index f806cea..954033f 100644 --- a/twython/api.py +++ b/twython/api.py @@ -556,62 +556,78 @@ class Twython(EndpointsMixin, object): suffix_text = orig_tweet_text[display_text_end:len(orig_tweet_text)] if 'entities' in tweet: - entities = tweet['entities'] + # We'll put all the bits of replacement HTML and their starts/ends + # in this list: + entities = [] # Mentions - for entity in sorted(entities['user_mentions'], - key=lambda mention: len(mention['screen_name']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] + if 'user_mentions' in tweet['entities']: + for entity in tweet['entities']['user_mentions']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] - 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) + mention_html = '@%(screen_name)s' % {'screen_name': entity['screen_name']} + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = mention_html + entities.append(temp) + else: + prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags - for entity in sorted(entities['hashtags'], - key=lambda hashtag: len(hashtag['text']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] + if 'hashtags' in tweet['entities']: + for entity in tweet['entities']['hashtags']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] - hashtag_html = '#%(hashtag)s' - display_text = re.sub(r'(?)' + orig_tweet_text[start:end] + '(?!)', - hashtag_html % {'hashtag': entity['text']}, display_text) + url_html = '#%(hashtag)s' % {'hashtag': entity['text']} + + temp['replacement'] = url_html + entities.append(temp) # Symbols - for entity in sorted(entities['symbols'], - key=lambda symbol: len(symbol['text']), reverse=True): - start, end = entity['indices'][0], entity['indices'][1] + if 'symbols' in tweet['entities']: + for entity in tweet['entities']['symbols']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = 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) + url_html = '$%(symbol)s' % {'symbol': entity['text']} - # Urls - for entity in entities['urls']: - start, end = entity['indices'][0], entity['indices'][1] - if use_display_url and entity.get('display_url') \ - and not use_expanded_url: - shown_url = entity['display_url'] - elif use_expanded_url and entity.get('expanded_url'): - shown_url = entity['expanded_url'] - else: - shown_url = entity['url'] + temp['replacement'] = url_html + entities.append(temp) - 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) + # URLs + if 'urls' in tweet['entities']: + for entity in tweet['entities']['urls']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] - # Media - if 'media' in entities: - for entity in entities['media']: - start, end = entity['indices'][0], entity['indices'][1] - if use_display_url and entity.get('display_url') \ - and not use_expanded_url: + if use_display_url and entity.get('display_url') and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] + + url_html = '%s' % (entity['url'], shown_url) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) + else: + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + + if 'media' in tweet['entities']: + for entity in tweet['entities']['media']: + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] + + if use_display_url and entity.get('display_url') and not use_expanded_url: shown_url = entity['display_url'] elif use_expanded_url and entity.get('expanded_url'): shown_url = entity['expanded_url'] @@ -619,11 +635,17 @@ class Twython(EndpointsMixin, object): shown_url = entity['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) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) else: - suffix_text = suffix_text.replace(orig_tweet_text[start:end], url_html) + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + + # Now do all the replacements, starting from the end, so that the + # start/end indices still work: + for entity in sorted(entities, key=lambda e: e['start'], reverse=True): + display_text = display_text[0:entity['start']] + entity['replacement'] + display_text[entity['end']:] quote_text = '' if expand_quoted_status and tweet.get('is_quote_status') and tweet.get('quoted_status'): From 6890802b2ae528557fdb1a528b4efd17470b5702 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 22 Aug 2017 13:55:22 +0100 Subject: [PATCH 24/80] Add test for missing `symbols` in entities If a tweet has no `symbols` in its `entities` then `html_for_tweet()` was failing. I'm not sure how common this is but, for example, tweets in a downloaded archive do not have `symbols` for some reason. The previous change (b366ab5) fixed this, but I'm adding a test for this case. --- tests/test_core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 45d856e..2a30df5 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -334,6 +334,18 @@ class TwythonAPITestCase(unittest.TestCase): self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) + def test_html_for_tweet_no_symbols(self): + """Should still work if tweet object has no symbols list""" + tweet = test_tweet_symbols_object + # Save a copy: + symbols = tweet['entities']['symbols'] + del tweet['entities']['symbols'] + tweet_text = self.api.html_for_tweet(tweet) + self.assertTrue('symbols: $AAPL and' in tweet_text) + self.assertTrue('and $ANOTHER and $A.' in tweet_text) + # Put the symbols back: + test_tweet_symbols_object['entities']['symbols'] = symbols + def test_html_for_tweet_compatmode(self): tweet_text = self.api.html_for_tweet(test_tweet_compat_object) # link to compat web status link From ede941cf1a259f927861d988bd4873f5cd4e5d65 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Wed, 23 Aug 2017 11:29:20 -0400 Subject: [PATCH 25/80] Version 3.6.0 and update travis python versions --- .travis.yml | 5 +++-- HISTORY.rst | 5 +++++ docs/conf.py | 4 ++-- setup.py | 5 ++--- twython/__init__.py | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 91ebf1d..8da6c43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ language: python python: - 2.6 - 2.7 - - 3.3 + - 3.5 + - 3.6 env: global: - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= @@ -17,7 +18,7 @@ env: - TEST_LIST_SLUG=team - TEST_LIST_OWNER_SCREEN_NAME=twitterapi - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= -install: +install: - pip install -r requirements.txt - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing diff --git a/HISTORY.rst b/HISTORY.rst index 4b0f752..4249539 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ History ------- +3.6.0 (2017-23-08) +++++++++++++++++++ +- Improve replacing of entities with links in `html_for_tweet()` +- Update classifiers for PyPI + 3.5.0 (2017-06-06) ++++++++++++++++++ - Added support for "symbols" in `Twython.html_for_tweet()` diff --git a/docs/conf.py b/docs/conf.py index 2a22092..601d066 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.5.0' +version = '3.6.0' # The full version, including alpha/beta/rc tags. -release = '3.5.0' +release = '3.6.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 1448fa8..40b05b7 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.5.0' +__version__ = '3.6.0' packages = [ 'twython', @@ -45,8 +45,7 @@ setup( 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ] ) diff --git a/twython/__init__.py b/twython/__init__.py index 7404b28..84e9ee6 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.5.0' +__version__ = '3.6.0' from .api import Twython from .streaming import TwythonStreamer From c63ed8559e04b098f965aa94ad5f3644ffca5fad Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 6 Sep 2017 18:30:32 -0400 Subject: [PATCH 26/80] Improve error handling for api.cursor --- twython/api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/twython/api.py b/twython/api.py index 954033f..5f0c50d 100644 --- a/twython/api.py +++ b/twython/api.py @@ -470,6 +470,11 @@ class Twython(EndpointsMixin, object): >>> print result """ + if not callable(function): + raise TypeError('.cursor() takes a Twython function as its first \ + argument. Did you provide the result of a \ + function call?') + if not hasattr(function, 'iter_mode'): raise TwythonError('Unable to create generator for Twython \ method "%s"' % function.__name__) From 6166e86807fdf474c6521ed3b818d2a28cbda687 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Mon, 11 Sep 2017 13:52:43 -0400 Subject: [PATCH 27/80] Add test for cursor creation --- tests/test_core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 2a30df5..44adf95 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -356,3 +356,15 @@ class TwythonAPITestCase(unittest.TestCase): tweet_text = self.api.html_for_tweet(test_tweet_extended_object) # full tweet rendered with suffix self.assertEqual(test_tweet_extended_html, tweet_text) + + def test_cursor_requires_twython_function(self): + """Test that cursor() raises when called without a Twython function""" + def init_and_iterate_cursor(*args, **kwargs): + cursor = self.api.cursor(*args, **kwargs) + return next(cursor) + + non_function = object() + non_twython_function = lambda x: x + + self.assertRaises(TypeError, init_and_iterate_cursor, non_function) + self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) From 0ee9b76b5cb6fd86ce8b75287ca33579fd1d18b5 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 3 Oct 2017 19:12:02 +0100 Subject: [PATCH 28/80] Make html_for_tweet() link a replied-to username If a tweet was a reply, then when `html_for_tweet()` tried to turn the initial "@username" into a link, there was: > NameError: name 'sub_expr' is not defined This is now fixed, with a test to ensure the "@username" becomes a link. --- tests/config.py | 2 ++ tests/test_core.py | 10 ++++++++-- twython/api.py | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/config.py b/tests/config.py index 9e0aa15..c3ea809 100644 --- a/tests/config.py +++ b/tests/config.py @@ -37,3 +37,5 @@ test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: p test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} +test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } + diff --git a/tests/test_core.py b/tests/test_core.py index 44adf95..b824f86 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,8 +3,8 @@ from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitErr from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, - test_tweet_identical_urls, + test_tweet_compat_object, test_tweet_extended_object, + test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, unittest ) @@ -308,6 +308,12 @@ class TwythonAPITestCase(unittest.TestCase): tweet_text = self.api.html_for_tweet(test_tweet_object) self.assertEqual(test_tweet_html, tweet_text) + def test_html_for_tweet_reply(self): + """Test HTML for Tweet links the replied-to username.""" + tweet_text = self.api.html_for_tweet(test_tweet_reply) + self.assertEqual(tweet_text, + u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') + def test_html_for_tweet_expanded_url(self): """Test using expanded url in HTML for Tweet displays full urls""" tweet_text = self.api.html_for_tweet(test_tweet_object, diff --git a/twython/api.py b/twython/api.py index 5f0c50d..2782168 100644 --- a/twython/api.py +++ b/twython/api.py @@ -578,6 +578,9 @@ class Twython(EndpointsMixin, object): temp['replacement'] = mention_html entities.append(temp) else: + # Make the '@username' at the start, before + # display_text, into a link: + sub_expr = r'(?)' + orig_tweet_text[temp['start']:temp['end']] + '(?!)' prefix_text = re.sub(sub_expr, mention_html, prefix_text) # Hashtags From 4f1e41a9e5e3cfc7814c6912f4213161e20c8d84 Mon Sep 17 00:00:00 2001 From: Clayton Davis Date: Fri, 6 Oct 2017 14:45:25 -0400 Subject: [PATCH 29/80] Update print statements to print() functions --- docs/usage/advanced_usage.rst | 2 +- docs/usage/special_functions.rst | 8 ++++---- docs/usage/streaming_api.rst | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index fac35c2..ce609a6 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -98,7 +98,7 @@ That being said, Twython offers a generator for search results and can be access results = twitter.cursor(twitter.search, q='python') for result in results: - print result + print(result) Manipulate the Request (headers, proxies, etc.) ----------------------------------------------- diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index 97e674c..cde8f99 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -25,7 +25,7 @@ The Old Way results = twitter.search(q='twitter') if results.get('statuses'): for result in results['statuses']: - print result['id_str'] + print(result['id_str']) The New Way ^^^^^^^^^^^ @@ -39,7 +39,7 @@ The New Way results = twitter.cursor(twitter.search, q='twitter') for result in results: - print result['id_str'] + print(result['id_str']) Another example: @@ -47,7 +47,7 @@ Another example: results = twitter.cursor(t.get_mentions_timeline) for result in results: - print result['id_str'] + print(result['id_str']) HTML for Tweet @@ -66,7 +66,7 @@ This function takes a tweet object received from the Twitter API and returns an include_rts=True) for tweet in user_tweets: tweet['text'] = Twython.html_for_tweet(tweet) - print tweet['text'] + print(tweet['text']) The above code takes all the tweets from a specific users timeline, loops over them and replaces the value of ``tweet['text']`` with HTML. diff --git a/docs/usage/streaming_api.rst b/docs/usage/streaming_api.rst index 8b21c70..4d6c1c3 100644 --- a/docs/usage/streaming_api.rst +++ b/docs/usage/streaming_api.rst @@ -27,10 +27,10 @@ Now set up how you want to handle the signals. class MyStreamer(TwythonStreamer): def on_success(self, data): if 'text' in data: - print data['text'].encode('utf-8') + print(data['text']) def on_error(self, status_code, data): - print status_code + print(status_code) # Want to stop trying to get data because of the error? # Uncomment the next line! From c086449818d53ed1f6e0eb3d6ef96946de2e6282 Mon Sep 17 00:00:00 2001 From: Clayton Davis Date: Fri, 6 Oct 2017 14:57:36 -0400 Subject: [PATCH 30/80] Update StringIO import for py3k --- docs/usage/advanced_usage.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index ce609a6..32334c0 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -62,7 +62,12 @@ with a status update. # Assume you are working with a JPEG from PIL import Image - from StringIO import StringIO + try: + # Python 3 + from io import StringIO + except ImportError: + # Python 2 + from StringIO import StringIO photo = Image.open('/path/to/file/image.jpg') From e87b80710db0f74af8508353611e05608cd1cd19 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Sat, 7 Oct 2017 16:46:56 +0200 Subject: [PATCH 31/80] update the link to the doc of the twitter API --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 1aa13e0..05d2838 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,7 +16,7 @@ Features - Twitter lists - Timelines - Direct Messages - - and anything found in `the Twitter API docs `_. + - and anything found in `the Twitter API docs `_. - Image Uploading: - Update user status with an image - Change user avatar From 748d28cc71f05a67407e36aa31c5de429d8ce8db Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Sat, 7 Oct 2017 18:26:21 +0200 Subject: [PATCH 32/80] twython/api.py: JSON error is not raised if the response content is empty and the status code is not 204. If params is not a dictionary, _transparent_params is not called --- twython/api.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/twython/api.py b/twython/api.py index 2782168..577d6a6 100644 --- a/twython/api.py +++ b/twython/api.py @@ -140,7 +140,11 @@ class Twython(EndpointsMixin, object): params = params or {} func = getattr(self.client, method) - params, files = _transparent_params(params) + if type(params) is dict: + params, files = _transparent_params(params) + else: + params = params + files = list() requests_args = {} for k, v in self.client_args.items(): @@ -192,15 +196,16 @@ class Twython(EndpointsMixin, object): error_message, error_code=response.status_code, retry_after=response.headers.get('X-Rate-Limit-Reset')) - + content="" try: if response.status_code == 204: content = response.content else: content = response.json() except ValueError: - raise TwythonError('Response was not valid JSON. \ - Unable to decode.') + if response.content!="": + raise TwythonError('Response was not valid JSON. \ + Unable to decode.') return content From 6fc7b9e0386110eb05ed897d561004115407c508 Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Sat, 7 Oct 2017 18:29:19 +0200 Subject: [PATCH 33/80] Added create_metadata endpoint --- twython/endpoints.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/twython/endpoints.py b/twython/endpoints.py index 95e33d2..1c1d80c 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -14,6 +14,7 @@ This map is organized the order functions are documented at: https://dev.twitter.com/docs/api/1.1 """ +import json import os import warnings from io import BytesIO @@ -150,6 +151,13 @@ class EndpointsMixin(object): return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def create_metadata(self, **params): + """ Adds metadata to a media element, such as image descriptions for visually impaired. + Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + """ + params = json.dumps(params) + return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params) + 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 From 1511ee7b4d983c27292f9cb6060c011366cc7ac1 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 7 Oct 2017 18:08:01 +0100 Subject: [PATCH 34/80] Split test_html_for_tweet() tests into their own file --- tests/test_core.py | 68 +-------------------------------- tests/test_html_for_tweet.py | 74 ++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 66 deletions(-) create mode 100644 tests/test_html_for_tweet.py diff --git a/tests/test_core.py b/tests/test_core.py index b824f86..4c215d4 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- from twython import Twython, TwythonError, TwythonAuthError, TwythonRateLimitError -from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, - test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - unittest -) +from .config import unittest import responses import requests @@ -303,66 +298,6 @@ class TwythonAPITestCase(unittest.TestCase): """Test encoding UTF-8 works""" self.api.encode('Twython is awesome!') - def test_html_for_tweet(self): - """Test HTML for Tweet returns what we want""" - tweet_text = self.api.html_for_tweet(test_tweet_object) - self.assertEqual(test_tweet_html, tweet_text) - - def test_html_for_tweet_reply(self): - """Test HTML for Tweet links the replied-to username.""" - tweet_text = self.api.html_for_tweet(test_tweet_reply) - self.assertEqual(tweet_text, - u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') - - def test_html_for_tweet_expanded_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, - use_expanded_url=True) - # Make sure full url is in HTML - self.assertTrue('http://google.com' in tweet_text) - - def test_html_for_tweet_short_url(self): - """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, False) - # Make sure HTML doesn't contain the display OR expanded url - self.assertTrue('http://google.com' not in tweet_text) - self.assertTrue('google.com' not in tweet_text) - - def test_html_for_tweet_identical_urls(self): - """If the 'url's for different url entities are identical, they should link correctly.""" - tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) - self.assertEqual(tweet_text, - u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') - - def test_html_for_tweet_symbols(self): - tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) - # Should only link symbols listed in entities: - self.assertTrue('$AAPL' in tweet_text) - self.assertTrue('$ANOTHER' not in tweet_text) - - def test_html_for_tweet_no_symbols(self): - """Should still work if tweet object has no symbols list""" - tweet = test_tweet_symbols_object - # Save a copy: - symbols = tweet['entities']['symbols'] - del tweet['entities']['symbols'] - tweet_text = self.api.html_for_tweet(tweet) - self.assertTrue('symbols: $AAPL and' in tweet_text) - self.assertTrue('and $ANOTHER and $A.' in tweet_text) - # Put the symbols back: - test_tweet_symbols_object['entities']['symbols'] = symbols - - def test_html_for_tweet_compatmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_compat_object) - # link to compat web status link - self.assertTrue( - u'twitter.com/i/web/status/7…' in tweet_text) - - def test_html_for_tweet_extendedmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_extended_object) - # full tweet rendered with suffix - self.assertEqual(test_tweet_extended_html, tweet_text) - def test_cursor_requires_twython_function(self): """Test that cursor() raises when called without a Twython function""" def init_and_iterate_cursor(*args, **kwargs): @@ -374,3 +309,4 @@ class TwythonAPITestCase(unittest.TestCase): self.assertRaises(TypeError, init_and_iterate_cursor, non_function) self.assertRaises(TwythonError, init_and_iterate_cursor, non_twython_function) + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py new file mode 100644 index 0000000..b8b5dc4 --- /dev/null +++ b/tests/test_html_for_tweet.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from twython import Twython, TwythonError + +from .config import ( + test_tweet_object, test_tweet_html, test_tweet_symbols_object, + test_tweet_compat_object, test_tweet_extended_object, + test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, + unittest +) + + +class TestHtmlForTweetTestCase(unittest.TestCase): + def setUp(self): + self.api = Twython('', '', '', '') + + def test_basic(self): + """Test HTML for Tweet returns what we want""" + tweet_text = self.api.html_for_tweet(test_tweet_object) + self.assertEqual(test_tweet_html, tweet_text) + + def test_reply(self): + """Test HTML for Tweet links the replied-to username.""" + tweet_text = self.api.html_for_tweet(test_tweet_reply) + self.assertEqual(tweet_text, + u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') + + def test_expanded_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, + use_expanded_url=True) + # Make sure full url is in HTML + self.assertTrue('http://google.com' in tweet_text) + + def test_short_url(self): + """Test using expanded url in HTML for Tweet displays full urls""" + tweet_text = self.api.html_for_tweet(test_tweet_object, False) + # Make sure HTML doesn't contain the display OR expanded url + self.assertTrue('http://google.com' not in tweet_text) + self.assertTrue('google.com' not in tweet_text) + + def test_identical_urls(self): + """If the 'url's for different url entities are identical, they should link correctly.""" + tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + self.assertEqual(tweet_text, + u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') + + def test_symbols(self): + tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + # Should only link symbols listed in entities: + self.assertTrue('$AAPL' in tweet_text) + self.assertTrue('$ANOTHER' not in tweet_text) + + def test_no_symbols(self): + """Should still work if tweet object has no symbols list""" + tweet = test_tweet_symbols_object + # Save a copy: + symbols = tweet['entities']['symbols'] + del tweet['entities']['symbols'] + tweet_text = self.api.html_for_tweet(tweet) + self.assertTrue('symbols: $AAPL and' in tweet_text) + self.assertTrue('and $ANOTHER and $A.' in tweet_text) + # Put the symbols back: + test_tweet_symbols_object['entities']['symbols'] = symbols + + def test_compatmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + # link to compat web status link + self.assertTrue( + u'twitter.com/i/web/status/7…' in tweet_text) + + def test_extendedmode(self): + tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + # full tweet rendered with suffix + self.assertEqual(test_tweet_extended_html, tweet_text) From a27efd9da8b6eb7350d650c0fc14aca80c32b466 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 7 Oct 2017 18:38:20 +0100 Subject: [PATCH 35/80] Fix html_for_tweet()s handling of media URLs We were trying to link to each media item using its `url`/`expanded_url`. But there is only one of these, shared across all of a tweet's media items. So attempting to put it in several times, in the same location, was a bit of a mess! So it now only puts the `url`/`expanded_url` in once, no matter how many media items there are. --- tests/config.py | 1 + tests/test_html_for_tweet.py | 9 +++ tweet.json | 142 +++++++++++++++++++++++++++++++++++ twython/api.py | 38 +++++----- 4 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 tweet.json diff --git a/tests/config.py b/tests/config.py index c3ea809..07ca96d 100644 --- a/tests/config.py +++ b/tests/config.py @@ -39,3 +39,4 @@ test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [] test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } +test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index b8b5dc4..dc44047 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,6 +5,7 @@ from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, + test_tweet_media, unittest ) @@ -72,3 +73,11 @@ class TestHtmlForTweetTestCase(unittest.TestCase): tweet_text = self.api.html_for_tweet(test_tweet_extended_object) # full tweet rendered with suffix self.assertEqual(test_tweet_extended_html, tweet_text) + + def test_media(self): + tweet_text = self.api.html_for_tweet(test_tweet_media) + + self.assertEqual( + """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", + tweet_text) + diff --git a/tweet.json b/tweet.json new file mode 100644 index 0000000..5e61ed8 --- /dev/null +++ b/tweet.json @@ -0,0 +1,142 @@ +{ + "source":"web", + "entities":{ + "user_mentions":[ ], + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "id":905105571765944320, + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "sizes":[ + { + "h":256, + "resize":"fit", + "w":680 + }, + { + "h":376, + "resize":"fit", + "w":1000 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":376, + "resize":"fit", + "w":1000 + }, + { + "h":376, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "id_str":"905105572529393668", + "id":905105572529393668, + "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "sizes":[ + { + "h":399, + "resize":"fit", + "w":1000 + }, + { + "h":271, + "resize":"fit", + "w":680 + }, + { + "h":399, + "resize":"fit", + "w":1000 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":399, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "indices":[ 131, 154 ], + "url":"https://t.co/OwNc6uJklg", + "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "id_str":"905105573255016448", + "id":905105573255016448, + "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "sizes":[ + { + "h":287, + "resize":"fit", + "w":1000 + }, + { + "h":195, + "resize":"fit", + "w":680 + }, + { + "h":150, + "resize":"crop", + "w":150 + }, + { + "h":287, + "resize":"fit", + "w":1000 + }, + { + "h":287, + "resize":"fit", + "w":1000 + } + ], + "display_url":"pic.twitter.com/OwNc6uJklg" + } + ], + "hashtags":[ ], + "urls":[ + { + "indices":[ 107, 130 ], + "url":"https://t.co/2yUmmn3TOc", + "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", + "display_url":"gyford.com/phil/writing/2\u2026" + } + ] + }, + "geo":{ }, + "id_str":"905105588279013377", + "text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", + "id":905105588279013377, + "created_at":"2017-09-05 16:29:22 +0000", + "user":{ + "name":"Phil Gyford", + "screen_name":"philgyford", + "protected":false, + "id_str":"12552", + "profile_image_url_https":"https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg", + "id":12552, + "verified":false + } +} diff --git a/twython/api.py b/twython/api.py index 2782168..fac7928 100644 --- a/twython/api.py +++ b/twython/api.py @@ -629,26 +629,30 @@ class Twython(EndpointsMixin, object): else: suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) - if 'media' in tweet['entities']: - for entity in tweet['entities']['media']: - temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + if 'media' in tweet['entities'] and len(tweet['entities']['media']) > 0: + # We just link to the overall URL for the tweet's media, + # rather than to each individual item. + # So, we get the URL from the first media item: + entity = tweet['entities']['media'][0] - if use_display_url and entity.get('display_url') and not use_expanded_url: - shown_url = entity['display_url'] - elif use_expanded_url and entity.get('expanded_url'): - shown_url = entity['expanded_url'] - else: - shown_url = entity['url'] + temp = {} + temp['start'] = entity['indices'][0] + temp['end'] = entity['indices'][1] - url_html = '%s' % (entity['url'], shown_url) + if use_display_url and entity.get('display_url') and not use_expanded_url: + shown_url = entity['display_url'] + elif use_expanded_url and entity.get('expanded_url'): + shown_url = entity['expanded_url'] + else: + shown_url = entity['url'] - if display_text_start <= temp['start'] <= display_text_end: - temp['replacement'] = url_html - entities.append(temp) - else: - suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) + url_html = '%s' % (entity['url'], shown_url) + + if display_text_start <= temp['start'] <= display_text_end: + temp['replacement'] = url_html + entities.append(temp) + else: + suffix_text = suffix_text.replace(orig_tweet_text[temp['start']:temp['end']], url_html) # Now do all the replacements, starting from the end, so that the # start/end indices still work: From 89755a8643805457aa874f2d7f34c3700e662609 Mon Sep 17 00:00:00 2001 From: FoxMaSk Date: Sun, 8 Oct 2017 08:57:04 +0200 Subject: [PATCH 36/80] update the link to the doc of the twitter API --- docs/usage/advanced_usage.rst | 10 +++++----- docs/usage/basic_usage.rst | 8 ++++---- docs/usage/starting_out.rst | 2 +- docs/usage/streaming_api.rst | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index 32334c0..bdb0b23 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -29,11 +29,11 @@ This uploads an image as a media object and associates it with a status update. photo = open('/path/to/file/image.jpg', 'rb') response = twitter.upload_media(media=photo) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) - + Documentation: -* https://dev.twitter.com/rest/reference/post/statuses/update -* https://dev.twitter.com/rest/reference/post/media/upload +* https://developer.twitter.com/en/docs/api-reference-index +* https://developer.twitter.com/en/docs/media/upload-media/overview Updating Status with Video -------------------------- @@ -48,8 +48,8 @@ This uploads a video as a media object and associates it with a status update. Documentation: -* https://dev.twitter.com/rest/reference/post/statuses/update -* https://dev.twitter.com/rest/reference/post/media/upload +* https://developer.twitter.com/en/docs/api-reference-index +* https://developer.twitter.com/en/docs/media/upload-media/overview Posting a Status with an Editing Image -------------------------------------- diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index 84ffb08..d7e32e3 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -28,7 +28,7 @@ Create a Twython instance with your application keys and the users OAuth tokens User Information ^^^^^^^^^^^^^^^^ -Documentation: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials +Documentation: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials .. code-block:: python @@ -37,7 +37,7 @@ Documentation: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentia Authenticated Users Home Timeline ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline +Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline .. code-block:: python @@ -48,7 +48,7 @@ Updating Status This method makes use of dynamic arguments, :ref:`read more about them ` -Documentation: https://dev.twitter.com/docs/api/1.1/post/statuses/update +Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update .. code-block:: python @@ -72,7 +72,7 @@ Searching .. note:: Searching can be done whether you're authenticated via OAuth 1 or OAuth 2 -Documentation: https://dev.twitter.com/docs/api/1.1/get/search/tweets +Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets .. code-block:: python diff --git a/docs/usage/starting_out.rst b/docs/usage/starting_out.rst index 0a681ac..7bb3d94 100644 --- a/docs/usage/starting_out.rst +++ b/docs/usage/starting_out.rst @@ -10,7 +10,7 @@ This section is going to help you understand creating a Twitter Application, aut Beginning --------- -First, you'll want to head over to https://dev.twitter.com/apps and register an application! +First, you'll want to head over to https://apps.twitter.com/ and register an application! After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. diff --git a/docs/usage/streaming_api.rst b/docs/usage/streaming_api.rst index 4d6c1c3..97ef6ea 100644 --- a/docs/usage/streaming_api.rst +++ b/docs/usage/streaming_api.rst @@ -5,7 +5,7 @@ Streaming API This section will cover how to use Twython and interact with the Twitter Streaming API. -Streaming Documentation: https://dev.twitter.com/docs/streaming-apis +Streaming Documentation: https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types .. important:: The Streaming API requires that you have OAuth 1 authentication credentials. If you don't have credentials, head over to the :ref:`authentication section ` and find out how! From 2cb2ed4a31826349ad81e39a5c035a7eed98c6b1 Mon Sep 17 00:00:00 2001 From: Jose Manuel Delicado Date: Mon, 9 Oct 2017 18:11:24 +0200 Subject: [PATCH 37/80] Twython/api.py: suggested changes in review have been made for pull request 460 --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 577d6a6..0ee942c 100644 --- a/twython/api.py +++ b/twython/api.py @@ -140,7 +140,7 @@ class Twython(EndpointsMixin, object): params = params or {} func = getattr(self.client, method) - if type(params) is dict: + if isinstance(params, dict): params, files = _transparent_params(params) else: params = params @@ -196,14 +196,14 @@ class Twython(EndpointsMixin, object): error_message, error_code=response.status_code, retry_after=response.headers.get('X-Rate-Limit-Reset')) - content="" + content = '' try: if response.status_code == 204: content = response.content else: content = response.json() except ValueError: - if response.content!="": + if response.content != '': raise TwythonError('Response was not valid JSON. \ Unable to decode.') From 13fd0a868436a1b38b7422c04da1755d94e6e1a8 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 11:57:03 +0100 Subject: [PATCH 38/80] Define encoding for tests config file --- tests/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/config.py b/tests/config.py index 07ca96d..7fc4759 100644 --- a/tests/config.py +++ b/tests/config.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os import sys From 9ade0946b58c4243759a393e35f87bd499134e5f Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:24:09 +0100 Subject: [PATCH 39/80] Add test for html_for_tweet() for quoted tweets --- tests/config.py | 5 +++++ tests/test_html_for_tweet.py | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/config.py b/tests/config.py index 7fc4759..795ba07 100644 --- a/tests/config.py +++ b/tests/config.py @@ -34,10 +34,15 @@ test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHE test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} + test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } + test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} + +test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index dc44047..6f1ffea 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,7 +5,7 @@ from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, + test_tweet_media, test_tweet_quoted, unittest ) @@ -81,3 +81,12 @@ class TestHtmlForTweetTestCase(unittest.TestCase): """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", tweet_text) + def test_quoted(self): + "With expand_quoted_status=True it should include a quoted tweet." + tweet_text = self.api.html_for_tweet(test_tweet_quoted, + expand_quoted_status=True) + + self.assertEqual( + u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", + tweet_text) + From 9ccdb48248c6ed033da60fc740fcfd8b808a94a3 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:31:57 +0100 Subject: [PATCH 40/80] Add test for html_for_tweet() for retweets --- tests/config.py | 2 ++ tests/test_html_for_tweet.py | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/config.py b/tests/config.py index 795ba07..d4fc471 100644 --- a/tests/config.py +++ b/tests/config.py @@ -46,3 +46,5 @@ test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': ' test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_retweet = {'coordinates': None, 'source': 'Tweetbot for Mac', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'has_extended_profile': False, 'following': True, 'url': None, 'profile_background_tile': False, 'geo_enabled': True, 'favourites_count': 0, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': '000000', 'verified': False, 'profile_use_background_image': True, 'name': 'Phil Gyford Test', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': True, 'friends_count': 18, 'profile_image_url': 'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'profile_image_url_https': 'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'default_profile': False, 'screen_name': 'philgyfordtest', 'profile_link_color': '0000FF', 'profile_text_color': '000000', 'id_str': '2030131', 'profile_background_color': '9AE4E8', 'followers_count': 12, 'entities': {'description': {'urls': [{'indices': [17, 40], 'url': 'https://t.co/FsYzXrATit', 'expanded_url': 'http://www.gyford.com', 'display_url': 'gyford.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 498, 'listed_count': 4, 'created_at': 'Fri Mar 23 16:56:52 +0000 2007', 'profile_sidebar_fill_color': 'E0FF92', 'id': 2030131, 'location': ''}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'Pepys\' Diary', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'has_extended_profile': False, 'following': True, 'url': 'https://t.co/b5ZyzWwQQA', 'profile_background_tile': False, 'geo_enabled': False, 'favourites_count': 1, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': 'CCCCCC', 'verified': False, 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/14475268/1432652170', 'profile_use_background_image': False, 'name': 'Samuel Pepys', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': False, 'friends_count': 14, 'profile_image_url': 'http://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'default_profile': False, 'screen_name': 'samuelpepys', 'profile_link_color': '549355', 'profile_text_color': '333333', 'id_str': '14475268', 'profile_background_color': 'F1F4EB', 'followers_count': 55980, 'entities': {'description': {'urls': []}, 'url': {'urls': [{'indices': [0, 23], 'url': 'https://t.co/b5ZyzWwQQA', 'expanded_url': 'http://www.pepysdiary.com/', 'display_url': 'pepysdiary.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'The diaries of Samuel Pepys in real time, 1660-69. Currently tweeting the events of 1664. Run by @philgyford.', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 11060, 'listed_count': 1223, 'created_at': 'Tue Apr 22 14:39:20 +0000 2008', 'profile_sidebar_fill_color': 'E8E7DC', 'id': 14475268, 'location': 'London, UK'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} + diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 6f1ffea..9ba25c6 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -5,7 +5,7 @@ from .config import ( test_tweet_object, test_tweet_html, test_tweet_symbols_object, test_tweet_compat_object, test_tweet_extended_object, test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, test_tweet_quoted, + test_tweet_media, test_tweet_quoted, test_tweet_retweet, unittest ) @@ -90,3 +90,11 @@ class TestHtmlForTweetTestCase(unittest.TestCase): u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", tweet_text) + def test_retweet(self): + "With expand_quoted_status=True it should include a quoted tweet." + tweet_text = self.api.html_for_tweet(test_tweet_retweet) + + self.assertEqual( + u"""My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.""", + tweet_text) + From 5c55aa88449a7110da1e1c4fea130d9109f303a6 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Tue, 10 Oct 2017 12:41:00 +0100 Subject: [PATCH 41/80] Cut some un-needed data out of the test tweet objects --- tests/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/config.py b/tests/config.py index d4fc471..4d76d1c 100644 --- a/tests/config.py +++ b/tests/config.py @@ -28,11 +28,12 @@ test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} + test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' -test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'location': u'', u'id_str': u'2030131', u'protected': False, u'profile_background_tile': False, u'friends_count': 18, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'entities': {u'description': {u'urls': []}}, u'lang': u'en', u'listed_count': 5, u'default_profile_image': True, u'default_profile': False, u'statuses_count': 447, u'notifications': False, u'profile_background_color': u'9AE4E8', u'profile_sidebar_fill_color': u'E0FF92', u'profile_link_color': u'0000FF', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'followers_count': 8, u'geo_enabled': True, u'following': True, u'has_extended_profile': False, u'profile_use_background_image': True, u'profile_text_color': u'000000', u'screen_name': u'philgyfordtest', u'contributors_enabled': False, u'verified': False, u'name': u'Phil Gyford Test', u'profile_sidebar_border_color': u'000000', u'utc_offset': 0, u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_5_normal.png', u'id': 2030131, u'favourites_count': 0, u'time_zone': u'London', u'url': None, u'is_translation_enabled': False, u'is_translator': False, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'description': u'', u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'follow_request_sent': False}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'Tweetbot for Mac'} +test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'web'} -test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'name': u'Twitter', u'screen_name': u'twitter'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' @@ -42,9 +43,10 @@ test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [] test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } -test_tweet_media = {'user': {'name': 'Phil Gyford', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', 'verified': False, 'screen_name': 'philgyford', 'id': 12552, 'id_str': '12552', 'protected': False}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} +test_tweet_media = {'user': {'name': 'Phil Gyford', 'screen_name': 'philgyford'}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} -test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'Tweetbot for Mac', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': False, u'default_profile_image': False, u'id': 12552, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'profile_sidebar_fill_color': u'EEEEEE', u'entities': {u'url': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [0, 23], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}, u'description': {u'urls': []}}, u'followers_count': 2615, u'profile_sidebar_border_color': u'FFFFFF', u'id_str': u'12552', u'profile_background_color': u'C0C0C0', u'listed_count': 130, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 19182, u'description': u'Revised and updated edition for 2004.', u'friends_count': 308, u'location': u'London, UK', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg', u'following': False, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyford', u'lang': u'en-gb', u'profile_background_tile': False, u'favourites_count': 2203, u'name': u'Phil Gyford', u'notifications': False, u'url': u'https://t.co/FsYzXrATit', u'created_at': u'Wed Nov 15 16:55:59 +0000 2006', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 2030131, u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png', u'verified': False, u'translator_type': u'none', u'profile_text_color': u'000000', u'profile_image_url_https': u'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'profile_sidebar_fill_color': u'E0FF92', u'entities': {u'description': {u'urls': [{u'url': u'https://t.co/FsYzXrATit', u'indices': [17, 40], u'expanded_url': u'http://www.gyford.com', u'display_url': u'gyford.com'}]}}, u'followers_count': 12, u'profile_sidebar_border_color': u'000000', u'id_str': u'2030131', u'profile_background_color': u'9AE4E8', u'listed_count': 4, u'is_translation_enabled': False, u'utc_offset': 3600, u'statuses_count': 497, u'description': u'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', u'friends_count': 18, u'location': u'', u'profile_link_color': u'0000FF', u'profile_image_url': u'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', u'following': True, u'geo_enabled': True, u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'philgyfordtest', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Phil Gyford Test', u'notifications': False, u'url': None, u'created_at': u'Fri Mar 23 16:56:52 +0000 2007', u'contributors_enabled': False, u'time_zone': u'London', u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} +test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'web', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'screen_name': u'philgyford', u'name': u'Phil Gyford'}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} + +test_tweet_retweet = {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'name': 'Phil Gyford Test', 'screen_name': 'philgyfordtest'}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'name': 'Samuel Pepys', 'screen_name': 'samuelpepys'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} -test_tweet_retweet = {'coordinates': None, 'source': 'Tweetbot for Mac', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'has_extended_profile': False, 'following': True, 'url': None, 'profile_background_tile': False, 'geo_enabled': True, 'favourites_count': 0, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': '000000', 'verified': False, 'profile_use_background_image': True, 'name': 'Phil Gyford Test', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': True, 'friends_count': 18, 'profile_image_url': 'http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'profile_image_url_https': 'https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png', 'default_profile': False, 'screen_name': 'philgyfordtest', 'profile_link_color': '0000FF', 'profile_text_color': '000000', 'id_str': '2030131', 'profile_background_color': '9AE4E8', 'followers_count': 12, 'entities': {'description': {'urls': [{'indices': [17, 40], 'url': 'https://t.co/FsYzXrATit', 'expanded_url': 'http://www.gyford.com', 'display_url': 'gyford.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'Testing #testing https://t.co/FsYzXrATit $IBM #test @philgyford', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 498, 'listed_count': 4, 'created_at': 'Fri Mar 23 16:56:52 +0000 2007', 'profile_sidebar_fill_color': 'E0FF92', 'id': 2030131, 'location': ''}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'Pepys\' Diary', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'has_extended_profile': False, 'following': True, 'url': 'https://t.co/b5ZyzWwQQA', 'profile_background_tile': False, 'geo_enabled': False, 'favourites_count': 1, 'lang': 'en', 'follow_request_sent': False, 'utc_offset': 3600, 'time_zone': 'London', 'profile_sidebar_border_color': 'CCCCCC', 'verified': False, 'profile_banner_url': 'https://pbs.twimg.com/profile_banners/14475268/1432652170', 'profile_use_background_image': False, 'name': 'Samuel Pepys', 'profile_background_image_url_https': 'https://abs.twimg.com/images/themes/theme1/bg.png', 'default_profile_image': False, 'friends_count': 14, 'profile_image_url': 'http://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'profile_image_url_https': 'https://pbs.twimg.com/profile_images/629231884304695296/VZ-9FQ28_normal.jpg', 'default_profile': False, 'screen_name': 'samuelpepys', 'profile_link_color': '549355', 'profile_text_color': '333333', 'id_str': '14475268', 'profile_background_color': 'F1F4EB', 'followers_count': 55980, 'entities': {'description': {'urls': []}, 'url': {'urls': [{'indices': [0, 23], 'url': 'https://t.co/b5ZyzWwQQA', 'expanded_url': 'http://www.pepysdiary.com/', 'display_url': 'pepysdiary.com'}]}}, 'profile_background_image_url': 'http://abs.twimg.com/images/themes/theme1/bg.png', 'protected': False, 'notifications': False, 'description': 'The diaries of Samuel Pepys in real time, 1660-69. Currently tweeting the events of 1664. Run by @philgyford.', 'is_translation_enabled': False, 'is_translator': False, 'translator_type': 'none', 'contributors_enabled': False, 'statuses_count': 11060, 'listed_count': 1223, 'created_at': 'Tue Apr 22 14:39:20 +0000 2008', 'profile_sidebar_fill_color': 'E8E7DC', 'id': 14475268, 'location': 'London, UK'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} From 5a008e7e77eec0016af938d833827332ebfaedc7 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 11 Oct 2017 18:27:53 +0100 Subject: [PATCH 42/80] Move all the raw tweets for tests into their own JSON files Seems better to have the raw data as JSON, like it comes from the API, then load it into python objects for each test. --- tests/config.py | 23 ---- tests/test_endpoints.py | 2 +- tests/test_html_for_tweet.py | 62 ++++++---- tests/tweets/basic.json | 170 +++++++++++++++++++++++++ tests/tweets/compat.json | 51 ++++++++ tests/tweets/extended.json | 204 ++++++++++++++++++++++++++++++ tests/tweets/identical_urls.json | 34 +++++ tests/tweets/media.json | 205 +++++++++++++++++++++++++++++++ tests/tweets/quoted.json | 94 ++++++++++++++ tests/tweets/reply.json | 62 ++++++++++ tests/tweets/retweet.json | 91 ++++++++++++++ tests/tweets/symbols.json | 61 +++++++++ 12 files changed, 1010 insertions(+), 49 deletions(-) create mode 100644 tests/tweets/basic.json create mode 100644 tests/tweets/compat.json create mode 100644 tests/tweets/extended.json create mode 100644 tests/tweets/identical_urls.json create mode 100644 tests/tweets/media.json create mode 100644 tests/tweets/quoted.json create mode 100644 tests/tweets/reply.json create mode 100644 tests/tweets/retweet.json create mode 100644 tests/tweets/symbols.json diff --git a/tests/config.py b/tests/config.py index 4d76d1c..8812b81 100644 --- a/tests/config.py +++ b/tests/config.py @@ -27,26 +27,3 @@ test_list_slug = os.environ.get('TEST_LIST_SLUG', 'team') test_list_owner_screen_name = os.environ.get('TEST_LIST_OWNER_SCREEN_NAME', 'twitterapi') -test_tweet_object = {u'contributors': None, u'truncated': False, u'text': u'http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71', u'in_reply_to_status_id': None, u'id': 349683012054683648, u'favorite_count': 0, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [{u'id': 29251354, u'indices': [45, 57], u'id_str': u'29251354', u'screen_name': u'mikehelmick', u'name': u'Mike Helmick'}, {u'id': 1431865928, u'indices': [81, 93], u'id_str': u'1431865928', u'screen_name': u'__twython__', u'name': u'Twython'}], u'hashtags': [{u'indices': [28, 33], u'text': u'cool'}, {u'indices': [62, 73], u'text': u'checkitout'}], u'urls': [{u'url': u'http://t.co/FCmXyI6VHd', u'indices': [0, 22], u'expanded_url': u'http://google.com', u'display_url': u'google.com'}, {u'url': u'https://t.co/67pwRvY6z9', u'indices': [94, 117], u'expanded_url': u'https://github.com', u'display_url': u'github.com'}], u'media': [{u'id': 537884378513162240, u'id_str': u'537884378513162240', u'indices': [118, 140], u'media_url': u'http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'media_url_https': u'https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg', u'url': u'http://t.co/N6InAO4B71', u'display_url': u'pic.twitter.com/N6InAO4B71', u'expanded_url': u'http://twitter.com/pingofglitch/status/537884380060844032/photo/1', u'type': u'photo', u'sizes': {u'large': {u'w': 1024, u'h': 640, u'resize': u'fit'}, u'thumb': {u'w': 150, u'h': 150, u'resize': u'crop'}, u'medium': {u'w': 600, u'h': 375, u'resize': u'fit'}, u'small': {u'w': 340, u'h': 212, u'resize': u'fit'}}}]}, u'in_reply_to_screen_name': None, u'id_str': u'349683012054683648', u'retweet_count': 0, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'profile_use_background_image': True, u'default_profile_image': True, u'id': 1431865928, u'verified': False, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'profile_sidebar_fill_color': u'DDEEF6', u'entities': {u'description': {u'urls': []}}, u'followers_count': 1, u'profile_sidebar_border_color': u'C0DEED', u'id_str': u'1431865928', u'profile_background_color': u'3D3D3D', u'listed_count': 0, u'profile_background_image_url_https': u'https://si0.twimg.com/images/themes/theme1/bg.png', u'utc_offset': None, u'statuses_count': 2, u'description': u'', u'friends_count': 1, u'location': u'', u'profile_link_color': u'0084B4', u'profile_image_url': u'http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png', u'following': False, u'geo_enabled': False, u'profile_background_image_url': u'http://a0.twimg.com/images/themes/theme1/bg.png', u'screen_name': u'__twython__', u'lang': u'en', u'profile_background_tile': False, u'favourites_count': 0, u'name': u'Twython', u'notifications': False, u'url': None, u'created_at': u'Thu May 16 01:11:09 +0000 2013', u'contributors_enabled': False, u'time_zone': None, u'protected': False, u'default_profile': False, u'is_translator': False}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'lang': u'en', u'created_at': u'Wed Jun 26 00:18:21 +0000 2013', u'in_reply_to_status_id_str': None, u'place': None} - -test_tweet_html = 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71' - -test_tweet_symbols_object = {u'text': u'Some symbols: $AAPL and $PEP and $ANOTHER and $A.', u'contributors': None, u'geo': None, u'favorited': True, u'in_reply_to_user_id_str': None, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'in_reply_to_user_id': None, u'retweeted': False, u'coordinates': None, u'place': None, u'in_reply_to_status_id': None, u'lang': u'en', u'in_reply_to_status_id_str': None, u'truncated': False, u'retweet_count': 0, u'is_quote_status': False, u'id': 662694880657989632, u'id_str': u'662694880657989632', u'in_reply_to_screen_name': None, u'favorite_count': 1, u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [{u'indices': [14, 19], u'text': u'AAPL'}, {u'indices': [24, 28], u'text': u'PEP'}, {u'indices': [46, 48], u'text': u'A'}], u'urls': []}, u'created_at': u'Fri Nov 06 18:15:46 +0000 2015', u'source': u'web'} - -test_tweet_compat_object = {u'contributors': None, u'truncated': True, u'text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/SRmsuks2ru', u'indices': [117, 140], u'expanded_url': u'https://twitter.com/i/web/status/777915304261193728', u'display_url': u'twitter.com/i/web/status/7\u2026'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'name': u'Twitter', u'screen_name': u'twitter'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None} -test_tweet_extended_object = {u'full_text': u"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", u'truncated': False, u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 777915304261193728, u'favorite_count': 13856, u'contributors': None, u'source': u'Twitter Web Client', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [], u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'id_str': u'777914712382058496', u'indices': [140, 163], u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'type': u'photo', u'id': 777914712382058496, u'display_url': u'pic.twitter.com/I9pUC0NdZC'}]}, u'in_reply_to_screen_name': None, u'id_str': u'777915304261193728', u'display_text_range': [0, 139], u'retweet_count': 14767, u'in_reply_to_user_id': None, u'favorited': False, u'user': {u'follow_request_sent': False, u'has_extended_profile': False, u'profile_use_background_image': True, u'id': 783214, u'verified': True, u'profile_text_color': u'333333', u'profile_image_url_https': u'https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'profile_sidebar_fill_color': u'F6F6F6', u'is_translator': False, u'geo_enabled': True, u'entities': {u'url': {u'urls': [{u'url': u'http://t.co/5iRhy7wTgu', u'indices': [0, 22], u'expanded_url': u'http://blog.twitter.com/', u'display_url': u'blog.twitter.com'}]}, u'description': {u'urls': [{u'url': u'https://t.co/qq1HEzvnrA', u'indices': [84, 107], u'expanded_url': u'http://support.twitter.com', u'display_url': u'support.twitter.com'}]}}, u'followers_count': 56827498, u'protected': False, u'location': u'San Francisco, CA', u'default_profile_image': False, u'id_str': u'783214', u'lang': u'en', u'utc_offset': -25200, u'statuses_count': 3161, u'description': u'Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.', u'friends_count': 145, u'profile_link_color': u'226699', u'profile_image_url': u'http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg', u'notifications': False, u'profile_background_image_url_https': u'https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'profile_background_color': u'ACDED6', u'profile_banner_url': u'https://pbs.twimg.com/profile_banners/783214/1471929200', u'profile_background_image_url': u'http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png', u'name': u'Twitter', u'is_translation_enabled': False, u'profile_background_tile': True, u'favourites_count': 2332, u'screen_name': u'twitter', u'url': u'http://t.co/5iRhy7wTgu', u'created_at': u'Tue Feb 20 14:35:54 +0000 2007', u'contributors_enabled': False, u'time_zone': u'Pacific Time (US & Canada)', u'profile_sidebar_border_color': u'FFFFFF', u'default_profile': False, u'following': False, u'listed_count': 90445}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Mon Sep 19 17:00:36 +0000 2016', u'in_reply_to_status_id_str': None, u'place': None, u'extended_entities': {u'media': [{u'expanded_url': u'https://twitter.com/twitter/status/777915304261193728/photo/1', u'display_url': u'pic.twitter.com/I9pUC0NdZC', u'url': u'https://t.co/I9pUC0NdZC', u'media_url_https': u'https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg', u'video_info': {u'aspect_ratio': [1, 1], u'variants': [{u'url': u'https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4', u'bitrate': 0, u'content_type': u'video/mp4'}]}, u'id_str': u'777914712382058496', u'sizes': {u'small': {u'h': 340, u'w': 340, u'resize': u'fit'}, u'large': {u'h': 700, u'w': 700, u'resize': u'fit'}, u'medium': {u'h': 600, u'w': 600, u'resize': u'fit'}, u'thumb': {u'h': 150, u'w': 150, u'resize': u'crop'}}, u'indices': [140, 163], u'type': u'animated_gif', u'id': 777914712382058496, u'media_url': u'http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg'}]}} - -test_tweet_extended_html = 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC' - -test_tweet_identical_urls = {u'entities': {u'hashtags': [], u'user_mentions': [], u'symbols': [], u'urls': [{u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [42, 65], u'url': u'https://t.co/W0uArTMk9N'}, {u'display_url': u'buff.ly/2sEhrgO', u'expanded_url': u'http://buff.ly/2sEhrgO', u'indices': [101, 124], u'url': u'https://t.co/W0uArTMk9N'}]}, u'full_text': u'Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N'} - -test_tweet_reply = { u'display_text_range': [12,114], u'in_reply_to_status_id_str':u'742374355531923456', u'source':u'web', u'geo':None, u'full_text':u'@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr', u'extended_entities':{ u'media':[] }, u'id_str':u'300', u'in_reply_to_status_id':742374355531923456, u'id':300, u'in_reply_to_screen_name':u'philgyford', u'retweet_count':0, u'user':{ }, u'created_at':u'Mon Jun 13 15:48:06 +0000 2016', u'lang':u'en', u'favorite_count':0, u'coordinates':None, u'place':None, u'contributors':None, u'in_reply_to_user_id':12552, u'in_reply_to_user_id_str':u'12552', u'retweeted':False, u'favorited':False, u'truncated':False, u'entities':{ u'user_mentions':[ { u'id_str':u'12552', u'id':12552, u'screen_name':u'philgyford', u'name':u'Phil Gyford', u'indices':[ 0, 11 ] } ], u'media':[ ], u'hashtags':[ ], u'symbols':[ ], u'urls':[ ] }, u'is_quote_status':False, u'possibly_sensitive':False } - - -test_tweet_media = {'user': {'name': 'Phil Gyford', 'screen_name': 'philgyford'}, 'geo': {}, 'id': 905105588279013377, 'text': 'I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg', 'entities': {'user_mentions': [], 'hashtags': [], 'media': [{'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'id': 905105571765944320, 'media_url': 'http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg', 'sizes': [{'h': 256, 'w': 680, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 376, 'w': 1000, 'resize': 'fit'}, {'h': 376, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105571765944320'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'id': 905105572529393668, 'media_url': 'http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg', 'sizes': [{'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 271, 'w': 680, 'resize': 'fit'}, {'h': 399, 'w': 1000, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 399, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105572529393668'}, {'url': 'https://t.co/OwNc6uJklg', 'display_url': 'pic.twitter.com/OwNc6uJklg', 'media_url_https': 'https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'id': 905105573255016448, 'media_url': 'http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg', 'sizes': [{'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 195, 'w': 680, 'resize': 'fit'}, {'h': 150, 'w': 150, 'resize': 'crop'}, {'h': 287, 'w': 1000, 'resize': 'fit'}, {'h': 287, 'w': 1000, 'resize': 'fit'}], 'indices': [131, 154], 'expanded_url': 'https://twitter.com/philgyford/status/905105588279013377/photo/1', 'id_str': '905105573255016448'}], 'urls': [{'display_url': 'gyford.com/phil/writing/2…', 'url': 'https://t.co/2yUmmn3TOc', 'indices': [107, 130], 'expanded_url': 'http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php'}]}, 'source': 'web', 'created_at': '2017-09-05 16:29:22 +0000', 'id_str': '905105588279013377'} - -test_tweet_quoted = {u'contributors': None, u'truncated': False, u'text': u'Here\u2019s a quoted tweet. https://t.co/3neKzof0gT', u'is_quote_status': True, u'in_reply_to_status_id': None, u'id': 917706313785905157, u'favorite_count': 0, u'source': u'web', u'quoted_status_id': 917699069916729344, u'retweeted': False, u'coordinates': None, u'quoted_status': {u'contributors': None, u'truncated': False, u'text': u'The quoted tweet text.', u'is_quote_status': False, u'in_reply_to_status_id': None, u'id': 917699069916729344, u'favorite_count': 1, u'source': u'web', u'retweeted': False, u'coordinates': None, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': []}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917699069916729344', u'favorited': False, u'user': {u'screen_name': u'philgyford', u'name': u'Phil Gyford'}, u'geo': None, u'in_reply_to_user_id_str': None, u'lang': u'ht', u'created_at': u'Tue Oct 10 10:31:22 +0000 2017', u'in_reply_to_status_id_str': None, u'place': None}, u'entities': {u'symbols': [], u'user_mentions': [], u'hashtags': [], u'urls': [{u'url': u'https://t.co/3neKzof0gT', u'indices': [23, 46], u'expanded_url': u'https://twitter.com/philgyford/status/917699069916729344', u'display_url': u'twitter.com/philgyford/sta\u2026'}]}, u'in_reply_to_screen_name': None, u'in_reply_to_user_id': None, u'retweet_count': 0, u'id_str': u'917706313785905157', u'favorited': False, u'user': {u'screen_name': u'philgyfordtest', u'name': u'Phil Gyford Test'}, u'geo': None, u'in_reply_to_user_id_str': None, u'possibly_sensitive': False, u'possibly_sensitive_appealable': False, u'lang': u'en', u'created_at': u'Tue Oct 10 11:00:10 +0000 2017', u'quoted_status_id_str': u'917699069916729344', u'in_reply_to_status_id_str': None, u'place': None} - -test_tweet_retweet = {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 0, 'user': {'name': 'Phil Gyford Test', 'screen_name': 'philgyfordtest'}, 'favorited': False, 'retweeted_status': {'coordinates': None, 'source': 'web', 'in_reply_to_user_id_str': None, 'truncated': False, 'in_reply_to_user_id': None, 'favorite_count': 21, 'user': {'name': 'Samuel Pepys', 'screen_name': 'samuelpepys'}, 'favorited': False, 'id': 917459832885653506, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917459832885653506', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Mon Oct 09 18:40:44 +0000 2017', 'is_quote_status': False, 'retweeted': False}, 'id': 917712989649801216, 'contributors': None, 'in_reply_to_screen_name': None, 'geo': None, 'in_reply_to_status_id_str': None, 'id_str': '917712989649801216', 'entities': {'hashtags': [], 'symbols': [], 'user_mentions': [{'id_str': '14475268', 'indices': [3, 15], 'name': 'Samuel Pepys', 'id': 14475268, 'screen_name': 'samuelpepys'}], 'urls': []}, 'in_reply_to_status_id': None, 'text': 'RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.', 'retweet_count': 3, 'place': None, 'lang': 'en', 'created_at': 'Tue Oct 10 11:26:41 +0000 2017', 'is_quote_status': False, 'retweeted': False} - - diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index aa79998..ecdd553 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -4,7 +4,7 @@ from .config import ( app_key, app_secret, oauth_token, oauth_token_secret, protected_twitter_1, protected_twitter_2, screen_name, test_tweet_id, test_list_slug, test_list_owner_screen_name, - access_token, test_tweet_object, test_tweet_html, unittest + access_token, unittest ) import time diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 9ba25c6..cc49121 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -1,98 +1,110 @@ # -*- coding: utf-8 -*- +import json + from twython import Twython, TwythonError -from .config import ( - test_tweet_object, test_tweet_html, test_tweet_symbols_object, - test_tweet_compat_object, test_tweet_extended_object, - test_tweet_extended_html, test_tweet_identical_urls, test_tweet_reply, - test_tweet_media, test_tweet_quoted, test_tweet_retweet, - unittest -) +from .config import unittest class TestHtmlForTweetTestCase(unittest.TestCase): def setUp(self): self.api = Twython('', '', '', '') + def load_tweet(self, name): + f = open('tests/tweets/%s.json' % name) + tweet = json.load(f) + f.close() + return tweet + def test_basic(self): """Test HTML for Tweet returns what we want""" - tweet_text = self.api.html_for_tweet(test_tweet_object) - self.assertEqual(test_tweet_html, tweet_text) + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object) + self.assertEqual(tweet_text, + 'google.com is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ github.com pic.twitter.com/N6InAO4B71') def test_reply(self): """Test HTML for Tweet links the replied-to username.""" - tweet_text = self.api.html_for_tweet(test_tweet_reply) + tweet_object = self.load_tweet('reply') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, u'@philgyford Here’s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr') def test_expanded_url(self): """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object, use_expanded_url=True) # Make sure full url is in HTML self.assertTrue('http://google.com' in tweet_text) def test_short_url(self): """Test using expanded url in HTML for Tweet displays full urls""" - tweet_text = self.api.html_for_tweet(test_tweet_object, False) + tweet_object = self.load_tweet('basic') + tweet_text = self.api.html_for_tweet(tweet_object, False) # Make sure HTML doesn't contain the display OR expanded url self.assertTrue('http://google.com' not in tweet_text) self.assertTrue('google.com' not in tweet_text) def test_identical_urls(self): """If the 'url's for different url entities are identical, they should link correctly.""" - tweet_text = self.api.html_for_tweet(test_tweet_identical_urls) + tweet_object = self.load_tweet('identical_urls') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, u'Use Cases, Trials and Making 5G a Reality buff.ly/2sEhrgO #5G #innovation via @5GWorldSeries buff.ly/2sEhrgO') def test_symbols(self): - tweet_text = self.api.html_for_tweet(test_tweet_symbols_object) + tweet_object = self.load_tweet('symbols') + tweet_text = self.api.html_for_tweet(tweet_object) # Should only link symbols listed in entities: self.assertTrue('$AAPL' in tweet_text) self.assertTrue('$ANOTHER' not in tweet_text) def test_no_symbols(self): """Should still work if tweet object has no symbols list""" - tweet = test_tweet_symbols_object + tweet = self.load_tweet('symbols') # Save a copy: symbols = tweet['entities']['symbols'] del tweet['entities']['symbols'] tweet_text = self.api.html_for_tweet(tweet) self.assertTrue('symbols: $AAPL and' in tweet_text) self.assertTrue('and $ANOTHER and $A.' in tweet_text) - # Put the symbols back: - test_tweet_symbols_object['entities']['symbols'] = symbols def test_compatmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_compat_object) + tweet_object = self.load_tweet('compat') + tweet_text = self.api.html_for_tweet(tweet_object) # link to compat web status link self.assertTrue( u'twitter.com/i/web/status/7…' in tweet_text) def test_extendedmode(self): - tweet_text = self.api.html_for_tweet(test_tweet_extended_object) + tweet_object = self.load_tweet('extended') + tweet_text = self.api.html_for_tweet(tweet_object) # full tweet rendered with suffix - self.assertEqual(test_tweet_extended_html, tweet_text) + self.assertEqual(tweet_text, + 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') def test_media(self): - tweet_text = self.api.html_for_tweet(test_tweet_media) + tweet_object = self.load_tweet('media') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual( - """I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2… pic.twitter.com/OwNc6uJklg""", + u"""I made some D3.js charts showing the years covered by books in a series compared to their publishing dates gyford.com/phil/writing/2\u2026 pic.twitter.com/OwNc6uJklg""", tweet_text) def test_quoted(self): "With expand_quoted_status=True it should include a quoted tweet." - tweet_text = self.api.html_for_tweet(test_tweet_quoted, + tweet_object = self.load_tweet('quoted') + tweet_text = self.api.html_for_tweet(tweet_object, expand_quoted_status=True) - self.assertEqual( u"""Here\u2019s a quoted tweet. twitter.com/philgyford/sta\u2026
The quoted tweet text.Phil Gyford@philgyford
""", tweet_text) def test_retweet(self): "With expand_quoted_status=True it should include a quoted tweet." - tweet_text = self.api.html_for_tweet(test_tweet_retweet) + tweet_object = self.load_tweet('retweet') + tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual( u"""My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.""", diff --git a/tests/tweets/basic.json b/tests/tweets/basic.json new file mode 100644 index 0000000..7243cd8 --- /dev/null +++ b/tests/tweets/basic.json @@ -0,0 +1,170 @@ +{ + "contributors":null, + "truncated":false, + "text":"http://t.co/FCmXyI6VHd is a #cool site, lol! @mikehelmick shd #checkitout. Love, @__twython__ https://t.co/67pwRvY6z9 http://t.co/N6InAO4B71", + "in_reply_to_status_id":null, + "id":349683012054683648, + "favorite_count":0, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + { + "id":29251354, + "indices":[ + 45, + 57 + ], + "id_str":"29251354", + "screen_name":"mikehelmick", + "name":"Mike Helmick" + }, + { + "id":1431865928, + "indices":[ + 81, + 93 + ], + "id_str":"1431865928", + "screen_name":"__twython__", + "name":"Twython" + } + ], + "hashtags":[ + { + "indices":[ + 28, + 33 + ], + "text":"cool" + }, + { + "indices":[ + 62, + 73 + ], + "text":"checkitout" + } + ], + "urls":[ + { + "url":"http://t.co/FCmXyI6VHd", + "indices":[ + 0, + 22 + ], + "expanded_url":"http://google.com", + "display_url":"google.com" + }, + { + "url":"https://t.co/67pwRvY6z9", + "indices":[ + 94, + 117 + ], + "expanded_url":"https://github.com", + "display_url":"github.com" + } + ], + "media":[ + { + "id":537884378513162240, + "id_str":"537884378513162240", + "indices":[ + 118, + 140 + ], + "media_url":"http://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", + "media_url_https":"https://pbs.twimg.com/media/B3by_g-CQAAhrO5.jpg", + "url":"http://t.co/N6InAO4B71", + "display_url":"pic.twitter.com/N6InAO4B71", + "expanded_url":"http://twitter.com/pingofglitch/status/537884380060844032/photo/1", + "type":"photo", + "sizes":{ + "large":{ + "w":1024, + "h":640, + "resize":"fit" + }, + "thumb":{ + "w":150, + "h":150, + "resize":"crop" + }, + "medium":{ + "w":600, + "h":375, + "resize":"fit" + }, + "small":{ + "w":340, + "h":212, + "resize":"fit" + } + } + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"349683012054683648", + "retweet_count":0, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "follow_request_sent":false, + "profile_use_background_image":true, + "default_profile_image":true, + "id":1431865928, + "verified":false, + "profile_text_color":"333333", + "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", + "profile_sidebar_fill_color":"DDEEF6", + "entities":{ + "description":{ + "urls":[ + + ] + } + }, + "followers_count":1, + "profile_sidebar_border_color":"C0DEED", + "id_str":"1431865928", + "profile_background_color":"3D3D3D", + "listed_count":0, + "profile_background_image_url_https":"https://si0.twimg.com/images/themes/theme1/bg.png", + "utc_offset":null, + "statuses_count":2, + "description":"", + "friends_count":1, + "location":"", + "profile_link_color":"0084B4", + "profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png", + "following":false, + "geo_enabled":false, + "profile_background_image_url":"http://a0.twimg.com/images/themes/theme1/bg.png", + "screen_name":"__twython__", + "lang":"en", + "profile_background_tile":false, + "favourites_count":0, + "name":"Twython", + "notifications":false, + "url":null, + "created_at":"Thu May 16 01:11:09 +0000 2013", + "contributors_enabled":false, + "time_zone":null, + "protected":false, + "default_profile":false, + "is_translator":false + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "lang":"en", + "created_at":"Wed Jun 26 00:18:21 +0000 2013", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/compat.json b/tests/tweets/compat.json new file mode 100644 index 0000000..b01b358 --- /dev/null +++ b/tests/tweets/compat.json @@ -0,0 +1,51 @@ +{ + "contributors":null, + "truncated":true, + "text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count tow\u2026 https://t.co/SRmsuks2ru", + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":777915304261193728, + "favorite_count":13856, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/SRmsuks2ru", + "indices":[ + 117, + 140 + ], + "expanded_url":"https://twitter.com/i/web/status/777915304261193728", + "display_url":"twitter.com/i/web/status/7\u2026" + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"777915304261193728", + "retweet_count":14767, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "name":"Twitter", + "screen_name":"twitter" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Mon Sep 19 17:00:36 +0000 2016", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/extended.json b/tests/tweets/extended.json new file mode 100644 index 0000000..105700a --- /dev/null +++ b/tests/tweets/extended.json @@ -0,0 +1,204 @@ +{ + "full_text":"Say more about what's happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. https://t.co/I9pUC0NdZC", + "truncated":false, + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":777915304261193728, + "favorite_count":13856, + "contributors":null, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + + ], + "media":[ + { + "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", + "sizes":{ + "small":{ + "h":340, + "w":340, + "resize":"fit" + }, + "large":{ + "h":700, + "w":700, + "resize":"fit" + }, + "medium":{ + "h":600, + "w":600, + "resize":"fit" + }, + "thumb":{ + "h":150, + "w":150, + "resize":"crop" + } + }, + "url":"https://t.co/I9pUC0NdZC", + "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "id_str":"777914712382058496", + "indices":[ + 140, + 163 + ], + "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "type":"photo", + "id":777914712382058496, + "display_url":"pic.twitter.com/I9pUC0NdZC" + } + ] + }, + "in_reply_to_screen_name":null, + "id_str":"777915304261193728", + "display_text_range":[ + 0, + 139 + ], + "retweet_count":14767, + "in_reply_to_user_id":null, + "favorited":false, + "user":{ + "follow_request_sent":false, + "has_extended_profile":false, + "profile_use_background_image":true, + "id":783214, + "verified":true, + "profile_text_color":"333333", + "profile_image_url_https":"https://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", + "profile_sidebar_fill_color":"F6F6F6", + "is_translator":false, + "geo_enabled":true, + "entities":{ + "url":{ + "urls":[ + { + "url":"http://t.co/5iRhy7wTgu", + "indices":[ + 0, + 22 + ], + "expanded_url":"http://blog.twitter.com/", + "display_url":"blog.twitter.com" + } + ] + }, + "description":{ + "urls":[ + { + "url":"https://t.co/qq1HEzvnrA", + "indices":[ + 84, + 107 + ], + "expanded_url":"http://support.twitter.com", + "display_url":"support.twitter.com" + } + ] + } + }, + "followers_count":56827498, + "protected":false, + "location":"San Francisco, CA", + "default_profile_image":false, + "id_str":"783214", + "lang":"en", + "utc_offset":-25200, + "statuses_count":3161, + "description":"Your official source for news, updates and tips from Twitter, Inc. Need help? Visit https://t.co/qq1HEzvnrA.", + "friends_count":145, + "profile_link_color":"226699", + "profile_image_url":"http://pbs.twimg.com/profile_images/767879603977191425/29zfZY6I_normal.jpg", + "notifications":false, + "profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", + "profile_background_color":"ACDED6", + "profile_banner_url":"https://pbs.twimg.com/profile_banners/783214/1471929200", + "profile_background_image_url":"http://pbs.twimg.com/profile_background_images/657090062/l1uqey5sy82r9ijhke1i.png", + "name":"Twitter", + "is_translation_enabled":false, + "profile_background_tile":true, + "favourites_count":2332, + "screen_name":"twitter", + "url":"http://t.co/5iRhy7wTgu", + "created_at":"Tue Feb 20 14:35:54 +0000 2007", + "contributors_enabled":false, + "time_zone":"Pacific Time (US & Canada)", + "profile_sidebar_border_color":"FFFFFF", + "default_profile":false, + "following":false, + "listed_count":90445 + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Mon Sep 19 17:00:36 +0000 2016", + "in_reply_to_status_id_str":null, + "place":null, + "extended_entities":{ + "media":[ + { + "expanded_url":"https://twitter.com/twitter/status/777915304261193728/photo/1", + "display_url":"pic.twitter.com/I9pUC0NdZC", + "url":"https://t.co/I9pUC0NdZC", + "media_url_https":"https://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg", + "video_info":{ + "aspect_ratio":[ + 1, + 1 + ], + "variants":[ + { + "url":"https://pbs.twimg.com/tweet_video/Csu1TzEVMAAAEv7.mp4", + "bitrate":0, + "content_type":"video/mp4" + } + ] + }, + "id_str":"777914712382058496", + "sizes":{ + "small":{ + "h":340, + "w":340, + "resize":"fit" + }, + "large":{ + "h":700, + "w":700, + "resize":"fit" + }, + "medium":{ + "h":600, + "w":600, + "resize":"fit" + }, + "thumb":{ + "h":150, + "w":150, + "resize":"crop" + } + }, + "indices":[ + 140, + 163 + ], + "type":"animated_gif", + "id":777914712382058496, + "media_url":"http://pbs.twimg.com/tweet_video_thumb/Csu1TzEVMAAAEv7.jpg" + } + ] + } +} diff --git a/tests/tweets/identical_urls.json b/tests/tweets/identical_urls.json new file mode 100644 index 0000000..69840f9 --- /dev/null +++ b/tests/tweets/identical_urls.json @@ -0,0 +1,34 @@ +{ + "entities":{ + "hashtags":[ + + ], + "user_mentions":[ + + ], + "symbols":[ + + ], + "urls":[ + { + "display_url":"buff.ly/2sEhrgO", + "expanded_url":"http://buff.ly/2sEhrgO", + "indices":[ + 42, + 65 + ], + "url":"https://t.co/W0uArTMk9N" + }, + { + "display_url":"buff.ly/2sEhrgO", + "expanded_url":"http://buff.ly/2sEhrgO", + "indices":[ + 101, + 124 + ], + "url":"https://t.co/W0uArTMk9N" + } + ] + }, + "full_text":"Use Cases, Trials and Making 5G a Reality https://t.co/W0uArTMk9N #5G #innovation via @5GWorldSeries https://t.co/W0uArTMk9N" +} diff --git a/tests/tweets/media.json b/tests/tweets/media.json new file mode 100644 index 0000000..4366794 --- /dev/null +++ b/tests/tweets/media.json @@ -0,0 +1,205 @@ +{ + "contributors":null, + "truncated":false, + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":905105588279013377, + "favorite_count":10, + "full_text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/2yUmmn3TOc", + "indices":[ + 107, + 130 + ], + "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", + "display_url":"gyford.com/phil/writing/2\u2026" + } + ], + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "sizes":{ + "small":{ + "h":256, + "resize":"fit", + "w":680 + }, + "large":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105571765944320, + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" + } + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "display_text_range":[ + 0, + 130 + ], + "retweet_count":1, + "id_str":"905105588279013377", + "favorited":false, + "user":{ + "screen_name":"philgyford", + "name":"Phil Gyford" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Tue Sep 05 16:29:22 +0000 2017", + "in_reply_to_status_id_str":null, + "place":null, + "extended_entities":{ + "media":[ + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", + "id_str":"905105571765944320", + "sizes":{ + "small":{ + "h":256, + "resize":"fit", + "w":680 + }, + "large":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":376, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105571765944320, + "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", + "id_str":"905105572529393668", + "sizes":{ + "large":{ + "h":399, + "resize":"fit", + "w":1000 + }, + "small":{ + "h":271, + "resize":"fit", + "w":680 + }, + "medium":{ + "h":399, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105572529393668, + "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg" + }, + { + "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", + "display_url":"pic.twitter.com/OwNc6uJklg", + "url":"https://t.co/OwNc6uJklg", + "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", + "id_str":"905105573255016448", + "sizes":{ + "small":{ + "h":195, + "resize":"fit", + "w":680 + }, + "large":{ + "h":287, + "resize":"fit", + "w":1000 + }, + "medium":{ + "h":287, + "resize":"fit", + "w":1000 + }, + "thumb":{ + "h":150, + "resize":"crop", + "w":150 + } + }, + "indices":[ + 131, + 154 + ], + "type":"photo", + "id":905105573255016448, + "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg" + } + ] + } +} diff --git a/tests/tweets/quoted.json b/tests/tweets/quoted.json new file mode 100644 index 0000000..c8e372b --- /dev/null +++ b/tests/tweets/quoted.json @@ -0,0 +1,94 @@ +{ + "contributors":null, + "truncated":false, + "text":"Here\u2019s a quoted tweet. https://t.co/3neKzof0gT", + "is_quote_status":true, + "in_reply_to_status_id":null, + "id":917706313785905157, + "favorite_count":0, + "source":"Twitter Web Client", + "quoted_status_id":917699069916729344, + "retweeted":false, + "coordinates":null, + "quoted_status":{ + "contributors":null, + "truncated":false, + "text":"The quoted tweet text.", + "is_quote_status":false, + "in_reply_to_status_id":null, + "id":917699069916729344, + "favorite_count":1, + "source":"Twitter Web Client", + "retweeted":false, + "coordinates":null, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "retweet_count":0, + "id_str":"917699069916729344", + "favorited":false, + "user":{ + "screen_name":"philgyford", + "name":"Phil Gyford" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "lang":"ht", + "created_at":"Tue Oct 10 10:31:22 +0000 2017", + "in_reply_to_status_id_str":null, + "place":null + }, + "entities":{ + "symbols":[ + + ], + "user_mentions":[ + + ], + "hashtags":[ + + ], + "urls":[ + { + "url":"https://t.co/3neKzof0gT", + "indices":[ + 23, + 46 + ], + "expanded_url":"https://twitter.com/philgyford/status/917699069916729344", + "display_url":"twitter.com/philgyford/sta\u2026" + } + ] + }, + "in_reply_to_screen_name":null, + "in_reply_to_user_id":null, + "retweet_count":0, + "id_str":"917706313785905157", + "favorited":false, + "user":{ + "screen_name":"philgyfordtest", + "name":"Phil Gyford Test" + }, + "geo":null, + "in_reply_to_user_id_str":null, + "possibly_sensitive":false, + "possibly_sensitive_appealable":false, + "lang":"en", + "created_at":"Tue Oct 10 11:00:10 +0000 2017", + "quoted_status_id_str":"917699069916729344", + "in_reply_to_status_id_str":null, + "place":null +} diff --git a/tests/tweets/reply.json b/tests/tweets/reply.json new file mode 100644 index 0000000..2a22712 --- /dev/null +++ b/tests/tweets/reply.json @@ -0,0 +1,62 @@ +{ + "display_text_range":[ + 12, + 114 + ], + "in_reply_to_status_id_str":"742374355531923456", + "source":"Twitter Web Client", + "geo":null, + "full_text":"@philgyford Here\u2019s a test tweet that goes on as much as possible and includes an image. Hi to my fans in testland! https://t.co/tzhyk2QWSr", + "extended_entities":{ + "media":[ + + ] + }, + "id_str":"300", + "in_reply_to_status_id":742374355531923456, + "id":300, + "in_reply_to_screen_name":"philgyford", + "retweet_count":0, + "user":{ + + }, + "created_at":"Mon Jun 13 15:48:06 +0000 2016", + "lang":"en", + "favorite_count":0, + "coordinates":null, + "place":null, + "contributors":null, + "in_reply_to_user_id":12552, + "in_reply_to_user_id_str":"12552", + "retweeted":false, + "favorited":false, + "truncated":false, + "entities":{ + "user_mentions":[ + { + "id_str":"12552", + "id":12552, + "screen_name":"philgyford", + "name":"Phil Gyford", + "indices":[ + 0, + 11 + ] + } + ], + "media":[ + + ], + "hashtags":[ + + ], + "symbols":[ + + ], + "urls":[ + + ] + }, + "is_quote_status":false, + "possibly_sensitive":false +} diff --git a/tests/tweets/retweet.json b/tests/tweets/retweet.json new file mode 100644 index 0000000..046bb64 --- /dev/null +++ b/tests/tweets/retweet.json @@ -0,0 +1,91 @@ +{ + "coordinates":null, + "source":"web", + "in_reply_to_user_id_str":null, + "truncated":false, + "in_reply_to_user_id":null, + "favorite_count":0, + "user":{ + "name":"Phil Gyford Test", + "screen_name":"philgyfordtest" + }, + "favorited":false, + "retweeted_status":{ + "coordinates":null, + "source":"Twitter Web Client", + "in_reply_to_user_id_str":null, + "truncated":false, + "in_reply_to_user_id":null, + "favorite_count":21, + "user":{ + "name":"Samuel Pepys", + "screen_name":"samuelpepys" + }, + "favorited":false, + "id":917459832885653506, + "contributors":null, + "in_reply_to_screen_name":null, + "geo":null, + "in_reply_to_status_id_str":null, + "id_str":"917459832885653506", + "entities":{ + "hashtags":[ + + ], + "symbols":[ + + ], + "user_mentions":[ + + ], + "urls":[ + + ] + }, + "in_reply_to_status_id":null, + "text":"My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", + "retweet_count":3, + "place":null, + "lang":"en", + "created_at":"Mon Oct 09 18:40:44 +0000 2017", + "is_quote_status":false, + "retweeted":false + }, + "id":917712989649801216, + "contributors":null, + "in_reply_to_screen_name":null, + "geo":null, + "in_reply_to_status_id_str":null, + "id_str":"917712989649801216", + "entities":{ + "hashtags":[ + + ], + "symbols":[ + + ], + "user_mentions":[ + { + "id_str":"14475268", + "indices":[ + 3, + 15 + ], + "name":"Samuel Pepys", + "id":14475268, + "screen_name":"samuelpepys" + } + ], + "urls":[ + + ] + }, + "in_reply_to_status_id":null, + "text":"RT @samuelpepys: My aunt and uncle in a very ill humour one with another, but I made shift with much ado to keep them from scolding.", + "retweet_count":3, + "place":null, + "lang":"en", + "created_at":"Tue Oct 10 11:26:41 +0000 2017", + "is_quote_status":false, + "retweeted":false +} diff --git a/tests/tweets/symbols.json b/tests/tweets/symbols.json new file mode 100644 index 0000000..7519a08 --- /dev/null +++ b/tests/tweets/symbols.json @@ -0,0 +1,61 @@ +{ + "text":"Some symbols: $AAPL and $PEP and $ANOTHER and $A.", + "contributors":null, + "geo":null, + "favorited":true, + "in_reply_to_user_id_str":null, + "user":{ + "screen_name":"philgyfordtest", + "name":"Phil Gyford Test" + }, + "in_reply_to_user_id":null, + "retweeted":false, + "coordinates":null, + "place":null, + "in_reply_to_status_id":null, + "lang":"en", + "in_reply_to_status_id_str":null, + "truncated":false, + "retweet_count":0, + "is_quote_status":false, + "id":662694880657989632, + "id_str":"662694880657989632", + "in_reply_to_screen_name":null, + "favorite_count":1, + "entities":{ + "hashtags":[ + + ], + "user_mentions":[ + + ], + "symbols":[ + { + "indices":[ + 14, + 19 + ], + "text":"AAPL" + }, + { + "indices":[ + 24, + 28 + ], + "text":"PEP" + }, + { + "indices":[ + 46, + 48 + ], + "text":"A" + } + ], + "urls":[ + + ] + }, + "created_at":"Fri Nov 06 18:15:46 +0000 2015", + "source":"Twitter Web Client" +} From d3f5361f4d58b693f013eeb07dd12b698cae5a58 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Wed, 11 Oct 2017 18:40:55 +0100 Subject: [PATCH 43/80] Try to fix loading of JSON files in tests on Travis --- tests/test_html_for_tweet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index cc49121..7331fa4 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import json +import os from twython import Twython, TwythonError @@ -11,7 +12,11 @@ class TestHtmlForTweetTestCase(unittest.TestCase): self.api = Twython('', '', '', '') def load_tweet(self, name): - f = open('tests/tweets/%s.json' % name) + f = open(os.path.join( + os.path.dirname(__file__), + 'tweets', + '%s.json' % name + )) tweet = json.load(f) f.close() return tweet From f3088b02894053485e0f3a6c46fa468032c99f43 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 11 Oct 2017 13:58:25 -0400 Subject: [PATCH 44/80] Update links to Twitter docs --- README.rst | 10 +++++----- docs/usage/basic_usage.rst | 2 +- twython/streaming/api.py | 6 +++--- twython/streaming/types.py | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 061d1b7..edddacc 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Features - Twitter lists - Timelines - Direct Messages - - and anything found in `the docs `_ + - and anything found in `the docs `_ - Image Uploading: - Update user status with an image - Change user avatar @@ -67,7 +67,7 @@ Documentation is available at https://twython.readthedocs.io/en/latest/ Starting Out ------------ -First, you'll want to head over to https://dev.twitter.com/apps and register an application! +First, you'll want to head over to https://apps.twitter.com and register an application! After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. @@ -165,7 +165,7 @@ Create a Twython instance with your application keys and the users OAuth tokens Authenticated Users Home Timeline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Documentation: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline +Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline .. code-block:: python @@ -176,7 +176,7 @@ Updating Status This method makes use of dynamic arguments, `read more about them `_ -Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update +Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update .. code-block:: python @@ -185,7 +185,7 @@ Documentation: https://dev.twitter.com/docs/api/1/post/statuses/update Searching ~~~~~~~~~ - https://dev.twitter.com/docs/api/1.1/get/search/tweets says it takes "q" and "result_type" amongst other arguments + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets says it takes "q" and "result_type" amongst other arguments .. code-block:: python diff --git a/docs/usage/basic_usage.rst b/docs/usage/basic_usage.rst index d7e32e3..8fee807 100644 --- a/docs/usage/basic_usage.rst +++ b/docs/usage/basic_usage.rst @@ -80,7 +80,7 @@ Documentation: https://developer.twitter.com/en/docs/tweets/search/api-reference .. _dynamicargexplaination: -.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" +.. important:: To help explain :ref:`dynamic function arguments ` a little more, you can see that the previous call used the keyword argument ``q``, that is because Twitter specifies in their `search documentation `_ that the search call accepts the parameter "q". You can pass mutiple keyword arguments. The search documentation also specifies that the call accepts the parameter "result_type" .. code-block:: python diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 47678e4..0195f38 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -169,9 +169,9 @@ class TwythonStreamer(object): Returns True if other handlers for this message should be invoked. Feel free to override this to handle your streaming data how you - want it handled. - See https://dev.twitter.com/docs/streaming-apis/messages for messages - sent along in stream responses. + want it handled. See + https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/streaming-message-types + for messages sent along in stream responses. :param data: data recieved from the stream :type data: dict diff --git a/twython/streaming/types.py b/twython/streaming/types.py index aa6b9ad..8755a8d 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -59,7 +59,7 @@ class TwythonStreamerTypesStatuses(object): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/post/statuses/filter + https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter """ url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version @@ -71,7 +71,7 @@ class TwythonStreamerTypesStatuses(object): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/statuses/sample + https://developer.twitter.com/en/docs/tweets/sample-realtime/api-reference/get-statuses-sample """ url = 'https://stream.twitter.com/%s/statuses/sample.json' \ % self.streamer.api_version @@ -95,7 +95,7 @@ class TwythonStreamerTypesStatuses(object): :param \*\*params: Parameters to send with your stream request Accepted params found at: - https://dev.twitter.com/docs/api/1.1/post/statuses/filter + https://developer.twitter.com/en/docs/tweets/filter-realtime/api-reference/post-statuses-filter """ self.params = params @@ -104,4 +104,4 @@ class TwythonStreamerTypesStatuses(object): url = 'https://stream.twitter.com/%s/statuses/filter.json' \ % self.streamer.api_version - self.streamer._request(url, 'POST', params=self.params) + self.streamer._request(url, 'POST', params=self.params) From 8f3db4bc8521e8cd0e567b54ac861af5c88a7703 Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 11 Oct 2017 17:51:40 -0400 Subject: [PATCH 45/80] Update endpoint docstrings to match new Twitter docs --- twython/endpoints.py | 296 ++++++++++++++++++++++++++++--------------- 1 file changed, 193 insertions(+), 103 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 1c1d80c..f3e4528 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -10,8 +10,8 @@ as a keyword argument. e.g. Twython.retweet(id=12345) -This map is organized the order functions are documented at: -https://dev.twitter.com/docs/api/1.1 +Official documentation for Twitter API endpoints can be found at: +https://developer.twitter.com/en/docs/api-reference-index """ import json @@ -34,7 +34,7 @@ class EndpointsMixin(object): @screen_name) for the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-mentions_timeline """ return self.get('statuses/mentions_timeline', params=params) @@ -44,7 +44,8 @@ class EndpointsMixin(object): """Returns a collection of the most recent Tweets posted by the user indicated by the screen_name or user_id parameters. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline + Docs: + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline """ return self.get('statuses/user_timeline', params=params) @@ -54,7 +55,8 @@ class EndpointsMixin(object): """Returns a collection of the most recent Tweets and retweets posted by the authenticating user and the users they follow. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/home_timeline + Docs: + https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline """ return self.get('statuses/home_timeline', params=params) @@ -64,7 +66,8 @@ class EndpointsMixin(object): """Returns the most recent tweets authored by the authenticating user that have been retweeted by others. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets_of_me + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweets_of_me """ return self.get('statuses/retweets_of_me', params=params) @@ -74,7 +77,8 @@ class EndpointsMixin(object): def get_retweets(self, **params): """Returns up to 100 of the first retweets of a given tweet. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id """ return self.get('statuses/retweets/%s' % params.get('id'), @@ -83,7 +87,8 @@ class EndpointsMixin(object): def show_status(self, **params): """Returns a single Tweet, specified by the id parameter - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/show/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id """ return self.get('statuses/show/%s' % params.get('id'), params=params) @@ -93,7 +98,8 @@ class EndpointsMixin(object): request, as specified by comma-separated values passed to the id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/lookup + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-lookup """ return self.post('statuses/lookup', params=params) @@ -101,7 +107,8 @@ class EndpointsMixin(object): def destroy_status(self, **params): """Destroys the status specified by the required ID parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/destroy/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id """ return self.post('statuses/destroy/%s' % params.get('id')) @@ -109,7 +116,8 @@ class EndpointsMixin(object): def update_status(self, **params): """Updates the authenticating user's current status, also known as tweeting - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/update + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update """ return self.post('statuses/update', params=params) @@ -117,7 +125,8 @@ class EndpointsMixin(object): def retweet(self, **params): """Retweets a tweet specified by the id parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/statuses/retweet/%3Aid + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id """ return self.post('statuses/retweet/%s' % params.get('id')) @@ -127,7 +136,7 @@ class EndpointsMixin(object): for upload. In other words, it creates a Tweet with a picture attached. Docs: - https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update_with_media """ warnings.warn( @@ -143,9 +152,10 @@ class EndpointsMixin(object): to the 'update_status' method using the 'media_ids' param. Docs: - https://dev.twitter.com/rest/reference/post/media/upload + https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload + """ - # https://dev.twitter.com/rest/reference/get/media/upload-status + # https://developer.twitter.com/en/docs/media/upload-media/api-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) @@ -153,7 +163,10 @@ class EndpointsMixin(object): def create_metadata(self, **params): """ Adds metadata to a media element, such as image descriptions for visually impaired. - Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + + Docs: + https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create + """ params = json.dumps(params) return self.post("https://upload.twitter.com/1.1/media/metadata/create.json", params=params) @@ -171,7 +184,8 @@ class EndpointsMixin(object): Twitter media upload api expects each chunk to be not more than 5mb. We are sending chunk of 1mb each. Docs: - https://dev.twitter.com/rest/public/uploading-media#chunkedupload + https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload + """ upload_url = 'https://upload.twitter.com/1.1/media/upload.json' if not size: @@ -216,7 +230,7 @@ class EndpointsMixin(object): response = self.post(upload_url, params=params) - # Only get the status if explicity asked to + # Only get the status if explicity asked to # Default to False if check_progress: @@ -250,7 +264,8 @@ class EndpointsMixin(object): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/oembed + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-oembed """ return self.get('statuses/oembed', params=params) @@ -259,7 +274,8 @@ class EndpointsMixin(object): """Returns a collection of up to 100 user IDs belonging to users who have retweeted the tweet specified by the id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweeters/ids + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweeters-ids """ return self.get('statuses/retweeters/ids', params=params) @@ -270,7 +286,8 @@ class EndpointsMixin(object): def search(self, **params): """Returns a collection of relevant Tweets matching a specified query. - Docs: https://dev.twitter.com/docs/api/1.1/get/search/tweets + Docs: + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets """ return self.get('search/tweets', params=params) @@ -282,7 +299,8 @@ class EndpointsMixin(object): def get_direct_messages(self, **params): """Returns the 20 most recent direct messages sent to the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-messages """ return self.get('direct_messages', params=params) @@ -291,7 +309,8 @@ class EndpointsMixin(object): def get_sent_messages(self, **params): """Returns the 20 most recent direct messages sent by the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/sent + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-sent-message """ return self.get('direct_messages/sent', params=params) @@ -300,7 +319,8 @@ class EndpointsMixin(object): def get_direct_message(self, **params): """Returns a single direct message, specified by an id parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/direct_messages/show + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message """ return self.get('direct_messages/show', params=params) @@ -308,7 +328,8 @@ class EndpointsMixin(object): def destroy_direct_message(self, **params): """Destroys the direct message specified in the required id parameter - Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/destroy + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message """ return self.post('direct_messages/destroy', params=params) @@ -317,7 +338,8 @@ class EndpointsMixin(object): """Sends a new direct message to the specified user from the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/direct_messages/new + Docs: + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message """ return self.post('direct_messages/new', params=params) @@ -328,7 +350,7 @@ class EndpointsMixin(object): user does not want to receive retweets from. Docs: - https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids """ return self.get('friendships/no_retweets/ids', params=params) @@ -337,7 +359,8 @@ class EndpointsMixin(object): """Returns a cursored collection of user IDs for every user the specified user is following (otherwise known as their "friends"). - Docs: https://dev.twitter.com/docs/api/1.1/get/friends/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids """ return self.get('friends/ids', params=params) @@ -348,7 +371,8 @@ class EndpointsMixin(object): """Returns a cursored collection of user IDs for every user following the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/followers/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids """ return self.get('followers/ids', params=params) @@ -359,7 +383,8 @@ class EndpointsMixin(object): """Returns the relationships of the authenticating user to the comma-separated list of up to 100 screen_names or user_ids provided. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/lookup + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup """ return self.get('friendships/lookup', params=params) @@ -368,7 +393,8 @@ class EndpointsMixin(object): """Returns a collection of numeric IDs for every user who has a pending request to follow the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/incoming + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming """ return self.get('friendships/incoming', params=params) @@ -379,7 +405,8 @@ class EndpointsMixin(object): """Returns a collection of numeric IDs for every protected user for whom the authenticating user has a pending follow request. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/outgoing + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing """ return self.get('friendships/outgoing', params=params) @@ -390,7 +417,8 @@ class EndpointsMixin(object): """Allows the authenticating users to follow the user specified in the ID parameter. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-create """ return self.post('friendships/create', params=params) @@ -399,7 +427,8 @@ class EndpointsMixin(object): """Allows the authenticating user to unfollow the user specified in the ID parameter. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy """ return self.post('friendships/destroy', params=params) @@ -408,7 +437,8 @@ class EndpointsMixin(object): """Allows one to enable or disable retweets and device notifications from the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/post/friendships/update + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-update """ return self.post('friendships/update', params=params) @@ -417,7 +447,8 @@ class EndpointsMixin(object): """Returns detailed information about the relationship between two arbitrary users. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-show """ return self.get('friendships/show', params=params) @@ -426,7 +457,8 @@ class EndpointsMixin(object): """Returns a cursored collection of user objects for every user the specified user is following (otherwise known as their "friends"). - Docs: https://dev.twitter.com/docs/api/1.1/get/friends/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list """ return self.get('friends/list', params=params) @@ -437,7 +469,8 @@ class EndpointsMixin(object): """Returns a cursored collection of user objects for users following the specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/followers/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list """ return self.get('followers/list', params=params) @@ -449,7 +482,8 @@ class EndpointsMixin(object): """Returns settings (including current trend, geo and sleep time information) for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/account/settings + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-settings """ return self.get('account/settings', params=params) @@ -460,7 +494,7 @@ class EndpointsMixin(object): code and an error message if not. Docs: - https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials """ return self.get('account/verify_credentials', params=params) @@ -468,7 +502,8 @@ class EndpointsMixin(object): def update_account_settings(self, **params): """Updates the authenticating user's settings. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/settings + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-settings """ return self.post('account/settings', params=params) @@ -486,7 +521,8 @@ class EndpointsMixin(object): """Sets values that users are able to set under the "Account" tab of their settings page. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile """ return self.post('account/update_profile', params=params) @@ -495,26 +531,35 @@ class EndpointsMixin(object): """Updates the authenticating user's profile background image. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_background_image """ return self.post('account/update_profile_banner', params=params) - def update_profile_colors(self, **params): + def update_profile_colors(self, **params): # pragma: no cover """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. + This method is deprecated, replaced by the profile_link_color + parameter to update_profile. + Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile """ + warnings.warn( + 'This method is deprecated. You should use the' + ' profile_link_color parameter in Twython.update_profile instead.', + TwythonDeprecationWarning, + stacklevel=2 + ) return self.post('account/update_profile_colors', params=params) def update_profile_image(self, **params): # pragma: no cover """Updates the authenticating user's profile image. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_image """ return self.post('account/update_profile_image', params=params) @@ -523,7 +568,8 @@ class EndpointsMixin(object): """Returns a collection of user objects that the authenticating user is blocking. - Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list """ return self.get('blocks/list', params=params) @@ -533,7 +579,8 @@ class EndpointsMixin(object): def list_block_ids(self, **params): """Returns an array of numeric user ids the authenticating user is blocking. - Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-ids """ return self.get('blocks/ids', params=params) @@ -543,7 +590,8 @@ class EndpointsMixin(object): def create_block(self, **params): """Blocks the specified user from following the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-create """ return self.post('blocks/create', params=params) @@ -552,7 +600,8 @@ class EndpointsMixin(object): """Un-blocks the user specified in the ID parameter for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/blocks/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-blocks-destroy """ return self.post('blocks/destroy', params=params) @@ -562,7 +611,8 @@ class EndpointsMixin(object): as specified by comma-separated values passed to the user_id and/or screen_name parameters. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/lookup + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup """ return self.get('users/lookup', params=params) @@ -571,7 +621,8 @@ class EndpointsMixin(object): """Returns a variety of information about the user specified by the required user_id or screen_name parameter. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show """ return self.get('users/show', params=params) @@ -580,7 +631,8 @@ class EndpointsMixin(object): """Provides a simple, relevance-based search interface to public user accounts on Twitter. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/search + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-search """ return self.get('users/search', params=params) @@ -606,7 +658,7 @@ class EndpointsMixin(object): Returns HTTP 200 upon success. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-remove_profile_banner """ return self.post('account/remove_profile_banner', params=params) @@ -615,7 +667,7 @@ class EndpointsMixin(object): """Uploads a profile banner on behalf of the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_banner """ return self.post('account/update_profile_background_image', @@ -625,7 +677,8 @@ class EndpointsMixin(object): """Returns a map of the available size variations of the specified user's profile banner. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/profile_banner + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-users-profile_banner """ return self.get('users/profile_banner', params=params) @@ -634,7 +687,8 @@ class EndpointsMixin(object): """Returns a collection of user objects that the authenticating user is muting. - Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-list """ return self.get('mutes/users/list', params=params) @@ -645,7 +699,8 @@ class EndpointsMixin(object): """Returns an array of numeric user ids the authenticating user is muting. - Docs: https://dev.twitter.com/docs/api/1.1/get/mutes/users/ids + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-mutes-users-ids """ return self.get('mutes/users/ids', params=params) @@ -656,7 +711,8 @@ class EndpointsMixin(object): """Mutes the specified user, preventing their tweets appearing in the authenticating user's timeline. - Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create """ return self.post('mutes/users/create', params=params) @@ -665,7 +721,8 @@ class EndpointsMixin(object): """Un-mutes the user specified in the user or ID parameter for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-destroy """ return self.post('mutes/users/destroy', params=params) @@ -675,7 +732,7 @@ class EndpointsMixin(object): """Access the users in a given category of the Twitter suggested user list. Docs: - https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug """ return self.get('users/suggestions/%s' % params.get('slug'), @@ -684,7 +741,8 @@ class EndpointsMixin(object): def get_user_suggestions(self, **params): """Access to Twitter's suggested user list. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions """ return self.get('users/suggestions', params=params) @@ -695,7 +753,7 @@ class EndpointsMixin(object): user. Docs: - https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members + https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-suggestions-slug-members """ return self.get('users/suggestions/%s/members' % params.get('slug'), @@ -706,7 +764,8 @@ class EndpointsMixin(object): """Returns the 20 most recent Tweets favorited by the authenticating or specified user. - Docs: https://dev.twitter.com/docs/api/1.1/get/favorites/list + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-favorites-list """ return self.get('favorites/list', params=params) @@ -716,7 +775,8 @@ class EndpointsMixin(object): """Un-favorites the status specified in the ID parameter as the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/destroy + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-destroy """ return self.post('favorites/destroy', params=params) @@ -725,7 +785,8 @@ class EndpointsMixin(object): """Favorites the status specified in the ID parameter as the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create + Docs: + https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-favorites-create """ return self.post('favorites/create', params=params) @@ -735,7 +796,8 @@ class EndpointsMixin(object): """Returns all lists the authenticating or specified user subscribes to, including their own. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/list + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-list """ return self.get('lists/list', params=params) @@ -743,7 +805,8 @@ class EndpointsMixin(object): def get_list_statuses(self, **params): """Returns a timeline of tweets authored by members of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/statuses + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-statuses """ return self.get('lists/statuses', params=params) @@ -752,7 +815,8 @@ class EndpointsMixin(object): def delete_list_member(self, **params): """Removes the specified member from the list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy """ return self.post('lists/members/destroy', params=params) @@ -760,7 +824,8 @@ class EndpointsMixin(object): def get_list_memberships(self, **params): """Returns the lists the specified user has been added to. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/memberships + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-memberships """ return self.get('lists/memberships', params=params) @@ -770,7 +835,8 @@ class EndpointsMixin(object): def get_list_subscribers(self, **params): """Returns the subscribers of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers """ return self.get('lists/subscribers', params=params) @@ -781,7 +847,7 @@ class EndpointsMixin(object): """Subscribes the authenticated user to the specified list. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-create """ return self.post('lists/subscribers/create', params=params) @@ -789,7 +855,8 @@ class EndpointsMixin(object): def is_list_subscriber(self, **params): """Check if the specified user is a subscriber of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscribers/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscribers-show """ return self.get('lists/subscribers/show', params=params) @@ -798,7 +865,7 @@ class EndpointsMixin(object): """Unsubscribes the authenticated user from the specified list. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy """ return self.post('lists/subscribers/destroy', params=params) @@ -808,7 +875,7 @@ class EndpointsMixin(object): list of member ids or screen names. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create_all """ return self.post('lists/members/create_all', params=params) @@ -816,7 +883,8 @@ class EndpointsMixin(object): def is_list_member(self, **params): """Check if the specified user is a member of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members-show """ return self.get('lists/members/show', params=params) @@ -824,7 +892,8 @@ class EndpointsMixin(object): def get_list_members(self, **params): """Returns the members of the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/members + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-members """ return self.get('lists/members', params=params) @@ -834,7 +903,8 @@ class EndpointsMixin(object): def add_list_member(self, **params): """Add a member to a list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-create """ return self.post('lists/members/create', params=params) @@ -842,7 +912,8 @@ class EndpointsMixin(object): def delete_list(self, **params): """Deletes the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/destroy + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-destroy """ return self.post('lists/destroy', params=params) @@ -850,7 +921,8 @@ class EndpointsMixin(object): def update_list(self, **params): """Updates the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/update + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-update """ return self.post('lists/update', params=params) @@ -858,7 +930,8 @@ class EndpointsMixin(object): def create_list(self, **params): """Creates a new list for the authenticated user. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-create """ return self.post('lists/create', params=params) @@ -866,7 +939,8 @@ class EndpointsMixin(object): def get_specific_list(self, **params): """Returns the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/show + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-show """ return self.get('lists/show', params=params) @@ -874,7 +948,8 @@ class EndpointsMixin(object): def get_list_subscriptions(self, **params): """Obtain a collection of the lists the specified user is subscribed to. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/subscriptions + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-subscriptions """ return self.get('lists/subscriptions', params=params) @@ -886,7 +961,7 @@ class EndpointsMixin(object): comma-separated list of member ids or screen names. Docs: - https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-members-destroy_all """ return self.post('lists/members/destroy_all', params=params) @@ -894,7 +969,8 @@ class EndpointsMixin(object): def show_owned_lists(self, **params): """Returns the lists owned by the specified Twitter user. - Docs: https://dev.twitter.com/docs/api/1.1/get/lists/ownerships + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships """ return self.get('lists/ownerships', params=params) @@ -905,7 +981,8 @@ class EndpointsMixin(object): def get_saved_searches(self, **params): """Returns the authenticated user's saved search queries. - Docs: https://dev.twitter.com/docs/api/1.1/get/saved_searches/list + Docs: + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-list """ return self.get('saved_searches/list', params=params) @@ -914,7 +991,7 @@ class EndpointsMixin(object): """Retrieve the information for the saved search represented by the given id. Docs: - https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid + https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-show-id """ return self.get('saved_searches/show/%s' % params.get('id'), @@ -923,7 +1000,8 @@ class EndpointsMixin(object): def create_saved_search(self, **params): """Create a new saved search for the authenticated user. - Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/create + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-mutes-users-create """ return self.post('saved_searches/create', params=params) @@ -932,7 +1010,7 @@ class EndpointsMixin(object): """Destroys a saved search for the authenticating user. Docs: - https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid + https://developer.twitter.com/en/docs/tweets/search/api-reference/post-saved_searches-destroy-id """ return self.post('saved_searches/destroy/%s' % params.get('id'), @@ -942,7 +1020,8 @@ class EndpointsMixin(object): def get_geo_info(self, **params): """Returns all the information about a known place. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/id/%3Aplace_id + Docs: + https://developer.twitter.com/en/docs/geo/place-information/api-reference/get-geo-id-place_id """ return self.get('geo/id/%s' % params.get('place_id'), params=params) @@ -951,7 +1030,8 @@ class EndpointsMixin(object): """Given a latitude and a longitude, searches for up to 20 places that can be used as a place_id when updating a status. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/reverse_geocode + Docs: + https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-reverse_geocode """ return self.get('geo/reverse_geocode', params=params) @@ -959,7 +1039,8 @@ class EndpointsMixin(object): def search_geo(self, **params): """Search for places that can be attached to a statuses/update. - Docs: https://dev.twitter.com/docs/api/1.1/get/geo/search + Docs: + https://developer.twitter.com/en/docs/geo/places-near-location/api-reference/get-geo-search """ return self.get('geo/search', params=params) @@ -985,7 +1066,8 @@ class EndpointsMixin(object): """Returns the top 10 trending topics for a specific WOEID, if trending information is available for it. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/place + Docs: + https://developer.twitter.com/en/docs/trends/trends-for-location/api-reference/get-trends-place """ return self.get('trends/place', params=params) @@ -993,7 +1075,8 @@ class EndpointsMixin(object): def get_available_trends(self, **params): """Returns the locations that Twitter has trending topic information for. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/available + Docs: + https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-available """ return self.get('trends/available', params=params) @@ -1002,7 +1085,8 @@ class EndpointsMixin(object): """Returns the locations that Twitter has trending topic information for, closest to a specified location. - Docs: https://dev.twitter.com/docs/api/1.1/get/trends/closest + Docs: + https://developer.twitter.com/en/docs/trends/locations-with-trending-topics/api-reference/get-trends-closest """ return self.get('trends/closest', params=params) @@ -1011,7 +1095,8 @@ class EndpointsMixin(object): def report_spam(self, **params): # pragma: no cover """Report the specified user as a spam account to Twitter. - Docs: https://dev.twitter.com/docs/api/1.1/post/users/report_spam + Docs: + https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/post-users-report_spam """ return self.post('users/report_spam', params=params) @@ -1021,7 +1106,8 @@ class EndpointsMixin(object): """Allows a registered application to revoke an issued OAuth 2 Bearer Token by presenting its client credentials. - Docs: https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token + Docs: + https://developer.twitter.com/en/docs/basics/authentication/api-reference/invalidate_token """ return self.post('oauth2/invalidate_token', params=params) @@ -1030,7 +1116,8 @@ class EndpointsMixin(object): def get_twitter_configuration(self, **params): """Returns the current configuration used by Twitter - Docs: https://dev.twitter.com/docs/api/1.1/get/help/configuration + Docs: + https://developer.twitter.com/en/docs/developer-utilities/configuration/api-reference/get-help-configuration """ return self.get('help/configuration', params=params) @@ -1039,7 +1126,8 @@ class EndpointsMixin(object): """Returns the list of languages supported by Twitter along with their ISO 639-1 code. - Docs: https://dev.twitter.com/docs/api/1.1/get/help/languages + Docs: + https://developer.twitter.com/en/docs/developer-utilities/supported-languages/api-reference/get-help-languages """ return self.get('help/languages', params=params) @@ -1047,7 +1135,8 @@ class EndpointsMixin(object): def get_privacy_policy(self, **params): """Returns Twitter's Privacy Policy - Docs: https://dev.twitter.com/docs/api/1.1/get/help/privacy + Docs: + https://developer.twitter.com/en/docs/developer-utilities/privacy-policy/api-reference/get-help-privacy """ return self.get('help/privacy', params=params) @@ -1055,7 +1144,8 @@ class EndpointsMixin(object): def get_tos(self, **params): """Return the Twitter Terms of Service - Docs: https://dev.twitter.com/docs/api/1.1/get/help/tos + Docs: + https://developer.twitter.com/en/docs/developer-utilities/terms-of-service/api-reference/get-help-tos """ return self.get('help/tos', params=params) @@ -1065,13 +1155,13 @@ class EndpointsMixin(object): specified resource families. Docs: - https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status + https://developer.twitter.com/en/docs/developer-utilities/rate-limit-status/api-reference/get-application-rate_limit_status """ return self.get('application/rate_limit_status', params=params) -# from https://dev.twitter.com/docs/error-codes-responses +# from https://developer.twitter.com/en/docs/ads/general/guides/response-codes TWITTER_HTTP_STATUS_CODE = { 200: ('OK', 'Success!'), 304: ('Not Modified', 'There was no new data to return.'), From 0f64978a08fe36281b1a24e27485c821e5cc4d0d Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 18 Oct 2017 14:01:34 -0400 Subject: [PATCH 46/80] Fix typo in cursor example --- docs/usage/special_functions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index cde8f99..9d40076 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -45,7 +45,7 @@ Another example: .. code-block:: python - results = twitter.cursor(t.get_mentions_timeline) + results = twitter.cursor(twitter.get_mentions_timeline) for result in results: print(result['id_str']) From db40a1c56c57e711937171e1072a7f965354440c Mon Sep 17 00:00:00 2001 From: Clayton A Davis Date: Wed, 18 Oct 2017 14:02:56 -0400 Subject: [PATCH 47/80] Add doc for cursor's return_pages kwarg --- docs/usage/special_functions.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/usage/special_functions.rst b/docs/usage/special_functions.rst index 9d40076..301abd3 100644 --- a/docs/usage/special_functions.rst +++ b/docs/usage/special_functions.rst @@ -49,6 +49,19 @@ Another example: for result in results: print(result['id_str']) +Items vs Pages +^^^^^^^^^^^^^^ + +By default, the cursor yields one item at a time. If instead you prefer to work with entire pages of results, specify ``return_pages=True`` as a keyword argument. + +.. code-block:: python + + results = twitter.cursor(twitter.get_mentions_timeline, return_pages=True) + # page is a list + for page in results: + for result in page: + print(result['id_str']) + HTML for Tweet -------------- From 05fbf6b8b261f70cd6c9f5bbcbb923983a7d836a Mon Sep 17 00:00:00 2001 From: jonathan vanasco Date: Mon, 6 Nov 2017 10:35:26 -0500 Subject: [PATCH 48/80] - Cleaned up some docstrings with Sphinx markup and param names. --- HISTORY.rst | 4 ++++ twython/endpoints.py | 46 +++++++++++++++++++------------------- twython/streaming/types.py | 7 +++--- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4249539..129f5e9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,10 @@ History ------- +Unreleased +++++++++++++++++++ +- Cleaned up some docstrings with Sphinx markup and param names. + 3.6.0 (2017-23-08) ++++++++++++++++++ - Improve replacing of entities with links in `html_for_tweet()` diff --git a/twython/endpoints.py b/twython/endpoints.py index f3e4528..00392b8 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -42,7 +42,7 @@ class EndpointsMixin(object): def get_user_timeline(self, **params): """Returns a collection of the most recent Tweets posted by the user - indicated by the screen_name or user_id parameters. + indicated by the ``screen_name`` or ``user_id`` parameters. Docs: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline @@ -85,7 +85,7 @@ class EndpointsMixin(object): params=params) def show_status(self, **params): - """Returns a single Tweet, specified by the id parameter + """Returns a single Tweet, specified by the ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id @@ -95,7 +95,7 @@ class EndpointsMixin(object): def lookup_status(self, **params): """Returns fully-hydrated tweet objects for up to 100 tweets per - request, as specified by comma-separated values passed to the id + request, as specified by comma-separated values passed to the ``id`` parameter. Docs: @@ -105,7 +105,7 @@ class EndpointsMixin(object): return self.post('statuses/lookup', params=params) def destroy_status(self, **params): - """Destroys the status specified by the required ID parameter + """Destroys the status specified by the required ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id @@ -123,7 +123,7 @@ class EndpointsMixin(object): return self.post('statuses/update', params=params) def retweet(self, **params): - """Retweets a tweet specified by the id parameter + """Retweets a tweet specified by the ``id`` parameter Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id @@ -149,7 +149,7 @@ class EndpointsMixin(object): def upload_media(self, **params): """Uploads media file to Twitter servers. 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. + to the :meth:`update_status` method using the ``media_ids`` param. Docs: https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload @@ -174,7 +174,7 @@ class EndpointsMixin(object): 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. + to the :meth:`update_status` method using the ``media_ids`` param. Upload happens in 3 stages: - INIT call with size of media to be uploaded(in bytes). If this is more than 15mb, twitter will return error. @@ -272,7 +272,7 @@ class EndpointsMixin(object): def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who - have retweeted the tweet specified by the id parameter. + have retweeted the tweet specified by the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-retweeters-ids @@ -317,7 +317,7 @@ class EndpointsMixin(object): get_sent_messages.iter_mode = 'id' def get_direct_message(self, **params): - """Returns a single direct message, specified by an id parameter. + """Returns a single direct message, specified by an ``id`` parameter. Docs: https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message @@ -326,7 +326,7 @@ class EndpointsMixin(object): return self.get('direct_messages/show', params=params) def destroy_direct_message(self, **params): - """Destroys the direct message specified in the required id parameter + """Destroys the direct message specified in the required ``id`` parameter Docs: https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message @@ -381,7 +381,7 @@ class EndpointsMixin(object): def lookup_friendships(self, **params): """Returns the relationships of the authenticating user to the - comma-separated list of up to 100 screen_names or user_ids provided. + comma-separated list of up to 100 ``screen_names`` or ``user_ids`` provided. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup @@ -415,7 +415,7 @@ class EndpointsMixin(object): def create_friendship(self, **params): """Allows the authenticating users to follow the user specified - in the ID parameter. + in the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-create @@ -425,7 +425,7 @@ class EndpointsMixin(object): def destroy_friendship(self, **params): """Allows the authenticating user to unfollow the user specified - in the ID parameter. + in the ``id`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy @@ -540,8 +540,8 @@ class EndpointsMixin(object): """Sets one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com. - This method is deprecated, replaced by the profile_link_color - parameter to update_profile. + This method is deprecated, replaced by the ``profile_link_color`` + parameter to :meth:`update_profile`. Docs: https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile @@ -597,7 +597,7 @@ class EndpointsMixin(object): return self.post('blocks/create', params=params) def destroy_block(self, **params): - """Un-blocks the user specified in the ID parameter for the + """Un-blocks the user specified in the ``id`` parameter for the authenticating user. Docs: @@ -608,8 +608,8 @@ class EndpointsMixin(object): def lookup_user(self, **params): """Returns fully-hydrated user objects for up to 100 users per request, - as specified by comma-separated values passed to the user_id and/or - screen_name parameters. + as specified by comma-separated values passed to the ``user_id`` and/or + ``screen_name`` parameters. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup @@ -619,7 +619,7 @@ class EndpointsMixin(object): def show_user(self, **params): """Returns a variety of information about the user specified by the - required user_id or screen_name parameter. + required ``user_id`` or ``screen_name`` parameter. Docs: https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show @@ -718,7 +718,7 @@ class EndpointsMixin(object): return self.post('mutes/users/create', params=params) def destroy_mute(self, **params): - """Un-mutes the user specified in the user or ID parameter for + """Un-mutes the user specified in the user or ``id`` parameter for the authenticating user. Docs: @@ -772,7 +772,7 @@ class EndpointsMixin(object): get_favorites.iter_mode = 'id' def destroy_favorite(self, **params): - """Un-favorites the status specified in the ID parameter as the + """Un-favorites the status specified in the ``id`` parameter as the authenticating user. Docs: @@ -782,7 +782,7 @@ class EndpointsMixin(object): return self.post('favorites/destroy', params=params) def create_favorite(self, **params): - """Favorites the status specified in the ID parameter as the + """Favorites the status specified in the ``id`` parameter as the authenticating user. Docs: @@ -988,7 +988,7 @@ class EndpointsMixin(object): return self.get('saved_searches/list', params=params) def show_saved_search(self, **params): - """Retrieve the information for the saved search represented by the given id. + """Retrieve the information for the saved search represented by the given ``id``. Docs: https://developer.twitter.com/en/docs/tweets/search/api-reference/get-saved_searches-show-id diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 8755a8d..81c5c07 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -44,9 +44,10 @@ class TwythonStreamerTypes(object): class TwythonStreamerTypesStatuses(object): """Class for different statuses endpoints - Available so TwythonStreamer.statuses.filter() is available. - Just a bit cleaner than TwythonStreamer.statuses_filter(), - statuses_sample(), etc. all being single methods in TwythonStreamer + Available so :meth:`TwythonStreamer.statuses.filter()` is available. + Just a bit cleaner than :meth:`TwythonStreamer.statuses_filter()`, + :meth:`statuses_sample()`, etc. all being single methods in + :class:`TwythonStreamer`. """ def __init__(self, streamer): From 9b46ca5845685d525ad92a67b9a386311dff9672 Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 6 Nov 2017 12:49:10 -0500 Subject: [PATCH 49/80] Update HISTORY.rst --- HISTORY.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 129f5e9..4087a68 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,9 +3,6 @@ History ------- -Unreleased -++++++++++++++++++ -- Cleaned up some docstrings with Sphinx markup and param names. 3.6.0 (2017-23-08) ++++++++++++++++++ From b009ed30b84cc41d624c6e68ff31ee138dc796d6 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 17:27:18 +0000 Subject: [PATCH 50/80] Removing unused file Sorry, I'm not sure how or why I added this in, but it's not needed by anything! --- tweet.json | 142 ----------------------------------------------------- 1 file changed, 142 deletions(-) delete mode 100644 tweet.json diff --git a/tweet.json b/tweet.json deleted file mode 100644 index 5e61ed8..0000000 --- a/tweet.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "source":"web", - "entities":{ - "user_mentions":[ ], - "media":[ - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "id_str":"905105571765944320", - "id":905105571765944320, - "media_url_https":"https://pbs.twimg.com/media/DI-UuNlWAAA_cvz.jpg", - "sizes":[ - { - "h":256, - "resize":"fit", - "w":680 - }, - { - "h":376, - "resize":"fit", - "w":1000 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":376, - "resize":"fit", - "w":1000 - }, - { - "h":376, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", - "id_str":"905105572529393668", - "id":905105572529393668, - "media_url_https":"https://pbs.twimg.com/media/DI-UuQbXUAQ1WlR.jpg", - "sizes":[ - { - "h":399, - "resize":"fit", - "w":1000 - }, - { - "h":271, - "resize":"fit", - "w":680 - }, - { - "h":399, - "resize":"fit", - "w":1000 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":399, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - }, - { - "expanded_url":"https://twitter.com/philgyford/status/905105588279013377/photo/1", - "indices":[ 131, 154 ], - "url":"https://t.co/OwNc6uJklg", - "media_url":"http://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", - "id_str":"905105573255016448", - "id":905105573255016448, - "media_url_https":"https://pbs.twimg.com/media/DI-UuTIXcAA-t1_.jpg", - "sizes":[ - { - "h":287, - "resize":"fit", - "w":1000 - }, - { - "h":195, - "resize":"fit", - "w":680 - }, - { - "h":150, - "resize":"crop", - "w":150 - }, - { - "h":287, - "resize":"fit", - "w":1000 - }, - { - "h":287, - "resize":"fit", - "w":1000 - } - ], - "display_url":"pic.twitter.com/OwNc6uJklg" - } - ], - "hashtags":[ ], - "urls":[ - { - "indices":[ 107, 130 ], - "url":"https://t.co/2yUmmn3TOc", - "expanded_url":"http://www.gyford.com/phil/writing/2017/09/05/book-series-charts.php", - "display_url":"gyford.com/phil/writing/2\u2026" - } - ] - }, - "geo":{ }, - "id_str":"905105588279013377", - "text":"I made some D3.js charts showing the years covered by books in a series compared to their publishing dates https://t.co/2yUmmn3TOc https://t.co/OwNc6uJklg", - "id":905105588279013377, - "created_at":"2017-09-05 16:29:22 +0000", - "user":{ - "name":"Phil Gyford", - "screen_name":"philgyford", - "protected":false, - "id_str":"12552", - "profile_image_url_https":"https://pbs.twimg.com/profile_images/1167616130/james_200208_300x300_normal.jpg", - "id":12552, - "verified":false - } -} From 73982c78f4bd541c82916c77d7aeb4ad757e9007 Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 19:03:17 +0000 Subject: [PATCH 51/80] Fix links in tweets when there's a prefix If a tweet had a prefix (@names that it was replying to) then the length of these is counted in the indices that show the locations of entities within the tweet. But we were applying those indices to the 'display' part of the tweet that doesn't include the prefix. So, if the tweet was: @bob Please meet @bill and the prefix was `@bob `, then the indices for linking `@bill` are something like `17,21`. But we were applying the link around `@bill` to the display text part of the tweet, which is: Please meet @bill And so the indices no longer lined up with `@bill`. Now they do, and the same for URLs and hashtags. --- tests/test_html_for_tweet.py | 13 +++++ tests/tweets/entities_with_prefix.json | 68 ++++++++++++++++++++++++++ twython/api.py | 14 +++--- 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/tweets/entities_with_prefix.json diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 7331fa4..934c2dc 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -89,6 +89,19 @@ class TestHtmlForTweetTestCase(unittest.TestCase): self.assertEqual(tweet_text, 'Say more about what\'s happening! Rolling out now: photos, videos, GIFs, polls, and Quote Tweets no longer count toward your 140 characters. pic.twitter.com/I9pUC0NdZC') + def test_entities_with_prefix(self): + """ + If there is a username mention at the start of a tweet it's in the + "prefix" and so isn't part of the main tweet display text. + But its length is still counted in the indices of any subsequent + mentions, urls, hashtags, etc. + """ + self.maxDiff = 2000 + tweet_object = self.load_tweet('entities_with_prefix') + tweet_text = self.api.html_for_tweet(tweet_object) + self.assertEqual(tweet_text, + '@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and 😃 for good measure AND that is longer than 140 characters. example.com') + def test_media(self): tweet_object = self.load_tweet('media') tweet_text = self.api.html_for_tweet(tweet_object) diff --git a/tests/tweets/entities_with_prefix.json b/tests/tweets/entities_with_prefix.json new file mode 100644 index 0000000..72043f9 --- /dev/null +++ b/tests/tweets/entities_with_prefix.json @@ -0,0 +1,68 @@ +{ + "created_at":"Sat Jan 06 18:56:35 +0000 2018", + "id":949716340755091458, + "id_str":"949716340755091458", + "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and \ud83d\ude03 for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", + "truncated":false, + "display_text_range":[ 12, 187 ], + "entities":{ + "hashtags":[ + { + "text":"hashtag", + "indices":[ 92, 100 ] + } + ], + "symbols":[ ], + "user_mentions":[ + { + "screen_name":"philgyford", + "name":"Phil Gyford", + "id":12552, + "id_str":"12552", + "indices":[ 0, 11 ] + }, + { + "screen_name":"visionphil", + "name":"Vision Phil", + "id":104456050, + "id_str":"104456050", + "indices":[ 31, 42 ] + } + ], + "urls":[ + { + "url":"https://t.co/sKw4J3A8SZ", + "expanded_url":"http://example.org", + "display_url":"example.org", + "indices":[ 64, 87 ] + }, + { + "url":"https://t.co/jnQdy7Zg7u", + "expanded_url":"http://example.com", + "display_url":"example.com", + "indices":[ 164, 187 ] + } + ] + }, + "source":"Tweetbot for Mac", + "in_reply_to_status_id":948561036889722880, + "in_reply_to_status_id_str":"948561036889722880", + "in_reply_to_user_id":12552, + "in_reply_to_user_id_str":"12552", + "in_reply_to_screen_name":"philgyford", + "user":{ + "id":2030131, + "id_str":"2030131" + }, + "geo":null, + "coordinates":null, + "place":null, + "contributors":null, + "is_quote_status":false, + "retweet_count":0, + "favorite_count":0, + "favorited":false, + "retweeted":false, + "possibly_sensitive":false, + "lang":"en" +} diff --git a/twython/api.py b/twython/api.py index f25cc97..57a10a6 100644 --- a/twython/api.py +++ b/twython/api.py @@ -581,6 +581,8 @@ class Twython(EndpointsMixin, object): if display_text_start <= temp['start'] <= display_text_end: temp['replacement'] = mention_html + temp['start'] -= display_text_start + temp['end'] -= display_text_start entities.append(temp) else: # Make the '@username' at the start, before @@ -592,8 +594,8 @@ class Twython(EndpointsMixin, object): if 'hashtags' in tweet['entities']: for entity in tweet['entities']['hashtags']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start url_html = '#%(hashtag)s' % {'hashtag': entity['text']} @@ -604,8 +606,8 @@ class Twython(EndpointsMixin, object): if 'symbols' in tweet['entities']: for entity in tweet['entities']['symbols']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start url_html = '$%(symbol)s' % {'symbol': entity['text']} @@ -616,8 +618,8 @@ class Twython(EndpointsMixin, object): if 'urls' in tweet['entities']: for entity in tweet['entities']['urls']: temp = {} - temp['start'] = entity['indices'][0] - temp['end'] = entity['indices'][1] + temp['start'] = entity['indices'][0] - display_text_start + temp['end'] = entity['indices'][1] - display_text_start if use_display_url and entity.get('display_url') and not use_expanded_url: shown_url = entity['display_url'] From 2cfdaaf6e44ced237edc493147c16a38a60926eb Mon Sep 17 00:00:00 2001 From: Phil Gyford Date: Sat, 6 Jan 2018 19:53:00 +0000 Subject: [PATCH 52/80] Remove an emoji from a tweet to make test work Rendering emojis in tweets in python 2 may be an entirely different issue to tackle... --- tests/test_html_for_tweet.py | 4 ++-- tests/tweets/entities_with_prefix.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_html_for_tweet.py b/tests/test_html_for_tweet.py index 934c2dc..921dfee 100644 --- a/tests/test_html_for_tweet.py +++ b/tests/test_html_for_tweet.py @@ -96,11 +96,11 @@ class TestHtmlForTweetTestCase(unittest.TestCase): But its length is still counted in the indices of any subsequent mentions, urls, hashtags, etc. """ - self.maxDiff = 2000 + self.maxDiff = 2200 tweet_object = self.load_tweet('entities_with_prefix') tweet_text = self.api.html_for_tweet(tweet_object) self.assertEqual(tweet_text, - '@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and 😃 for good measure AND that is longer than 140 characters. example.com') + u'@philgyford This is a test for @visionphil that includes a link example.org and #hashtag and X for good measure AND that is longer than 140 characters. example.com') def test_media(self): tweet_object = self.load_tweet('media') diff --git a/tests/tweets/entities_with_prefix.json b/tests/tweets/entities_with_prefix.json index 72043f9..8ba8975 100644 --- a/tests/tweets/entities_with_prefix.json +++ b/tests/tweets/entities_with_prefix.json @@ -2,7 +2,7 @@ "created_at":"Sat Jan 06 18:56:35 +0000 2018", "id":949716340755091458, "id_str":"949716340755091458", - "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and \ud83d\ude03 for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", + "full_text":"@philgyford This is a test for @visionphil that includes a link https://t.co/sKw4J3A8SZ and #hashtag and X for good measure AND that is longer than 140 characters. https://t.co/jnQdy7Zg7u", "truncated":false, "display_text_range":[ 12, 187 ], "entities":{ From c9e8a462000898dcd91b9babf130b907986591ea Mon Sep 17 00:00:00 2001 From: Mike Helmick Date: Mon, 7 May 2018 12:52:54 -0400 Subject: [PATCH 53/80] Version 3.7.0 --- HISTORY.rst | 9 +++++++++ docs/conf.py | 4 ++-- .../get_direct_messages.py | 0 setup.py | 2 +- twython/__init__.py | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) rename get_direct_message example => examples/get_direct_messages.py (100%) diff --git a/HISTORY.rst b/HISTORY.rst index 4087a68..75964ff 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ History ------- +3.7.0 (2018-07-05) +++++++++++++++++++ +- Fixes for cursoring API endpoints +- Improve `html_for_tweet()` parsing +- Documentation cleanup +- Documentation for cursor's `return_pages` keyword argument +- Update links to Twitter API in documentation +- Added `create_metadata` endpoint +- Raise error for when cursor is not provided a callable 3.6.0 (2017-23-08) ++++++++++++++++++ diff --git a/docs/conf.py b/docs/conf.py index 601d066..8f3c3d2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.6.0' +version = '3.7.0' # The full version, including alpha/beta/rc tags. -release = '3.6.0' +release = '3.7.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/get_direct_message example b/examples/get_direct_messages.py similarity index 100% rename from get_direct_message example rename to examples/get_direct_messages.py diff --git a/setup.py b/setup.py index 40b05b7..11cc2b4 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.6.0' +__version__ = '3.7.0' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index 84e9ee6..dc161d1 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.6.0' +__version__ = '3.7.0' from .api import Twython from .streaming import TwythonStreamer From 36fda7ac02a8047d11ad4de12161a6ffd1e869ea Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 22 Sep 2018 12:01:57 -0500 Subject: [PATCH 54/80] Added new endpoints for direct messages --- twython/api.py | 22 +++++++++++++++------- twython/endpoints.py | 12 ++++++------ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/twython/api.py b/twython/api.py index 4953129..10c4963 100644 --- a/twython/api.py +++ b/twython/api.py @@ -135,13 +135,13 @@ class Twython(EndpointsMixin, object): def __repr__(self): return '' % (self.app_key) - def _request(self, url, method='GET', params=None, api_call=None): + def _request(self, url, method='GET', params=None, api_call=None, json_encoded=False): """Internal request method""" method = method.lower() params = params or {} func = getattr(self.client, method) - if isinstance(params, dict): + if isinstance(params, dict) and json_encoded == False: params, files = _transparent_params(params) else: params = params @@ -156,8 +156,13 @@ class Twython(EndpointsMixin, object): if method == 'get': requests_args['params'] = params else: + # Check for json_encoded so we will sent params as "data" or "json" + if json_encoded: + data_key = "json" + else: + data_key = "data" requests_args.update({ - 'data': params, + data_key: params, 'files': files, }) try: @@ -230,7 +235,7 @@ class Twython(EndpointsMixin, object): return error_message - def request(self, endpoint, method='GET', params=None, version='1.1'): + def request(self, endpoint, method='GET', params=None, version='1.1', json_encoded=False): """Return dict of response received from Twitter's API :param endpoint: (required) Full url or Twitter API endpoint @@ -246,6 +251,9 @@ class Twython(EndpointsMixin, object): :param version: (optional) Twitter API version to access (default 1.1) :type version: string + :param json_encoded: (optional) Flag to indicate if this method should send data encoded as json + (default False) + :type json_encoded: bool :rtype: dict """ @@ -261,7 +269,7 @@ class Twython(EndpointsMixin, object): url = '%s/%s.json' % (self.api_url % version, endpoint) content = self._request(url, method=method, params=params, - api_call=url) + api_call=url, json_encoded=json_encoded) return content @@ -269,9 +277,9 @@ class Twython(EndpointsMixin, object): """Shortcut for GET requests via :class:`request`""" return self.request(endpoint, params=params, version=version) - def post(self, endpoint, params=None, version='1.1'): + def post(self, endpoint, params=None, version='1.1', json_encoded=False): """Shortcut for POST requests via :class:`request`""" - return self.request(endpoint, 'POST', params=params, version=version) + return self.request(endpoint, 'POST', params=params, version=version, json_encoded=json_encoded) def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call diff --git a/twython/endpoints.py b/twython/endpoints.py index 00392b8..f7e0a5a 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -300,10 +300,10 @@ class EndpointsMixin(object): """Returns the 20 most recent direct messages sent to the authenticating user. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-messages + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/list-events """ - return self.get('direct_messages', params=params) + return self.get('direct_messages/events/list', params=params) get_direct_messages.iter_mode = 'id' def get_sent_messages(self, **params): @@ -320,10 +320,10 @@ class EndpointsMixin(object): """Returns a single direct message, specified by an ``id`` parameter. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-event """ - return self.get('direct_messages/show', params=params) + return self.get('direct_messages/events/show', params=params) def destroy_direct_message(self, **params): """Destroys the direct message specified in the required ``id`` parameter @@ -339,10 +339,10 @@ class EndpointsMixin(object): authenticating user. Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-event """ - return self.post('direct_messages/new', params=params) + return self.post('direct_messages/events/new', params=params, json_encoded=True) # Friends & Followers def get_user_ids_of_blocked_retweets(self, **params): From 340fb4ea162b3e02fb5e245ad8651ec62d34cea5 Mon Sep 17 00:00:00 2001 From: Vishvajit Pathak Date: Sat, 29 Sep 2018 15:18:31 +0530 Subject: [PATCH 55/80] ISSUE-497 : Fixed the api end point in get_oembed_tweet --- twython/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index 00392b8..9da30db 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -268,7 +268,7 @@ class EndpointsMixin(object): https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-oembed """ - return self.get('statuses/oembed', params=params) + return self.get('oembed', params=params) def get_retweeters_ids(self, **params): """Returns a collection of up to 100 user IDs belonging to users who From a8a0777f7266f87ca1f6af921c35f573a71f45e3 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 29 Sep 2018 11:09:00 -0500 Subject: [PATCH 56/80] Added delete methods to Twython's API --- twython/api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/twython/api.py b/twython/api.py index 10c4963..96d1ae7 100644 --- a/twython/api.py +++ b/twython/api.py @@ -153,7 +153,7 @@ class Twython(EndpointsMixin, object): if k in ('timeout', 'allow_redirects', 'stream', 'verify'): requests_args[k] = v - if method == 'get': + if method == 'get' or method == 'delete': requests_args['params'] = params else: # Check for json_encoded so we will sent params as "data" or "json" @@ -242,7 +242,7 @@ class Twython(EndpointsMixin, object): (e.g. search/tweets) :type endpoint: string :param method: (optional) Method of accessing data, either - GET or POST. (default GET) + GET, POST or DELETE. (default GET) :type method: string :param params: (optional) Dict of parameters (if any) accepted the by Twitter API endpoint you are trying to @@ -281,6 +281,10 @@ class Twython(EndpointsMixin, object): """Shortcut for POST requests via :class:`request`""" return self.request(endpoint, 'POST', params=params, version=version, json_encoded=json_encoded) + def delete(self, endpoint, params=None, version='1.1', json_encoded=False): + """Shortcut for delete requests via :class:`request`""" + return self.request(endpoint, 'DELETE', params=params, version=version, json_encoded=json_encoded) + def get_lastfunction_header(self, header, default_return_value=None): """Returns a specific header from the last API call This will return None if the header is not present From 96dd5b289792fff6b5f438471bc6b2ed21f62bcc Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sat, 29 Sep 2018 11:09:39 -0500 Subject: [PATCH 57/80] Added support for delete direct_messages/events/destroy endpoint --- twython/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twython/endpoints.py b/twython/endpoints.py index f7e0a5a..9adf800 100644 --- a/twython/endpoints.py +++ b/twython/endpoints.py @@ -329,10 +329,10 @@ class EndpointsMixin(object): """Destroys the direct message specified in the required ``id`` parameter Docs: - https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message + https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message-event """ - return self.post('direct_messages/destroy', params=params) + return self.delete('direct_messages/events/destroy', params=params) def send_direct_message(self, **params): """Sends a new direct message to the specified user from the From 449807a75965a583f139df16d5e30c1e0e814613 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Sun, 30 Sep 2018 10:53:08 -0500 Subject: [PATCH 58/80] Fixed code styling --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index 96d1ae7..b0b7287 100644 --- a/twython/api.py +++ b/twython/api.py @@ -141,7 +141,7 @@ class Twython(EndpointsMixin, object): params = params or {} func = getattr(self.client, method) - if isinstance(params, dict) and json_encoded == False: + if isinstance(params, dict) and json_encoded is False: params, files = _transparent_params(params) else: params = params @@ -158,9 +158,9 @@ class Twython(EndpointsMixin, object): else: # Check for json_encoded so we will sent params as "data" or "json" if json_encoded: - data_key = "json" + data_key = 'json' else: - data_key = "data" + data_key = 'data' requests_args.update({ data_key: params, 'files': files, From 4f29fd041b960569e46cdf17e08455d40cfc7f66 Mon Sep 17 00:00:00 2001 From: Manuel Cortez Date: Fri, 16 Nov 2018 05:34:52 -0600 Subject: [PATCH 59/80] Fixed indentation --- twython/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/api.py b/twython/api.py index b0b7287..bb10db0 100644 --- a/twython/api.py +++ b/twython/api.py @@ -160,7 +160,7 @@ class Twython(EndpointsMixin, object): if json_encoded: data_key = 'json' else: - data_key = 'data' + data_key = 'data' requests_args.update({ data_key: params, 'files': files, From 02995d7e886fa1b01a1bb0ed0b759d75021835db Mon Sep 17 00:00:00 2001 From: Domenico Nappo Date: Mon, 19 Nov 2018 14:37:31 +0100 Subject: [PATCH 60/80] Adding response.headers to on_error method in twython.streaming.api #503 --- twython/streaming/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/twython/streaming/api.py b/twython/streaming/api.py index 0195f38..dd4ae89 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -125,7 +125,7 @@ class TwythonStreamer(object): self.on_timeout() else: if response.status_code != 200: - self.on_error(response.status_code, response.content) + self.on_error(response.status_code, response.content, response.headers) if self.retry_count and \ (self.retry_count - retry_counter) > 0: @@ -178,7 +178,7 @@ class TwythonStreamer(object): """ return True - def on_error(self, status_code, data): # pragma: no cover + def on_error(self, status_code, data, headers=None): # pragma: no cover """Called when stream returns non-200 status code Feel free to override this to handle your streaming data how you @@ -189,6 +189,9 @@ class TwythonStreamer(object): :param data: Error message sent from stream :type data: dict + + :param headers: Response headers sent from the stream (i.e. Retry-After) + :type headers: dict """ return From 554fba43575e61c46e6e4b583484697fccf4a172 Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Sun, 10 Mar 2019 20:01:14 +0700 Subject: [PATCH 61/80] MANIFEST.in: Add docs, examples and tests Closes https://github.com/ryanmcgrath/twython/issues/507 --- MANIFEST.in | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index eae175a..bcfd314 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,9 @@ include README.rst HISTORY.rst LICENSE requirements.txt + +recursive-include docs * +prune docs/_build + +recursive-include examples *.py + +recursive-include tests *.py +recursive-include tests/tweets *.json From a029433247c4bdd15d56e27f4463a6a8a3ad503f Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Sat, 30 Mar 2019 11:03:51 +0700 Subject: [PATCH 62/80] Add Python 3.7 support Also remove branch restriction in .travis.yml which prevents contributors from seeing builds on their forks. --- .travis.yml | 9 ++++++--- setup.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8da6c43..6ec2958 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ python: - 2.7 - 3.5 - 3.6 +# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs +matrix: + include: + - python: 3.7 + dist: xenial + sudo: true env: global: - secure: USjLDneiXlVvEjkUVqTt+LBi0XJ4QhkRcJzqVXA9gEau1NTjAkNTPmHjUbOygp0dkfoV0uWrZKCw6fL1g+HJgWl0vHeHzcNl4mUkA+OwkGFHgaeIhvUfnyyJA8P3Zm21XHC+ehzMpEFN5fVNNhREjnRj+CXMc0FgA6knwBRobu4= @@ -24,9 +30,6 @@ install: script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false -branches: - only: - - master after_success: - coveralls before_script: diff --git a/setup.py b/setup.py index 11cc2b4..3300b4d 100755 --- a/setup.py +++ b/setup.py @@ -47,5 +47,6 @@ setup( 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ] ) From d05fe7516e5a3cd09656ccce6f0a7276dff9e0c6 Mon Sep 17 00:00:00 2001 From: AnnaYasenova Date: Tue, 20 Aug 2019 12:38:20 +0300 Subject: [PATCH 63/80] Update compat.py --- twython/compat.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index 7c049b0..2ee7d37 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -9,6 +9,7 @@ Python 3 compatibility. """ import sys +import numpy as np _ver = sys.version_info @@ -29,7 +30,7 @@ if is_py2: str = unicode basestring = basestring - numeric_types = (int, long, float) + numeric_types = (int, long, float, np.int64, np.float64) elif is_py3: @@ -37,4 +38,4 @@ elif is_py3: str = str basestring = (str, bytes) - numeric_types = (int, float) + numeric_types = (int, float, np.int64, np.float64) From 1b085180ff6d9cb4e395551682c5a628545cb70c Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Fri, 28 Feb 2020 05:37:05 +1100 Subject: [PATCH 64/80] Fix simple typo: specifcally -> specifically Closes #526 --- twython/advisory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twython/advisory.py b/twython/advisory.py index 31657ee..9a0863a 100644 --- a/twython/advisory.py +++ b/twython/advisory.py @@ -17,6 +17,6 @@ only TwythonDeprecationWarnings. class TwythonDeprecationWarning(DeprecationWarning): """Custom DeprecationWarning to be raised when methods/variables are being deprecated in Twython. Python 2.7 > ignores DeprecationWarning - so we want to specifcally bubble up ONLY Twython Deprecation Warnings + so we want to specifically bubble up ONLY Twython Deprecation Warnings """ pass From 33fccac46b89262d5f41aee7d2e7a82c0891ae26 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 22:48:57 -0700 Subject: [PATCH 65/80] Fix this so Pypi publishing works again --- HISTORY.rst | 86 ++++--------- README.md | 163 +++++++++++++++++++++++ README.rst | 222 -------------------------------- examples/get_direct_messages.py | 14 +- examples/get_user_timeline.py | 2 +- setup.py | 5 +- 6 files changed, 193 insertions(+), 299 deletions(-) create mode 100644 README.md delete mode 100644 README.rst diff --git a/HISTORY.rst b/HISTORY.rst index 75964ff..fd2b8ee 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,10 +1,10 @@ -.. :changelog: +# History -History -------- +## 3.8.0 (2020-04-02) +- Bump release with latest patches from GitHub. +- Fix Direct Messages with patches from @manuelcortez. -3.7.0 (2018-07-05) -++++++++++++++++++ +## 3.7.0 (2018-07-05) - Fixes for cursoring API endpoints - Improve `html_for_tweet()` parsing - Documentation cleanup @@ -13,32 +13,27 @@ History - Added `create_metadata` endpoint - Raise error for when cursor is not provided a callable -3.6.0 (2017-23-08) -++++++++++++++++++ +## 3.6.0 (2017-23-08) - Improve replacing of entities with links in `html_for_tweet()` - Update classifiers for PyPI -3.5.0 (2017-06-06) -++++++++++++++++++ +## 3.5.0 (2017-06-06) - Added support for "symbols" in `Twython.html_for_tweet()` - Added support for extended tweets in `Twython.html_for_tweet()` - You can now check progress of video uploads to Twitter when using `Twython.upload_video()` -3.4.0 (2016-30-04) -++++++++++++++++++ +## 3.4.0 (2016-30-04) - Added `upload_video` endpoint - Fix quoted status checks in `html_for_tweet` - Fix `html_for_tweet` method response when hashtag/mention is a substring of another -3.3.0 (2015-18-07) -++++++++++++++++++ +## 3.3.0 (2015-18-07) - Added support for muting users - Fix typos in documentation - Updated documentation examples - Added dynamic filtering to streamer -3.2.0 (2014-10-30) -++++++++++++++++++ +## 3.2.0 (2014-10-30) - PEP8'd some code - Added `lookup_status` function to `endpoints.py` - Added keyword argument to `cursor` to return full pages rather than individual results @@ -51,23 +46,16 @@ History - Deprecating `update_with_media` per Twitter API 1.1 (https://dev.twitter.com/rest/reference/post/statuses/update_with_media) - Unpin `requests` and `requests-oauthlib` in `requirements.txt` - -3.1.2 (2013-12-05) -++++++++++++++++++ - +## 3.1.2 (2013-12-05) - Fixed Changelog (HISTORY.rst) -3.1.1 (2013-12-05) -++++++++++++++++++ - +## 3.1.1 (2013-12-05) - Update `requests` version to 2.1.0. - Fixed: Streaming issue where `Exceptions` in handlers or `on_success` which subclass `ValueError` would previously be caught and reported as a JSON decoding problem, and `on_error()` would be called (with status_code=200) - Fixed issue where XML was returned when bad tokens were passed to `get_authorized_tokens` - Fixed import for `setup` causing installation to fail on some devices (eg. Nokia N9/MeeGo) -3.1.0 (2013-09-25) -++++++++++++++++++ - +## 3.1.0 (2013-09-25) - Added ``html_for_tweet`` static method. This method accepts a tweet object returned from a Twitter API call and will return a string with urls, mentions and hashtags in the tweet replaced with HTML. - Pass ``client_args`` to the streaming ``__init__``, much like in core Twython (you can pass headers, timeout, hooks, proxies, etc.). - Streamer has new parameter ``handlers`` which accepts a list of strings related to functions that are apart of the Streaming class and start with "on\_". i.e. ['delete'] is passed, when 'delete' is received from a stream response; ``on_delete`` will be called. @@ -79,9 +67,7 @@ History - Fixed streaming issue where results wouldn't be returned for streams that weren't so active (See https://github.com/ryanmcgrath/twython/issues/202#issuecomment-19915708) - Streaming API now uses ``_transparent_params`` so when passed ``True`` or ``False`` or an array, etc. Twython formats it to meet Twitter parameter standards (i.e. ['ryanmcgrath', 'mikehelmick', 'twitterapi'] would convert to string 'ryanmcgrath,mikehelmick,twitterapi') -3.0.0 (2013-06-18) -++++++++++++++++++ - +## 3.0.0 (2013-06-18) - Changed ``twython/twython.py`` to ``twython/api.py`` in attempt to make structure look a little neater - Removed all camelCase function access (anything like ``getHomeTimeline`` is now ``get_home_timeline``) - Removed ``shorten_url``. With the ``requests`` library, shortening a URL on your own is simple enough @@ -100,9 +86,7 @@ History - Added ``invalidate_token`` API method which allows registed apps to revoke an access token presenting its client credentials - ``get_lastfunction_header`` now accepts a ``default_return_value`` parameter. This means that if you pass a second value (ex. ``Twython.get_lastfunction_header('x-rate-limit-remaining', 0)``) and the value is not found, it returns your default value -2.10.1 (2013-05-29) -++++++++++++++++++ - +## 2.10.1 (2013-05-29) - More test coverage! - Fix ``search_gen`` - Fixed ``get_lastfunction_header`` to actually do what its docstring says, returns ``None`` if header is not found @@ -112,9 +96,7 @@ History - No longer raise ``TwythonStreamError`` when stream line can't be decoded. Instead, sends signal to ``TwythonStreamer.on_error`` - Allow for (int, long, float) params to be passed to Twython Twitter API functions in Python 2, and (int, float) in Python 3 -2.10.0 (2013-05-21) -++++++++++++++++++ - +## 2.10.0 (2013-05-21) - Added ``get_retweeters_ids`` method - Fixed ``TwythonDeprecationWarning`` on camelCase functions if the camelCase was the same as the PEP8 function (i.e. ``Twython.retweet`` did not change) - Fixed error message bubbling when error message returned from Twitter was not an array (i.e. if you try to retweet something twice, the error is not found at index 0) @@ -125,20 +107,14 @@ History - Cleaned up ``Twython.construct_api_url``, uses "transparent" parameters (see 4th bullet in this version for explaination) - Update ``requests`` and ``requests-oauthlib`` requirements, fixing posting files AND post data together, making authenticated requests in general in Python 3.3 -2.9.1 (2013-05-04) -++++++++++++++++++ - +## 2.9.1 (2013-05-04) - "PEP8" all the functions. Switch functions from camelCase() to underscore_funcs(). (i.e. ``updateStatus()`` is now ``update_status()``) -2.9.0 (2013-05-04) -++++++++++++++++++ - +## 2.9.0 (2013-05-04) - Fixed streaming issue #144, added ``TwythonStreamer`` to aid users in a friendly streaming experience (streaming examples in ``examples`` and README's have been updated as well) - ``Twython`` now requires ``requests-oauthlib`` 0.3.1, fixes #154 (unable to upload media when sending POST data with the file) -2.8.0 (2013-04-29) -++++++++++++++++++ - +## 2.8.0 (2013-04-29) - Added a ``HISTORY.rst`` to start tracking history of changes - Updated ``twitter_endpoints.py`` to ``endpoints.py`` for cleanliness - Removed twython3k directory, no longer needed @@ -161,36 +137,24 @@ History - Twython now takes ``ssl_verify`` parameter, defaults True. Set False if you're having development server issues - Removed internal ``_media_update`` function, we could have always just used ``self.post`` -2.7.3 (2013-04-12) -++++++++++++++++++ - +## 2.7.3 (2013-04-12) - Fixed issue where Twython Exceptions were not being logged correctly -2.7.2 (2013-04-08) -++++++++++++++++++ - +## 2.7.2 (2013-04-08) - Fixed ``AttributeError`` when trying to decode the JSON response via ``Response.json()`` -2.7.1 (2013-04-08) -++++++++++++++++++ - +## 2.7.1 (2013-04-08) - Removed ``simplejson`` dependency - Fixed ``destroyDirectMessage``, ``createBlock``, ``destroyBlock`` endpoints in ``twitter_endpoints.py`` - Added ``getProfileBannerSizes`` method to ``twitter_endpoints.py`` - Made oauth_verifier argument required in ``get_authorized_tokens`` - Update ``updateProfileBannerImage`` to use v1.1 endpoint -2.7.0 (2013-04-04) -++++++++++++++++++ - +## 2.7.0 (2013-04-04) - New ``showOwnedLists`` method -2.7.0 (2013-03-31) -++++++++++++++++++ - +## 2.7.0 (2013-03-31) - Added missing slash to ``getMentionsTimeline`` in ``twitter_endpoints.py`` -2.6.0 (2013-03-29) -++++++++++++++++++ - +## 2.6.0 (2013-03-29) - Updated ``twitter_endpoints.py`` to better reflect order of API endpoints on the Twitter API v1.1 docs site diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe0aa1b --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# Twython + + + + + + +`Twython` is a Python library providing an easy way to access Twitter data. Supports Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! + +**Note**: As of Twython 3.7.0, there's a general call for maintainers put out. If you find the project useful and want to help out, reach out to Ryan with the info from the bottom of this README. Great open source project to get your feet wet with! + +## Features +- Query data for: + - User information + - Twitter lists + - Timelines + - Direct Messages + - and anything found in [the docs](https://developer.twitter.com/en/docs) +- Image Uploading: + - Update user status with an image + - Change user avatar + - Change user background image + - Change user banner image +- OAuth 2 Application Only (read-only) Support +- Support for Twitter's Streaming API +- Seamless Python 3 support! + +## Installation +Install Twython via pip: + +```bash +$ pip install twython +``` + +Or, if you want the code that is currently on GitHub + +```bash +git clone git://github.com/ryanmcgrath/twython.git +cd twython +python setup.py install +``` + +## Documentation +Documentation is available at https://twython.readthedocs.io/en/latest/ + +## Starting Out +First, you'll want to head over to https://apps.twitter.com and register an application! + +After you register, grab your applications `Consumer Key` and `Consumer Secret` from the application details tab. + +The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! + +First, you'll want to import Twython + +```python +from twython import Twython +``` + +## Obtain Authorization URL +Now, you'll want to create a Twython instance with your `Consumer Key` and `Consumer Secret`: + +- Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application +- Desktop and Mobile Applications **do not** require a callback_url + +```python +APP_KEY = 'YOUR_APP_KEY' +APP_SECRET = 'YOUR_APP_SECRET' + +twitter = Twython(APP_KEY, APP_SECRET) + +auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') +``` + +From the `auth` variable, save the `oauth_token` and `oauth_token_secret` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable + +```python +OAUTH_TOKEN = auth['oauth_token'] +OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] +``` + +Send the user to the authentication url, you can obtain it by accessing + +```python +auth['auth_url'] +``` + +## Handling the Callback +If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code + +After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in `get_authentication_tokens`. + +You'll want to extract the `oauth_verifier` from the url. + +Django example: + +```python +oauth_verifier = request.GET['oauth_verifier'] +``` + +Now that you have the `oauth_verifier` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens + +```python +twitter = Twython( + APP_KEY, APP_SECRET, + OAUTH_TOKEN, OAUTH_TOKEN_SECRET +) + +final_step = twitter.get_authorized_tokens(oauth_verifier) +``` + +Once you have the final user tokens, store them in a database for later use:: + +```python + OAUTH_TOKEN = final_step['oauth_token'] + OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] +``` + +For OAuth 2 (Application Only, read-only) authentication, see [our documentation](https://twython.readthedocs.io/en/latest/usage/starting_out.html#oauth-2-application-authentication). + +## Dynamic Function Arguments +Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. + +Basic Usage +----------- + +**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** + +Create a Twython instance with your application keys and the users OAuth tokens + +```python +from twython import Twython +twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) +``` + +## Authenticated Users Home Timeline +```python +twitter.get_home_timeline() +``` + +## Updating Status +This method makes use of dynamic arguments, [read more about them](https://twython.readthedocs.io/en/latest/usage/starting_out.html#dynamic-function-arguments). + +```python +twitter.update_status(status='See how easy using Twython is!') +``` + +## Advanced Usage +- [Advanced Twython Usage](https://twython.readthedocs.io/en/latest/usage/advanced_usage.html) +- [Streaming with Twython](https://twython.readthedocs.io/en/latest/usage/streaming_api.html) + + +## Questions, Comments, etc? +My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. + +Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. + +Follow us on Twitter: + +- [@ryanmcgrath](https://twitter.com/ryanmcgrath) +- [@mikehelmick](https://twitter.com/mikehelmick) + +## Want to help? +Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/README.rst b/README.rst deleted file mode 100644 index edddacc..0000000 --- a/README.rst +++ /dev/null @@ -1,222 +0,0 @@ -Twython -======= - - -.. image:: https://img.shields.io/pypi/v/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - -.. image:: https://img.shields.io/pypi/dw/twython.svg?style=flat-square - :target: https://pypi.python.org/pypi/twython - -.. image:: https://img.shields.io/travis/ryanmcgrath/twython.svg?style=flat-square - :target: https://travis-ci.org/ryanmcgrath/twython - -.. image:: https://img.shields.io/coveralls/ryanmcgrath/twython/master.svg?style=flat-square - :target: https://coveralls.io/r/ryanmcgrath/twython?branch=master - -``Twython`` is the premier Python library providing an easy (and up-to-date) way to access Twitter data. Actively maintained and featuring support for Python 2.6+ and Python 3. It's been battle tested by companies, educational institutions and individuals alike. Try it today! - -Features --------- - -- Query data for: - - User information - - Twitter lists - - Timelines - - Direct Messages - - and anything found in `the docs `_ -- Image Uploading: - - Update user status with an image - - Change user avatar - - Change user background image - - Change user banner image -- OAuth 2 Application Only (read-only) Support -- Support for Twitter's Streaming API -- Seamless Python 3 support! - -Installation ------------- - -Install Twython via `pip `_ - -.. code-block:: bash - - $ pip install twython - -or, with `easy_install `_ - -.. code-block:: bash - - $ easy_install twython - -But, hey... `that's up to you `_. - -Or, if you want the code that is currently on GitHub - -.. code-block:: bash - - git clone git://github.com/ryanmcgrath/twython.git - cd twython - python setup.py install - -Documentation -------------- - -Documentation is available at https://twython.readthedocs.io/en/latest/ - -Starting Out ------------- - -First, you'll want to head over to https://apps.twitter.com and register an application! - -After you register, grab your applications ``Consumer Key`` and ``Consumer Secret`` from the application details tab. - -The most common type of authentication is Twitter user authentication using OAuth 1. If you're a web app planning to have users sign up with their Twitter account and interact with their timelines, updating their status, and stuff like that this **is** the authentication for you! - -First, you'll want to import Twython - -.. code-block:: python - - from twython import Twython - -Authentication -~~~~~~~~~~~~~~ - -Obtain Authorization URL -^^^^^^^^^^^^^^^^^^^^^^^^ - -Now, you'll want to create a Twython instance with your ``Consumer Key`` and ``Consumer Secret`` - - Only pass *callback_url* to *get_authentication_tokens* if your application is a Web Application - - Desktop and Mobile Applications **do not** require a callback_url - -.. code-block:: python - - APP_KEY = 'YOUR_APP_KEY' - APP_SECRET = 'YOUR_APP_SECRET' - - twitter = Twython(APP_KEY, APP_SECRET) - - auth = twitter.get_authentication_tokens(callback_url='http://mysite.com/callback') - -From the ``auth`` variable, save the ``oauth_token`` and ``oauth_token_secret`` for later use (these are not the final auth tokens). In Django or other web frameworks, you might want to store it to a session variable - -.. code-block:: python - - OAUTH_TOKEN = auth['oauth_token'] - OAUTH_TOKEN_SECRET = auth['oauth_token_secret'] - -Send the user to the authentication url, you can obtain it by accessing - -.. code-block:: python - - auth['auth_url'] - -Handling the Callback -^^^^^^^^^^^^^^^^^^^^^ - - If your application is a Desktop or Mobile Application *oauth_verifier* will be the PIN code - -After they authorize your application to access some of their account details, they'll be redirected to the callback url you specified in ``get_authentication_tokens`` - -You'll want to extract the ``oauth_verifier`` from the url. - -Django example: - -.. code-block:: python - - oauth_verifier = request.GET['oauth_verifier'] - -Now that you have the ``oauth_verifier`` stored to a variable, you'll want to create a new instance of Twython and grab the final user tokens - -.. code-block:: python - - twitter = Twython(APP_KEY, APP_SECRET, - OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - - final_step = twitter.get_authorized_tokens(oauth_verifier) - -Once you have the final user tokens, store them in a database for later use:: - - OAUTH_TOKEN = final_step['oauth_token'] - OAUTH_TOKEN_SECRET = final_step['oauth_token_secret'] - -For OAuth 2 (Application Only, read-only) authentication, see `our documentation `_ - -Dynamic Function Arguments -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Keyword arguments to functions are mapped to the functions available for each endpoint in the Twitter API docs. Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up from you using them by this library. - -Basic Usage ------------ - -**Function definitions (i.e. get_home_timeline()) can be found by reading over twython/endpoints.py** - -Create a Twython instance with your application keys and the users OAuth tokens - -.. code-block:: python - - from twython import Twython - twitter = Twython(APP_KEY, APP_SECRET, - OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - -Authenticated Users Home Timeline -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Documentation: https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline - -.. code-block:: python - - twitter.get_home_timeline() - -Updating Status -~~~~~~~~~~~~~~~ - -This method makes use of dynamic arguments, `read more about them `_ - -Documentation: https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update - -.. code-block:: python - - twitter.update_status(status='See how easy using Twython is!') - -Searching -~~~~~~~~~ - - https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets says it takes "q" and "result_type" amongst other arguments - -.. code-block:: python - - twitter.search(q='twitter') - twitter.search(q='twitter', result_type='popular') - -Advanced Usage --------------- - -- `Advanced Twython Usage `_ -- `Streaming with Twython `_ - - -Notes ------ - -- Twython 3.0.0 has been injected with 1000mgs of pure awesomeness! OAuth 2 application authentication is now supported. And a *whole lot* more! See the `CHANGELOG `_ for more details! - -Questions, Comments, etc? -------------------------- - -My hope is that Twython is so simple that you'd never *have* to ask any questions, but if you feel the need to contact me for this (or other) reasons, you can hit me up at ryan@venodesigns.net. - -Or if I'm to busy to answer, feel free to ping mikeh@ydekproductions.com as well. - -Follow us on Twitter: - -- `@ryanmcgrath `_ -- `@mikehelmick `_ - -Want to help? -------------- - -Twython is useful, but ultimately only as useful as the people using it (say that ten times fast!). If you'd like to help, write example code, contribute patches, document things on the wiki, tweet about it. Your help is always appreciated! diff --git a/examples/get_direct_messages.py b/examples/get_direct_messages.py index 5a27a06..fa80865 100644 --- a/examples/get_direct_messages.py +++ b/examples/get_direct_messages.py @@ -1,17 +1,5 @@ from twython import Twython, TwythonError twitter = Twython(APP_KEY, APP_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) - get_list = twitter.get_direct_messages() -#Returns All Twitter DM information which is a lot in a list format - -dm_dict = get_list[0] -#Sets get_list to a dictionary, the number in the list is the direct message retrieved -#That means that 0 is the most recent and n-1 is the last DM revieved. -#You can cycle through all the numbers and it will return the text and the sender id of each - -print dm_dict['text'] -#Gets the text from the dictionary - -print dm_dict['sender']['id'] -#Gets the ID of the sender +print(get_list) diff --git a/examples/get_user_timeline.py b/examples/get_user_timeline.py index 0a1f4df..55ab427 100644 --- a/examples/get_user_timeline.py +++ b/examples/get_user_timeline.py @@ -7,4 +7,4 @@ try: except TwythonError as e: print e -print user_timeline +print(user_timeline) diff --git a/setup.py b/setup.py index 11cc2b4..0531221 100755 --- a/setup.py +++ b/setup.py @@ -31,8 +31,9 @@ setup( keywords='twitter search api tweet twython stream', description='Actively maintained, pure Python wrapper for the \ Twitter API. Supports both normal and streaming Twitter APIs', - long_description=open('README.rst').read() + '\n\n' + - open('HISTORY.rst').read(), + long_description=open('README.md').read() + '\n\n' + + open('HISTORY.md').read(), + long_description_content_type='text/markdown', include_package_data=True, packages=packages, classifiers=[ From 7ce058e6fd3a7307e4ac88e855257a9e99cc7163 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 22:58:15 -0700 Subject: [PATCH 66/80] Resolve issues with Pypi publishing (RST -> MD) and bump to 3.8.0 --- HISTORY.rst => HISTORY.md | 0 setup.py | 12 +++++------- 2 files changed, 5 insertions(+), 7 deletions(-) rename HISTORY.rst => HISTORY.md (100%) diff --git a/HISTORY.rst b/HISTORY.md similarity index 100% rename from HISTORY.rst rename to HISTORY.md diff --git a/setup.py b/setup.py index 0531221..0c0e758 100755 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ try: except ImportError: from distutils.core import setup -__author__ = 'Ryan McGrath ' -__version__ = '3.7.0' +__author__ = 'Ryan McGrath ' +__version__ = '3.8.0' packages = [ 'twython', @@ -26,13 +26,11 @@ setup( install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], author='Ryan McGrath', author_email='ryan@venodesigns.net', - license=open('LICENSE').read(), + license='MIT', url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', - description='Actively maintained, pure Python wrapper for the \ - Twitter API. Supports both normal and streaming Twitter APIs', - long_description=open('README.md').read() + '\n\n' + - open('HISTORY.md').read(), + description='Actively maintained, pure Python wrapper for the Twitter API. Supports both normal and streaming Twitter APIs', + long_description=open('README.md', encoding='utf-8').read() + '\n\n' +open('HISTORY.md', encoding='utf-8').read(), long_description_content_type='text/markdown', include_package_data=True, packages=packages, From ea2979c75f8f771a70617e607b8398809dba8dac Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:02:01 -0700 Subject: [PATCH 67/80] Remove this merge as numpy shouldn't be a dependency --- twython/compat.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/twython/compat.py b/twython/compat.py index 2ee7d37..7c049b0 100644 --- a/twython/compat.py +++ b/twython/compat.py @@ -9,7 +9,6 @@ Python 3 compatibility. """ import sys -import numpy as np _ver = sys.version_info @@ -30,7 +29,7 @@ if is_py2: str = unicode basestring = basestring - numeric_types = (int, long, float, np.int64, np.float64) + numeric_types = (int, long, float) elif is_py3: @@ -38,4 +37,4 @@ elif is_py3: str = str basestring = (str, bytes) - numeric_types = (int, float, np.int64, np.float64) + numeric_types = (int, float) From b8d927df8eeff88717f3b7547461b637de87caf7 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:08:52 -0700 Subject: [PATCH 68/80] Kill 2.6/2.7 --- .travis.yml | 3 --- tests/config.py | 5 +---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6ec2958..231b15c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: python python: - - 2.6 - - 2.7 - 3.5 - 3.6 # Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs @@ -26,7 +24,6 @@ env: - ACCESS_TOKEN_B64=U2FsdGVkX18QdBhvMNshM4PGy04tU3HLwKP+nNSoNZHKsvGLjELcWEXN2LIu/T+yngX1vGONf9lo14ElnfS4k7sfhiru8phR4+rZuBVP3bDvC2A6fXJuhuLqNhBrWqg32WQewvxLWDWBoKmnvRHg5b74GHh+IN/12tU0cBF2HK8= install: - pip install -r requirements.txt - - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2; fi script: nosetests -v -w tests/ --logging-filter="twython" --with-cov --cov twython --cov-config .coveragerc --cov-report term-missing notifications: email: false diff --git a/tests/config.py b/tests/config.py index 8812b81..10ab3ea 100644 --- a/tests/config.py +++ b/tests/config.py @@ -2,10 +2,7 @@ import os import sys -if sys.version_info[0] == 2 and sys.version_info[1] == 6: - import unittest2 as unittest -else: - import unittest +import unittest app_key = os.environ.get('APP_KEY') app_secret = os.environ.get('APP_SECRET') From cbfec150dfffb63abae8c0e6edd137d02a0fe417 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:28:35 -0700 Subject: [PATCH 69/80] Close #486 - given how long ago this was deprecated I'll assume it's not even used --- twython/streaming/types.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 81c5c07..27c9ea8 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -20,26 +20,6 @@ class TwythonStreamerTypes(object): self.streamer = streamer self.statuses = TwythonStreamerTypesStatuses(streamer) - def user(self, **params): - """Stream user - - Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/user - """ - url = 'https://userstream.twitter.com/%s/user.json' \ - % self.streamer.api_version - self.streamer._request(url, params=params) - - def site(self, **params): - """Stream site - - Accepted params found at: - https://dev.twitter.com/docs/api/1.1/get/site - """ - url = 'https://sitestream.twitter.com/%s/site.json' \ - % self.streamer.api_version - self.streamer._request(url, params=params) - class TwythonStreamerTypesStatuses(object): """Class for different statuses endpoints From 74c72f88fddf2623e23601785d8a80c1152989eb Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 2 Apr 2020 23:39:07 -0700 Subject: [PATCH 70/80] Remove old links in docs --- docs/_themes/basicstrap/customsidebar.html | 15 +-------------- docs/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/docs/_themes/basicstrap/customsidebar.html b/docs/_themes/basicstrap/customsidebar.html index ac8c583..dbeab99 100644 --- a/docs/_themes/basicstrap/customsidebar.html +++ b/docs/_themes/basicstrap/customsidebar.html @@ -1,18 +1,5 @@ -

{{ _('Donate') }}

- -

-Find Twython useful? Consider supporting the author on Gittip: -

- -

- -

-

{{ _('Links') }}

\ No newline at end of file + diff --git a/docs/conf.py b/docs/conf.py index 8f3c3d2..3ffa5b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2013, Ryan McGrath' # built documents. # # The short X.Y version. -version = '3.7.0' +version = '3.8.0' # The full version, including alpha/beta/rc tags. -release = '3.7.0' +release = '3.8.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From ba1110d4b8c6c385bf59fe5318b4aa4726ca00f7 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 3 Apr 2020 02:22:17 -0700 Subject: [PATCH 71/80] Update Manifest to fix installation --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index bcfd314..79f570a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include README.rst HISTORY.rst LICENSE requirements.txt +include README.md HISTORY.md LICENSE requirements.txt recursive-include docs * prune docs/_build From e6b5364d28ba3e7f3688bcdbdb8441e2d33d3ecf Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 3 Apr 2020 02:22:33 -0700 Subject: [PATCH 72/80] Bump version for patching manifest --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bff13d7..ff6dee8 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.0' +__version__ = '3.8.1' packages = [ 'twython', From 02fb35651dd253254ff22c51c591d1bd0c9cf807 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sat, 4 Apr 2020 16:21:59 -0700 Subject: [PATCH 73/80] Fixes #530, bump version to 3.8.2 --- setup.py | 2 +- twython/streaming/api.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ff6dee8..3742f24 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.1' +__version__ = '3.8.2' packages = [ 'twython', diff --git a/twython/streaming/api.py b/twython/streaming/api.py index dd4ae89..6073abb 100644 --- a/twython/streaming/api.py +++ b/twython/streaming/api.py @@ -86,8 +86,6 @@ class TwythonStreamer(object): # Set up type methods StreamTypes = TwythonStreamerTypes(self) self.statuses = StreamTypes.statuses - self.user = StreamTypes.user - self.site = StreamTypes.site self.connected = False From 233b20a71057c98cba6f745ecfeada47c76bb67d Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Sun, 12 Apr 2020 05:25:01 +0000 Subject: [PATCH 74/80] Fix deprecation warning regarding invalid escape sequences. --- twython/api.py | 4 ++-- twython/streaming/types.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/twython/api.py b/twython/api.py index bb10db0..76b6f28 100644 --- a/twython/api.py +++ b/twython/api.py @@ -430,7 +430,7 @@ class Twython(EndpointsMixin, object): @staticmethod def construct_api_url(api_url, **params): - """Construct a Twitter API url, encoded, with parameters + r"""Construct a Twitter API url, encoded, with parameters :param api_url: URL of the Twitter API endpoint you are attempting to construct @@ -469,7 +469,7 @@ class Twython(EndpointsMixin, object): return self.cursor(self.search, q=search_query, **params) def cursor(self, function, return_pages=False, **params): - """Returns a generator for results that match a specified query. + r"""Returns a generator for results that match a specified query. :param function: Instance of a Twython function (Twython.get_home_timeline, Twython.search) diff --git a/twython/streaming/types.py b/twython/streaming/types.py index 27c9ea8..5042d29 100644 --- a/twython/streaming/types.py +++ b/twython/streaming/types.py @@ -35,7 +35,7 @@ class TwythonStreamerTypesStatuses(object): self.params = None def filter(self, **params): - """Stream statuses/filter + r"""Stream statuses/filter :param \*\*params: Parameters to send with your stream request @@ -47,7 +47,7 @@ class TwythonStreamerTypesStatuses(object): self.streamer._request(url, 'POST', params=params) def sample(self, **params): - """Stream statuses/sample + r"""Stream statuses/sample :param \*\*params: Parameters to send with your stream request @@ -59,7 +59,7 @@ class TwythonStreamerTypesStatuses(object): self.streamer._request(url, params=params) def firehose(self, **params): - """Stream statuses/firehose + r"""Stream statuses/firehose :param \*\*params: Parameters to send with your stream request @@ -71,7 +71,7 @@ class TwythonStreamerTypesStatuses(object): self.streamer._request(url, params=params) def set_dynamic_filter(self, **params): - """Set/update statuses/filter + r"""Set/update statuses/filter :param \*\*params: Parameters to send with your stream request From 33f46c087ec6c92dd325101169f3c5c0894d3b7c Mon Sep 17 00:00:00 2001 From: Hannes Date: Fri, 17 Jul 2020 11:50:06 +0200 Subject: [PATCH 75/80] update example for a post involving image editing Python 2 support was dropped from Twython, thanks! In Python3 we actually have to use BytesIO, see https://github.com/python-pillow/Pillow/issues/2205 --- docs/usage/advanced_usage.rst | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/usage/advanced_usage.rst b/docs/usage/advanced_usage.rst index bdb0b23..df3bc48 100644 --- a/docs/usage/advanced_usage.rst +++ b/docs/usage/advanced_usage.rst @@ -59,15 +59,10 @@ with a status update. .. code-block:: python - # Assume you are working with a JPEG + # Assuming that you are working with a JPEG from PIL import Image - try: - # Python 3 - from io import StringIO - except ImportError: - # Python 2 - from StringIO import StringIO + from io import BytesIO photo = Image.open('/path/to/file/image.jpg') @@ -76,14 +71,13 @@ with a status update. height = int((float(photo.size[1]) * float(wpercent))) photo = photo.resize((basewidth, height), Image.ANTIALIAS) - image_io = StringIO.StringIO() + image_io = BytesIO() photo.save(image_io, format='JPEG') # If you do not seek(0), the image will be at the end of the file and # unable to be read image_io.seek(0) - response = twitter.upload_media(media=image_io) twitter.update_status(status='Checkout this cool image!', media_ids=[response['media_id']]) From 1a54c15a71d054c6421e29cbcefcb9f2186cad49 Mon Sep 17 00:00:00 2001 From: Erik Zscheile Date: Tue, 26 Jan 2021 22:19:13 +0100 Subject: [PATCH 76/80] PEP 479: Change StopIteration handling inside generators --- twython/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/twython/api.py b/twython/api.py index bb10db0..bd49c49 100644 --- a/twython/api.py +++ b/twython/api.py @@ -501,7 +501,7 @@ class Twython(EndpointsMixin, object): content = function(**params) if not content: - raise StopIteration + return if hasattr(function, 'iter_key'): results = content.get(function.iter_key) @@ -516,7 +516,7 @@ class Twython(EndpointsMixin, object): if function.iter_mode == 'cursor' and \ content['next_cursor_str'] == '0': - raise StopIteration + return try: if function.iter_mode == 'id': @@ -529,7 +529,7 @@ class Twython(EndpointsMixin, object): params = dict(parse_qsl(next_results.query)) else: # No more results - raise StopIteration + return else: # Twitter gives tweets in reverse chronological order: params['max_id'] = str(int(content[-1]['id_str']) - 1) From 61c1ba9600986d8263af81b8cee954828bb7ce7c Mon Sep 17 00:00:00 2001 From: Erik Zscheile Date: Tue, 26 Jan 2021 22:22:56 +0100 Subject: [PATCH 77/80] PEP 479: add appropriate __future__ tag --- twython/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/twython/api.py b/twython/api.py index bd49c49..5de6c67 100644 --- a/twython/api.py +++ b/twython/api.py @@ -9,6 +9,7 @@ Twitter Authentication, and miscellaneous methods that are useful when dealing with the Twitter API """ +from __future__ import generator_stop import warnings import re From 0b6f372620e864055f9c141c5d72b85a93230d50 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Sat, 6 Feb 2021 17:47:42 -0500 Subject: [PATCH 78/80] Update metadata to describe present support and CI testing --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3742f24..39a8e5f 100755 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ setup( name='twython', version=__version__, install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], + python_requires='>=3.5', author='Ryan McGrath', author_email='ryan@venodesigns.net', license='MIT', @@ -41,9 +42,8 @@ setup( 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Communications :: Chat', 'Topic :: Internet', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', From 4be4a504a30ad5e1b2ade398582e6a09e7d97759 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 16 Jul 2021 14:32:48 -0700 Subject: [PATCH 79/80] Push 3.9.0 --- setup.py | 4 ++-- twython/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 3742f24..307ad8b 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.8.2' +__version__ = '3.9.0' packages = [ 'twython', @@ -25,7 +25,7 @@ setup( version=__version__, install_requires=['requests>=2.1.0', 'requests_oauthlib>=0.4.0'], author='Ryan McGrath', - author_email='ryan@venodesigns.net', + author_email='ryan@rymc.io', license='MIT', url='https://github.com/ryanmcgrath/twython/tree/master', keywords='twitter search api tweet twython stream', diff --git a/twython/__init__.py b/twython/__init__.py index dc161d1..e01d85d 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.7.0' +__version__ = '3.9.0' from .api import Twython from .streaming import TwythonStreamer From 0c405604285364457f3c309969f11ba68163bd05 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 16 Jul 2021 15:33:17 -0700 Subject: [PATCH 80/80] 3.9.1 --- README.md | 8 +++++++- setup.py | 2 +- twython/__init__.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe0aa1b..853136e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,13 @@ Install Twython via pip: $ pip install twython ``` -Or, if you want the code that is currently on GitHub +If you're on a legacy project that needs Python 2.7 support, you can install the last version of Twython that supported 2.7: + +``` +pip install twython==3.7.0` +``` + +Or, if you want the code that is currently on GitHub: ```bash git clone git://github.com/ryanmcgrath/twython.git diff --git a/setup.py b/setup.py index 4c0cc12..a3600de 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: from distutils.core import setup __author__ = 'Ryan McGrath ' -__version__ = '3.9.0' +__version__ = '3.9.1' packages = [ 'twython', diff --git a/twython/__init__.py b/twython/__init__.py index e01d85d..7d25ef3 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.9.0' +__version__ = '3.9.1' from .api import Twython from .streaming import TwythonStreamer