From 4d4816a61b791774bd318d1aac365ea8951eb4bc Mon Sep 17 00:00:00 2001 From: Bill Dengler Date: Sat, 9 May 2015 16:29:20 -0400 Subject: [PATCH] Close #29 --- src/sessionmanager/session.py | 4 +- src/twython/__init__.py | 2 +- src/twython/api.py | 41 ++++-- src/twython/endpoints.py | 238 ++++++++++++++++++++++++--------- src/twython/streaming/types.py | 18 +++ 5 files changed, 223 insertions(+), 80 deletions(-) diff --git a/src/sessionmanager/session.py b/src/sessionmanager/session.py index 6253fd7f..b7e7098e 100644 --- a/src/sessionmanager/session.py +++ b/src/sessionmanager/session.py @@ -254,8 +254,8 @@ class Session(object): """ Gets muted users (oh really?).""" - self.db["muted_users"] = self.twitter.twitter.get_muted_users_ids()["ids"] - +# self.db["muted_users"] = self.twitter.twitter.get_muted_users_ids()["ids"] + pass @_require_login def get_stream(self, name, function, *args, **kwargs): diff --git a/src/twython/__init__.py b/src/twython/__init__.py index e0920a65..a79667d0 100644 --- a/src/twython/__init__.py +++ b/src/twython/__init__.py @@ -19,7 +19,7 @@ Questions, comments? ryan@venodesigns.net """ __author__ = 'Ryan McGrath ' -__version__ = '3.1.2' +__version__ = '3.2.0' from .api import Twython from .streaming import TwythonStreamer diff --git a/src/twython/api.py b/src/twython/api.py index d129d4d4..d2d5c03f 100644 --- a/src/twython/api.py +++ b/src/twython/api.py @@ -9,6 +9,8 @@ Twitter Authentication, and miscellaneous methods that are useful when dealing with the Twitter API """ +import warnings + import requests from requests.auth import HTTPBasicAuth from requests_oauthlib import OAuth1, OAuth2 @@ -20,8 +22,6 @@ from .endpoints import EndpointsMixin from .exceptions import TwythonError, TwythonAuthError, TwythonRateLimitError from .helpers import _transparent_params -import warnings - warnings.simplefilter('always', TwythonDeprecationWarning) # For Python 2.7 > @@ -86,8 +86,7 @@ class Twython(EndpointsMixin, object): self.request_token_url = self.api_url % 'oauth2/token' self.client_args = client_args or {} - default_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0'} - # 'Twython v' + __version__} + default_headers = {'User-Agent': 'Twython v' + __version__} if 'headers' not in self.client_args: # If they didn't set any headers, set our defaults for them self.client_args['headers'] = default_headers @@ -193,10 +192,10 @@ class Twython(EndpointsMixin, object): # app keys/user tokens ExceptionType = TwythonAuthError - raise ExceptionType(error_message, - error_code=response.status_code, - retry_after=response.headers.get('retry-\ - after')) + raise ExceptionType( + error_message, + error_code=response.status_code, + retry_after=response.headers.get('X-Rate-Limit-Reset')) try: content = response.json() @@ -215,6 +214,8 @@ class Twython(EndpointsMixin, object): # {"errors":[{"code":34,"message":"Sorry, # that page does not exist"}]} error_message = content['errors'][0]['message'] + except TypeError: + error_message = content['errors'] except ValueError: # bad json data from Twitter for an error pass @@ -244,9 +245,12 @@ class Twython(EndpointsMixin, object): :rtype: dict """ + if endpoint.startswith('http://'): + raise TwythonError('api.twitter.com is restricted to SSL/TLS traffic.') + # In case they want to pass a full Twitter URL # i.e. https://api.twitter.com/1.1/search/tweets.json - if endpoint.startswith('http://') or endpoint.startswith('https://'): + if endpoint.startswith('https://'): url = endpoint else: url = '%s/%s.json' % (self.api_url % version, endpoint) @@ -498,8 +502,7 @@ class Twython(EndpointsMixin, object): # 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') + since_id = content[function.iter_metadata].get('since_id_str') else: since_id = content[0]['id_str'] params['since_id'] = (int(since_id) - 1) @@ -576,4 +579,20 @@ class Twython(EndpointsMixin, object): text = text.replace(tweet['text'][start:end], url_html % (entity['url'], shown_url)) + # 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: + 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' + text = text.replace(tweet['text'][start:end], + url_html % (entity['url'], shown_url)) + return text diff --git a/src/twython/endpoints.py b/src/twython/endpoints.py index 39c884bd..60663cac 100644 --- a/src/twython/endpoints.py +++ b/src/twython/endpoints.py @@ -5,7 +5,8 @@ twython.endpoints ~~~~~~~~~~~~~~~~~ This module provides a mixin for a :class:`Twython ` instance. -Parameters that need to be embedded in the API url just need to be passed as a keyword argument. +Parameters that need to be embedded in the API url just need to be passed +as a keyword argument. e.g. Twython.retweet(id=12345) @@ -13,6 +14,10 @@ This map is organized the order functions are documented at: https://dev.twitter.com/docs/api/1.1 """ +import warnings + +from .advisory import TwythonDeprecationWarning + class EndpointsMixin(object): # Timelines @@ -20,7 +25,8 @@ class EndpointsMixin(object): """Returns the 20 most recent mentions (tweets containing a users's @screen_name) for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline + Docs: + https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline """ return self.get('statuses/mentions_timeline', params=params) @@ -63,7 +69,8 @@ class EndpointsMixin(object): Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/retweets/%3Aid """ - return self.get('statuses/retweets/%s' % params.get('id'), params=params) + return self.get('statuses/retweets/%s' % params.get('id'), + params=params) def show_status(self, **params): """Returns a single Tweet, specified by the id parameter @@ -73,6 +80,16 @@ class EndpointsMixin(object): """ return self.get('statuses/show/%s' % params.get('id'), params=params) + 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 + parameter. + + Docs: https://dev.twitter.com/docs/api/1.1/get/statuses/lookup + + """ + return self.post('statuses/lookup', params=params) + def destroy_status(self, **params): """Destroys the status specified by the required ID parameter @@ -101,11 +118,27 @@ class EndpointsMixin(object): """Updates the authenticating user's current status and attaches media 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 + Docs: + https://dev.twitter.com/docs/api/1.1/post/statuses/update_with_media """ + warnings.warn( + 'This method is deprecated. You should use Twython.upload_media instead.', + TwythonDeprecationWarning, + stacklevel=2 + ) return self.post('statuses/update_with_media', params=params) + 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. + + Docs: + https://dev.twitter.com/rest/reference/post/media/upload + """ + return self.post('https://upload.twitter.com/1.1/media/upload.json', params=params) + def get_oembed_tweet(self, **params): """Returns information allowing the creation of an embedded representation of a Tweet on third party sites. @@ -174,7 +207,8 @@ class EndpointsMixin(object): return self.post('direct_messages/destroy', params=params) def send_direct_message(self, **params): - """Sends a new direct message to the specified user from the authenticating user. + """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 @@ -186,7 +220,8 @@ class EndpointsMixin(object): """Returns a collection of user_ids that the currently authenticated user does not want to receive retweets from. - Docs: https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids + Docs: + https://dev.twitter.com/docs/api/1.1/get/friendships/no_retweets/ids """ return self.get('friendships/no_retweets/ids', params=params) @@ -317,7 +352,8 @@ class EndpointsMixin(object): requesting user if authentication was successful; returns a 401 status code and an error message if not. - Docs: https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials + Docs: + https://dev.twitter.com/docs/api/1.1/get/account/verify_credentials """ return self.get('account/verify_credentials', params=params) @@ -333,13 +369,15 @@ class EndpointsMixin(object): def update_delivery_service(self, **params): """Sets which device Twitter delivers updates to for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_delivery_device """ return self.post('account/update_delivery_device', params=params) def update_profile(self, **params): - """Sets values that users are able to set under the "Account" tab of their settings page. + """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 @@ -349,7 +387,8 @@ class EndpointsMixin(object): def update_profile_banner_image(self, **params): # pragma: no cover """Updates the authenticating user's profile background image. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_background_image """ return self.post('account/update_profile_banner', params=params) @@ -358,7 +397,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. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_colors """ return self.post('account/update_profile_colors', params=params) @@ -366,13 +406,15 @@ class EndpointsMixin(object): 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 + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_image """ return self.post('account/update_profile_image', params=params) def list_blocks(self, **params): - """Returns a collection of user objects that the authenticating user is blocking. + """Returns a collection of user objects that the authenticating user + is blocking. Docs: https://dev.twitter.com/docs/api/1.1/get/blocks/list @@ -400,7 +442,8 @@ 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 authenticating user. + """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 @@ -409,7 +452,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://dev.twitter.com/docs/api/1.1/get/users/lookup @@ -426,7 +470,8 @@ class EndpointsMixin(object): return self.get('users/show', params=params) def search_users(self, **params): - """Provides a simple, relevance-based search interface to public user accounts on Twitter. + """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 @@ -453,7 +498,8 @@ class EndpointsMixin(object): """Removes the uploaded profile banner for the authenticating user. Returns HTTP 200 upon success. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/remove_profile_banner """ return self.post('account/remove_profile_banner', params=params) @@ -461,27 +507,72 @@ class EndpointsMixin(object): def update_profile_background_image(self, **params): """Uploads a profile banner on behalf of the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner + Docs: + https://dev.twitter.com/docs/api/1.1/post/account/update_profile_banner """ - return self.post('account/update_profile_background_image', params=params) + return self.post('account/update_profile_background_image', + params=params) def get_profile_banner_sizes(self, **params): - """Returns a map of the available size variations of the specified user's profile banner. + """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 """ return self.get('users/profile_banner', params=params) + def list_mutes(self, **params): + """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 + + """ + return self.get('mutes/users/list', params=params) + list_mutes.iter_mode = 'cursor' + list_mutes.iter_key = 'users' + + def list_mute_ids(self, **params): + """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 + + """ + return self.get('mutes/users/ids', params=params) + list_mute_ids.iter_mode = 'cursor' + list_mute_ids.iter_key = 'ids' + + def create_mute(self, **params): + """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 + + """ + 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 + the authenticating user. + + Docs: https://dev.twitter.com/docs/api/1.1/post/mutes/users/destroy + + """ + return self.post('mutes/users/destroy', params=params) + # Suggested Users def get_user_suggestions_by_slug(self, **params): """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 + Docs: + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug """ - return self.get('users/suggestions/%s' % params.get('slug'), params=params) + return self.get('users/suggestions/%s' % params.get('slug'), + params=params) def get_user_suggestions(self, **params): """Access to Twitter's suggested user list. @@ -493,16 +584,20 @@ class EndpointsMixin(object): def get_user_suggestions_statuses_by_slug(self, **params): """Access the users in a given category of the Twitter suggested user - list and return their most recent status if they are not a protected user. + list and return their most recent status if they are not a protected + user. - Docs: https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members + Docs: + https://dev.twitter.com/docs/api/1.1/get/users/suggestions/%3Aslug/members """ - return self.get('users/suggestions/%s/members' % params.get('slug'), params=params) + return self.get('users/suggestions/%s/members' % params.get('slug'), + params=params) # Favorites def get_favorites(self, **params): - """Returns the 20 most recent Tweets favorited by the authenticating or specified user. + """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 @@ -511,7 +606,8 @@ class EndpointsMixin(object): get_favorites.iter_mode = 'id' def destroy_favorite(self, **params): - """Un-favorites the status specified in the ID parameter as the authenticating user. + """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 @@ -519,33 +615,18 @@ 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 authenticating user. + """Favorites the status specified in the ID parameter as the + authenticating user. Docs: https://dev.twitter.com/docs/api/1.1/post/favorites/create """ return self.post('favorites/create', params=params) - # Mute - def create_mute(self, **params): - return self.post('mutes/users/create', params=params) - - def destroy_mute(self, **params): - return self.post('mutes/users/destroy', params=params) - - def get_muted_users_ids(self, **params): - return self.get('mutes/users/ids', params=params) - get_muted_users_ids.iter_mode = 'cursor' - get_muted_users_ids.iter_key = 'ids' - - def get_muted_users_list(self, **params): - return self.get('mutes/users/list', params=params) - get_muted_users_list.iter_mode = 'cursor' - get_muted_users_list.iter_key = 'users' - # Lists def show_lists(self, **params): - """Returns all lists the authenticating or specified user subscribes to, including their own. + """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 @@ -592,7 +673,8 @@ class EndpointsMixin(object): def subscribe_to_list(self, **params): """Subscribes the authenticated user to the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/create """ return self.post('lists/subscribers/create', params=params) @@ -608,7 +690,8 @@ class EndpointsMixin(object): def unsubscribe_from_list(self, **params): """Unsubscribes the authenticated user from the specified list. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/subscribers/destroy """ return self.post('lists/subscribers/destroy', params=params) @@ -617,7 +700,8 @@ class EndpointsMixin(object): """Adds multiple members to a list, by specifying a comma-separated list of member ids or screen names. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/members/create_all """ return self.post('lists/members/create_all', params=params) @@ -694,7 +778,8 @@ class EndpointsMixin(object): """Removes multiple members from a list, by specifying a comma-separated list of member ids or screen names. - Docs: https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all + Docs: + https://dev.twitter.com/docs/api/1.1/post/lists/members/destroy_all """ return self.post('lists/members/destroy_all', params=params) @@ -721,10 +806,12 @@ class EndpointsMixin(object): def show_saved_search(self, **params): """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 + Docs: + https://dev.twitter.com/docs/api/1.1/get/saved_searches/show/%3Aid """ - return self.get('saved_searches/show/%s' % params.get('id'), params=params) + return self.get('saved_searches/show/%s' % params.get('id'), + params=params) def create_saved_search(self, **params): """Create a new saved search for the authenticated user. @@ -737,10 +824,12 @@ class EndpointsMixin(object): def destroy_saved_search(self, **params): """Destroys a saved search for the authenticating user. - Docs: https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid + Docs: + https://dev.twitter.com/docs/api/1.1/post/saved_searches/destroy/%3Aid """ - return self.post('saved_searches/destroy/%s' % params.get('id'), params=params) + return self.post('saved_searches/destroy/%s' % params.get('id'), + params=params) # Places & Geo def get_geo_info(self, **params): @@ -868,7 +957,8 @@ class EndpointsMixin(object): """Returns the current rate limits for methods belonging to the specified resource families. - Docs: https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status + Docs: + https://dev.twitter.com/docs/api/1.1/get/application/rate_limit_status """ return self.get('application/rate_limit_status', params=params) @@ -878,16 +968,32 @@ class EndpointsMixin(object): TWITTER_HTTP_STATUS_CODE = { 200: ('OK', 'Success!'), 304: ('Not Modified', 'There was no new data to return.'), - 400: ('Bad Request', 'The request was invalid. An accompanying error message will explain why. This is the status code will be returned during rate limiting.'), - 401: ('Unauthorized', 'Authentication credentials were missing or incorrect.'), - 403: ('Forbidden', 'The request is understood, but it has been refused. An accompanying error message will explain why. This code is used when requests are being denied due to update limits.'), - 404: ('Not Found', 'The URI requested is invalid or the resource requested, such as a user, does not exists.'), - 406: ('Not Acceptable', 'Returned by the Search API when an invalid format is specified in the request.'), - 410: ('Gone', 'This resource is gone. Used to indicate that an API endpoint has been turned off.'), - 422: ('Unprocessable Entity', 'Returned when an image uploaded to POST account/update_profile_banner is unable to be processed.'), - 429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot be served due to the application\'s rate limit having been exhausted for the resource.'), - 500: ('Internal Server Error', 'Something is broken. Please post to the group so the Twitter team can investigate.'), + 400: ('Bad Request', 'The request was invalid. An accompanying \ + error message will explain why. This is the status code \ + will be returned during rate limiting.'), + 401: ('Unauthorized', 'Authentication credentials were missing \ + or incorrect.'), + 403: ('Forbidden', 'The request is understood, but it has been \ + refused. An accompanying error message will explain why. \ + This code is used when requests are being denied due to \ + update limits.'), + 404: ('Not Found', 'The URI requested is invalid or the resource \ + requested, such as a user, does not exists.'), + 406: ('Not Acceptable', 'Returned by the Search API when an \ + invalid format is specified in the request.'), + 410: ('Gone', 'This resource is gone. Used to indicate that an \ + API endpoint has been turned off.'), + 422: ('Unprocessable Entity', 'Returned when an image uploaded to \ + POST account/update_profile_banner is unable to be processed.'), + 429: ('Too Many Requests', 'Returned in API v1.1 when a request cannot \ + be served due to the application\'s rate limit having been \ + exhausted for the resource.'), + 500: ('Internal Server Error', 'Something is broken. Please post to the \ + group so the Twitter team can investigate.'), 502: ('Bad Gateway', 'Twitter is down or being upgraded.'), - 503: ('Service Unavailable', 'The Twitter servers are up, but overloaded with requests. Try again later.'), - 504: ('Gateway Timeout', 'The Twitter servers are up, but the request couldn\'t be serviced due to some failure within our stack. Try again later.'), + 503: ('Service Unavailable', 'The Twitter servers are up, but overloaded \ + with requests. Try again later.'), + 504: ('Gateway Timeout', 'The Twitter servers are up, but the request \ + couldn\'t be serviced due to some failure within our stack. Try \ + again later.'), } diff --git a/src/twython/streaming/types.py b/src/twython/streaming/types.py index 39a9ccbf..aa6b9adb 100644 --- a/src/twython/streaming/types.py +++ b/src/twython/streaming/types.py @@ -51,6 +51,7 @@ class TwythonStreamerTypesStatuses(object): """ def __init__(self, streamer): self.streamer = streamer + self.params = None def filter(self, **params): """Stream statuses/filter @@ -87,3 +88,20 @@ class TwythonStreamerTypesStatuses(object): url = 'https://stream.twitter.com/%s/statuses/firehose.json' \ % self.streamer.api_version self.streamer._request(url, params=params) + + def set_dynamic_filter(self, **params): + """Set/update statuses/filter + + :param \*\*params: Parameters to send with your stream request + + Accepted params found at: + https://dev.twitter.com/docs/api/1.1/post/statuses/filter + """ + self.params = params + + def dynamic_filter(self): + """Stream statuses/filter with dynamic parameters""" + + url = 'https://stream.twitter.com/%s/statuses/filter.json' \ + % self.streamer.api_version + self.streamer._request(url, 'POST', params=self.params)