From acc18b8d682190a9e52a5f967620c9b1f96700fa Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sun, 15 May 2016 00:03:45 -0700 Subject: [PATCH] Include posters in Twitter notifications * Also cleanup Facebook --- lib/{pythonfacebook => facebook}/__init__.py | 0 lib/{pythonfacebook => facebook}/version.py | 0 lib/pythontwitter/__init__.py | 4651 ----------------- lib/requests_oauthlib/__init__.py | 22 + .../compliance_fixes/__init__.py | 7 + .../compliance_fixes/douban.py | 18 + .../compliance_fixes/facebook.py | 33 + .../compliance_fixes/linkedin.py | 24 + .../compliance_fixes/mailchimp.py | 22 + .../compliance_fixes/slack.py | 37 + .../compliance_fixes/weibo.py | 17 + lib/requests_oauthlib/oauth1_auth.py | 95 + lib/requests_oauthlib/oauth1_session.py | 378 ++ lib/requests_oauthlib/oauth2_auth.py | 36 + lib/requests_oauthlib/oauth2_session.py | 359 ++ lib/twitter/__init__.py | 56 + lib/twitter/_file_cache.py | 161 + lib/twitter/api.py | 4534 ++++++++++++++++ lib/twitter/error.py | 10 + lib/twitter/models.py | 476 ++ lib/twitter/parse_tweet.py | 98 + lib/twitter/ratelimit.py | 215 + lib/twitter/twitter_utils.py | 265 + plexpy/config.py | 1 + plexpy/notifiers.py | 200 +- 25 files changed, 6970 insertions(+), 4745 deletions(-) rename lib/{pythonfacebook => facebook}/__init__.py (100%) rename lib/{pythonfacebook => facebook}/version.py (100%) delete mode 100644 lib/pythontwitter/__init__.py create mode 100644 lib/requests_oauthlib/__init__.py create mode 100644 lib/requests_oauthlib/compliance_fixes/__init__.py create mode 100644 lib/requests_oauthlib/compliance_fixes/douban.py create mode 100644 lib/requests_oauthlib/compliance_fixes/facebook.py create mode 100644 lib/requests_oauthlib/compliance_fixes/linkedin.py create mode 100644 lib/requests_oauthlib/compliance_fixes/mailchimp.py create mode 100644 lib/requests_oauthlib/compliance_fixes/slack.py create mode 100644 lib/requests_oauthlib/compliance_fixes/weibo.py create mode 100644 lib/requests_oauthlib/oauth1_auth.py create mode 100644 lib/requests_oauthlib/oauth1_session.py create mode 100644 lib/requests_oauthlib/oauth2_auth.py create mode 100644 lib/requests_oauthlib/oauth2_session.py create mode 100644 lib/twitter/__init__.py create mode 100644 lib/twitter/_file_cache.py create mode 100644 lib/twitter/api.py create mode 100644 lib/twitter/error.py create mode 100644 lib/twitter/models.py create mode 100644 lib/twitter/parse_tweet.py create mode 100644 lib/twitter/ratelimit.py create mode 100644 lib/twitter/twitter_utils.py diff --git a/lib/pythonfacebook/__init__.py b/lib/facebook/__init__.py similarity index 100% rename from lib/pythonfacebook/__init__.py rename to lib/facebook/__init__.py diff --git a/lib/pythonfacebook/version.py b/lib/facebook/version.py similarity index 100% rename from lib/pythonfacebook/version.py rename to lib/facebook/version.py diff --git a/lib/pythontwitter/__init__.py b/lib/pythontwitter/__init__.py deleted file mode 100644 index b13b46d9..00000000 --- a/lib/pythontwitter/__init__.py +++ /dev/null @@ -1,4651 +0,0 @@ -#!/usr/bin/env python -# -# vim: sw=2 ts=2 sts=2 -# -# Copyright 2007 The Python-Twitter Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''A library that provides a Python interface to the Twitter API''' - -__author__ = 'python-twitter@googlegroups.com' -__version__ = '1.0.1' - - -import base64 -import calendar -import datetime -import httplib -import os -import rfc822 -import sys -import tempfile -import textwrap -import time -import urllib -import urllib2 -import urlparse -import gzip -import StringIO - -try: - # Python >= 2.6 - import json as simplejson -except ImportError: - try: - # Python < 2.6 - import simplejson as simplejson - except ImportError: - try: - # Google App Engine - from django.utils import simplejson - except ImportError: - raise ImportError, "Unable to load a json library" - -# parse_qsl moved to urlparse module in v2.6 -try: - from urlparse import parse_qsl, parse_qs -except ImportError: - from cgi import parse_qsl, parse_qs - -try: - from hashlib import md5 -except ImportError: - from md5 import md5 - -import oauth2 as oauth - - -CHARACTER_LIMIT = 140 - -# A singleton representing a lazily instantiated FileCache. -DEFAULT_CACHE = object() - -REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token' -ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token' -AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize' -SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate' - - -class TwitterError(Exception): - '''Base class for Twitter errors''' - - @property - def message(self): - '''Returns the first argument used to construct this error.''' - return self.args[0] - - -class Status(object): - '''A class representing the Status structure used by the twitter API. - - The Status structure exposes the following properties: - - status.created_at - status.created_at_in_seconds # read only - status.favorited - status.favorite_count - status.in_reply_to_screen_name - status.in_reply_to_user_id - status.in_reply_to_status_id - status.truncated - status.source - status.id - status.text - status.location - status.relative_created_at # read only - status.user - status.urls - status.user_mentions - status.hashtags - status.geo - status.place - status.coordinates - status.contributors - ''' - def __init__(self, - created_at=None, - favorited=None, - favorite_count=None, - id=None, - text=None, - location=None, - user=None, - in_reply_to_screen_name=None, - in_reply_to_user_id=None, - in_reply_to_status_id=None, - truncated=None, - source=None, - now=None, - urls=None, - user_mentions=None, - hashtags=None, - media=None, - geo=None, - place=None, - coordinates=None, - contributors=None, - retweeted=None, - retweeted_status=None, - current_user_retweet=None, - retweet_count=None, - possibly_sensitive=None, - scopes=None, - withheld_copyright=None, - withheld_in_countries=None, - withheld_scope=None): - '''An object to hold a Twitter status message. - - This class is normally instantiated by the twitter.Api class and - returned in a sequence. - - Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007" - - Args: - created_at: - The time this status message was posted. [Optional] - favorited: - Whether this is a favorite of the authenticated user. [Optional] - favorite_count: - Number of times this status message has been favorited. [Optional] - id: - The unique id of this status message. [Optional] - text: - The text of this status message. [Optional] - location: - the geolocation string associated with this message. [Optional] - relative_created_at: - A human readable string representing the posting time. [Optional] - user: - A twitter.User instance representing the person posting the - message. [Optional] - now: - The current time, if the client chooses to set it. - Defaults to the wall clock time. [Optional] - urls: - user_mentions: - hashtags: - geo: - place: - coordinates: - contributors: - retweeted: - retweeted_status: - current_user_retweet: - retweet_count: - possibly_sensitive: - scopes: - withheld_copyright: - withheld_in_countries: - withheld_scope: - ''' - self.created_at = created_at - self.favorited = favorited - self.favorite_count = favorite_count - self.id = id - self.text = text - self.location = location - self.user = user - self.now = now - self.in_reply_to_screen_name = in_reply_to_screen_name - self.in_reply_to_user_id = in_reply_to_user_id - self.in_reply_to_status_id = in_reply_to_status_id - self.truncated = truncated - self.retweeted = retweeted - self.source = source - self.urls = urls - self.user_mentions = user_mentions - self.hashtags = hashtags - self.media = media - self.geo = geo - self.place = place - self.coordinates = coordinates - self.contributors = contributors - self.retweeted_status = retweeted_status - self.current_user_retweet = current_user_retweet - self.retweet_count = retweet_count - self.possibly_sensitive = possibly_sensitive - self.scopes = scopes - self.withheld_copyright = withheld_copyright - self.withheld_in_countries = withheld_in_countries - self.withheld_scope = withheld_scope - - def GetCreatedAt(self): - '''Get the time this status message was posted. - - Returns: - The time this status message was posted - ''' - return self._created_at - - def SetCreatedAt(self, created_at): - '''Set the time this status message was posted. - - Args: - created_at: - The time this status message was created - ''' - self._created_at = created_at - - created_at = property(GetCreatedAt, SetCreatedAt, - doc='The time this status message was posted.') - - def GetCreatedAtInSeconds(self): - '''Get the time this status message was posted, in seconds since the epoch. - - Returns: - The time this status message was posted, in seconds since the epoch. - ''' - return calendar.timegm(rfc822.parsedate(self.created_at)) - - created_at_in_seconds = property(GetCreatedAtInSeconds, - doc="The time this status message was " - "posted, in seconds since the epoch") - - def GetFavorited(self): - '''Get the favorited setting of this status message. - - Returns: - True if this status message is favorited; False otherwise - ''' - return self._favorited - - def SetFavorited(self, favorited): - '''Set the favorited state of this status message. - - Args: - favorited: - boolean True/False favorited state of this status message - ''' - self._favorited = favorited - - favorited = property(GetFavorited, SetFavorited, - doc='The favorited state of this status message.') - - def GetFavoriteCount(self): - '''Get the favorite count of this status message. - - Returns: - number of times this status message has been favorited - ''' - return self._favorite_count - - def SetFavoriteCount(self, favorite_count): - '''Set the favorited state of this status message. - - Args: - favorite_count: - int number of favorites for this status message - ''' - self._favorite_count = favorite_count - - favorite_count = property(GetFavoriteCount, SetFavoriteCount, - doc='The number of favorites for this status message.') - - def GetId(self): - '''Get the unique id of this status message. - - Returns: - The unique id of this status message - ''' - return self._id - - def SetId(self, id): - '''Set the unique id of this status message. - - Args: - id: - The unique id of this status message - ''' - self._id = id - - id = property(GetId, SetId, - doc='The unique id of this status message.') - - def GetInReplyToScreenName(self): - return self._in_reply_to_screen_name - - def SetInReplyToScreenName(self, in_reply_to_screen_name): - self._in_reply_to_screen_name = in_reply_to_screen_name - - in_reply_to_screen_name = property(GetInReplyToScreenName, SetInReplyToScreenName, - doc='') - - def GetInReplyToUserId(self): - return self._in_reply_to_user_id - - def SetInReplyToUserId(self, in_reply_to_user_id): - self._in_reply_to_user_id = in_reply_to_user_id - - in_reply_to_user_id = property(GetInReplyToUserId, SetInReplyToUserId, - doc='') - - def GetInReplyToStatusId(self): - return self._in_reply_to_status_id - - def SetInReplyToStatusId(self, in_reply_to_status_id): - self._in_reply_to_status_id = in_reply_to_status_id - - in_reply_to_status_id = property(GetInReplyToStatusId, SetInReplyToStatusId, - doc='') - - def GetTruncated(self): - return self._truncated - - def SetTruncated(self, truncated): - self._truncated = truncated - - truncated = property(GetTruncated, SetTruncated, - doc='') - - def GetRetweeted(self): - return self._retweeted - - def SetRetweeted(self, retweeted): - self._retweeted = retweeted - - retweeted = property(GetRetweeted, SetRetweeted, - doc='') - - def GetSource(self): - return self._source - - def SetSource(self, source): - self._source = source - - source = property(GetSource, SetSource, - doc='') - - def GetText(self): - '''Get the text of this status message. - - Returns: - The text of this status message. - ''' - return self._text - - def SetText(self, text): - '''Set the text of this status message. - - Args: - text: - The text of this status message - ''' - self._text = text - - text = property(GetText, SetText, - doc='The text of this status message') - - def GetLocation(self): - '''Get the geolocation associated with this status message - - Returns: - The geolocation string of this status message. - ''' - return self._location - - def SetLocation(self, location): - '''Set the geolocation associated with this status message - - Args: - location: - The geolocation string of this status message - ''' - self._location = location - - location = property(GetLocation, SetLocation, - doc='The geolocation string of this status message') - - def GetRelativeCreatedAt(self): - '''Get a human readable string representing the posting time - - Returns: - A human readable string representing the posting time - ''' - fudge = 1.25 - delta = long(self.now) - long(self.created_at_in_seconds) - - if delta < (1 * fudge): - return 'about a second ago' - elif delta < (60 * (1/fudge)): - return 'about %d seconds ago' % (delta) - elif delta < (60 * fudge): - return 'about a minute ago' - elif delta < (60 * 60 * (1/fudge)): - return 'about %d minutes ago' % (delta / 60) - elif delta < (60 * 60 * fudge) or delta / (60 * 60) == 1: - return 'about an hour ago' - elif delta < (60 * 60 * 24 * (1/fudge)): - return 'about %d hours ago' % (delta / (60 * 60)) - elif delta < (60 * 60 * 24 * fudge) or delta / (60 * 60 * 24) == 1: - return 'about a day ago' - else: - return 'about %d days ago' % (delta / (60 * 60 * 24)) - - relative_created_at = property(GetRelativeCreatedAt, - doc='Get a human readable string representing ' - 'the posting time') - - def GetUser(self): - '''Get a twitter.User representing the entity posting this status message. - - Returns: - A twitter.User representing the entity posting this status message - ''' - return self._user - - def SetUser(self, user): - '''Set a twitter.User representing the entity posting this status message. - - Args: - user: - A twitter.User representing the entity posting this status message - ''' - self._user = user - - user = property(GetUser, SetUser, - doc='A twitter.User representing the entity posting this ' - 'status message') - - def GetNow(self): - '''Get the wallclock time for this status message. - - Used to calculate relative_created_at. Defaults to the time - the object was instantiated. - - Returns: - Whatever the status instance believes the current time to be, - in seconds since the epoch. - ''' - if self._now is None: - self._now = time.time() - return self._now - - def SetNow(self, now): - '''Set the wallclock time for this status message. - - Used to calculate relative_created_at. Defaults to the time - the object was instantiated. - - Args: - now: - The wallclock time for this instance. - ''' - self._now = now - - now = property(GetNow, SetNow, - doc='The wallclock time for this status instance.') - - def GetGeo(self): - return self._geo - - def SetGeo(self, geo): - self._geo = geo - - geo = property(GetGeo, SetGeo, - doc='') - - def GetPlace(self): - return self._place - - def SetPlace(self, place): - self._place = place - - place = property(GetPlace, SetPlace, - doc='') - - def GetCoordinates(self): - return self._coordinates - - def SetCoordinates(self, coordinates): - self._coordinates = coordinates - - coordinates = property(GetCoordinates, SetCoordinates, - doc='') - - def GetContributors(self): - return self._contributors - - def SetContributors(self, contributors): - self._contributors = contributors - - contributors = property(GetContributors, SetContributors, - doc='') - - def GetRetweeted_status(self): - return self._retweeted_status - - def SetRetweeted_status(self, retweeted_status): - self._retweeted_status = retweeted_status - - retweeted_status = property(GetRetweeted_status, SetRetweeted_status, - doc='') - - def GetRetweetCount(self): - return self._retweet_count - - def SetRetweetCount(self, retweet_count): - self._retweet_count = retweet_count - - retweet_count = property(GetRetweetCount, SetRetweetCount, - doc='') - - def GetCurrent_user_retweet(self): - return self._current_user_retweet - - def SetCurrent_user_retweet(self, current_user_retweet): - self._current_user_retweet = current_user_retweet - - current_user_retweet = property(GetCurrent_user_retweet, SetCurrent_user_retweet, - doc='') - - def GetPossibly_sensitive(self): - return self._possibly_sensitive - - def SetPossibly_sensitive(self, possibly_sensitive): - self._possibly_sensitive = possibly_sensitive - - possibly_sensitive = property(GetPossibly_sensitive, SetPossibly_sensitive, - doc='') - - def GetScopes(self): - return self._scopes - - def SetScopes(self, scopes): - self._scopes = scopes - - scopes = property(GetScopes, SetScopes, doc='') - - def GetWithheld_copyright(self): - return self._withheld_copyright - - def SetWithheld_copyright(self, withheld_copyright): - self._withheld_copyright = withheld_copyright - - withheld_copyright = property(GetWithheld_copyright, SetWithheld_copyright, - doc='') - - def GetWithheld_in_countries(self): - return self._withheld_in_countries - - def SetWithheld_in_countries(self, withheld_in_countries): - self._withheld_in_countries = withheld_in_countries - - withheld_in_countries = property(GetWithheld_in_countries, SetWithheld_in_countries, - doc='') - - def GetWithheld_scope(self): - return self._withheld_scope - - def SetWithheld_scope(self, withheld_scope): - self._withheld_scope = withheld_scope - - withheld_scope = property(GetWithheld_scope, SetWithheld_scope, - doc='') - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - try: - return other and \ - self.created_at == other.created_at and \ - self.id == other.id and \ - self.text == other.text and \ - self.location == other.location and \ - self.user == other.user and \ - self.in_reply_to_screen_name == other.in_reply_to_screen_name and \ - self.in_reply_to_user_id == other.in_reply_to_user_id and \ - self.in_reply_to_status_id == other.in_reply_to_status_id and \ - self.truncated == other.truncated and \ - self.retweeted == other.retweeted and \ - self.favorited == other.favorited and \ - self.favorite_count == other.favorite_count and \ - self.source == other.source and \ - self.geo == other.geo and \ - self.place == other.place and \ - self.coordinates == other.coordinates and \ - self.contributors == other.contributors and \ - self.retweeted_status == other.retweeted_status and \ - self.retweet_count == other.retweet_count and \ - self.current_user_retweet == other.current_user_retweet and \ - self.possibly_sensitive == other.possibly_sensitive and \ - self.scopes == other.scopes and \ - self.withheld_copyright == other.withheld_copyright and \ - self.withheld_in_countries == other.withheld_in_countries and \ - self.withheld_scope == other.withheld_scope - except AttributeError: - return False - - def __str__(self): - '''A string representation of this twitter.Status instance. - - The return value is the same as the JSON string representation. - - Returns: - A string representation of this twitter.Status instance. - ''' - return self.AsJsonString() - - def AsJsonString(self): - '''A JSON string representation of this twitter.Status instance. - - Returns: - A JSON string representation of this twitter.Status instance - ''' - return simplejson.dumps(self.AsDict(), sort_keys=True) - - def AsDict(self): - '''A dict representation of this twitter.Status instance. - - The return value uses the same key names as the JSON representation. - - Return: - A dict representing this twitter.Status instance - ''' - data = {} - if self.created_at: - data['created_at'] = self.created_at - if self.favorited: - data['favorited'] = self.favorited - if self.favorite_count: - data['favorite_count'] = self.favorite_count - if self.id: - data['id'] = self.id - if self.text: - data['text'] = self.text - if self.location: - data['location'] = self.location - if self.user: - data['user'] = self.user.AsDict() - if self.in_reply_to_screen_name: - data['in_reply_to_screen_name'] = self.in_reply_to_screen_name - if self.in_reply_to_user_id: - data['in_reply_to_user_id'] = self.in_reply_to_user_id - if self.in_reply_to_status_id: - data['in_reply_to_status_id'] = self.in_reply_to_status_id - if self.truncated is not None: - data['truncated'] = self.truncated - if self.retweeted is not None: - data['retweeted'] = self.retweeted - if self.favorited is not None: - data['favorited'] = self.favorited - if self.source: - data['source'] = self.source - if self.geo: - data['geo'] = self.geo - if self.place: - data['place'] = self.place - if self.coordinates: - data['coordinates'] = self.coordinates - if self.contributors: - data['contributors'] = self.contributors - if self.hashtags: - data['hashtags'] = [h.text for h in self.hashtags] - if self.retweeted_status: - data['retweeted_status'] = self.retweeted_status.AsDict() - if self.retweet_count: - data['retweet_count'] = self.retweet_count - if self.urls: - data['urls'] = dict([(url.url, url.expanded_url) for url in self.urls]) - if self.user_mentions: - data['user_mentions'] = [um.AsDict() for um in self.user_mentions] - if self.current_user_retweet: - data['current_user_retweet'] = self.current_user_retweet - if self.possibly_sensitive: - data['possibly_sensitive'] = self.possibly_sensitive - if self.scopes: - data['scopes'] = self.scopes - if self.withheld_copyright: - data['withheld_copyright'] = self.withheld_copyright - if self.withheld_in_countries: - data['withheld_in_countries'] = self.withheld_in_countries - if self.withheld_scope: - data['withheld_scope'] = self.withheld_scope - return data - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: A JSON dict, as converted from the JSON in the twitter API - Returns: - A twitter.Status instance - ''' - if 'user' in data: - user = User.NewFromJsonDict(data['user']) - else: - user = None - if 'retweeted_status' in data: - retweeted_status = Status.NewFromJsonDict(data['retweeted_status']) - else: - retweeted_status = None - - if 'current_user_retweet' in data: - current_user_retweet = data['current_user_retweet']['id'] - else: - current_user_retweet = None - - urls = None - user_mentions = None - hashtags = None - media = None - if 'entities' in data: - if 'urls' in data['entities']: - urls = [Url.NewFromJsonDict(u) for u in data['entities']['urls']] - if 'user_mentions' in data['entities']: - user_mentions = [User.NewFromJsonDict(u) for u in data['entities']['user_mentions']] - if 'hashtags' in data['entities']: - hashtags = [Hashtag.NewFromJsonDict(h) for h in data['entities']['hashtags']] - if 'media' in data['entities']: - media = data['entities']['media'] - else: - media = [] - return Status(created_at=data.get('created_at', None), - favorited=data.get('favorited', None), - favorite_count=data.get('favorite_count', None), - id=data.get('id', None), - text=data.get('text', None), - location=data.get('location', None), - in_reply_to_screen_name=data.get('in_reply_to_screen_name', None), - in_reply_to_user_id=data.get('in_reply_to_user_id', None), - in_reply_to_status_id=data.get('in_reply_to_status_id', None), - truncated=data.get('truncated', None), - retweeted=data.get('retweeted', None), - source=data.get('source', None), - user=user, - urls=urls, - user_mentions=user_mentions, - hashtags=hashtags, - media=media, - geo=data.get('geo', None), - place=data.get('place', None), - coordinates=data.get('coordinates', None), - contributors=data.get('contributors', None), - retweeted_status=retweeted_status, - current_user_retweet=current_user_retweet, - retweet_count=data.get('retweet_count', None), - possibly_sensitive=data.get('possibly_sensitive', None), - scopes=data.get('scopes', None), - withheld_copyright=data.get('withheld_copyright', None), - withheld_in_countries=data.get('withheld_in_countries', None), - withheld_scope=data.get('withheld_scope', None)) - - -class User(object): - '''A class representing the User structure used by the twitter API. - - The User structure exposes the following properties: - - user.id - user.name - user.screen_name - user.location - user.description - user.profile_image_url - user.profile_background_tile - user.profile_background_image_url - user.profile_sidebar_fill_color - user.profile_background_color - user.profile_link_color - user.profile_text_color - user.protected - user.utc_offset - user.time_zone - user.url - user.status - user.statuses_count - user.followers_count - user.friends_count - user.favourites_count - user.geo_enabled - user.verified - user.lang - user.notifications - user.contributors_enabled - user.created_at - user.listed_count - ''' - def __init__(self, - id=None, - name=None, - screen_name=None, - location=None, - description=None, - profile_image_url=None, - profile_background_tile=None, - profile_background_image_url=None, - profile_sidebar_fill_color=None, - profile_background_color=None, - profile_link_color=None, - profile_text_color=None, - protected=None, - utc_offset=None, - time_zone=None, - followers_count=None, - friends_count=None, - statuses_count=None, - favourites_count=None, - url=None, - status=None, - geo_enabled=None, - verified=None, - lang=None, - notifications=None, - contributors_enabled=None, - created_at=None, - listed_count=None): - self.id = id - self.name = name - self.screen_name = screen_name - self.location = location - self.description = description - self.profile_image_url = profile_image_url - self.profile_background_tile = profile_background_tile - self.profile_background_image_url = profile_background_image_url - self.profile_sidebar_fill_color = profile_sidebar_fill_color - self.profile_background_color = profile_background_color - self.profile_link_color = profile_link_color - self.profile_text_color = profile_text_color - self.protected = protected - self.utc_offset = utc_offset - self.time_zone = time_zone - self.followers_count = followers_count - self.friends_count = friends_count - self.statuses_count = statuses_count - self.favourites_count = favourites_count - self.url = url - self.status = status - self.geo_enabled = geo_enabled - self.verified = verified - self.lang = lang - self.notifications = notifications - self.contributors_enabled = contributors_enabled - self.created_at = created_at - self.listed_count = listed_count - - def GetId(self): - '''Get the unique id of this user. - - Returns: - The unique id of this user - ''' - return self._id - - def SetId(self, id): - '''Set the unique id of this user. - - Args: - id: The unique id of this user. - ''' - self._id = id - - id = property(GetId, SetId, - doc='The unique id of this user.') - - def GetName(self): - '''Get the real name of this user. - - Returns: - The real name of this user - ''' - return self._name - - def SetName(self, name): - '''Set the real name of this user. - - Args: - name: The real name of this user - ''' - self._name = name - - name = property(GetName, SetName, - doc='The real name of this user.') - - def GetScreenName(self): - '''Get the short twitter name of this user. - - Returns: - The short twitter name of this user - ''' - return self._screen_name - - def SetScreenName(self, screen_name): - '''Set the short twitter name of this user. - - Args: - screen_name: the short twitter name of this user - ''' - self._screen_name = screen_name - - screen_name = property(GetScreenName, SetScreenName, - doc='The short twitter name of this user.') - - def GetLocation(self): - '''Get the geographic location of this user. - - Returns: - The geographic location of this user - ''' - return self._location - - def SetLocation(self, location): - '''Set the geographic location of this user. - - Args: - location: The geographic location of this user - ''' - self._location = location - - location = property(GetLocation, SetLocation, - doc='The geographic location of this user.') - - def GetDescription(self): - '''Get the short text description of this user. - - Returns: - The short text description of this user - ''' - return self._description - - def SetDescription(self, description): - '''Set the short text description of this user. - - Args: - description: The short text description of this user - ''' - self._description = description - - description = property(GetDescription, SetDescription, - doc='The short text description of this user.') - - def GetUrl(self): - '''Get the homepage url of this user. - - Returns: - The homepage url of this user - ''' - return self._url - - def SetUrl(self, url): - '''Set the homepage url of this user. - - Args: - url: The homepage url of this user - ''' - self._url = url - - url = property(GetUrl, SetUrl, - doc='The homepage url of this user.') - - def GetProfileImageUrl(self): - '''Get the url of the thumbnail of this user. - - Returns: - The url of the thumbnail of this user - ''' - return self._profile_image_url - - def SetProfileImageUrl(self, profile_image_url): - '''Set the url of the thumbnail of this user. - - Args: - profile_image_url: The url of the thumbnail of this user - ''' - self._profile_image_url = profile_image_url - - profile_image_url= property(GetProfileImageUrl, SetProfileImageUrl, - doc='The url of the thumbnail of this user.') - - def GetProfileBackgroundTile(self): - '''Boolean for whether to tile the profile background image. - - Returns: - True if the background is to be tiled, False if not, None if unset. - ''' - return self._profile_background_tile - - def SetProfileBackgroundTile(self, profile_background_tile): - '''Set the boolean flag for whether to tile the profile background image. - - Args: - profile_background_tile: Boolean flag for whether to tile or not. - ''' - self._profile_background_tile = profile_background_tile - - profile_background_tile = property(GetProfileBackgroundTile, SetProfileBackgroundTile, - doc='Boolean for whether to tile the background image.') - - def GetProfileBackgroundImageUrl(self): - return self._profile_background_image_url - - def SetProfileBackgroundImageUrl(self, profile_background_image_url): - self._profile_background_image_url = profile_background_image_url - - profile_background_image_url = property(GetProfileBackgroundImageUrl, SetProfileBackgroundImageUrl, - doc='The url of the profile background of this user.') - - def GetProfileSidebarFillColor(self): - return self._profile_sidebar_fill_color - - def SetProfileSidebarFillColor(self, profile_sidebar_fill_color): - self._profile_sidebar_fill_color = profile_sidebar_fill_color - - profile_sidebar_fill_color = property(GetProfileSidebarFillColor, SetProfileSidebarFillColor) - - def GetProfileBackgroundColor(self): - return self._profile_background_color - - def SetProfileBackgroundColor(self, profile_background_color): - self._profile_background_color = profile_background_color - - profile_background_color = property(GetProfileBackgroundColor, SetProfileBackgroundColor) - - def GetProfileLinkColor(self): - return self._profile_link_color - - def SetProfileLinkColor(self, profile_link_color): - self._profile_link_color = profile_link_color - - profile_link_color = property(GetProfileLinkColor, SetProfileLinkColor) - - def GetProfileTextColor(self): - return self._profile_text_color - - def SetProfileTextColor(self, profile_text_color): - self._profile_text_color = profile_text_color - - profile_text_color = property(GetProfileTextColor, SetProfileTextColor) - - def GetProtected(self): - return self._protected - - def SetProtected(self, protected): - self._protected = protected - - protected = property(GetProtected, SetProtected) - - def GetUtcOffset(self): - return self._utc_offset - - def SetUtcOffset(self, utc_offset): - self._utc_offset = utc_offset - - utc_offset = property(GetUtcOffset, SetUtcOffset) - - def GetTimeZone(self): - '''Returns the current time zone string for the user. - - Returns: - The descriptive time zone string for the user. - ''' - return self._time_zone - - def SetTimeZone(self, time_zone): - '''Sets the user's time zone string. - - Args: - time_zone: - The descriptive time zone to assign for the user. - ''' - self._time_zone = time_zone - - time_zone = property(GetTimeZone, SetTimeZone) - - def GetStatus(self): - '''Get the latest twitter.Status of this user. - - Returns: - The latest twitter.Status of this user - ''' - return self._status - - def SetStatus(self, status): - '''Set the latest twitter.Status of this user. - - Args: - status: - The latest twitter.Status of this user - ''' - self._status = status - - status = property(GetStatus, SetStatus, - doc='The latest twitter.Status of this user.') - - def GetFriendsCount(self): - '''Get the friend count for this user. - - Returns: - The number of users this user has befriended. - ''' - return self._friends_count - - def SetFriendsCount(self, count): - '''Set the friend count for this user. - - Args: - count: - The number of users this user has befriended. - ''' - self._friends_count = count - - friends_count = property(GetFriendsCount, SetFriendsCount, - doc='The number of friends for this user.') - - def GetListedCount(self): - '''Get the listed count for this user. - - Returns: - The number of lists this user belongs to. - ''' - return self._listed_count - - def SetListedCount(self, count): - '''Set the listed count for this user. - - Args: - count: - The number of lists this user belongs to. - ''' - self._listed_count = count - - listed_count = property(GetListedCount, SetListedCount, - doc='The number of lists this user belongs to.') - - def GetFollowersCount(self): - '''Get the follower count for this user. - - Returns: - The number of users following this user. - ''' - return self._followers_count - - def SetFollowersCount(self, count): - '''Set the follower count for this user. - - Args: - count: - The number of users following this user. - ''' - self._followers_count = count - - followers_count = property(GetFollowersCount, SetFollowersCount, - doc='The number of users following this user.') - - def GetStatusesCount(self): - '''Get the number of status updates for this user. - - Returns: - The number of status updates for this user. - ''' - return self._statuses_count - - def SetStatusesCount(self, count): - '''Set the status update count for this user. - - Args: - count: - The number of updates for this user. - ''' - self._statuses_count = count - - statuses_count = property(GetStatusesCount, SetStatusesCount, - doc='The number of updates for this user.') - - def GetFavouritesCount(self): - '''Get the number of favourites for this user. - - Returns: - The number of favourites for this user. - ''' - return self._favourites_count - - def SetFavouritesCount(self, count): - '''Set the favourite count for this user. - - Args: - count: - The number of favourites for this user. - ''' - self._favourites_count = count - - favourites_count = property(GetFavouritesCount, SetFavouritesCount, - doc='The number of favourites for this user.') - - def GetGeoEnabled(self): - '''Get the setting of geo_enabled for this user. - - Returns: - True/False if Geo tagging is enabled - ''' - return self._geo_enabled - - def SetGeoEnabled(self, geo_enabled): - '''Set the latest twitter.geo_enabled of this user. - - Args: - geo_enabled: - True/False if Geo tagging is to be enabled - ''' - self._geo_enabled = geo_enabled - - geo_enabled = property(GetGeoEnabled, SetGeoEnabled, - doc='The value of twitter.geo_enabled for this user.') - - def GetVerified(self): - '''Get the setting of verified for this user. - - Returns: - True/False if user is a verified account - ''' - return self._verified - - def SetVerified(self, verified): - '''Set twitter.verified for this user. - - Args: - verified: - True/False if user is a verified account - ''' - self._verified = verified - - verified = property(GetVerified, SetVerified, - doc='The value of twitter.verified for this user.') - - def GetLang(self): - '''Get the setting of lang for this user. - - Returns: - language code of the user - ''' - return self._lang - - def SetLang(self, lang): - '''Set twitter.lang for this user. - - Args: - lang: - language code for the user - ''' - self._lang = lang - - lang = property(GetLang, SetLang, - doc='The value of twitter.lang for this user.') - - def GetNotifications(self): - '''Get the setting of notifications for this user. - - Returns: - True/False for the notifications setting of the user - ''' - return self._notifications - - def SetNotifications(self, notifications): - '''Set twitter.notifications for this user. - - Args: - notifications: - True/False notifications setting for the user - ''' - self._notifications = notifications - - notifications = property(GetNotifications, SetNotifications, - doc='The value of twitter.notifications for this user.') - - def GetContributorsEnabled(self): - '''Get the setting of contributors_enabled for this user. - - Returns: - True/False contributors_enabled of the user - ''' - return self._contributors_enabled - - def SetContributorsEnabled(self, contributors_enabled): - '''Set twitter.contributors_enabled for this user. - - Args: - contributors_enabled: - True/False contributors_enabled setting for the user - ''' - self._contributors_enabled = contributors_enabled - - contributors_enabled = property(GetContributorsEnabled, SetContributorsEnabled, - doc='The value of twitter.contributors_enabled for this user.') - - def GetCreatedAt(self): - '''Get the setting of created_at for this user. - - Returns: - created_at value of the user - ''' - return self._created_at - - def SetCreatedAt(self, created_at): - '''Set twitter.created_at for this user. - - Args: - created_at: - created_at value for the user - ''' - self._created_at = created_at - - created_at = property(GetCreatedAt, SetCreatedAt, - doc='The value of twitter.created_at for this user.') - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - try: - return other and \ - self.id == other.id and \ - self.name == other.name and \ - self.screen_name == other.screen_name and \ - self.location == other.location and \ - self.description == other.description and \ - self.profile_image_url == other.profile_image_url and \ - self.profile_background_tile == other.profile_background_tile and \ - self.profile_background_image_url == other.profile_background_image_url and \ - self.profile_sidebar_fill_color == other.profile_sidebar_fill_color and \ - self.profile_background_color == other.profile_background_color and \ - self.profile_link_color == other.profile_link_color and \ - self.profile_text_color == other.profile_text_color and \ - self.protected == other.protected and \ - self.utc_offset == other.utc_offset and \ - self.time_zone == other.time_zone and \ - self.url == other.url and \ - self.statuses_count == other.statuses_count and \ - self.followers_count == other.followers_count and \ - self.favourites_count == other.favourites_count and \ - self.friends_count == other.friends_count and \ - self.status == other.status and \ - self.geo_enabled == other.geo_enabled and \ - self.verified == other.verified and \ - self.lang == other.lang and \ - self.notifications == other.notifications and \ - self.contributors_enabled == other.contributors_enabled and \ - self.created_at == other.created_at and \ - self.listed_count == other.listed_count - - except AttributeError: - return False - - def __str__(self): - '''A string representation of this twitter.User instance. - - The return value is the same as the JSON string representation. - - Returns: - A string representation of this twitter.User instance. - ''' - return self.AsJsonString() - - def AsJsonString(self): - '''A JSON string representation of this twitter.User instance. - - Returns: - A JSON string representation of this twitter.User instance - ''' - return simplejson.dumps(self.AsDict(), sort_keys=True) - - def AsDict(self): - '''A dict representation of this twitter.User instance. - - The return value uses the same key names as the JSON representation. - - Return: - A dict representing this twitter.User instance - ''' - data = {} - if self.id: - data['id'] = self.id - if self.name: - data['name'] = self.name - if self.screen_name: - data['screen_name'] = self.screen_name - if self.location: - data['location'] = self.location - if self.description: - data['description'] = self.description - if self.profile_image_url: - data['profile_image_url'] = self.profile_image_url - if self.profile_background_tile is not None: - data['profile_background_tile'] = self.profile_background_tile - if self.profile_background_image_url: - data['profile_sidebar_fill_color'] = self.profile_background_image_url - if self.profile_background_color: - data['profile_background_color'] = self.profile_background_color - if self.profile_link_color: - data['profile_link_color'] = self.profile_link_color - if self.profile_text_color: - data['profile_text_color'] = self.profile_text_color - if self.protected is not None: - data['protected'] = self.protected - if self.utc_offset: - data['utc_offset'] = self.utc_offset - if self.time_zone: - data['time_zone'] = self.time_zone - if self.url: - data['url'] = self.url - if self.status: - data['status'] = self.status.AsDict() - if self.friends_count: - data['friends_count'] = self.friends_count - if self.followers_count: - data['followers_count'] = self.followers_count - if self.statuses_count: - data['statuses_count'] = self.statuses_count - if self.favourites_count: - data['favourites_count'] = self.favourites_count - if self.geo_enabled: - data['geo_enabled'] = self.geo_enabled - if self.verified: - data['verified'] = self.verified - if self.lang: - data['lang'] = self.lang - if self.notifications: - data['notifications'] = self.notifications - if self.contributors_enabled: - data['contributors_enabled'] = self.contributors_enabled - if self.created_at: - data['created_at'] = self.created_at - if self.listed_count: - data['listed_count'] = self.listed_count - - return data - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: - A JSON dict, as converted from the JSON in the twitter API - - Returns: - A twitter.User instance - ''' - if 'status' in data: - status = Status.NewFromJsonDict(data['status']) - else: - status = None - return User(id=data.get('id', None), - name=data.get('name', None), - screen_name=data.get('screen_name', None), - location=data.get('location', None), - description=data.get('description', None), - statuses_count=data.get('statuses_count', None), - followers_count=data.get('followers_count', None), - favourites_count=data.get('favourites_count', None), - friends_count=data.get('friends_count', None), - profile_image_url=data.get('profile_image_url_https', data.get('profile_image_url', None)), - profile_background_tile = data.get('profile_background_tile', None), - profile_background_image_url = data.get('profile_background_image_url', None), - profile_sidebar_fill_color = data.get('profile_sidebar_fill_color', None), - profile_background_color = data.get('profile_background_color', None), - profile_link_color = data.get('profile_link_color', None), - profile_text_color = data.get('profile_text_color', None), - protected = data.get('protected', None), - utc_offset = data.get('utc_offset', None), - time_zone = data.get('time_zone', None), - url=data.get('url', None), - status=status, - geo_enabled=data.get('geo_enabled', None), - verified=data.get('verified', None), - lang=data.get('lang', None), - notifications=data.get('notifications', None), - contributors_enabled=data.get('contributors_enabled', None), - created_at=data.get('created_at', None), - listed_count=data.get('listed_count', None)) - -class List(object): - '''A class representing the List structure used by the twitter API. - - The List structure exposes the following properties: - - list.id - list.name - list.slug - list.description - list.full_name - list.mode - list.uri - list.member_count - list.subscriber_count - list.following - ''' - def __init__(self, - id=None, - name=None, - slug=None, - description=None, - full_name=None, - mode=None, - uri=None, - member_count=None, - subscriber_count=None, - following=None, - user=None): - self.id = id - self.name = name - self.slug = slug - self.description = description - self.full_name = full_name - self.mode = mode - self.uri = uri - self.member_count = member_count - self.subscriber_count = subscriber_count - self.following = following - self.user = user - - def GetId(self): - '''Get the unique id of this list. - - Returns: - The unique id of this list - ''' - return self._id - - def SetId(self, id): - '''Set the unique id of this list. - - Args: - id: - The unique id of this list. - ''' - self._id = id - - id = property(GetId, SetId, - doc='The unique id of this list.') - - def GetName(self): - '''Get the real name of this list. - - Returns: - The real name of this list - ''' - return self._name - - def SetName(self, name): - '''Set the real name of this list. - - Args: - name: - The real name of this list - ''' - self._name = name - - name = property(GetName, SetName, - doc='The real name of this list.') - - def GetSlug(self): - '''Get the slug of this list. - - Returns: - The slug of this list - ''' - return self._slug - - def SetSlug(self, slug): - '''Set the slug of this list. - - Args: - slug: - The slug of this list. - ''' - self._slug = slug - - slug = property(GetSlug, SetSlug, - doc='The slug of this list.') - - def GetDescription(self): - '''Get the description of this list. - - Returns: - The description of this list - ''' - return self._description - - def SetDescription(self, description): - '''Set the description of this list. - - Args: - description: - The description of this list. - ''' - self._description = description - - description = property(GetDescription, SetDescription, - doc='The description of this list.') - - def GetFull_name(self): - '''Get the full_name of this list. - - Returns: - The full_name of this list - ''' - return self._full_name - - def SetFull_name(self, full_name): - '''Set the full_name of this list. - - Args: - full_name: - The full_name of this list. - ''' - self._full_name = full_name - - full_name = property(GetFull_name, SetFull_name, - doc='The full_name of this list.') - - def GetMode(self): - '''Get the mode of this list. - - Returns: - The mode of this list - ''' - return self._mode - - def SetMode(self, mode): - '''Set the mode of this list. - - Args: - mode: - The mode of this list. - ''' - self._mode = mode - - mode = property(GetMode, SetMode, - doc='The mode of this list.') - - def GetUri(self): - '''Get the uri of this list. - - Returns: - The uri of this list - ''' - return self._uri - - def SetUri(self, uri): - '''Set the uri of this list. - - Args: - uri: - The uri of this list. - ''' - self._uri = uri - - uri = property(GetUri, SetUri, - doc='The uri of this list.') - - def GetMember_count(self): - '''Get the member_count of this list. - - Returns: - The member_count of this list - ''' - return self._member_count - - def SetMember_count(self, member_count): - '''Set the member_count of this list. - - Args: - member_count: - The member_count of this list. - ''' - self._member_count = member_count - - member_count = property(GetMember_count, SetMember_count, - doc='The member_count of this list.') - - def GetSubscriber_count(self): - '''Get the subscriber_count of this list. - - Returns: - The subscriber_count of this list - ''' - return self._subscriber_count - - def SetSubscriber_count(self, subscriber_count): - '''Set the subscriber_count of this list. - - Args: - subscriber_count: - The subscriber_count of this list. - ''' - self._subscriber_count = subscriber_count - - subscriber_count = property(GetSubscriber_count, SetSubscriber_count, - doc='The subscriber_count of this list.') - - def GetFollowing(self): - '''Get the following status of this list. - - Returns: - The following status of this list - ''' - return self._following - - def SetFollowing(self, following): - '''Set the following status of this list. - - Args: - following: - The following of this list. - ''' - self._following = following - - following = property(GetFollowing, SetFollowing, - doc='The following status of this list.') - - def GetUser(self): - '''Get the user of this list. - - Returns: - The owner of this list - ''' - return self._user - - def SetUser(self, user): - '''Set the user of this list. - - Args: - user: - The owner of this list. - ''' - self._user = user - - user = property(GetUser, SetUser, - doc='The owner of this list.') - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - try: - return other and \ - self.id == other.id and \ - self.name == other.name and \ - self.slug == other.slug and \ - self.description == other.description and \ - self.full_name == other.full_name and \ - self.mode == other.mode and \ - self.uri == other.uri and \ - self.member_count == other.member_count and \ - self.subscriber_count == other.subscriber_count and \ - self.following == other.following and \ - self.user == other.user - - except AttributeError: - return False - - def __str__(self): - '''A string representation of this twitter.List instance. - - The return value is the same as the JSON string representation. - - Returns: - A string representation of this twitter.List instance. - ''' - return self.AsJsonString() - - def AsJsonString(self): - '''A JSON string representation of this twitter.List instance. - - Returns: - A JSON string representation of this twitter.List instance - ''' - return simplejson.dumps(self.AsDict(), sort_keys=True) - - def AsDict(self): - '''A dict representation of this twitter.List instance. - - The return value uses the same key names as the JSON representation. - - Return: - A dict representing this twitter.List instance - ''' - data = {} - if self.id: - data['id'] = self.id - if self.name: - data['name'] = self.name - if self.slug: - data['slug'] = self.slug - if self.description: - data['description'] = self.description - if self.full_name: - data['full_name'] = self.full_name - if self.mode: - data['mode'] = self.mode - if self.uri: - data['uri'] = self.uri - if self.member_count is not None: - data['member_count'] = self.member_count - if self.subscriber_count is not None: - data['subscriber_count'] = self.subscriber_count - if self.following is not None: - data['following'] = self.following - if self.user is not None: - data['user'] = self.user.AsDict() - return data - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: - A JSON dict, as converted from the JSON in the twitter API - - Returns: - A twitter.List instance - ''' - if 'user' in data: - user = User.NewFromJsonDict(data['user']) - else: - user = None - return List(id=data.get('id', None), - name=data.get('name', None), - slug=data.get('slug', None), - description=data.get('description', None), - full_name=data.get('full_name', None), - mode=data.get('mode', None), - uri=data.get('uri', None), - member_count=data.get('member_count', None), - subscriber_count=data.get('subscriber_count', None), - following=data.get('following', None), - user=user) - -class DirectMessage(object): - '''A class representing the DirectMessage structure used by the twitter API. - - The DirectMessage structure exposes the following properties: - - direct_message.id - direct_message.created_at - direct_message.created_at_in_seconds # read only - direct_message.sender_id - direct_message.sender_screen_name - direct_message.recipient_id - direct_message.recipient_screen_name - direct_message.text - ''' - - def __init__(self, - id=None, - created_at=None, - sender_id=None, - sender_screen_name=None, - recipient_id=None, - recipient_screen_name=None, - text=None): - '''An object to hold a Twitter direct message. - - This class is normally instantiated by the twitter.Api class and - returned in a sequence. - - Note: Dates are posted in the form "Sat Jan 27 04:17:38 +0000 2007" - - Args: - id: - The unique id of this direct message. [Optional] - created_at: - The time this direct message was posted. [Optional] - sender_id: - The id of the twitter user that sent this message. [Optional] - sender_screen_name: - The name of the twitter user that sent this message. [Optional] - recipient_id: - The id of the twitter that received this message. [Optional] - recipient_screen_name: - The name of the twitter that received this message. [Optional] - text: - The text of this direct message. [Optional] - ''' - self.id = id - self.created_at = created_at - self.sender_id = sender_id - self.sender_screen_name = sender_screen_name - self.recipient_id = recipient_id - self.recipient_screen_name = recipient_screen_name - self.text = text - - def GetId(self): - '''Get the unique id of this direct message. - - Returns: - The unique id of this direct message - ''' - return self._id - - def SetId(self, id): - '''Set the unique id of this direct message. - - Args: - id: - The unique id of this direct message - ''' - self._id = id - - id = property(GetId, SetId, - doc='The unique id of this direct message.') - - def GetCreatedAt(self): - '''Get the time this direct message was posted. - - Returns: - The time this direct message was posted - ''' - return self._created_at - - def SetCreatedAt(self, created_at): - '''Set the time this direct message was posted. - - Args: - created_at: - The time this direct message was created - ''' - self._created_at = created_at - - created_at = property(GetCreatedAt, SetCreatedAt, - doc='The time this direct message was posted.') - - def GetCreatedAtInSeconds(self): - '''Get the time this direct message was posted, in seconds since the epoch. - - Returns: - The time this direct message was posted, in seconds since the epoch. - ''' - return calendar.timegm(rfc822.parsedate(self.created_at)) - - created_at_in_seconds = property(GetCreatedAtInSeconds, - doc="The time this direct message was " - "posted, in seconds since the epoch") - - def GetSenderId(self): - '''Get the unique sender id of this direct message. - - Returns: - The unique sender id of this direct message - ''' - return self._sender_id - - def SetSenderId(self, sender_id): - '''Set the unique sender id of this direct message. - - Args: - sender_id: - The unique sender id of this direct message - ''' - self._sender_id = sender_id - - sender_id = property(GetSenderId, SetSenderId, - doc='The unique sender id of this direct message.') - - def GetSenderScreenName(self): - '''Get the unique sender screen name of this direct message. - - Returns: - The unique sender screen name of this direct message - ''' - return self._sender_screen_name - - def SetSenderScreenName(self, sender_screen_name): - '''Set the unique sender screen name of this direct message. - - Args: - sender_screen_name: - The unique sender screen name of this direct message - ''' - self._sender_screen_name = sender_screen_name - - sender_screen_name = property(GetSenderScreenName, SetSenderScreenName, - doc='The unique sender screen name of this direct message.') - - def GetRecipientId(self): - '''Get the unique recipient id of this direct message. - - Returns: - The unique recipient id of this direct message - ''' - return self._recipient_id - - def SetRecipientId(self, recipient_id): - '''Set the unique recipient id of this direct message. - - Args: - recipient_id: - The unique recipient id of this direct message - ''' - self._recipient_id = recipient_id - - recipient_id = property(GetRecipientId, SetRecipientId, - doc='The unique recipient id of this direct message.') - - def GetRecipientScreenName(self): - '''Get the unique recipient screen name of this direct message. - - Returns: - The unique recipient screen name of this direct message - ''' - return self._recipient_screen_name - - def SetRecipientScreenName(self, recipient_screen_name): - '''Set the unique recipient screen name of this direct message. - - Args: - recipient_screen_name: - The unique recipient screen name of this direct message - ''' - self._recipient_screen_name = recipient_screen_name - - recipient_screen_name = property(GetRecipientScreenName, SetRecipientScreenName, - doc='The unique recipient screen name of this direct message.') - - def GetText(self): - '''Get the text of this direct message. - - Returns: - The text of this direct message. - ''' - return self._text - - def SetText(self, text): - '''Set the text of this direct message. - - Args: - text: - The text of this direct message - ''' - self._text = text - - text = property(GetText, SetText, - doc='The text of this direct message') - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - try: - return other and \ - self.id == other.id and \ - self.created_at == other.created_at and \ - self.sender_id == other.sender_id and \ - self.sender_screen_name == other.sender_screen_name and \ - self.recipient_id == other.recipient_id and \ - self.recipient_screen_name == other.recipient_screen_name and \ - self.text == other.text - except AttributeError: - return False - - def __str__(self): - '''A string representation of this twitter.DirectMessage instance. - - The return value is the same as the JSON string representation. - - Returns: - A string representation of this twitter.DirectMessage instance. - ''' - return self.AsJsonString() - - def AsJsonString(self): - '''A JSON string representation of this twitter.DirectMessage instance. - - Returns: - A JSON string representation of this twitter.DirectMessage instance - ''' - return simplejson.dumps(self.AsDict(), sort_keys=True) - - def AsDict(self): - '''A dict representation of this twitter.DirectMessage instance. - - The return value uses the same key names as the JSON representation. - - Return: - A dict representing this twitter.DirectMessage instance - ''' - data = {} - if self.id: - data['id'] = self.id - if self.created_at: - data['created_at'] = self.created_at - if self.sender_id: - data['sender_id'] = self.sender_id - if self.sender_screen_name: - data['sender_screen_name'] = self.sender_screen_name - if self.recipient_id: - data['recipient_id'] = self.recipient_id - if self.recipient_screen_name: - data['recipient_screen_name'] = self.recipient_screen_name - if self.text: - data['text'] = self.text - return data - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: - A JSON dict, as converted from the JSON in the twitter API - - Returns: - A twitter.DirectMessage instance - ''' - return DirectMessage(created_at=data.get('created_at', None), - recipient_id=data.get('recipient_id', None), - sender_id=data.get('sender_id', None), - text=data.get('text', None), - sender_screen_name=data.get('sender_screen_name', None), - id=data.get('id', None), - recipient_screen_name=data.get('recipient_screen_name', None)) - -class Hashtag(object): - ''' A class representing a twitter hashtag - ''' - def __init__(self, - text=None): - self.text = text - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: - A JSON dict, as converted from the JSON in the twitter API - - Returns: - A twitter.Hashtag instance - ''' - return Hashtag(text = data.get('text', None)) - -class Trend(object): - ''' A class representing a trending topic - ''' - def __init__(self, name=None, query=None, timestamp=None, url=None): - self.name = name - self.query = query - self.timestamp = timestamp - self.url = url - - def __str__(self): - return 'Name: %s\nQuery: %s\nTimestamp: %s\nSearch URL: %s\n' % (self.name, self.query, self.timestamp, self.url) - - def __ne__(self, other): - return not self.__eq__(other) - - def __eq__(self, other): - try: - return other and \ - self.name == other.name and \ - self.query == other.query and \ - self.timestamp == other.timestamp and \ - self.url == self.url - except AttributeError: - return False - - @staticmethod - def NewFromJsonDict(data, timestamp = None): - '''Create a new instance based on a JSON dict - - Args: - data: - A JSON dict - timestamp: - Gets set as the timestamp property of the new object - - Returns: - A twitter.Trend object - ''' - return Trend(name=data.get('name', None), - query=data.get('query', None), - url=data.get('url', None), - timestamp=timestamp) - -class Url(object): - '''A class representing an URL contained in a tweet''' - def __init__(self, - url=None, - expanded_url=None): - self.url = url - self.expanded_url = expanded_url - - @staticmethod - def NewFromJsonDict(data): - '''Create a new instance based on a JSON dict. - - Args: - data: - A JSON dict, as converted from the JSON in the twitter API - - Returns: - A twitter.Url instance - ''' - return Url(url=data.get('url', None), - expanded_url=data.get('expanded_url', None)) - -class Api(object): - '''A python interface into the Twitter API - - By default, the Api caches results for 1 minute. - - Example usage: - - To create an instance of the twitter.Api class, with no authentication: - - >>> import twitter - >>> api = twitter.Api() - - To fetch the most recently posted public twitter status messages: - - >>> statuses = api.GetPublicTimeline() - >>> print [s.user.name for s in statuses] - [u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #... - - To fetch a single user's public status messages, where "user" is either - a Twitter "short name" or their user id. - - >>> statuses = api.GetUserTimeline(user) - >>> print [s.text for s in statuses] - - To use authentication, instantiate the twitter.Api class with a - consumer key and secret; and the oAuth key and secret: - - >>> api = twitter.Api(consumer_key='twitter consumer key', - consumer_secret='twitter consumer secret', - access_token_key='the_key_given', - access_token_secret='the_key_secret') - - To fetch your friends (after being authenticated): - - >>> users = api.GetFriends() - >>> print [u.name for u in users] - - To post a twitter status message (after being authenticated): - - >>> status = api.PostUpdate('I love python-twitter!') - >>> print status.text - I love python-twitter! - - There are many other methods, including: - - >>> api.PostUpdates(status) - >>> api.PostDirectMessage(user, text) - >>> api.GetUser(user) - >>> api.GetReplies() - >>> api.GetUserTimeline(user) - >>> api.GetHomeTimeLine() - >>> api.GetStatus(id) - >>> api.DestroyStatus(id) - >>> api.GetFriendsTimeline(user) - >>> api.GetFriends(user) - >>> api.GetFollowers() - >>> api.GetFeatured() - >>> api.GetDirectMessages() - >>> api.GetSentDirectMessages() - >>> api.PostDirectMessage(user, text) - >>> api.DestroyDirectMessage(id) - >>> api.DestroyFriendship(user) - >>> api.CreateFriendship(user) - >>> api.GetUserByEmail(email) - >>> api.VerifyCredentials() - ''' - - DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute - _API_REALM = 'Twitter API' - - def __init__(self, - consumer_key=None, - consumer_secret=None, - access_token_key=None, - access_token_secret=None, - input_encoding=None, - request_headers=None, - cache=DEFAULT_CACHE, - shortner=None, - base_url=None, - use_gzip_compression=False, - debugHTTP=False): - '''Instantiate a new twitter.Api object. - - Args: - consumer_key: - Your Twitter user's consumer_key. - consumer_secret: - Your Twitter user's consumer_secret. - access_token_key: - The oAuth access token key value you retrieved - from running get_access_token.py. - access_token_secret: - The oAuth access token's secret, also retrieved - from the get_access_token.py run. - input_encoding: - The encoding used to encode input strings. [Optional] - request_header: - A dictionary of additional HTTP request headers. [Optional] - cache: - The cache instance to use. Defaults to DEFAULT_CACHE. - Use None to disable caching. [Optional] - shortner: - The shortner instance to use. Defaults to None. - See shorten_url.py for an example shortner. [Optional] - base_url: - The base URL to use to contact the Twitter API. - Defaults to https://api.twitter.com. [Optional] - use_gzip_compression: - Set to True to tell enable gzip compression for any call - made to Twitter. Defaults to False. [Optional] - debugHTTP: - Set to True to enable debug output from urllib2 when performing - any HTTP requests. Defaults to False. [Optional] - ''' - self.SetCache(cache) - self._urllib = urllib2 - self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT - self._input_encoding = input_encoding - self._use_gzip = use_gzip_compression - self._debugHTTP = debugHTTP - self._oauth_consumer = None - self._shortlink_size = 19 - - self._InitializeRequestHeaders(request_headers) - self._InitializeUserAgent() - self._InitializeDefaultParameters() - - if base_url is None: - self.base_url = 'https://api.twitter.com/1.1' - else: - self.base_url = base_url - - if consumer_key is not None and (access_token_key is None or - access_token_secret is None): - print >> sys.stderr, 'Twitter now requires an oAuth Access Token for API calls.' - print >> sys.stderr, 'If your using this library from a command line utility, please' - print >> sys.stderr, 'run the the included get_access_token.py tool to generate one.' - - raise TwitterError('Twitter requires oAuth Access Token for all API access') - - self.SetCredentials(consumer_key, consumer_secret, access_token_key, access_token_secret) - - def SetCredentials(self, - consumer_key, - consumer_secret, - access_token_key=None, - access_token_secret=None): - '''Set the consumer_key and consumer_secret for this instance - - Args: - consumer_key: - The consumer_key of the twitter account. - consumer_secret: - The consumer_secret for the twitter account. - access_token_key: - The oAuth access token key value you retrieved - from running get_access_token.py. - access_token_secret: - The oAuth access token's secret, also retrieved - from the get_access_token.py run. - ''' - self._consumer_key = consumer_key - self._consumer_secret = consumer_secret - self._access_token_key = access_token_key - self._access_token_secret = access_token_secret - self._oauth_consumer = None - - if consumer_key is not None and consumer_secret is not None and \ - access_token_key is not None and access_token_secret is not None: - self._signature_method_plaintext = oauth.SignatureMethod_PLAINTEXT() - self._signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() - - self._oauth_token = oauth.Token(key=access_token_key, secret=access_token_secret) - self._oauth_consumer = oauth.Consumer(key=consumer_key, secret=consumer_secret) - - def ClearCredentials(self): - '''Clear the any credentials for this instance - ''' - self._consumer_key = None - self._consumer_secret = None - self._access_token_key = None - self._access_token_secret = None - self._oauth_consumer = None - - def GetSearch(self, - term=None, - geocode=None, - since_id=None, - max_id=None, - until=None, - count=15, - lang=None, - locale=None, - result_type="mixed", - include_entities=None): - '''Return twitter search results for a given term. - - Args: - term: - Term to search by. Optional if you include geocode. - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns only statuses with an ID less than (that is, older - than) or equal to the specified ID. [Optional] - until: - Returns tweets generated before the given date. Date should be - formatted as YYYY-MM-DD. [Optional] - geocode: - Geolocation information in the form (latitude, longitude, radius) - [Optional] - count: - Number of results to return. Default is 15 [Optional] - lang: - Language for results as ISO 639-1 code. Default is None (all languages) - [Optional] - locale: - Language of the search query. Currently only 'ja' is effective. This is - intended for language-specific consumers and the default should work in - the majority of cases. - result_type: - Type of result which should be returned. Default is "mixed". Other - valid options are "recent" and "popular". [Optional] - include_entities: - If True, each tweet will include a node called "entities,". - This node offers a variety of metadata about the tweet in a - discrete structure, including: user_mentions, urls, and - hashtags. [Optional] - - Returns: - A sequence of twitter.Status instances, one for each message containing - the term - ''' - # Build request parameters - parameters = {} - - if since_id: - try: - parameters['since_id'] = long(since_id) - except: - raise TwitterError("since_id must be an integer") - - if max_id: - try: - parameters['max_id'] = long(max_id) - except: - raise TwitterError("max_id must be an integer") - - if until: - parameters['until'] = until - - if lang: - parameters['lang'] = lang - - if locale: - parameters['locale'] = locale - - if term is None and geocode is None: - return [] - - if term is not None: - parameters['q'] = term - - if geocode is not None: - parameters['geocode'] = ','.join(map(str, geocode)) - - if include_entities: - parameters['include_entities'] = 1 - - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - - if result_type in ["mixed", "popular", "recent"]: - parameters['result_type'] = result_type - - # Make and send requests - url = '%s/search/tweets.json' % self.base_url - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - - # Return built list of statuses - return [Status.NewFromJsonDict(x) for x in data['statuses']] - - def GetUsersSearch(self, - term=None, - page=1, - count=20, - include_entities=None): - '''Return twitter user search results for a given term. - - Args: - term: - Term to search by. - page: - Page of results to return. Default is 1 - [Optional] - count: - Number of results to return. Default is 20 - [Optional] - include_entities: - If True, each tweet will include a node called "entities,". - This node offers a variety of metadata about the tweet in a - discrete structure, including: user_mentions, urls, and hashtags. - [Optional] - - Returns: - A sequence of twitter.User instances, one for each message containing - the term - ''' - # Build request parameters - parameters = {} - - if term is not None: - parameters['q'] = term - - if include_entities: - parameters['include_entities'] = 1 - - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - - # Make and send requests - url = '%s/users/search.json' % self.base_url - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [User.NewFromJsonDict(x) for x in data] - - def GetTrendsCurrent(self, exclude=None): - '''Get the current top trending topics (global) - - Args: - exclude: - Appends the exclude parameter as a request parameter. - Currently only exclude=hashtags is supported. [Optional] - - Returns: - A list with 10 entries. Each entry contains a trend. - ''' - return self.GetTrendsWoeid(id=1, exclude=exclude) - - def GetTrendsWoeid(self, id, exclude=None): - '''Return the top 10 trending topics for a specific WOEID, if trending - information is available for it. - - Args: - woeid: - the Yahoo! Where On Earth ID for a location. - exclude: - Appends the exclude parameter as a request parameter. - Currently only exclude=hashtags is supported. [Optional] - - Returns: - A list with 10 entries. Each entry contains a trend. - ''' - url = '%s/trends/place.json' % (self.base_url) - parameters = {'id': id} - - if exclude: - parameters['exclude'] = exclude - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - - trends = [] - timestamp = data[0]['as_of'] - - for trend in data[0]['trends']: - trends.append(Trend.NewFromJsonDict(trend, timestamp = timestamp)) - return trends - - def GetHomeTimeline(self, - count=None, - since_id=None, - max_id=None, - trim_user=False, - exclude_replies=False, - contributor_details=False, - include_entities=True): - ''' - Fetch a collection of the most recent Tweets and retweets posted by the - authenticating user and the users they follow. - - The home timeline is central to how most users interact with the Twitter - service. - - The twitter.Api instance must be authenticated. - - Args: - count: - Specifies the number of statuses to retrieve. May not be - greater than 200. Defaults to 20. [Optional] - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns results with an ID less than (that is, older than) or - equal to the specified ID. [Optional] - trim_user: - When True, each tweet returned in a timeline will include a user - object including only the status authors numerical ID. Omit this - parameter to receive the complete user object. [Optional] - exclude_replies: - This parameter will prevent replies from appearing in the - returned timeline. Using exclude_replies with the count - parameter will mean you will receive up-to count tweets - - this is because the count parameter retrieves that many - tweets before filtering out retweets and replies. - [Optional] - contributor_details: - This parameter enhances the contributors element of the - status response to include the screen_name of the contributor. - By default only the user_id of the contributor is included. - [Optional] - include_entities: - The entities node will be disincluded when set to false. - This node offers a variety of metadata about the tweet in a - discreet structure, including: user_mentions, urls, and - hashtags. [Optional] - - Returns: - A sequence of twitter.Status instances, one for each message - ''' - url = '%s/statuses/home_timeline.json' % self.base_url - - if not self._oauth_consumer: - raise TwitterError("API must be authenticated.") - parameters = {} - if count is not None: - try: - if int(count) > 200: - raise TwitterError("'count' may not be greater than 200") - except ValueError: - raise TwitterError("'count' must be an integer") - parameters['count'] = count - if since_id: - try: - parameters['since_id'] = long(since_id) - except ValueError: - raise TwitterError("'since_id' must be an integer") - if max_id: - try: - parameters['max_id'] = long(max_id) - except ValueError: - raise TwitterError("'max_id' must be an integer") - if trim_user: - parameters['trim_user'] = 1 - if exclude_replies: - parameters['exclude_replies'] = 1 - if contributor_details: - parameters['contributor_details'] = 1 - if not include_entities: - parameters['include_entities'] = 'false' - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(x) for x in data] - - def GetUserTimeline(self, - user_id=None, - screen_name=None, - since_id=None, - max_id=None, - count=None, - include_rts=None, - trim_user=None, - exclude_replies=None): - '''Fetch the sequence of public Status messages for a single user. - - The twitter.Api instance must be authenticated if the user is private. - - Args: - user_id: - Specifies the ID of the user for whom to return the - user_timeline. Helpful for disambiguating when a valid user ID - is also a valid screen name. [Optional] - screen_name: - Specifies the screen name of the user for whom to return the - user_timeline. Helpful for disambiguating when a valid screen - name is also a user ID. [Optional] - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns only statuses with an ID less than (that is, older - than) or equal to the specified ID. [Optional] - count: - Specifies the number of statuses to retrieve. May not be - greater than 200. [Optional] - include_rts: - If True, the timeline will contain native retweets (if they - exist) in addition to the standard stream of tweets. [Optional] - trim_user: - If True, statuses will only contain the numerical user ID only. - Otherwise a full user object will be returned for each status. - [Optional] - exclude_replies: - If True, this will prevent replies from appearing in the returned - timeline. Using exclude_replies with the count parameter will mean you - will receive up-to count tweets - this is because the count parameter - retrieves that many tweets before filtering out retweets and replies. - This parameter is only supported for JSON and XML responses. [Optional] - - Returns: - A sequence of Status instances, one for each message up to count - ''' - parameters = {} - - url = '%s/statuses/user_timeline.json' % (self.base_url) - - if user_id: - parameters['user_id'] = user_id - elif screen_name: - parameters['screen_name'] = screen_name - - if since_id: - try: - parameters['since_id'] = long(since_id) - except: - raise TwitterError("since_id must be an integer") - - if max_id: - try: - parameters['max_id'] = long(max_id) - except: - raise TwitterError("max_id must be an integer") - - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - - if include_rts: - parameters['include_rts'] = 1 - - if trim_user: - parameters['trim_user'] = 1 - - if exclude_replies: - parameters['exclude_replies'] = 1 - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(x) for x in data] - - def GetStatus(self, - id, - trim_user=False, - include_my_retweet=True, - include_entities=True): - '''Returns a single status message, specified by the id parameter. - - The twitter.Api instance must be authenticated. - - Args: - id: - The numeric ID of the status you are trying to retrieve. - trim_user: - When set to True, each tweet returned in a timeline will include - a user object including only the status authors numerical ID. - Omit this parameter to receive the complete user object. - [Optional] - include_my_retweet: - When set to True, any Tweets returned that have been retweeted by - the authenticating user will include an additional - current_user_retweet node, containing the ID of the source status - for the retweet. [Optional] - include_entities: - If False, the entities node will be disincluded. - This node offers a variety of metadata about the tweet in a - discreet structure, including: user_mentions, urls, and - hashtags. [Optional] - Returns: - A twitter.Status instance representing that status message - ''' - url = '%s/statuses/show.json' % (self.base_url) - - if not self._oauth_consumer: - raise TwitterError("API must be authenticated.") - - parameters = {} - - try: - parameters['id'] = long(id) - except ValueError: - raise TwitterError("'id' must be an integer.") - - if trim_user: - parameters['trim_user'] = 1 - if include_my_retweet: - parameters['include_my_retweet'] = 1 - if not include_entities: - parameters['include_entities'] = 'none' - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - def DestroyStatus(self, id, trim_user=False): - '''Destroys the status specified by the required ID parameter. - - The twitter.Api instance must be authenticated and the - authenticating user must be the author of the specified status. - - Args: - id: - The numerical ID of the status you're trying to destroy. - - Returns: - A twitter.Status instance representing the destroyed status message - ''' - if not self._oauth_consumer: - raise TwitterError("API must be authenticated.") - - try: - post_data = {'id': long(id)} - except: - raise TwitterError("id must be an integer") - url = '%s/statuses/destroy/%s.json' % (self.base_url, id) - if trim_user: - post_data['trim_user'] = 1 - json = self._FetchUrl(url, post_data=post_data) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - @classmethod - def _calculate_status_length(cls, status, linksize=19): - dummy_link_replacement = 'https://-%d-chars%s/' % (linksize, '-'*(linksize - 18)) - shortened = ' '.join([x if not (x.startswith('http://') or - x.startswith('https://')) - else - dummy_link_replacement - for x in status.split(' ')]) - return len(shortened) - - def PostUpdate(self, status, in_reply_to_status_id=None, latitude=None, longitude=None, place_id=None, display_coordinates=False, trim_user=False): - '''Post a twitter status message from the authenticated user. - - The twitter.Api instance must be authenticated. - - https://dev.twitter.com/docs/api/1.1/post/statuses/update - - Args: - status: - The message text to be posted. - Must be less than or equal to 140 characters. - in_reply_to_status_id: - The ID of an existing status that the status to be posted is - in reply to. This implicitly sets the in_reply_to_user_id - attribute of the resulting status to the user ID of the - message being replied to. Invalid/missing status IDs will be - ignored. [Optional] - latitude: - Latitude coordinate of the tweet in degrees. Will only work - in conjunction with longitude argument. Both longitude and - latitude will be ignored by twitter if the user has a false - geo_enabled setting. [Optional] - longitude: - Longitude coordinate of the tweet in degrees. Will only work - in conjunction with latitude argument. Both longitude and - latitude will be ignored by twitter if the user has a false - geo_enabled setting. [Optional] - place_id: - A place in the world. These IDs can be retrieved from - GET geo/reverse_geocode. [Optional] - display_coordinates: - Whether or not to put a pin on the exact coordinates a tweet - has been sent from. [Optional] - trim_user: - If True the returned payload will only contain the user IDs, - otherwise the payload will contain the full user data item. - [Optional] - Returns: - A twitter.Status instance representing the message posted. - ''' - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - - url = '%s/statuses/update.json' % self.base_url - - if isinstance(status, unicode) or self._input_encoding is None: - u_status = status - else: - u_status = unicode(status, self._input_encoding) - - #if self._calculate_status_length(u_status, self._shortlink_size) > CHARACTER_LIMIT: - # raise TwitterError("Text must be less than or equal to %d characters. " - # "Consider using PostUpdates." % CHARACTER_LIMIT) - - data = {'status': status} - if in_reply_to_status_id: - data['in_reply_to_status_id'] = in_reply_to_status_id - if latitude is not None and longitude is not None: - data['lat'] = str(latitude) - data['long'] = str(longitude) - if place_id is not None: - data['place_id'] = str(place_id) - if display_coordinates: - data['display_coordinates'] = 'true' - if trim_user: - data['trim_user'] = 'true' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - def PostUpdates(self, status, continuation=None, **kwargs): - '''Post one or more twitter status messages from the authenticated user. - - Unlike api.PostUpdate, this method will post multiple status updates - if the message is longer than 140 characters. - - The twitter.Api instance must be authenticated. - - Args: - status: - The message text to be posted. - May be longer than 140 characters. - continuation: - The character string, if any, to be appended to all but the - last message. Note that Twitter strips trailing '...' strings - from messages. Consider using the unicode \u2026 character - (horizontal ellipsis) instead. [Defaults to None] - **kwargs: - See api.PostUpdate for a list of accepted parameters. - - Returns: - A of list twitter.Status instance representing the messages posted. - ''' - results = list() - if continuation is None: - continuation = '' - line_length = CHARACTER_LIMIT - len(continuation) - lines = textwrap.wrap(status, line_length) - for line in lines[0:-1]: - results.append(self.PostUpdate(line + continuation, **kwargs)) - results.append(self.PostUpdate(lines[-1], **kwargs)) - return results - - def PostRetweet(self, original_id, trim_user=False): - '''Retweet a tweet with the Retweet API. - - The twitter.Api instance must be authenticated. - - Args: - original_id: - The numerical id of the tweet that will be retweeted - trim_user: - If True the returned payload will only contain the user IDs, - otherwise the payload will contain the full user data item. - [Optional] - - Returns: - A twitter.Status instance representing the original tweet with retweet details embedded. - ''' - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - - try: - if int(original_id) <= 0: - raise TwitterError("'original_id' must be a positive number") - except ValueError: - raise TwitterError("'original_id' must be an integer") - - url = '%s/statuses/retweet/%s.json' % (self.base_url, original_id) - - data = {'id': original_id} - if trim_user: - data['trim_user'] = 'true' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - def GetUserRetweets(self, count=None, since_id=None, max_id=None, trim_user=False): - '''Fetch the sequence of retweets made by the authenticated user. - - The twitter.Api instance must be authenticated. - - Args: - count: - The number of status messages to retrieve. [Optional] - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns results with an ID less than (that is, older than) or - equal to the specified ID. [Optional] - trim_user: - If True the returned payload will only contain the user IDs, - otherwise the payload will contain the full user data item. - [Optional] - - Returns: - A sequence of twitter.Status instances, one for each message up to count - ''' - return self.GetUserTimeline(since_id=since_id, count=count, max_id=max_id, trim_user=trim_user, exclude_replies=True, include_rts=True) - - def GetReplies(self, since_id=None, count=None, max_id=None, trim_user=False): - '''Get a sequence of status messages representing the 20 most - recent replies (status updates prefixed with @twitterID) to the - authenticating user. - - Args: - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns results with an ID less than (that is, older than) or - equal to the specified ID. [Optional] - trim_user: - If True the returned payload will only contain the user IDs, - otherwise the payload will contain the full user data item. - [Optional] - - Returns: - A sequence of twitter.Status instances, one for each reply to the user. - ''' - return self.GetUserTimeline(since_id=since_id, count=count, max_id=max_id, trim_user=trim_user, exclude_replies=False, include_rts=False) - - def GetRetweets(self, statusid, count=None, trim_user=False): - '''Returns up to 100 of the first retweets of the tweet identified - by statusid - - Args: - statusid: - The ID of the tweet for which retweets should be searched for - count: - The number of status messages to retrieve. [Optional] - trim_user: - If True the returned payload will only contain the user IDs, - otherwise the payload will contain the full user data item. - [Optional] - - Returns: - A list of twitter.Status instances, which are retweets of statusid - ''' - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instsance must be authenticated.") - url = '%s/statuses/retweets/%s.json' % (self.base_url, statusid) - parameters = {} - if trim_user: - parameters['trim_user'] = 'true' - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(s) for s in data] - - def GetRetweetsOfMe(self, - count=None, - since_id=None, - max_id=None, - trim_user=False, - include_entities=True, - include_user_entities=True): - '''Returns up to 100 of the most recent tweets of the user that have been - retweeted by others. - - Args: - count: - The number of retweets to retrieve, up to 100. If omitted, 20 is - assumed. - since_id: - Returns results with an ID greater than (newer than) this ID. - max_id: - Returns results with an ID less than or equal to this ID. - trim_user: - When True, the user object for each tweet will only be an ID. - include_entities: - When True, the tweet entities will be included. - include_user_entities: - When True, the user entities will be included. - ''' - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - url = '%s/statuses/retweets_of_me.json' % self.base_url - parameters = {} - if count is not None: - try: - if int(count) > 100: - raise TwitterError("'count' may not be greater than 100") - except ValueError: - raise TwitterError("'count' must be an integer") - if count: - parameters['count'] = count - if since_id: - parameters['since_id'] = since_id - if max_id: - parameters['max_id'] = max_id - if trim_user: - parameters['trim_user'] = trim_user - if not include_entities: - parameters['include_entities'] = include_entities - if not include_user_entities: - parameters['include_user_entities'] = include_user_entities - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(s) for s in data] - - def GetFriends(self, user_id=None, screen_name=None, cursor=-1, skip_status=False, include_user_entities=False): - '''Fetch the sequence of twitter.User instances, one for each friend. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - The twitter id of the user whose friends you are fetching. - If not specified, defaults to the authenticated user. [Optional] - screen_name: - The twitter name of the user whose friends you are fetching. - If not specified, defaults to the authenticated user. [Optional] - cursor: - Should be set to -1 for the initial call and then is used to - control what result page Twitter returns [Optional(ish)] - skip_status: - If True the statuses will not be returned in the user items. - [Optional] - include_user_entities: - When True, the user entities will be included. - - Returns: - A sequence of twitter.User instances, one for each friend - ''' - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - url = '%s/friends/list.json' % self.base_url - result = [] - parameters = {} - if user_id is not None: - parameters['user_id'] = user_id - if screen_name is not None: - parameters['screen_name'] = screen_name - if skip_status: - parameters['skip_status'] = True - if include_user_entities: - parameters['include_user_entities'] = True - while True: - parameters['cursor'] = cursor - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - result += [User.NewFromJsonDict(x) for x in data['users']] - if 'next_cursor' in data: - if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']: - break - else: - cursor = data['next_cursor'] - else: - break - return result - - def GetFriendIDs(self, user_id=None, screen_name=None, cursor=-1, stringify_ids=False, count=None): - '''Returns a list of twitter user id's for every person - the specified user is following. - - Args: - user_id: - The id of the user to retrieve the id list for - [Optional] - screen_name: - The screen_name of the user to retrieve the id list for - [Optional] - cursor: - Specifies the Twitter API Cursor location to start at. - Note: there are pagination limits. - [Optional] - stringify_ids: - if True then twitter will return the ids as strings instead of integers. - [Optional] - count: - The number of status messages to retrieve. [Optional] - - Returns: - A list of integers, one for each user id. - ''' - url = '%s/friends/ids.json' % self.base_url - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - parameters = {} - if user_id is not None: - parameters['user_id'] = user_id - if screen_name is not None: - parameters['screen_name'] = screen_name - if stringify_ids: - parameters['stringify_ids'] = True - if count is not None: - parameters['count'] = count - result = [] - while True: - parameters['cursor'] = cursor - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - result += [x for x in data['ids']] - if 'next_cursor' in data: - if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']: - break - else: - cursor = data['next_cursor'] - else: - break - return result - - - def GetFollowerIDs(self, user_id=None, screen_name=None, cursor=-1, stringify_ids=False, count=None, total_count=None): - '''Returns a list of twitter user id's for every person - that is following the specified user. - - Args: - user_id: - The id of the user to retrieve the id list for - [Optional] - screen_name: - The screen_name of the user to retrieve the id list for - [Optional] - cursor: - Specifies the Twitter API Cursor location to start at. - Note: there are pagination limits. - [Optional] - stringify_ids: - if True then twitter will return the ids as strings instead of integers. - [Optional] - count: - The number of user id's to retrieve per API request. Please be aware that - this might get you rate-limited if set to a small number. By default Twitter - will retrieve 5000 UIDs per call. - [Optional] - total_count: - The total amount of UIDs to retrieve. Good if the account has many followers - and you don't want to get rate limited. The data returned might contain more - UIDs if total_count is not a multiple of count (5000 by default). - [Optional] - - - Returns: - A list of integers, one for each user id. - ''' - url = '%s/followers/ids.json' % self.base_url - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - parameters = {} - if user_id is not None: - parameters['user_id'] = user_id - if screen_name is not None: - parameters['screen_name'] = screen_name - if stringify_ids: - parameters['stringify_ids'] = True - if count is not None: - parameters['count'] = count - result = [] - while True: - if total_count and total_count < count: - parameters['count'] = total_count - parameters['cursor'] = cursor - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - result += [x for x in data['ids']] - if 'next_cursor' in data: - if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']: - break - else: - cursor = data['next_cursor'] - total_count -= len(data['ids']) - if total_count < 1: - break - else: - break - return result - - def GetFollowers(self, user_id=None, screen_name=None, cursor=-1, skip_status=False, include_user_entities=False): - '''Fetch the sequence of twitter.User instances, one for each follower - - The twitter.Api instance must be authenticated. - - Args: - user_id: - The twitter id of the user whose followers you are fetching. - If not specified, defaults to the authenticated user. [Optional] - screen_name: - The twitter name of the user whose followers you are fetching. - If not specified, defaults to the authenticated user. [Optional] - cursor: - Should be set to -1 for the initial call and then is used to - control what result page Twitter returns [Optional(ish)] - skip_status: - If True the statuses will not be returned in the user items. - [Optional] - include_user_entities: - When True, the user entities will be included. - - Returns: - A sequence of twitter.User instances, one for each follower - ''' - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - url = '%s/followers/list.json' % self.base_url - result = [] - parameters = {} - if user_id is not None: - parameters['user_id'] = user_id - if screen_name is not None: - parameters['screen_name'] = screen_name - if skip_status: - parameters['skip_status'] = True - if include_user_entities: - parameters['include_user_entities'] = True - while True: - parameters['cursor'] = cursor - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - result += [User.NewFromJsonDict(x) for x in data['users']] - if 'next_cursor' in data: - if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']: - break - else: - cursor = data['next_cursor'] - else: - break - return result - - def UsersLookup(self, user_id=None, screen_name=None, users=None, include_entities=True): - '''Fetch extended information for the specified users. - - Users may be specified either as lists of either user_ids, - screen_names, or twitter.User objects. The list of users that - are queried is the union of all specified parameters. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - A list of user_ids to retrieve extended information. - [Optional] - screen_name: - A list of screen_names to retrieve extended information. - [Optional] - users: - A list of twitter.User objects to retrieve extended information. - [Optional] - include_entities: - The entities node that may appear within embedded statuses will be - disincluded when set to False. - [Optional] - - Returns: - A list of twitter.User objects for the requested users - ''' - - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - if not user_id and not screen_name and not users: - raise TwitterError("Specify at least one of user_id, screen_name, or users.") - url = '%s/users/lookup.json' % self.base_url - parameters = {} - uids = list() - if user_id: - uids.extend(user_id) - if users: - uids.extend([u.id for u in users]) - if len(uids): - parameters['user_id'] = ','.join(["%s" % u for u in uids]) - if screen_name: - parameters['screen_name'] = ','.join(screen_name) - if not include_entities: - parameters['include_entities'] = 'false' - json = self._FetchUrl(url, parameters=parameters) - try: - data = self._ParseAndCheckTwitter(json) - except TwitterError, e: - _, e, _ = sys.exc_info() - t = e.args[0] - if len(t) == 1 and ('code' in t[0]) and (t[0]['code'] == 34): - data = [] - else: - raise - - return [User.NewFromJsonDict(u) for u in data] - - def GetUser(self, user_id=None, screen_name=None, include_entities=True): - '''Returns a single user. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - The id of the user to retrieve. - [Optional] - screen_name: - The screen name of the user for whom to return results for. Either a - user_id or screen_name is required for this method. - [Optional] - include_entities: - if set to False, the 'entities' node will not be included. - [Optional] - - - Returns: - A twitter.User instance representing that user - ''' - url = '%s/users/show.json' % (self.base_url) - parameters = {} - - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - - if user_id: - parameters['user_id'] = user_id - elif screen_name: - parameters['screen_name'] = screen_name - else: - raise TwitterError("Specify at least one of user_id or screen_name.") - - if not include_entities: - parameters['include_entities'] = 'false' - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return User.NewFromJsonDict(data) - - def GetDirectMessages(self, since_id=None, max_id=None, count=None, include_entities=True, skip_status=False): - '''Returns a list of the direct messages sent to the authenticating user. - - The twitter.Api instance must be authenticated. - - Args: - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns results with an ID less than (that is, older than) or - equal to the specified ID. [Optional] - count: - Specifies the number of direct messages to try and retrieve, up to a - maximum of 200. The value of count is best thought of as a limit to the - number of Tweets to return because suspended or deleted content is - removed after the count has been applied. [Optional] - include_entities: - The entities node will not be included when set to False. - [Optional] - skip_status: - When set to True statuses will not be included in the returned user - objects. [Optional] - - Returns: - A sequence of twitter.DirectMessage instances - ''' - url = '%s/direct_messages.json' % self.base_url - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - parameters = {} - if since_id: - parameters['since_id'] = since_id - if max_id: - parameters['max_id'] = max_id - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - if not include_entities: - parameters['include_entities'] = 'false' - if skip_status: - parameters['skip_status'] = 1 - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [DirectMessage.NewFromJsonDict(x) for x in data] - - def GetSentDirectMessages(self, since_id=None, max_id=None, count=None, page=None, include_entities=True): - '''Returns a list of the direct messages sent by the authenticating user. - - The twitter.Api instance must be authenticated. - - Args: - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occured since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns results with an ID less than (that is, older than) or - equal to the specified ID. [Optional] - count: - Specifies the number of direct messages to try and retrieve, up to a - maximum of 200. The value of count is best thought of as a limit to the - number of Tweets to return because suspended or deleted content is - removed after the count has been applied. [Optional] - page: - Specifies the page of results to retrieve. - Note: there are pagination limits. [Optional] - include_entities: - The entities node will not be included when set to False. - [Optional] - - Returns: - A sequence of twitter.DirectMessage instances - ''' - url = '%s/direct_messages/sent.json' % self.base_url - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - parameters = {} - if since_id: - parameters['since_id'] = since_id - if page: - parameters['page'] = page - if max_id: - parameters['max_id'] = max_id - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - if not include_entities: - parameters['include_entities'] = 'false' - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [DirectMessage.NewFromJsonDict(x) for x in data] - - def PostDirectMessage(self, text, user_id=None, screen_name=None): - '''Post a twitter direct message from the authenticated user - - The twitter.Api instance must be authenticated. user_id or screen_name - must be specified. - - Args: - text: The message text to be posted. Must be less than 140 characters. - user_id: - The ID of the user who should receive the direct message. - [Optional] - screen_name: - The screen name of the user who should receive the direct message. - [Optional] - - Returns: - A twitter.DirectMessage instance representing the message posted - ''' - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - url = '%s/direct_messages/new.json' % self.base_url - data = {'text': text} - if user_id: - data['user_id'] = user_id - elif screen_name: - data['screen_name'] = screen_name - else: - raise TwitterError("Specify at least one of user_id or screen_name.") - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return DirectMessage.NewFromJsonDict(data) - - def DestroyDirectMessage(self, id, include_entities=True): - '''Destroys the direct message specified in the required ID parameter. - - The twitter.Api instance must be authenticated, and the - authenticating user must be the recipient of the specified direct - message. - - Args: - id: The id of the direct message to be destroyed - - Returns: - A twitter.DirectMessage instance representing the message destroyed - ''' - url = '%s/direct_messages/destroy.json' % self.base_url - data = {'id': id} - if not include_entities: - data['include_entities'] = 'false' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return DirectMessage.NewFromJsonDict(data) - - def CreateFriendship(self, user_id=None, screen_name=None, follow=True): - '''Befriends the user specified by the user_id or screen_name. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - A user_id to follow [Optional] - screen_name: - A screen_name to follow [Optional] - follow: - Set to False to disable notifications for the target user - Returns: - A twitter.User instance representing the befriended user. - ''' - url = '%s/friendships/create.json' % (self.base_url) - data = {} - if user_id: - data['user_id'] = user_id - elif screen_name: - data['screen_name'] = screen_name - else: - raise TwitterError("Specify at least one of user_id or screen_name.") - if follow: - data['follow'] = 'true' - else: - data['follow'] = 'false' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return User.NewFromJsonDict(data) - - def DestroyFriendship(self, user_id=None, screen_name=None): - '''Discontinues friendship with a user_id or screen_name. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - A user_id to unfollow [Optional] - screen_name: - A screen_name to unfollow [Optional] - Returns: - A twitter.User instance representing the discontinued friend. - ''' - url = '%s/friendships/destroy.json' % self.base_url - data = {} - if user_id: - data['user_id'] = user_id - elif screen_name: - data['screen_name'] = screen_name - else: - raise TwitterError("Specify at least one of user_id or screen_name.") - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return User.NewFromJsonDict(data) - - def CreateFavorite(self, status=None, id=None, include_entities=True): - '''Favorites the specified status object or id as the authenticating user. - Returns the favorite status when successful. - - The twitter.Api instance must be authenticated. - - Args: - id: - The id of the twitter status to mark as a favorite. - [Optional] - status: - The twitter.Status object to mark as a favorite. - [Optional] - include_entities: - The entities node will be omitted when set to False. - Returns: - A twitter.Status instance representing the newly-marked favorite. - ''' - url = '%s/favorites/create.json' % self.base_url - data = {} - if id: - data['id'] = id - elif status: - data['id'] = status.id - else: - raise TwitterError("Specify id or status") - if not include_entities: - data['include_entities'] = 'false' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - def DestroyFavorite(self, status=None, id=None, include_entities=True): - '''Un-Favorites the specified status object or id as the authenticating user. - Returns the un-favorited status when successful. - - The twitter.Api instance must be authenticated. - - Args: - id: - The id of the twitter status to unmark as a favorite. - [Optional] - status: - The twitter.Status object to unmark as a favorite. - [Optional] - include_entities: - The entities node will be omitted when set to False. - Returns: - A twitter.Status instance representing the newly-unmarked favorite. - ''' - url = '%s/favorites/destroy.json' % self.base_url - data = {} - if id: - data['id'] = id - elif status: - data['id'] = status.id - else: - raise TwitterError("Specify id or status") - if not include_entities: - data['include_entities'] = 'false' - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return Status.NewFromJsonDict(data) - - def GetFavorites(self, - user_id=None, - screen_name=None, - count=None, - since_id=None, - max_id=None, - include_entities=True): - '''Return a list of Status objects representing favorited tweets. - By default, returns the (up to) 20 most recent tweets for the - authenticated user. - - Args: - user: - The twitter name or id of the user whose favorites you are fetching. - If not specified, defaults to the authenticated user. [Optional] - page: - Specifies the page of results to retrieve. - Note: there are pagination limits. [Optional] - ''' - parameters = {} - - url = '%s/favorites/list.json' % self.base_url - - if user_id: - parameters['user_id'] = user_id - elif screen_name: - parameters['screen_name'] = user_id - - if since_id: - try: - parameters['since_id'] = long(since_id) - except: - raise TwitterError("since_id must be an integer") - - if max_id: - try: - parameters['max_id'] = long(max_id) - except: - raise TwitterError("max_id must be an integer") - - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - - if include_entities: - parameters['include_entities'] = True - - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(x) for x in data] - - def GetMentions(self, - count=None, - since_id=None, - max_id=None, - trim_user=False, - contributor_details=False, - include_entities=True): - '''Returns the 20 most recent mentions (status containing @screen_name) - for the authenticating user. - - Args: - count: - Specifies the number of tweets to try and retrieve, up to a maximum of - 200. The value of count is best thought of as a limit to the number of - tweets to return because suspended or deleted content is removed after - the count has been applied. [Optional] - since_id: - Returns results with an ID greater than (that is, more recent - than) the specified ID. There are limits to the number of - Tweets which can be accessed through the API. If the limit of - Tweets has occurred since the since_id, the since_id will be - forced to the oldest ID available. [Optional] - max_id: - Returns only statuses with an ID less than - (that is, older than) the specified ID. [Optional] - trim_user: - When set to True, each tweet returned in a timeline will include a user - object including only the status authors numerical ID. Omit this - parameter to receive the complete user object. - contributor_details: - If set to True, this parameter enhances the contributors element of the - status response to include the screen_name of the contributor. By - default only the user_id of the contributor is included. - include_entities: - The entities node will be disincluded when set to False. - - Returns: - A sequence of twitter.Status instances, one for each mention of the user. - ''' - - url = '%s/statuses/mentions_timeline.json' % self.base_url - - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - - parameters = {} - - if count: - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - if since_id: - try: - parameters['since_id'] = long(since_id) - except: - raise TwitterError("since_id must be an integer") - if max_id: - try: - parameters['max_id'] = long(max_id) - except: - raise TwitterError("max_id must be an integer") - if trim_user: - parameters['trim_user'] = 1 - if contributor_details: - parameters['contributor_details'] = 'true' - if not include_entities: - parameters['include_entities'] = 'false' - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [Status.NewFromJsonDict(x) for x in data] - - def CreateList(self, name, mode=None, description=None): - '''Creates a new list with the give name for the authenticated user. - - The twitter.Api instance must be authenticated. - - Args: - name: - New name for the list - mode: - 'public' or 'private'. - Defaults to 'public'. [Optional] - description: - Description of the list. [Optional] - - Returns: - A twitter.List instance representing the new list - ''' - url = '%s/lists/create.json' % self.base_url - - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - parameters = {'name': name} - if mode is not None: - parameters['mode'] = mode - if description is not None: - parameters['description'] = description - json = self._FetchUrl(url, post_data=parameters) - data = self._ParseAndCheckTwitter(json) - return List.NewFromJsonDict(data) - - def DestroyList(self, - owner_screen_name=False, - owner_id=False, - list_id=None, - slug=None): - ''' - Destroys the list identified by list_id or owner_screen_name/owner_id and - slug. - - The twitter.Api instance must be authenticated. - - Args: - owner_screen_name: - The screen_name of the user who owns the list being requested by a slug. - owner_id: - The user ID of the user who owns the list being requested by a slug. - list_id: - The numerical id of the list. - slug: - You can identify a list by its slug instead of its numerical id. If you - decide to do so, note that you'll also have to specify the list owner - using the owner_id or owner_screen_name parameters. - Returns: - A twitter.List instance representing the removed list. - ''' - url = '%s/lists/destroy.json' % self.base_url - data = {} - if list_id: - try: - data['list_id']= long(list_id) - except: - raise TwitterError("list_id must be an integer") - elif slug: - data['slug'] = slug - if owner_id: - try: - data['owner_id'] = long(owner_id) - except: - raise TwitterError("owner_id must be an integer") - elif owner_screen_name: - data['owner_screen_name'] = owner_screen_name - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return List.NewFromJsonDict(data) - - def CreateSubscription(self, - owner_screen_name=False, - owner_id=False, - list_id=None, - slug=None): - '''Creates a subscription to a list by the authenticated user - - The twitter.Api instance must be authenticated. - - Args: - owner_screen_name: - The screen_name of the user who owns the list being requested by a slug. - owner_id: - The user ID of the user who owns the list being requested by a slug. - list_id: - The numerical id of the list. - slug: - You can identify a list by its slug instead of its numerical id. If you - decide to do so, note that you'll also have to specify the list owner - using the owner_id or owner_screen_name parameters. - Returns: - A twitter.List instance representing the list subscribed to - ''' - url = '%s/lists/subscribers/create.json' % (self.base_url) - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - data = {} - if list_id: - try: - data['list_id']= long(list_id) - except: - raise TwitterError("list_id must be an integer") - elif slug: - data['slug'] = slug - if owner_id: - try: - data['owner_id'] = long(owner_id) - except: - raise TwitterError("owner_id must be an integer") - elif owner_screen_name: - data['owner_screen_name'] = owner_screen_name - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return List.NewFromJsonDict(data) - - def DestroySubscription(self, - owner_screen_name=False, - owner_id=False, - list_id=None, - slug=None): - '''Destroys the subscription to a list for the authenticated user - - The twitter.Api instance must be authenticated. - - Args: - owner_screen_name: - The screen_name of the user who owns the list being requested by a slug. - owner_id: - The user ID of the user who owns the list being requested by a slug. - list_id: - The numerical id of the list. - slug: - You can identify a list by its slug instead of its numerical id. If you - decide to do so, note that you'll also have to specify the list owner - using the owner_id or owner_screen_name parameters. - Returns: - A twitter.List instance representing the removed list. - ''' - url = '%s/lists/subscribers/destroy.json' % (self.base_url) - if not self._oauth_consumer: - raise TwitterError("The twitter.Api instance must be authenticated.") - data = {} - if list_id: - try: - data['list_id']= long(list_id) - except: - raise TwitterError("list_id must be an integer") - elif slug: - data['slug'] = slug - if owner_id: - try: - data['owner_id'] = long(owner_id) - except: - raise TwitterError("owner_id must be an integer") - elif owner_screen_name: - data['owner_screen_name'] = owner_screen_name - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - else: - raise TwitterError("Identify list by list_id or owner_screen_name/owner_id and slug") - json = self._FetchUrl(url, post_data=data) - data = self._ParseAndCheckTwitter(json) - return List.NewFromJsonDict(data) - - def GetSubscriptions(self, user_id=None, screen_name=None, count=20, cursor=-1): - ''' - Obtain a collection of the lists the specified user is subscribed to, 20 - lists per page by default. Does not include the user's own lists. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - The ID of the user for whom to return results for. [Optional] - screen_name: - The screen name of the user for whom to return results for. - [Optional] - count: - The amount of results to return per page. Defaults to 20. - No more than 1000 results will ever be returned in a single page. - cursor: - "page" value that Twitter will use to start building the - list sequence from. -1 to start at the beginning. - Twitter will return in the result the values for next_cursor - and previous_cursor. [Optional] - - Returns: - A sequence of twitter.List instances, one for each list - ''' - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - - url = '%s/lists/subscriptions.json' % (self.base_url) - parameters = {} - - try: - parameters['cursor'] = int(cursor) - except: - raise TwitterError("cursor must be an integer") - - try: - parameters['count'] = int(count) - except: - raise TwitterError("count must be an integer") - - if user_id is not None: - try: - parameters['user_id'] = long(user_id) - except: - raise TwitterError('user_id must be an integer') - elif screen_name is not None: - parameters['screen_name'] = screen_name - else: - raise TwitterError('Specify user_id or screen_name') - - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - return [List.NewFromJsonDict(x) for x in data['lists']] - - def GetLists(self, user_id=None, screen_name=None, count=None, cursor=-1): - '''Fetch the sequence of lists for a user. - - The twitter.Api instance must be authenticated. - - Args: - user_id: - The ID of the user for whom to return results for. [Optional] - screen_name: - The screen name of the user for whom to return results for. - [Optional] - count: - The amount of results to return per page. Defaults to 20. No more than - 1000 results will ever be returned in a single page. - [Optional] - cursor: - "page" value that Twitter will use to start building the - list sequence from. -1 to start at the beginning. - Twitter will return in the result the values for next_cursor - and previous_cursor. [Optional] - - Returns: - A sequence of twitter.List instances, one for each list - ''' - if not self._oauth_consumer: - raise TwitterError("twitter.Api instance must be authenticated") - - url = '%s/lists/ownerships.json' % self.base_url - result = [] - parameters = {} - if user_id is not None: - try: - parameters['user_id'] = long(user_id) - except: - raise TwitterError('user_id must be an integer') - elif screen_name is not None: - parameters['screen_name'] = screen_name - else: - raise TwitterError('Specify user_id or screen_name') - if count is not None: - parameters['count'] = count - - while True: - parameters['cursor'] = cursor - json = self._FetchUrl(url, parameters=parameters) - data = self._ParseAndCheckTwitter(json) - result += [List.NewFromJsonDict(x) for x in data['lists']] - if 'next_cursor' in data: - if data['next_cursor'] == 0 or data['next_cursor'] == data['previous_cursor']: - break - else: - cursor = data['next_cursor'] - else: - break - return result - - def VerifyCredentials(self): - '''Returns a twitter.User instance if the authenticating user is valid. - - Returns: - A twitter.User instance representing that user if the - credentials are valid, None otherwise. - ''' - if not self._oauth_consumer: - raise TwitterError("Api instance must first be given user credentials.") - url = '%s/account/verify_credentials.json' % self.base_url - try: - json = self._FetchUrl(url, no_cache=True) - except urllib2.HTTPError, http_error: - if http_error.code == httplib.UNAUTHORIZED: - return None - else: - raise http_error - data = self._ParseAndCheckTwitter(json) - return User.NewFromJsonDict(data) - - def SetCache(self, cache): - '''Override the default cache. Set to None to prevent caching. - - Args: - cache: - An instance that supports the same API as the twitter._FileCache - ''' - if cache == DEFAULT_CACHE: - self._cache = _FileCache() - else: - self._cache = cache - - def SetUrllib(self, urllib): - '''Override the default urllib implementation. - - Args: - urllib: - An instance that supports the same API as the urllib2 module - ''' - self._urllib = urllib - - def SetCacheTimeout(self, cache_timeout): - '''Override the default cache timeout. - - Args: - cache_timeout: - Time, in seconds, that responses should be reused. - ''' - self._cache_timeout = cache_timeout - - def SetUserAgent(self, user_agent): - '''Override the default user agent - - Args: - user_agent: - A string that should be send to the server as the User-agent - ''' - self._request_headers['User-Agent'] = user_agent - - def SetXTwitterHeaders(self, client, url, version): - '''Set the X-Twitter HTTP headers that will be sent to the server. - - Args: - client: - The client name as a string. Will be sent to the server as - the 'X-Twitter-Client' header. - url: - The URL of the meta.xml as a string. Will be sent to the server - as the 'X-Twitter-Client-URL' header. - version: - The client version as a string. Will be sent to the server - as the 'X-Twitter-Client-Version' header. - ''' - self._request_headers['X-Twitter-Client'] = client - self._request_headers['X-Twitter-Client-URL'] = url - self._request_headers['X-Twitter-Client-Version'] = version - - def SetSource(self, source): - '''Suggest the "from source" value to be displayed on the Twitter web site. - - The value of the 'source' parameter must be first recognized by - the Twitter server. New source values are authorized on a case by - case basis by the Twitter development team. - - Args: - source: - The source name as a string. Will be sent to the server as - the 'source' parameter. - ''' - self._default_params['source'] = source - - def GetRateLimitStatus(self, resources=None): - '''Fetch the rate limit status for the currently authorized user. - - Args: - resources: - A comma seperated list of resource families you want to know the current - rate limit disposition of. - [Optional] - - Returns: - A dictionary containing the time the limit will reset (reset_time), - the number of remaining hits allowed before the reset (remaining_hits), - the number of hits allowed in a 60-minute period (hourly_limit), and - the time of the reset in seconds since The Epoch (reset_time_in_seconds). - ''' - parameters = {} - if resources is not None: - parameters['resources'] = resources - - url = '%s/application/rate_limit_status.json' % self.base_url - json = self._FetchUrl(url, parameters=parameters, no_cache=True) - data = self._ParseAndCheckTwitter(json) - return data - - def MaximumHitFrequency(self): - '''Determines the minimum number of seconds that a program must wait - before hitting the server again without exceeding the rate_limit - imposed for the currently authenticated user. - - Returns: - The minimum second interval that a program must use so as to not - exceed the rate_limit imposed for the user. - ''' - rate_status = self.GetRateLimitStatus() - reset_time = rate_status.get('reset_time', None) - limit = rate_status.get('remaining_hits', None) - - if reset_time: - # put the reset time into a datetime object - reset = datetime.datetime(*rfc822.parsedate(reset_time)[:7]) - - # find the difference in time between now and the reset time + 1 hour - delta = reset + datetime.timedelta(hours=1) - datetime.datetime.utcnow() - - if not limit: - return int(delta.seconds) - - # determine the minimum number of seconds allowed as a regular interval - max_frequency = int(delta.seconds / limit) + 1 - - # return the number of seconds - return max_frequency - - return 60 - - def _BuildUrl(self, url, path_elements=None, extra_params=None): - # Break url into constituent parts - (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) - - # Add any additional path elements to the path - if path_elements: - # Filter out the path elements that have a value of None - p = [i for i in path_elements if i] - if not path.endswith('/'): - path += '/' - path += '/'.join(p) - - # Add any additional query parameters to the query string - if extra_params and len(extra_params) > 0: - extra_query = self._EncodeParameters(extra_params) - # Add it to the existing query - if query: - query += '&' + extra_query - else: - query = extra_query - - # Return the rebuilt URL - return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) - - def _InitializeRequestHeaders(self, request_headers): - if request_headers: - self._request_headers = request_headers - else: - self._request_headers = {} - - def _InitializeUserAgent(self): - user_agent = 'Python-urllib/%s (python-twitter/%s)' % \ - (self._urllib.__version__, __version__) - self.SetUserAgent(user_agent) - - def _InitializeDefaultParameters(self): - self._default_params = {} - - def _DecompressGzippedResponse(self, response): - raw_data = response.read() - if response.headers.get('content-encoding', None) == 'gzip': - url_data = gzip.GzipFile(fileobj=StringIO.StringIO(raw_data)).read() - else: - url_data = raw_data - return url_data - - def _Encode(self, s): - if self._input_encoding: - return unicode(s, self._input_encoding).encode('utf-8') - else: - return unicode(s).encode('utf-8') - - def _EncodeParameters(self, parameters): - '''Return a string in key=value&key=value form - - Values of None are not included in the output string. - - Args: - parameters: - A dict of (key, value) tuples, where value is encoded as - specified by self._encoding - - Returns: - A URL-encoded string in "key=value&key=value" form - ''' - if parameters is None: - return None - else: - return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None])) - - def _EncodePostData(self, post_data): - '''Return a string in key=value&key=value form - - Values are assumed to be encoded in the format specified by self._encoding, - and are subsequently URL encoded. - - Args: - post_data: - A dict of (key, value) tuples, where value is encoded as - specified by self._encoding - - Returns: - A URL-encoded string in "key=value&key=value" form - ''' - if post_data is None: - return None - else: - return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()])) - - def _ParseAndCheckTwitter(self, json): - """Try and parse the JSON returned from Twitter and return - an empty dictionary if there is any error. This is a purely - defensive check because during some Twitter network outages - it will return an HTML failwhale page.""" - try: - data = simplejson.loads(json) - self._CheckForTwitterError(data) - except ValueError: - if "Twitter / Over capacity" in json: - raise TwitterError("Capacity Error") - if "Twitter / Error" in json: - raise TwitterError("Technical Error") - raise TwitterError("json decoding") - - return data - - def _CheckForTwitterError(self, data): - """Raises a TwitterError if twitter returns an error message. - - Args: - data: - A python dict created from the Twitter json response - - Raises: - TwitterError wrapping the twitter error message if one exists. - """ - # Twitter errors are relatively unlikely, so it is faster - # to check first, rather than try and catch the exception - if 'error' in data: - raise TwitterError(data['error']) - if 'errors' in data: - raise TwitterError(data['errors']) - - def _FetchUrl(self, - url, - post_data=None, - parameters=None, - no_cache=None, - use_gzip_compression=None): - '''Fetch a URL, optionally caching for a specified time. - - Args: - url: - The URL to retrieve - post_data: - A dict of (str, unicode) key/value pairs. - If set, POST will be used. - parameters: - A dict whose key/value pairs should encoded and added - to the query string. [Optional] - no_cache: - If true, overrides the cache on the current request - use_gzip_compression: - If True, tells the server to gzip-compress the response. - It does not apply to POST requests. - Defaults to None, which will get the value to use from - the instance variable self._use_gzip [Optional] - - Returns: - A string containing the body of the response. - ''' - # Build the extra parameters dict - extra_params = {} - if self._default_params: - extra_params.update(self._default_params) - if parameters: - extra_params.update(parameters) - - if post_data: - http_method = "POST" - else: - http_method = "GET" - - if self._debugHTTP: - _debug = 1 - else: - _debug = 0 - - http_handler = self._urllib.HTTPHandler(debuglevel=_debug) - https_handler = self._urllib.HTTPSHandler(debuglevel=_debug) - http_proxy = os.environ.get('http_proxy') - https_proxy = os.environ.get('https_proxy') - - if http_proxy is None or https_proxy is None : - proxy_status = False - else : - proxy_status = True - - opener = self._urllib.OpenerDirector() - opener.add_handler(http_handler) - opener.add_handler(https_handler) - - if proxy_status is True : - proxy_handler = self._urllib.ProxyHandler({'http':str(http_proxy),'https': str(https_proxy)}) - opener.add_handler(proxy_handler) - - if use_gzip_compression is None: - use_gzip = self._use_gzip - else: - use_gzip = use_gzip_compression - - # Set up compression - if use_gzip and not post_data: - opener.addheaders.append(('Accept-Encoding', 'gzip')) - - if self._oauth_consumer is not None: - if post_data and http_method == "POST": - parameters = post_data.copy() - - req = oauth.Request.from_consumer_and_token(self._oauth_consumer, - token=self._oauth_token, - http_method=http_method, - http_url=url, parameters=parameters) - - req.sign_request(self._signature_method_hmac_sha1, self._oauth_consumer, self._oauth_token) - - headers = req.to_header() - - if http_method == "POST": - encoded_post_data = req.to_postdata() - else: - encoded_post_data = None - url = req.to_url() - else: - url = self._BuildUrl(url, extra_params=extra_params) - encoded_post_data = self._EncodePostData(post_data) - - # Open and return the URL immediately if we're not going to cache - if encoded_post_data or no_cache or not self._cache or not self._cache_timeout: - response = opener.open(url, encoded_post_data) - url_data = self._DecompressGzippedResponse(response) - opener.close() - else: - # Unique keys are a combination of the url and the oAuth Consumer Key - if self._consumer_key: - key = self._consumer_key + ':' + url - else: - key = url - - # See if it has been cached before - last_cached = self._cache.GetCachedTime(key) - - # If the cached version is outdated then fetch another and store it - if not last_cached or time.time() >= last_cached + self._cache_timeout: - try: - response = opener.open(url, encoded_post_data) - url_data = self._DecompressGzippedResponse(response) - self._cache.Set(key, url_data) - except urllib2.HTTPError, e: - print e - opener.close() - else: - url_data = self._cache.Get(key) - - # Always return the latest version - return url_data - -class _FileCacheError(Exception): - '''Base exception class for FileCache related errors''' - -class _FileCache(object): - - DEPTH = 3 - - def __init__(self,root_directory=None): - self._InitializeRootDirectory(root_directory) - - def Get(self,key): - path = self._GetPath(key) - if os.path.exists(path): - return open(path).read() - else: - return None - - def Set(self,key,data): - path = self._GetPath(key) - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - if not os.path.isdir(directory): - raise _FileCacheError('%s exists but is not a directory' % directory) - temp_fd, temp_path = tempfile.mkstemp() - temp_fp = os.fdopen(temp_fd, 'w') - temp_fp.write(data) - temp_fp.close() - if not path.startswith(self._root_directory): - raise _FileCacheError('%s does not appear to live under %s' % - (path, self._root_directory)) - if os.path.exists(path): - os.remove(path) - os.rename(temp_path, path) - - def Remove(self,key): - path = self._GetPath(key) - if not path.startswith(self._root_directory): - raise _FileCacheError('%s does not appear to live under %s' % - (path, self._root_directory )) - if os.path.exists(path): - os.remove(path) - - def GetCachedTime(self,key): - path = self._GetPath(key) - if os.path.exists(path): - return os.path.getmtime(path) - else: - return None - - def _GetUsername(self): - '''Attempt to find the username in a cross-platform fashion.''' - try: - return os.getenv('USER') or \ - os.getenv('LOGNAME') or \ - os.getenv('USERNAME') or \ - os.getlogin() or \ - 'nobody' - except (AttributeError, IOError, OSError), e: - return 'nobody' - - def _GetTmpCachePath(self): - username = self._GetUsername() - cache_directory = 'python.cache_' + username - return os.path.join(tempfile.gettempdir(), cache_directory) - - def _InitializeRootDirectory(self, root_directory): - if not root_directory: - root_directory = self._GetTmpCachePath() - root_directory = os.path.abspath(root_directory) - if not os.path.exists(root_directory): - os.mkdir(root_directory) - if not os.path.isdir(root_directory): - raise _FileCacheError('%s exists but is not a directory' % - root_directory) - self._root_directory = root_directory - - def _GetPath(self,key): - try: - hashed_key = md5(key).hexdigest() - except TypeError: - hashed_key = md5.new(key).hexdigest() - - return os.path.join(self._root_directory, - self._GetPrefix(hashed_key), - hashed_key) - - def _GetPrefix(self,hashed_key): - return os.path.sep.join(hashed_key[0:_FileCache.DEPTH]) diff --git a/lib/requests_oauthlib/__init__.py b/lib/requests_oauthlib/__init__.py new file mode 100644 index 00000000..8b320523 --- /dev/null +++ b/lib/requests_oauthlib/__init__.py @@ -0,0 +1,22 @@ +from .oauth1_auth import OAuth1 +from .oauth1_session import OAuth1Session +from .oauth2_auth import OAuth2 +from .oauth2_session import OAuth2Session, TokenUpdated + +__version__ = '0.6.1' + +import requests +if requests.__version__ < '2.0.0': + msg = ('You are using requests version %s, which is older than ' + 'requests-oauthlib expects, please upgrade to 2.0.0 or later.') + raise Warning(msg % requests.__version__) + +import logging +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +logging.getLogger('requests_oauthlib').addHandler(NullHandler()) diff --git a/lib/requests_oauthlib/compliance_fixes/__init__.py b/lib/requests_oauthlib/compliance_fixes/__init__.py new file mode 100644 index 00000000..46eacb8b --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import + +from .facebook import facebook_compliance_fix +from .linkedin import linkedin_compliance_fix +from .slack import slack_compliance_fix +from .mailchimp import mailchimp_compliance_fix +from .weibo import weibo_compliance_fix diff --git a/lib/requests_oauthlib/compliance_fixes/douban.py b/lib/requests_oauthlib/compliance_fixes/douban.py new file mode 100644 index 00000000..2e45b3b9 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/douban.py @@ -0,0 +1,18 @@ +import json + +from oauthlib.common import to_unicode + + +def douban_compliance_fix(session): + + def fix_token_type(r): + token = json.loads(r.text) + token.setdefault('token_type', 'Bearer') + fixed_token = json.dumps(token) + r._content = to_unicode(fixed_token).encode('utf-8') + return r + + session._client_default_token_placement = 'query' + session.register_compliance_hook('access_token_response', fix_token_type) + + return session diff --git a/lib/requests_oauthlib/compliance_fixes/facebook.py b/lib/requests_oauthlib/compliance_fixes/facebook.py new file mode 100644 index 00000000..07181c39 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/facebook.py @@ -0,0 +1,33 @@ +from json import dumps +try: + from urlparse import parse_qsl +except ImportError: + from urllib.parse import parse_qsl + +from oauthlib.common import to_unicode + + +def facebook_compliance_fix(session): + + def _compliance_fix(r): + # if Facebook claims to be sending us json, let's trust them. + if 'application/json' in r.headers.get('content-type', {}): + return r + + # Facebook returns a content-type of text/plain when sending their + # x-www-form-urlencoded responses, along with a 200. If not, let's + # assume we're getting JSON and bail on the fix. + if 'text/plain' in r.headers.get('content-type', {}) and r.status_code == 200: + token = dict(parse_qsl(r.text, keep_blank_values=True)) + else: + return r + + expires = token.get('expires') + if expires is not None: + token['expires_in'] = expires + token['token_type'] = 'Bearer' + r._content = to_unicode(dumps(token)).encode('UTF-8') + return r + + session.register_compliance_hook('access_token_response', _compliance_fix) + return session diff --git a/lib/requests_oauthlib/compliance_fixes/linkedin.py b/lib/requests_oauthlib/compliance_fixes/linkedin.py new file mode 100644 index 00000000..e697ced9 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/linkedin.py @@ -0,0 +1,24 @@ +from json import loads, dumps + +from oauthlib.common import add_params_to_uri, to_unicode + + +def linkedin_compliance_fix(session): + + def _missing_token_type(r): + token = loads(r.text) + token['token_type'] = 'Bearer' + r._content = to_unicode(dumps(token)).encode('UTF-8') + return r + + def _non_compliant_param_name(url, headers, data): + token = [('oauth2_access_token', session.access_token)] + url = add_params_to_uri(url, token) + return url, headers, data + + session._client.default_token_placement = 'query' + session.register_compliance_hook('access_token_response', + _missing_token_type) + session.register_compliance_hook('protected_request', + _non_compliant_param_name) + return session diff --git a/lib/requests_oauthlib/compliance_fixes/mailchimp.py b/lib/requests_oauthlib/compliance_fixes/mailchimp.py new file mode 100644 index 00000000..ee9bc942 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/mailchimp.py @@ -0,0 +1,22 @@ +import json + +from oauthlib.common import to_unicode + +def mailchimp_compliance_fix(session): + def _null_scope(r): + token = json.loads(r.text) + if 'scope' in token and token['scope'] is None: + token.pop('scope') + r._content = to_unicode(json.dumps(token)).encode('utf-8') + return r + + def _non_zero_expiration(r): + token = json.loads(r.text) + if 'expires_in' in token and token['expires_in'] == 0: + token['expires_in'] = 3600 + r._content = to_unicode(json.dumps(token)).encode('utf-8') + return r + + session.register_compliance_hook('access_token_response', _null_scope) + session.register_compliance_hook('access_token_response', _non_zero_expiration) + return session diff --git a/lib/requests_oauthlib/compliance_fixes/slack.py b/lib/requests_oauthlib/compliance_fixes/slack.py new file mode 100644 index 00000000..ab2d9382 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/slack.py @@ -0,0 +1,37 @@ +try: + from urlparse import urlparse, parse_qs +except ImportError: + from urllib.parse import urlparse, parse_qs + +from oauthlib.common import add_params_to_uri + + +def slack_compliance_fix(session): + def _non_compliant_param_name(url, headers, data): + # If the user has already specified the token, either in the URL + # or in a data dictionary, then there's nothing to do. + # If the specified token is different from ``session.access_token``, + # we assume the user intends to override the access token. + url_query = dict(parse_qs(urlparse(url).query)) + token = url_query.get("token") + if not token and isinstance(data, dict): + token = data.get("token") + + if token: + # Nothing to do, just return. + return url, headers, data + + if not data: + data = {"token": session.access_token} + elif isinstance(data, dict): + data["token"] = session.access_token + else: + # ``data`` is something other than a dict: maybe a stream, + # maybe a file object, maybe something else. We can't easily + # modify it, so we'll set the token by modifying the URL instead. + token = [('token', session.access_token)] + url = add_params_to_uri(url, token) + return url, headers, data + + session.register_compliance_hook('protected_request', _non_compliant_param_name) + return session diff --git a/lib/requests_oauthlib/compliance_fixes/weibo.py b/lib/requests_oauthlib/compliance_fixes/weibo.py new file mode 100644 index 00000000..28aca327 --- /dev/null +++ b/lib/requests_oauthlib/compliance_fixes/weibo.py @@ -0,0 +1,17 @@ +from json import loads, dumps + +from oauthlib.common import to_unicode + + +def weibo_compliance_fix(session): + + def _missing_token_type(r): + token = loads(r.text) + token['token_type'] = 'Bearer' + r._content = to_unicode(dumps(token)).encode('UTF-8') + return r + + session._client.default_token_placement = 'query' + session.register_compliance_hook('access_token_response', + _missing_token_type) + return session diff --git a/lib/requests_oauthlib/oauth1_auth.py b/lib/requests_oauthlib/oauth1_auth.py new file mode 100644 index 00000000..46263841 --- /dev/null +++ b/lib/requests_oauthlib/oauth1_auth.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import logging + +from oauthlib.common import extract_params +from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER +from oauthlib.oauth1 import SIGNATURE_TYPE_BODY +from requests.compat import is_py3 +from requests.utils import to_native_string +from requests.auth import AuthBase + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' +CONTENT_TYPE_MULTI_PART = 'multipart/form-data' + +if is_py3: + unicode = str + +log = logging.getLogger(__name__) + +# OBS!: Correct signing of requests are conditional on invoking OAuth1 +# as the last step of preparing a request, or at least having the +# content-type set properly. +class OAuth1(AuthBase): + """Signs the request using OAuth 1 (RFC5849)""" + + client_class = Client + + def __init__(self, client_key, + client_secret=None, + resource_owner_key=None, + resource_owner_secret=None, + callback_uri=None, + signature_method=SIGNATURE_HMAC, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + rsa_key=None, verifier=None, + decoding='utf-8', + client_class=None, + force_include_body=False, + **kwargs): + + try: + signature_type = signature_type.upper() + except AttributeError: + pass + + client_class = client_class or self.client_class + + self.force_include_body = force_include_body + + self.client = client_class(client_key, client_secret, resource_owner_key, + resource_owner_secret, callback_uri, signature_method, + signature_type, rsa_key, verifier, decoding=decoding, **kwargs) + + def __call__(self, r): + """Add OAuth parameters to the request. + + Parameters may be included from the body if the content-type is + urlencoded, if no content type is set a guess is made. + """ + # Overwriting url is safe here as request will not modify it past + # this point. + log.debug('Signing request %s using client %s', r, self.client) + + content_type = r.headers.get('Content-Type', '') + if (not content_type and extract_params(r.body) + or self.client.signature_type == SIGNATURE_TYPE_BODY): + content_type = CONTENT_TYPE_FORM_URLENCODED + if not isinstance(content_type, unicode): + content_type = content_type.decode('utf-8') + + is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) + + log.debug('Including body in call to sign: %s', + is_form_encoded or self.force_include_body) + + if is_form_encoded: + r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED + r.url, headers, r.body = self.client.sign( + unicode(r.url), unicode(r.method), r.body or '', r.headers) + elif self.force_include_body: + # To allow custom clients to work on non form encoded bodies. + r.url, headers, r.body = self.client.sign( + unicode(r.url), unicode(r.method), r.body or '', r.headers) + else: + # Omit body data in the signing of non form-encoded requests + r.url, headers, _ = self.client.sign( + unicode(r.url), unicode(r.method), None, r.headers) + + r.prepare_headers(headers) + r.url = to_native_string(r.url) + log.debug('Updated url: %s', r.url) + log.debug('Updated headers: %s', headers) + log.debug('Updated body: %r', r.body) + return r diff --git a/lib/requests_oauthlib/oauth1_session.py b/lib/requests_oauthlib/oauth1_session.py new file mode 100644 index 00000000..ad7b9069 --- /dev/null +++ b/lib/requests_oauthlib/oauth1_session.py @@ -0,0 +1,378 @@ +from __future__ import unicode_literals + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + +import logging + +from oauthlib.common import add_params_to_uri +from oauthlib.common import urldecode as _urldecode +from oauthlib.oauth1 import ( + SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER +) +import requests + +from . import OAuth1 + +import sys +if sys.version > "3": + unicode = str + + +log = logging.getLogger(__name__) + + +def urldecode(body): + """Parse query or json to python dictionary""" + try: + return _urldecode(body) + except: + import json + return json.loads(body) + + +class TokenRequestDenied(ValueError): + + def __init__(self, message, response): + super(TokenRequestDenied, self).__init__(message) + self.response = response + + @property + def status_code(self): + """For backwards-compatibility purposes""" + return self.response.status_code + + +class TokenMissing(ValueError): + def __init__(self, message, response): + super(TokenMissing, self).__init__(message) + self.response = response + + +class VerifierMissing(ValueError): + pass + + +class OAuth1Session(requests.Session): + """Request signing and convenience methods for the oauth dance. + + What is the difference between OAuth1Session and OAuth1? + + OAuth1Session actually uses OAuth1 internally and its purpose is to assist + in the OAuth workflow through convenience methods to prepare authorization + URLs and parse the various token and redirection responses. It also provide + rudimentary validation of responses. + + An example of the OAuth workflow using a basic CLI app and Twitter. + + >>> # Credentials obtained during the registration. + >>> client_key = 'client key' + >>> client_secret = 'secret' + >>> callback_uri = 'https://127.0.0.1/callback' + >>> + >>> # Endpoints found in the OAuth provider API documentation + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + >>> + >>> oauth_session = OAuth1Session(client_key,client_secret=client_secret, callback_uri=callback_uri) + >>> + >>> # First step, fetch the request token. + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'kjerht2309u', + 'oauth_token_secret': 'lsdajfh923874', + } + >>> + >>> # Second step. Follow this link and authorize + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback' + >>> + >>> # Third step. Fetch the access token + >>> redirect_response = raw_input('Paste the full redirect URL here.') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + >>> oauth_session.fetch_access_token(access_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> # Done. You can now make OAuth requests. + >>> status_url = 'http://api.twitter.com/1/statuses/update.json' + >>> new_status = {'status': 'hello world!'} + >>> oauth_session.post(status_url, data=new_status) + + """ + + def __init__(self, client_key, + client_secret=None, + resource_owner_key=None, + resource_owner_secret=None, + callback_uri=None, + signature_method=SIGNATURE_HMAC, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + rsa_key=None, + verifier=None, + client_class=None, + force_include_body=False, + **kwargs): + """Construct the OAuth 1 session. + + :param client_key: A client specific identifier. + :param client_secret: A client specific secret used to create HMAC and + plaintext signatures. + :param resource_owner_key: A resource owner key, also referred to as + request token or access token depending on + when in the workflow it is used. + :param resource_owner_secret: A resource owner secret obtained with + either a request or access token. Often + referred to as token secret. + :param callback_uri: The URL the user is redirect back to after + authorization. + :param signature_method: Signature methods determine how the OAuth + signature is created. The three options are + oauthlib.oauth1.SIGNATURE_HMAC (default), + oauthlib.oauth1.SIGNATURE_RSA and + oauthlib.oauth1.SIGNATURE_PLAIN. + :param signature_type: Signature type decides where the OAuth + parameters are added. Either in the + Authorization header (default) or to the URL + query parameters or the request body. Defined as + oauthlib.oauth1.SIGNATURE_TYPE_AUTH_HEADER, + oauthlib.oauth1.SIGNATURE_TYPE_QUERY and + oauthlib.oauth1.SIGNATURE_TYPE_BODY + respectively. + :param rsa_key: The private RSA key as a string. Can only be used with + signature_method=oauthlib.oauth1.SIGNATURE_RSA. + :param verifier: A verifier string to prove authorization was granted. + :param client_class: A subclass of `oauthlib.oauth1.Client` to use with + `requests_oauthlib.OAuth1` instead of the default + :param force_include_body: Always include the request body in the + signature creation. + :param **kwargs: Additional keyword arguments passed to `OAuth1` + """ + super(OAuth1Session, self).__init__() + self._client = OAuth1(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret, + callback_uri=callback_uri, + signature_method=signature_method, + signature_type=signature_type, + rsa_key=rsa_key, + verifier=verifier, + client_class=client_class, + force_include_body=force_include_body, + **kwargs) + self.auth = self._client + + @property + def authorized(self): + """Boolean that indicates whether this session has an OAuth token + or not. If `self.authorized` is True, you can reasonably expect + OAuth-protected requests to the resource to succeed. If + `self.authorized` is False, you need the user to go through the OAuth + authentication dance before OAuth-protected requests to the resource + will succeed. + """ + if self._client.client.signature_method == SIGNATURE_RSA: + # RSA only uses resource_owner_key + return bool(self._client.client.resource_owner_key) + else: + # other methods of authentication use all three pieces + return ( + bool(self._client.client.client_secret) and + bool(self._client.client.resource_owner_key) and + bool(self._client.client.resource_owner_secret) + ) + + def authorization_url(self, url, request_token=None, **kwargs): + """Create an authorization URL by appending request_token and optional + kwargs to url. + + This is the second step in the OAuth 1 workflow. The user should be + redirected to this authorization URL, grant access to you, and then + be redirected back to you. The redirection back can either be specified + during client registration or by supplying a callback URI per request. + + :param url: The authorization endpoint URL. + :param request_token: The previously obtained request token. + :param kwargs: Optional parameters to append to the URL. + :returns: The authorization URL with new parameters embedded. + + An example using a registered default callback URI. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf' + >>> oauth_session.authorization_url(authorization_url, foo='bar') + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&foo=bar' + + An example using an explicit callback URI. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret', callback_uri='https://127.0.0.1/callback') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback' + """ + kwargs['oauth_token'] = request_token or self._client.client.resource_owner_key + log.debug('Adding parameters %s to url %s', kwargs, url) + return add_params_to_uri(url, kwargs.items()) + + def fetch_request_token(self, url, realm=None, **request_kwargs): + """Fetch a request token. + + This is the first step in the OAuth 1 workflow. A request token is + obtained by making a signed post request to url. The token is then + parsed from the application/x-www-form-urlencoded response and ready + to be used to construct an authorization url. + + :param url: The request token endpoint URL. + :param realm: A list of realms to request access to. + :param \*\*request_kwargs: Optional arguments passed to ''post'' + function in ''requests.Session'' + :returns: The response in dict format. + + Note that a previously set callback_uri will be reset for your + convenience, or else signature creation will be incorrect on + consecutive requests. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + """ + self._client.client.realm = ' '.join(realm) if realm else None + token = self._fetch_token(url, **request_kwargs) + log.debug('Resetting callback_uri and realm (not needed in next phase).') + self._client.client.callback_uri = None + self._client.client.realm = None + return token + + def fetch_access_token(self, url, verifier=None, **request_kwargs): + """Fetch an access token. + + This is the final step in the OAuth 1 workflow. An access token is + obtained using all previously obtained credentials, including the + verifier from the authorization step. + + Note that a previously set verifier will be reset for your + convenience, or else signature creation will be incorrect on + consecutive requests. + + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + >>> oauth_session.fetch_access_token(access_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + """ + if verifier: + self._client.client.verifier = verifier + if not getattr(self._client.client, 'verifier', None): + raise VerifierMissing('No client verifier has been set.') + token = self._fetch_token(url, **request_kwargs) + log.debug('Resetting verifier attribute, should not be used anymore.') + self._client.client.verifier = None + return token + + def parse_authorization_response(self, url): + """Extract parameters from the post authorization redirect response URL. + + :param url: The full URL that resulted from the user being redirected + back from the OAuth provider to you, the client. + :returns: A dict of parameters extracted from the URL. + + >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + """ + log.debug('Parsing token from query part of url %s', url) + token = dict(urldecode(urlparse(url).query)) + log.debug('Updating internal client token attribute.') + self._populate_attributes(token) + return token + + def _populate_attributes(self, token): + if 'oauth_token' in token: + self._client.client.resource_owner_key = token['oauth_token'] + else: + raise TokenMissing( + 'Response does not contain a token: {resp}'.format(resp=token), + token, + ) + if 'oauth_token_secret' in token: + self._client.client.resource_owner_secret = ( + token['oauth_token_secret']) + if 'oauth_verifier' in token: + self._client.client.verifier = token['oauth_verifier'] + + def _fetch_token(self, url, **request_kwargs): + log.debug('Fetching token from %s using client %s', url, self._client.client) + r = self.post(url, **request_kwargs) + + if r.status_code >= 400: + error = "Token request failed with code %s, response was '%s'." + raise TokenRequestDenied(error % (r.status_code, r.text), r) + + log.debug('Decoding token from response "%s"', r.text) + try: + token = dict(urldecode(r.text)) + except ValueError as e: + error = ("Unable to decode token from token response. " + "This is commonly caused by an unsuccessful request where" + " a non urlencoded error message is returned. " + "The decoding error was %s""" % e) + raise ValueError(error) + + log.debug('Obtained token %s', token) + log.debug('Updating internal client attributes from token data.') + self._populate_attributes(token) + return token + + def rebuild_auth(self, prepared_request, response): + """ + When being redirected we should always strip Authorization + header, since nonce may not be reused as per OAuth spec. + """ + if 'Authorization' in prepared_request.headers: + # If we get redirected to a new host, we should strip out + # any authentication headers. + prepared_request.headers.pop('Authorization', True) + prepared_request.prepare_auth(self.auth) + return diff --git a/lib/requests_oauthlib/oauth2_auth.py b/lib/requests_oauthlib/oauth2_auth.py new file mode 100644 index 00000000..0ce58cc9 --- /dev/null +++ b/lib/requests_oauthlib/oauth2_auth.py @@ -0,0 +1,36 @@ +from __future__ import unicode_literals +from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError +from oauthlib.oauth2 import is_secure_transport +from requests.auth import AuthBase + + +class OAuth2(AuthBase): + """Adds proof of authorization (OAuth2 token) to the request.""" + + def __init__(self, client_id=None, client=None, token=None): + """Construct a new OAuth 2 authorization object. + + :param client_id: Client id obtained during registration + :param client: :class:`oauthlib.oauth2.Client` to be used. Default is + WebApplicationClient which is useful for any + hosted application but not mobile or desktop. + :param token: Token dictionary, must include access_token + and token_type. + """ + self._client = client or WebApplicationClient(client_id, token=token) + if token: + for k, v in token.items(): + setattr(self._client, k, v) + + def __call__(self, r): + """Append an OAuth 2 token to the request. + + Note that currently HTTPS is required for all requests. There may be + a token type that allows for plain HTTP in the future and then this + should be updated to allow plain HTTP on a white list basis. + """ + if not is_secure_transport(r.url): + raise InsecureTransportError() + r.url, r.headers, r.body = self._client.add_token(r.url, + http_method=r.method, body=r.body, headers=r.headers) + return r diff --git a/lib/requests_oauthlib/oauth2_session.py b/lib/requests_oauthlib/oauth2_session.py new file mode 100644 index 00000000..b026a7f3 --- /dev/null +++ b/lib/requests_oauthlib/oauth2_session.py @@ -0,0 +1,359 @@ +from __future__ import unicode_literals + +import logging + +from oauthlib.common import generate_token, urldecode +from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError +from oauthlib.oauth2 import TokenExpiredError, is_secure_transport +import requests + +log = logging.getLogger(__name__) + + +class TokenUpdated(Warning): + def __init__(self, token): + super(TokenUpdated, self).__init__() + self.token = token + + +class OAuth2Session(requests.Session): + """Versatile OAuth 2 extension to :class:`requests.Session`. + + Supports any grant type adhering to :class:`oauthlib.oauth2.Client` spec + including the four core OAuth 2 grants. + + Can be used to create authorization urls, fetch tokens and access protected + resources using the :class:`requests.Session` interface you are used to. + + - :class:`oauthlib.oauth2.WebApplicationClient` (default): Authorization Code Grant + - :class:`oauthlib.oauth2.MobileApplicationClient`: Implicit Grant + - :class:`oauthlib.oauth2.LegacyApplicationClient`: Password Credentials Grant + - :class:`oauthlib.oauth2.BackendApplicationClient`: Client Credentials Grant + + Note that the only time you will be using Implicit Grant from python is if + you are driving a user agent able to obtain URL fragments. + """ + + def __init__(self, client_id=None, client=None, auto_refresh_url=None, + auto_refresh_kwargs=None, scope=None, redirect_uri=None, token=None, + state=None, token_updater=None, **kwargs): + """Construct a new OAuth 2 client session. + + :param client_id: Client id obtained during registration + :param client: :class:`oauthlib.oauth2.Client` to be used. Default is + WebApplicationClient which is useful for any + hosted application but not mobile or desktop. + :param scope: List of scopes you wish to request access to + :param redirect_uri: Redirect URI you registered as callback + :param token: Token dictionary, must include access_token + and token_type. + :param state: State string used to prevent CSRF. This will be given + when creating the authorization url and must be supplied + when parsing the authorization response. + Can be either a string or a no argument callable. + :auto_refresh_url: Refresh token endpoint URL, must be HTTPS. Supply + this if you wish the client to automatically refresh + your access tokens. + :auto_refresh_kwargs: Extra arguments to pass to the refresh token + endpoint. + :token_updater: Method with one argument, token, to be used to update + your token databse on automatic token refresh. If not + set a TokenUpdated warning will be raised when a token + has been refreshed. This warning will carry the token + in its token argument. + :param kwargs: Arguments to pass to the Session constructor. + """ + super(OAuth2Session, self).__init__(**kwargs) + self._client = client or WebApplicationClient(client_id, token=token) + self.token = token or {} + self.scope = scope + self.redirect_uri = redirect_uri + self.state = state or generate_token + self._state = state + self.auto_refresh_url = auto_refresh_url + self.auto_refresh_kwargs = auto_refresh_kwargs or {} + self.token_updater = token_updater + + # Allow customizations for non compliant providers through various + # hooks to adjust requests and responses. + self.compliance_hook = { + 'access_token_response': set([]), + 'refresh_token_response': set([]), + 'protected_request': set([]), + } + + def new_state(self): + """Generates a state string to be used in authorizations.""" + try: + self._state = self.state() + log.debug('Generated new state %s.', self._state) + except TypeError: + self._state = self.state + log.debug('Re-using previously supplied state %s.', self._state) + return self._state + + @property + def client_id(self): + return getattr(self._client, "client_id", None) + + @client_id.setter + def client_id(self, value): + self._client.client_id = value + + @client_id.deleter + def client_id(self): + del self._client.client_id + + @property + def token(self): + return getattr(self._client, "token", None) + + @token.setter + def token(self, value): + self._client.token = value + self._client._populate_attributes(value) + + @property + def access_token(self): + return getattr(self._client, "access_token", None) + + @access_token.setter + def access_token(self, value): + self._client.access_token = value + + @access_token.deleter + def access_token(self): + del self._client.access_token + + @property + def authorized(self): + """Boolean that indicates whether this session has an OAuth token + or not. If `self.authorized` is True, you can reasonably expect + OAuth-protected requests to the resource to succeed. If + `self.authorized` is False, you need the user to go through the OAuth + authentication dance before OAuth-protected requests to the resource + will succeed. + """ + return bool(self.access_token) + + def authorization_url(self, url, state=None, **kwargs): + """Form an authorization URL. + + :param url: Authorization endpoint url, must be HTTPS. + :param state: An optional state string for CSRF protection. If not + given it will be generated for you. + :param kwargs: Extra parameters to include. + :return: authorization_url, state + """ + state = state or self.new_state() + return self._client.prepare_request_uri(url, + redirect_uri=self.redirect_uri, + scope=self.scope, + state=state, + **kwargs), state + + def fetch_token(self, token_url, code=None, authorization_response=None, + body='', auth=None, username=None, password=None, method='POST', + timeout=None, headers=None, verify=True, **kwargs): + """Generic method for fetching an access token from the token endpoint. + + If you are using the MobileApplicationClient you will want to use + token_from_fragment instead of fetch_token. + + :param token_url: Token endpoint URL, must use HTTPS. + :param code: Authorization code (used by WebApplicationClients). + :param authorization_response: Authorization response URL, the callback + URL of the request back to you. Used by + WebApplicationClients instead of code. + :param body: Optional application/x-www-form-urlencoded body to add the + include in the token request. Prefer kwargs over body. + :param auth: An auth tuple or method as accepted by requests. + :param username: Username used by LegacyApplicationClients. + :param password: Password used by LegacyApplicationClients. + :param method: The HTTP method used to make the request. Defaults + to POST, but may also be GET. Other methods should + be added as needed. + :param headers: Dict to default request headers with. + :param timeout: Timeout of the request in seconds. + :param verify: Verify SSL certificate. + :param kwargs: Extra parameters to include in the token request. + :return: A token dict + """ + if not is_secure_transport(token_url): + raise InsecureTransportError() + + if not code and authorization_response: + self._client.parse_request_uri_response(authorization_response, + state=self._state) + code = self._client.code + elif not code and isinstance(self._client, WebApplicationClient): + code = self._client.code + if not code: + raise ValueError('Please supply either code or ' + 'authorization_code parameters.') + + + body = self._client.prepare_request_body(code=code, body=body, + redirect_uri=self.redirect_uri, username=username, + password=password, **kwargs) + + if (not auth) and username: + if password is None: + raise ValueError('Username was supplied, but not password.') + auth = requests.auth.HTTPBasicAuth(username, password) + + headers = headers or { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', + } + self.token = {} + if method.upper() == 'POST': + r = self.post(token_url, data=dict(urldecode(body)), + timeout=timeout, headers=headers, auth=auth, + verify=verify) + log.debug('Prepared fetch token request body %s', body) + elif method.upper() == 'GET': + # if method is not 'POST', switch body to querystring and GET + r = self.get(token_url, params=dict(urldecode(body)), + timeout=timeout, headers=headers, auth=auth, + verify=verify) + log.debug('Prepared fetch token request querystring %s', body) + else: + raise ValueError('The method kwarg must be POST or GET.') + + log.debug('Request to fetch token completed with status %s.', + r.status_code) + log.debug('Request headers were %s', r.request.headers) + log.debug('Request body was %s', r.request.body) + log.debug('Response headers were %s and content %s.', + r.headers, r.text) + log.debug('Invoking %d token response hooks.', + len(self.compliance_hook['access_token_response'])) + for hook in self.compliance_hook['access_token_response']: + log.debug('Invoking hook %s.', hook) + r = hook(r) + + self._client.parse_request_body_response(r.text, scope=self.scope) + self.token = self._client.token + log.debug('Obtained token %s.', self.token) + return self.token + + def token_from_fragment(self, authorization_response): + """Parse token from the URI fragment, used by MobileApplicationClients. + + :param authorization_response: The full URL of the redirect back to you + :return: A token dict + """ + self._client.parse_request_uri_response(authorization_response, + state=self._state) + self.token = self._client.token + return self.token + + def refresh_token(self, token_url, refresh_token=None, body='', auth=None, + timeout=None, headers=None, verify=True, **kwargs): + """Fetch a new access token using a refresh token. + + :param token_url: The token endpoint, must be HTTPS. + :param refresh_token: The refresh_token to use. + :param body: Optional application/x-www-form-urlencoded body to add the + include in the token request. Prefer kwargs over body. + :param auth: An auth tuple or method as accepted by requests. + :param timeout: Timeout of the request in seconds. + :param verify: Verify SSL certificate. + :param kwargs: Extra parameters to include in the token request. + :return: A token dict + """ + if not token_url: + raise ValueError('No token endpoint set for auto_refresh.') + + if not is_secure_transport(token_url): + raise InsecureTransportError() + + refresh_token = refresh_token or self.token.get('refresh_token') + + log.debug('Adding auto refresh key word arguments %s.', + self.auto_refresh_kwargs) + kwargs.update(self.auto_refresh_kwargs) + body = self._client.prepare_refresh_body(body=body, + refresh_token=refresh_token, scope=self.scope, **kwargs) + log.debug('Prepared refresh token request body %s', body) + + if headers is None: + headers = { + 'Accept': 'application/json', + 'Content-Type': ( + 'application/x-www-form-urlencoded;charset=UTF-8' + ), + } + + r = self.post(token_url, data=dict(urldecode(body)), auth=auth, + timeout=timeout, headers=headers, verify=verify, withhold_token=True) + log.debug('Request to refresh token completed with status %s.', + r.status_code) + log.debug('Response headers were %s and content %s.', + r.headers, r.text) + log.debug('Invoking %d token response hooks.', + len(self.compliance_hook['refresh_token_response'])) + for hook in self.compliance_hook['refresh_token_response']: + log.debug('Invoking hook %s.', hook) + r = hook(r) + + self.token = self._client.parse_request_body_response(r.text, scope=self.scope) + if not 'refresh_token' in self.token: + log.debug('No new refresh token given. Re-using old.') + self.token['refresh_token'] = refresh_token + return self.token + + def request(self, method, url, data=None, headers=None, withhold_token=False, **kwargs): + """Intercept all requests and add the OAuth 2 token if present.""" + if not is_secure_transport(url): + raise InsecureTransportError() + if self.token and not withhold_token: + log.debug('Invoking %d protected resource request hooks.', + len(self.compliance_hook['protected_request'])) + for hook in self.compliance_hook['protected_request']: + log.debug('Invoking hook %s.', hook) + url, headers, data = hook(url, headers, data) + + log.debug('Adding token %s to request.', self.token) + try: + url, headers, data = self._client.add_token(url, + http_method=method, body=data, headers=headers) + # Attempt to retrieve and save new access token if expired + except TokenExpiredError: + if self.auto_refresh_url: + log.debug('Auto refresh is set, attempting to refresh at %s.', + self.auto_refresh_url) + token = self.refresh_token(self.auto_refresh_url, **kwargs) + if self.token_updater: + log.debug('Updating token to %s using %s.', + token, self.token_updater) + self.token_updater(token) + url, headers, data = self._client.add_token(url, + http_method=method, body=data, headers=headers) + else: + raise TokenUpdated(token) + else: + raise + + log.debug('Requesting url %s using method %s.', url, method) + log.debug('Supplying headers %s and data %s', headers, data) + log.debug('Passing through key word arguments %s.', kwargs) + return super(OAuth2Session, self).request(method, url, + headers=headers, data=data, **kwargs) + + def register_compliance_hook(self, hook_type, hook): + """Register a hook for request/response tweaking. + + Available hooks are: + access_token_response invoked before token parsing. + refresh_token_response invoked before refresh token parsing. + protected_request invoked before making a request. + + If you find a new hook is needed please send a GitHub PR request + or open an issue. + """ + if hook_type not in self.compliance_hook: + raise ValueError('Hook type %s is not in %s.', + hook_type, self.compliance_hook) + self.compliance_hook[hook_type].add(hook) diff --git a/lib/twitter/__init__.py b/lib/twitter/__init__.py new file mode 100644 index 00000000..586e9c6f --- /dev/null +++ b/lib/twitter/__init__.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# vim: sw=2 ts=2 sts=2 +# +# Copyright 2007 The Python-Twitter Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A library that provides a Python interface to the Twitter API""" +from __future__ import absolute_import + +__author__ = 'The Python-Twitter Developers' +__email__ = 'python-twitter@googlegroups.com' +__copyright__ = 'Copyright (c) 2007-2016 The Python-Twitter Developers' +__license__ = 'Apache License 2.0' +__version__ = '3.0rc1' +__url__ = 'https://github.com/bear/python-twitter' +__download_url__ = 'https://pypi.python.org/pypi/python-twitter' +__description__ = 'A Python wrapper around the Twitter API' + + +import json # noqa + +try: + from hashlib import md5 # noqa +except ImportError: + from md5 import md5 # noqa + +from ._file_cache import _FileCache # noqa +from .error import TwitterError # noqa +from .parse_tweet import ParseTweet # noqa + +from .models import ( # noqa + Category, # noqa + DirectMessage, # noqa + Hashtag, # noqa + List, # noqa + Media, # noqa + Trend, # noqa + Url, # noqa + User, # noqa + UserStatus, # noqa + Status # noqa +) + +from .api import Api # noqa diff --git a/lib/twitter/_file_cache.py b/lib/twitter/_file_cache.py new file mode 100644 index 00000000..197b1909 --- /dev/null +++ b/lib/twitter/_file_cache.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +import errno +import os +import re +import tempfile + +from hashlib import md5 + + +class _FileCacheError(Exception): + """Base exception class for FileCache related errors""" + + +class _FileCache(object): + DEPTH = 3 + + def __init__(self, root_directory=None): + self._InitializeRootDirectory(root_directory) + + def Get(self, key): + path = self._GetPath(key) + if os.path.exists(path): + with open(path) as f: + return f.read() + else: + return None + + def Set(self, key, data): + path = self._GetPath(key) + directory = os.path.dirname(path) + if not os.path.exists(directory): + os.makedirs(directory) + if not os.path.isdir(directory): + raise _FileCacheError('%s exists but is not a directory' % directory) + temp_fd, temp_path = tempfile.mkstemp() + temp_fp = os.fdopen(temp_fd, 'w') + temp_fp.write(data) + temp_fp.close() + if not path.startswith(self._root_directory): + raise _FileCacheError('%s does not appear to live under %s' % + (path, self._root_directory)) + if os.path.exists(path): + os.remove(path) + os.rename(temp_path, path) + + def Remove(self, key): + path = self._GetPath(key) + if not path.startswith(self._root_directory): + raise _FileCacheError('%s does not appear to live under %s' % + (path, self._root_directory )) + if os.path.exists(path): + os.remove(path) + + def GetCachedTime(self, key): + path = self._GetPath(key) + if os.path.exists(path): + return os.path.getmtime(path) + else: + return None + + def _GetUsername(self): + """Attempt to find the username in a cross-platform fashion.""" + try: + return os.getenv('USER') or \ + os.getenv('LOGNAME') or \ + os.getenv('USERNAME') or \ + os.getlogin() or \ + 'nobody' + except (AttributeError, IOError, OSError): + return 'nobody' + + def _GetTmpCachePath(self): + username = self._GetUsername() + cache_directory = 'python.cache_' + username + return os.path.join(tempfile.gettempdir(), cache_directory) + + def _InitializeRootDirectory(self, root_directory): + if not root_directory: + root_directory = self._GetTmpCachePath() + root_directory = os.path.abspath(root_directory) + try: + os.mkdir(root_directory) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(root_directory): + # directory already exists + pass + else: + # exists but is a file, or no permissions, or... + raise + self._root_directory = root_directory + + def _GetPath(self, key): + try: + hashed_key = md5(key.encode('utf-8')).hexdigest() + except TypeError: + hashed_key = md5.new(key).hexdigest() + + return os.path.join(self._root_directory, + self._GetPrefix(hashed_key), + hashed_key) + + def _GetPrefix(self, hashed_key): + return os.path.sep.join(hashed_key[0:_FileCache.DEPTH]) + + +class ParseTweet(object): + # compile once on import + regexp = {"RT": "^RT", "MT": r"^MT", "ALNUM": r"(@[a-zA-Z0-9_]+)", + "HASHTAG": r"(#[\w\d]+)", "URL": r"([http://]?[a-zA-Z\d\/]+[\.]+[a-zA-Z\d\/\.]+)"} + regexp = dict((key, re.compile(value)) for key, value in list(regexp.items())) + + def __init__(self, timeline_owner, tweet): + """ timeline_owner : twitter handle of user account. tweet - 140 chars from feed; object does all computation on construction + properties: + RT, MT - boolean + URLs - list of URL + Hashtags - list of tags + """ + self.Owner = timeline_owner + self.tweet = tweet + self.UserHandles = ParseTweet.getUserHandles(tweet) + self.Hashtags = ParseTweet.getHashtags(tweet) + self.URLs = ParseTweet.getURLs(tweet) + self.RT = ParseTweet.getAttributeRT(tweet) + self.MT = ParseTweet.getAttributeMT(tweet) + + # additional intelligence + if ( self.RT and len(self.UserHandles) > 0 ): # change the owner of tweet? + self.Owner = self.UserHandles[0] + return + + def __str__(self): + """ for display method """ + return "owner %s, urls: %d, hashtags %d, user_handles %d, len_tweet %d, RT = %s, MT = %s" % ( + self.Owner, len(self.URLs), len(self.Hashtags), len(self.UserHandles), + len(self.tweet), self.RT, self.MT) + + @staticmethod + def getAttributeRT(tweet): + """ see if tweet is a RT """ + return re.search(ParseTweet.regexp["RT"], tweet.strip()) is not None + + @staticmethod + def getAttributeMT(tweet): + """ see if tweet is a MT """ + return re.search(ParseTweet.regexp["MT"], tweet.strip()) is not None + + @staticmethod + def getUserHandles(tweet): + """ given a tweet we try and extract all user handles in order of occurrence""" + return re.findall(ParseTweet.regexp["ALNUM"], tweet) + + @staticmethod + def getHashtags(tweet): + """ return all hashtags""" + return re.findall(ParseTweet.regexp["HASHTAG"], tweet) + + @staticmethod + def getURLs(tweet): + """ URL : [http://]?[\w\.?/]+""" + return re.findall(ParseTweet.regexp["URL"], tweet) diff --git a/lib/twitter/api.py b/lib/twitter/api.py new file mode 100644 index 00000000..60d5851b --- /dev/null +++ b/lib/twitter/api.py @@ -0,0 +1,4534 @@ +#!/usr/bin/env python + +# +# +# Copyright 2007 The Python-Twitter Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A library that provides a Python interface to the Twitter API""" +from __future__ import division +from __future__ import print_function + +import sys +import gzip +import time +import base64 +import re +import requests +from requests_oauthlib import OAuth1 +import io +import warnings +from uuid import uuid4 + +try: + # python 3 + from urllib.parse import urlparse, urlunparse, urlencode + from urllib.request import urlopen + from urllib.request import __version__ as urllib_version +except ImportError: + from urlparse import urlparse, urlunparse + from urllib2 import urlopen + from urllib import urlencode + from urllib import __version__ as urllib_version + +from twitter import (__version__, _FileCache, json, DirectMessage, List, + Status, Trend, TwitterError, User, UserStatus, Category) + +from twitter.ratelimit import RateLimit + +from twitter.twitter_utils import ( + calc_expected_status_length, + is_url, + parse_media_file, + enf_type) + +warnings.simplefilter('always', DeprecationWarning) + +CHARACTER_LIMIT = 140 + +# A singleton representing a lazily instantiated FileCache. +DEFAULT_CACHE = object() + + +class Api(object): + """A python interface into the Twitter API + + By default, the Api caches results for 1 minute. + + Example usage: + + To create an instance of the twitter.Api class, with no authentication: + + >>> import twitter + >>> api = twitter.Api() + + To fetch a single user's public status messages, where "user" is either + a Twitter "short name" or their user id. + + >>> statuses = api.GetUserTimeline(user) + >>> print([s.text for s in statuses]) + + To use authentication, instantiate the twitter.Api class with a + consumer key and secret; and the oAuth key and secret: + + >>> api = twitter.Api(consumer_key='twitter consumer key', + consumer_secret='twitter consumer secret', + access_token_key='the_key_given', + access_token_secret='the_key_secret') + + To fetch your friends (after being authenticated): + + >>> users = api.GetFriends() + >>> print([u.name for u in users]) + + To post a twitter status message (after being authenticated): + + >>> status = api.PostUpdate('I love python-twitter!') + >>> print(status.text) + I love python-twitter! + + There are many other methods, including: + + >>> api.PostUpdates(status) + >>> api.PostDirectMessage(user, text) + >>> api.GetUser(user) + >>> api.GetReplies() + >>> api.GetUserTimeline(user) + >>> api.GetHomeTimeline() + >>> api.GetStatus(status_id) + >>> api.DestroyStatus(status_id) + >>> api.GetFriends(user) + >>> api.GetFollowers() + >>> api.GetFeatured() + >>> api.GetDirectMessages() + >>> api.GetSentDirectMessages() + >>> api.PostDirectMessage(user, text) + >>> api.DestroyDirectMessage(message_id) + >>> api.DestroyFriendship(user) + >>> api.CreateFriendship(user) + >>> api.LookupFriendship(user) + >>> api.VerifyCredentials() + """ + + DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute + _API_REALM = 'Twitter API' + + def __init__(self, + consumer_key=None, + consumer_secret=None, + access_token_key=None, + access_token_secret=None, + input_encoding=None, + request_headers=None, + cache=DEFAULT_CACHE, + base_url=None, + stream_url=None, + upload_url=None, + chunk_size=1024 * 1024, + use_gzip_compression=False, + debugHTTP=False, + timeout=None, + sleep_on_rate_limit=False): + """Instantiate a new twitter.Api object. + + Args: + consumer_key: + Your Twitter user's consumer_key. + consumer_secret: + Your Twitter user's consumer_secret. + access_token_key: + The oAuth access token key value you retrieved + from running get_access_token.py. + access_token_secret: + The oAuth access token's secret, also retrieved + from the get_access_token.py run. + input_encoding: + The encoding used to encode input strings. [Optional] + request_header: + A dictionary of additional HTTP request headers. [Optional] + cache: + The cache instance to use. Defaults to DEFAULT_CACHE. + Use None to disable caching. [Optional] + base_url: + The base URL to use to contact the Twitter API. + Defaults to https://api.twitter.com. [Optional] + use_gzip_compression: + Set to True to tell enable gzip compression for any call + made to Twitter. Defaults to False. [Optional] + debugHTTP: + Set to True to enable debug output from urllib2 when performing + any HTTP requests. Defaults to False. [Optional] + timeout: + Set timeout (in seconds) of the http/https requests. If None the + requests lib default will be used. Defaults to None. [Optional] + """ + self.SetCache(cache) + self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT + self._input_encoding = input_encoding + self._use_gzip = use_gzip_compression + self._debugHTTP = debugHTTP + self._shortlink_size = 19 + self._timeout = timeout + self.__auth = None + + self._InitializeRequestHeaders(request_headers) + self._InitializeUserAgent() + self._InitializeDefaultParameters() + + self.rate_limit = None + self.sleep_on_rate_limit = sleep_on_rate_limit + + if base_url is None: + self.base_url = 'https://api.twitter.com/1.1' + else: + self.base_url = base_url + + if stream_url is None: + self.stream_url = 'https://stream.twitter.com/1.1' + else: + self.stream_url = stream_url + + if upload_url is None: + self.upload_url = 'https://upload.twitter.com/1.1' + else: + self.upload_url = upload_url + + self.chunk_size = chunk_size + + if self.chunk_size < 1024 * 16: + warnings.warn(( + "A chunk size lower than 16384 may result in too many " + "requests to the Twitter API when uploading videos. You are " + "strongly advised to increase it above 16384" + )) + + if consumer_key is not None and (access_token_key is None or + access_token_secret is None): + print('Twitter now requires an oAuth Access Token for API calls. ' + 'If you\'re using this library from a command line utility, ' + 'please run the included get_access_token.py tool to ' + 'generate one.', file=sys.stderr) + + raise TwitterError({'message': "Twitter requires oAuth Access Token for all API access"}) + + self.SetCredentials(consumer_key, consumer_secret, access_token_key, access_token_secret) + + if debugHTTP: + import logging + import http.client + + http.client.HTTPConnection.debuglevel = 1 + + logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True + + def SetCredentials(self, + consumer_key, + consumer_secret, + access_token_key=None, + access_token_secret=None): + """Set the consumer_key and consumer_secret for this instance + + Args: + consumer_key: + The consumer_key of the twitter account. + consumer_secret: + The consumer_secret for the twitter account. + access_token_key: + The oAuth access token key value you retrieved + from running get_access_token.py. + access_token_secret: + The oAuth access token's secret, also retrieved + from the get_access_token.py run. + """ + self._consumer_key = consumer_key + self._consumer_secret = consumer_secret + self._access_token_key = access_token_key + self._access_token_secret = access_token_secret + auth_list = [consumer_key, consumer_secret, + access_token_key, access_token_secret] + + if all(auth_list): + self.__auth = OAuth1(consumer_key, consumer_secret, + access_token_key, access_token_secret) + + self._config = None + + def GetHelpConfiguration(self): + if self._config is None: + url = '%s/help/configuration.json' % self.base_url + resp = self._RequestUrl(url, 'GET') + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + self._config = data + return self._config + + def GetShortUrlLength(self, https=False): + config = self.GetHelpConfiguration() + if https: + return config['short_url_length_https'] + else: + return config['short_url_length'] + + def ClearCredentials(self): + """Clear any credentials for this instance + """ + self._consumer_key = None + self._consumer_secret = None + self._access_token_key = None + self._access_token_secret = None + self.__auth = None # for request upgrade + + def GetSearch(self, + term=None, + raw_query=None, + geocode=None, + since_id=None, + max_id=None, + until=None, + since=None, + count=15, + lang=None, + locale=None, + result_type="mixed", + include_entities=None): + """Return twitter search results for a given term. You must specify one + of term, geocode, or raw_query. + + Args: + term (str, optional): + Term to search by. Optional if you include geocode. + raw_query (str, optional): + A raw query as a string. This should be everything after the "?" in + the URL (i.e., the query parameters). You are responsible for all + type checking and ensuring that the query string is properly + formatted, as it will only be URL-encoded before be passed directly + to Twitter with no other checks performed. For advanced usage only. + since_id (int, optional): + Returns results with an ID greater than (that is, more recent + than) the specified ID. There are limits to the number of + Tweets which can be accessed through the API. If the limit of + Tweets has occurred since the since_id, the since_id will be + forced to the oldest ID available. + max_id (int, optional): + Returns only statuses with an ID less than (that is, older + than) or equal to the specified ID. + until (str, optional): + Returns tweets generated before the given date. Date should be + formatted as YYYY-MM-DD. + since (str, optional): + Returns tweets generated since the given date. Date should be + formatted as YYYY-MM-DD. + geocode (str or list or tuple, optional): + Geolocation within which to search for tweets. Can be either a + string in the form of "latitude,longitude,radius" where latitude + and longitude are floats and radius is a string such as "1mi" or + "1km" ("mi" or "km" are the only units allowed). For example: + >>> api.GetSearch(geocode="37.781157,-122.398720,1mi"). + Otherwise, you can pass a list of either floats or strings for + lat/long and a string for radius: + >>> api.GetSearch(geocode=[37.781157, -122.398720, "1mi"]) + >>> # or: + >>> api.GetSearch(geocode=(37.781157, -122.398720, "1mi")) + >>> # or: + >>> api.GetSearch(geocode=("37.781157", "-122.398720", "1mi")) + count (int, optional): + Number of results to return. Default is 15 and maxmimum that + Twitter returns is 100 irrespective of what you type in. + lang (str, optional): + Language for results as ISO 639-1 code. Default is None + (all languages). + locale (str, optional): + Language of the search query. Currently only 'ja' is effective. + This is intended for language-specific consumers and the default + should work in the majority of cases. + result_type (str, optional): + Type of result which should be returned. Default is "mixed". + Valid options are "mixed, "recent", and "popular". + include_entities (bool, optional): + If True, each tweet will include a node called "entities". + This node offers a variety of metadata about the tweet in a + discrete structure, including: user_mentions, urls, and + hashtags. + + Returns: + list: A sequence of twitter.Status instances, one for each message + containing the term, within the bounds of the geocoded area, or + given by the raw_query. + """ + + url = '%s/search/tweets.json' % self.base_url + parameters = {} + + if since_id: + parameters['since_id'] = enf_type('since_id', int, since_id) + + if max_id: + parameters['max_id'] = enf_type('max_id', int, max_id) + + if until: + parameters['until'] = enf_type('until', str, until) + + if since: + parameters['since'] = enf_type('since', str, since) + + if lang: + parameters['lang'] = enf_type('lang', str, lang) + + if locale: + parameters['locale'] = enf_type('locale', str, locale) + + if term is None and geocode is None and raw_query is None: + return [] + + if term is not None: + parameters['q'] = term + + if geocode is not None: + if isinstance(geocode, list) or isinstance(geocode, tuple): + parameters['geocode'] = ','.join([str(geo) for geo in geocode]) + else: + parameters['geocode'] = enf_type('geocode', str, geocode) + + if include_entities: + parameters['include_entities'] = enf_type('include_entities', + bool, + include_entities) + + parameters['count'] = enf_type('count', int, count) + + if result_type in ["mixed", "popular", "recent"]: + parameters['result_type'] = result_type + + if raw_query is not None: + url = "{url}?{raw_query}".format( + url=url, + raw_query=raw_query) + resp = self._RequestUrl(url, 'GET') + else: + resp = self._RequestUrl(url, 'GET', data=parameters) + + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + return [Status.NewFromJsonDict(x) for x in data.get('statuses', '')] + + def GetUsersSearch(self, + term=None, + page=1, + count=20, + include_entities=None): + """Return twitter user search results for a given term. + + Args: + term: + Term to search by. + page: + Page of results to return. Default is 1 + [Optional] + count: + Number of results to return. Default is 20 + [Optional] + include_entities: + If True, each tweet will include a node called "entities,". + This node offers a variety of metadata about the tweet in a + discrete structure, including: user_mentions, urls, and hashtags. + [Optional] + + Returns: + A sequence of twitter.User instances, one for each message containing + the term + """ + # Build request parameters + parameters = {} + + if term is not None: + parameters['q'] = term + + if page != 1: + parameters['page'] = page + + if include_entities: + parameters['include_entities'] = 1 + + try: + parameters['count'] = int(count) + except ValueError: + raise TwitterError({'message': "count must be an integer"}) + + # Make and send requests + url = '%s/users/search.json' % self.base_url + resp = self._RequestUrl(url, 'GET', data=parameters) + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + return [User.NewFromJsonDict(x) for x in data] + + def GetTrendsCurrent(self, exclude=None): + """Get the current top trending topics (global) + + Args: + exclude: + Appends the exclude parameter as a request parameter. + Currently only exclude=hashtags is supported. [Optional] + + Returns: + A list with 10 entries. Each entry contains a trend. + """ + return self.GetTrendsWoeid(woeid=1, exclude=exclude) + + def GetTrendsWoeid(self, woeid, exclude=None): + """Return the top 10 trending topics for a specific WOEID, if trending + information is available for it. + + Args: + woeid: + the Yahoo! Where On Earth ID for a location. + exclude: + Appends the exclude parameter as a request parameter. + Currently only exclude=hashtags is supported. [Optional] + + Returns: + A list with 10 entries. Each entry contains a trend. + """ + url = '%s/trends/place.json' % (self.base_url) + parameters = {'id': woeid} + + if exclude: + parameters['exclude'] = exclude + + resp = self._RequestUrl(url, verb='GET', data=parameters) + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + trends = [] + timestamp = data[0]['as_of'] + + for trend in data[0]['trends']: + trends.append(Trend.NewFromJsonDict(trend, timestamp=timestamp)) + return trends + + def GetUserSuggestionCategories(self): + """ Return the list of suggested user categories, this can be used in + GetUserSuggestion function + Returns: + A list of categories + """ + url = '%s/users/suggestions.json' % (self.base_url) + resp = self._RequestUrl(url, verb='GET') + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + categories = [] + + for category in data: + categories.append(Category.NewFromJsonDict(category)) + return categories + + def GetUserSuggestion(self, category): + """ Returns a list of users in a category + Args: + category: + The Category object to limit the search by + Returns: + A list of users in that category + """ + url = '%s/users/suggestions/%s.json' % (self.base_url, category.Slug) + + resp = self._RequestUrl(url, verb='GET') + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + users = [] + for user in data['users']: + users.append(User.NewFromJsonDict(user)) + return users + + def GetHomeTimeline(self, + count=None, + since_id=None, + max_id=None, + trim_user=False, + exclude_replies=False, + contributor_details=False, + include_entities=True): + """Fetch a collection of the most recent Tweets and retweets posted + by the authenticating user and the users they follow. + + The home timeline is central to how most users interact with Twitter. + + Args: + count: + Specifies the number of statuses to retrieve. May not be + greater than 200. Defaults to 20. [Optional] + since_id: + Returns results with an ID greater than (that is, more recent + than) the specified ID. There are limits to the number of + Tweets which can be accessed through the API. If the limit of + Tweets has occurred since the since_id, the since_id will be + forced to the oldest ID available. [Optional] + max_id: + Returns results with an ID less than (that is, older than) or + equal to the specified ID. [Optional] + trim_user: + When True, each tweet returned in a timeline will include a user + object including only the status authors numerical ID. Omit this + parameter to receive the complete user object. [Optional] + exclude_replies: + This parameter will prevent replies from appearing in the + returned timeline. Using exclude_replies with the count + parameter will mean you will receive up-to count tweets - + this is because the count parameter retrieves that many + tweets before filtering out retweets and replies. [Optional] + contributor_details: + This parameter enhances the contributors element of the + status response to include the screen_name of the contributor. + By default only the user_id of the contributor is included. [Optional] + include_entities: + The entities node will be disincluded when set to false. + This node offers a variety of metadata about the tweet in a + discreet structure, including: user_mentions, urls, and + hashtags. [Optional] + + Returns: + A sequence of twitter.Status instances, one for each message + """ + url = '%s/statuses/home_timeline.json' % self.base_url + + parameters = {} + if count is not None: + try: + if int(count) > 200: + raise TwitterError({'message': "'count' may not be greater than 200"}) + except ValueError: + raise TwitterError({'message': "'count' must be an integer"}) + parameters['count'] = count + if since_id: + try: + parameters['since_id'] = int(since_id) + except ValueError: + raise TwitterError({'message': "'since_id' must be an integer"}) + if max_id: + try: + parameters['max_id'] = int(max_id) + except ValueError: + raise TwitterError({'message': "'max_id' must be an integer"}) + if trim_user: + parameters['trim_user'] = 1 + if exclude_replies: + parameters['exclude_replies'] = 1 + if contributor_details: + parameters['contributor_details'] = 1 + if not include_entities: + parameters['include_entities'] = 'false' + resp = self._RequestUrl(url, 'GET', data=parameters) + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + return [Status.NewFromJsonDict(x) for x in data] + + def GetUserTimeline(self, + user_id=None, + screen_name=None, + since_id=None, + max_id=None, + count=None, + include_rts=True, + trim_user=None, + exclude_replies=None): + """Fetch the sequence of public Status messages for a single user. + + The twitter.Api instance must be authenticated if the user is private. + + Args: + user_id: + Specifies the ID of the user for whom to return the + user_timeline. Helpful for disambiguating when a valid user ID + is also a valid screen name. [Optional] + screen_name: + Specifies the screen name of the user for whom to return the + user_timeline. Helpful for disambiguating when a valid screen + name is also a user ID. [Optional] + since_id: + Returns results with an ID greater than (that is, more recent + than) the specified ID. There are limits to the number of + Tweets which can be accessed through the API. If the limit of + Tweets has occurred since the since_id, the since_id will be + forced to the oldest ID available. [Optional] + max_id: + Returns only statuses with an ID less than (that is, older + than) or equal to the specified ID. [Optional] + count: + Specifies the number of statuses to retrieve. May not be + greater than 200. [Optional] + include_rts: + If True, the timeline will contain native retweets (if they + exist) in addition to the standard stream of tweets. [Optional] + trim_user: + If True, statuses will only contain the numerical user ID only. + Otherwise a full user object will be returned for each status. + [Optional] + exclude_replies: + If True, this will prevent replies from appearing in the returned + timeline. Using exclude_replies with the count parameter will mean you + will receive up-to count tweets - this is because the count parameter + retrieves that many tweets before filtering out retweets and replies. + This parameter is only supported for JSON and XML responses. [Optional] + + Returns: + A sequence of Status instances, one for each message up to count + """ + parameters = {} + url = '%s/statuses/user_timeline.json' % (self.base_url) + + if user_id: + parameters['user_id'] = user_id + elif screen_name: + parameters['screen_name'] = screen_name + if since_id: + try: + parameters['since_id'] = int(since_id) + except ValueError: + raise TwitterError({'message': "since_id must be an integer"}) + if max_id: + try: + parameters['max_id'] = int(max_id) + except ValueError: + raise TwitterError({'message': "max_id must be an integer"}) + if count: + try: + parameters['count'] = int(count) + except ValueError: + raise TwitterError({'message': "count must be an integer"}) + if not include_rts: + parameters['include_rts'] = 0 + if trim_user: + parameters['trim_user'] = 1 + if exclude_replies: + parameters['exclude_replies'] = 1 + + resp = self._RequestUrl(url, 'GET', data=parameters) + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + return [Status.NewFromJsonDict(x) for x in data] + + def GetStatus(self, + status_id, + trim_user=False, + include_my_retweet=True, + include_entities=True): + """Returns a single status message, specified by the status_id parameter. + + Args: + status_id: + The numeric ID of the status you are trying to retrieve. + trim_user: + When set to True, each tweet returned in a timeline will include + a user object including only the status authors numerical ID. + Omit this parameter to receive the complete user object. [Optional] + include_my_retweet: + When set to True, any Tweets returned that have been retweeted by + the authenticating user will include an additional + current_user_retweet node, containing the ID of the source status + for the retweet. [Optional] + include_entities: + If False, the entities node will be disincluded. + This node offers a variety of metadata about the tweet in a + discreet structure, including: user_mentions, urls, and + hashtags. [Optional] + Returns: + A twitter.Status instance representing that status message + """ + url = '%s/statuses/show.json' % (self.base_url) + + parameters = {} + + try: + parameters['id'] = int(status_id) + except ValueError: + raise TwitterError({'message': "'status_id' must be an integer."}) + + if trim_user: + parameters['trim_user'] = 1 + if include_my_retweet: + parameters['include_my_retweet'] = 1 + if not include_entities: + parameters['include_entities'] = 'none' + + resp = self._RequestUrl(url, 'GET', data=parameters) + data = self._ParseAndCheckTwitter(resp.content.decode('utf-8')) + + return Status.NewFromJsonDict(data) + + def GetStatusOembed(self, + status_id=None, + url=None, + maxwidth=None, + hide_media=False, + hide_thread=False, + omit_script=False, + align=None, + related=None, + lang=None): + """Returns information allowing the creation of an embedded representation of a + Tweet on third party sites. + + Specify tweet by the id or url parameter. + + Args: + status_id: + The numeric ID of the status you are trying to embed. + url: + The url of the status you are trying to embed. + maxwidth: + The maximum width in pixels that the embed should be rendered at. + This value is constrained to be between 250 and 550 pixels. [Optional] + hide_media: + Specifies whether the embedded Tweet should automatically expand images. [Optional] + hide_thread: + Specifies whether the embedded Tweet should automatically show the original + message in the case that the embedded Tweet is a reply. [Optional] + omit_script: + Specifies whether the embedded Tweet HTML should include a