From c0b56f33a74cd06a20b2a19746e1dd451209aba5 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 6 Aug 2009 01:48:14 -0400 Subject: [PATCH] Reorganizing structure, finally made the setup.py install function actually work - thanks to the guys in #python for their help. --- {twython => build/lib}/twython.py | 0 dist/twython-0.5-py2.5.egg | Bin 15177 -> 29112 bytes setup.py | 2 +- twython.egg-info/SOURCES.txt | 1 + twython.egg-info/top_level.txt | 2 +- twython.py | 654 +++++++++++++++++++++++++++ twython/twython3k.py => twython3k.py | 2 +- 7 files changed, 658 insertions(+), 3 deletions(-) rename {twython => build/lib}/twython.py (100%) create mode 100644 twython.py rename twython/twython3k.py => twython3k.py (99%) diff --git a/twython/twython.py b/build/lib/twython.py similarity index 100% rename from twython/twython.py rename to build/lib/twython.py diff --git a/dist/twython-0.5-py2.5.egg b/dist/twython-0.5-py2.5.egg index dc20f75564b5a5734470e0a392907e562c5b718b..c81e15bb706bc0594009b0ea771a40a94d5fca7c 100644 GIT binary patch delta 14468 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

}L0hgv%@1-l~7EQ@CU0OqsqY5bs%6#G7( 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 YG)z7bp9c>C4gd#$`n!XfcK?X`KXg7SfdBvi delta 508 zcmdn-nDJy8Z-6&5iwFY)0|&!LHs;B8Cf3YAdUK6QCM$?B`GuuBFOatWtg3n<6{u{_ zWE(4^`pfN?Je;alzI?fBW@YBjUyS$O?V4AY_iu*L!G8-K&#gOHr7EgAW0K}d)|Hi- zD=SY<{b)I}^5)SSp`uI0rlpB3eKbM$q<+VFPhIb`jGoL4C{D1xv-ad3UZ70^Kr9Bt zuI}!-o_=or`X%`V@j0nwsX2O+-&%1AU)FED5QD0BGP||@=4KmyA+R5&`Uxq3{UDt$ z&B!p>$3&q%z?+dtgctm0v9kOL*U~mCiCv+nW;(+*xw}AqvTmUq(?7P!zJ(B<7Zge}@k&naEtKWs0LHK|69a>S HCCCf_%GQl$ diff --git a/setup.py b/setup.py index d73134f..94f70e4 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ __version__ = '0.5' METADATA = dict( name = "twython", version = __version__, - py_modules = ['twython/twython'], + py_modules = ['twython'], author='Ryan McGrath', author_email='ryan@venodesigns.net', description='A new and easy way to access Twitter data with Python.', diff --git a/twython.egg-info/SOURCES.txt b/twython.egg-info/SOURCES.txt index 696cd92..08928a4 100644 --- a/twython.egg-info/SOURCES.txt +++ b/twython.egg-info/SOURCES.txt @@ -1,5 +1,6 @@ README setup.py +twython.py twython/twython.py twython.egg-info/PKG-INFO twython.egg-info/SOURCES.txt diff --git a/twython.egg-info/top_level.txt b/twython.egg-info/top_level.txt index 54fa13c..292a670 100644 --- a/twython.egg-info/top_level.txt +++ b/twython.egg-info/top_level.txt @@ -1 +1 @@ -twython/twython +twython diff --git a/twython.py b/twython.py new file mode 100644 index 0000000..ea0ec31 --- /dev/null +++ b/twython.py @@ -0,0 +1,654 @@ +#!/usr/bin/python + +""" + NOTE: Tango is being renamed to Twython; all basic strings have been changed below, but there's still work ongoing in this department. + + Twython is an up-to-date library for Python that wraps the Twitter API. + Other Python Twitter libraries seem to have fallen a bit behind, and + Twitter's API has evolved a bit. Here's hoping this helps. + + TODO: OAuth, Streaming API? + + Questions, comments? ryan@venodesigns.net +""" + +import httplib, urllib, urllib2, mimetypes, mimetools + +from urlparse import urlparse +from urllib2 import HTTPError + +__author__ = "Ryan McGrath " +__version__ = "0.5" + +"""Twython - Easy Twitter utilities in Python""" + +try: + import simplejson +except ImportError: + try: + import json as simplejson + except: + raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + +try: + import oauth +except ImportError: + pass + +class TangoError(Exception): + def __init__(self, msg, error_code=None): + self.msg = msg + if error_code == 400: + raise APILimit(msg) + def __str__(self): + return repr(self.msg) + +class APILimit(TangoError): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return repr(self.msg) + +class setup: + def __init__(self, authtype = "OAuth", username = None, password = None, consumer_secret = None, consumer_key = None, headers = None): + self.authtype = authtype + self.authenticated = False + self.username = username + self.password = password + # OAuth specific variables below + self.request_token_url = 'https://twitter.com/oauth/request_token' + self.access_token_url = 'https://twitter.com/oauth/access_token' + self.authorization_url = 'http://twitter.com/oauth/authorize' + self.signin_url = 'http://twitter.com/oauth/authenticate' + self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1() + self.consumer_key = consumer_key + self.consumer_secret = consumer_secret + self.request_token = None + self.access_token = None + # Check and set up authentication + if self.username is not None and self.password is not None: + if self.authtype == "Basic": + # Basic authentication ritual + self.auth_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + self.auth_manager.add_password(None, "http://twitter.com", self.username, self.password) + self.handler = urllib2.HTTPBasicAuthHandler(self.auth_manager) + self.opener = urllib2.build_opener(self.handler) + if headers is not None: + self.opener.addheaders = [('User-agent', headers)] + try: + simplejson.load(self.opener.open("http://twitter.com/account/verify_credentials.json")) + self.authenticated = True + except HTTPError, e: + raise TangoError("Authentication failed with your provided credentials. Try again? (%s failure)" % `e.code`, e.code) + else: + # Awesome OAuth authentication ritual + if consumer_secret is not None and consumer_key is not None: + #req = oauth.OAuthRequest.from_consumer_and_token + #req.sign_request(self.signature_method, self.consumer_key, self.token) + #self.opener = urllib2.build_opener() + pass + else: + raise TwythonError("Woah there, buddy. We've defaulted to OAuth authentication, but you didn't provide API keys. Try again.") + + def getRequestToken(self): + response = self.oauth_request(self.request_token_url) + token = self.parseOAuthResponse(response) + self.request_token = oauth.OAuthConsumer(token['oauth_token'],token['oauth_token_secret']) + return self.request_token + + def parseOAuthResponse(self, response_string): + # Partial credit goes to Harper Reed for this gem. + lol = {} + for param in response_string.split("&"): + pair = param.split("=") + if(len(pair) != 2): + break + lol[pair[0]] = pair[1] + return lol + + # URL Shortening function huzzah + def shortenURL(self, url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): + try: + return urllib2.urlopen(shortener + "?" + urllib.urlencode({query: url_to_shorten})).read() + except HTTPError, e: + raise TangoError("shortenURL() failed with a %s error code." % `e.code`) + + def constructApiURL(self, base_url, params): + return base_url + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in params.iteritems()]) + + def getRateLimitStatus(self, rate_for = "requestingIP"): + try: + if rate_for == "requestingIP": + return simplejson.load(urllib2.urlopen("http://twitter.com/account/rate_limit_status.json")) + else: + if self.authenticated is True: + return simplejson.load(self.opener.open("http://twitter.com/account/rate_limit_status.json")) + else: + raise TangoError("You need to be authenticated to check a rate limit status on an account.") + except HTTPError, e: + raise TangoError("It seems that there's something wrong. Twitter gave you a %s error code; are you doing something you shouldn't be?" % `e.code`, e.code) + + def getPublicTimeline(self): + try: + return simplejson.load(urllib2.urlopen("http://twitter.com/statuses/public_timeline.json")) + except HTTPError, e: + raise TangoError("getPublicTimeline() failed with a %s error code." % `e.code`) + + def getFriendsTimeline(self, **kwargs): + if self.authenticated is True: + try: + friendsTimelineURL = self.constructApiURL("http://twitter.com/statuses/friends_timeline.json", kwargs) + return simplejson.load(self.opener.open(friendsTimelineURL)) + except HTTPError, e: + raise TangoError("getFriendsTimeline() failed with a %s error code." % `e.code`) + else: + raise TangoError("getFriendsTimeline() requires you to be authenticated.") + + def getUserTimeline(self, id = None, **kwargs): + if id is not None and kwargs.has_key("user_id") is False and kwargs.has_key("screen_name") is False: + userTimelineURL = self.constructApiURL("http://twitter.com/statuses/user_timeline/" + id + ".json", kwargs) + elif id is None and kwargs.has_key("user_id") is False and kwargs.has_key("screen_name") is False and self.authenticated is True: + userTimelineURL = self.constructApiURL("http://twitter.com/statuses/user_timeline/" + self.username + ".json", kwargs) + else: + userTimelineURL = self.constructApiURL("http://twitter.com/statuses/user_timeline.json", kwargs) + try: + # We do our custom opener if we're authenticated, as it helps avoid cases where it's a protected user + if self.authenticated is True: + return simplejson.load(self.opener.open(userTimelineURL)) + else: + return simplejson.load(urllib2.urlopen(userTimelineURL)) + except HTTPError, e: + raise TangoError("Failed with a %s error code. Does this user hide/protect their updates? If so, you'll need to authenticate and be their friend to get their timeline." + % `e.code`, e.code) + + def getUserMentions(self, **kwargs): + if self.authenticated is True: + try: + mentionsFeedURL = self.constructApiURL("http://twitter.com/statuses/mentions.json", kwargs) + return simplejson.load(self.opener.open(mentionsFeedURL)) + except HTTPError, e: + raise TangoError("getUserMentions() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getUserMentions() requires you to be authenticated.") + + def showStatus(self, id): + try: + if self.authenticated is True: + return simplejson.load(self.opener.open("http://twitter.com/statuses/show/%s.json" % id)) + else: + return simplejson.load(urllib2.urlopen("http://twitter.com/statuses/show/%s.json" % id)) + except HTTPError, e: + raise TangoError("Failed with a %s error code. Does this user hide/protect their updates? You'll need to authenticate and be friends to get their timeline." + % `e.code`, e.code) + + def updateStatus(self, status, in_reply_to_status_id = None): + if self.authenticated is True: + if len(list(status)) > 140: + raise TangoError("This status message is over 140 characters. Trim it down!") + try: + return simplejson.load(self.opener.open("http://twitter.com/statuses/update.json?", urllib.urlencode({"status": status, "in_reply_to_status_id": in_reply_to_status_id}))) + except HTTPError, e: + raise TangoError("updateStatus() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("updateStatus() requires you to be authenticated.") + + def destroyStatus(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/status/destroy/%s.json", "POST" % id)) + except HTTPError, e: + raise TangoError("destroyStatus() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("destroyStatus() requires you to be authenticated.") + + def endSession(self): + if self.authenticated is True: + try: + self.opener.open("http://twitter.com/account/end_session.json", "") + self.authenticated = False + except HTTPError, e: + raise TangoError("endSession failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("You can't end a session when you're not authenticated to begin with.") + + def getDirectMessages(self, since_id = None, max_id = None, count = None, page = "1"): + if self.authenticated is True: + apiURL = "http://twitter.com/direct_messages.json?page=" + page + if since_id is not None: + apiURL += "&since_id=" + since_id + if max_id is not None: + apiURL += "&max_id=" + max_id + if count is not None: + apiURL += "&count=" + count + + try: + return simplejson.load(self.opener.open(apiURL)) + except HTTPError, e: + raise TangoError("getDirectMessages() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getDirectMessages() requires you to be authenticated.") + + def getSentMessages(self, since_id = None, max_id = None, count = None, page = "1"): + if self.authenticated is True: + apiURL = "http://twitter.com/direct_messages/sent.json?page=" + page + if since_id is not None: + apiURL += "&since_id=" + since_id + if max_id is not None: + apiURL += "&max_id=" + max_id + if count is not None: + apiURL += "&count=" + count + + try: + return simplejson.load(self.opener.open(apiURL)) + except HTTPError, e: + raise TangoError("getSentMessages() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getSentMessages() requires you to be authenticated.") + + def sendDirectMessage(self, user, text): + if self.authenticated is True: + if len(list(text)) < 140: + try: + return self.opener.open("http://twitter.com/direct_messages/new.json", urllib.urlencode({"user": user, "text": text})) + except HTTPError, e: + raise TangoError("sendDirectMessage() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("Your message must not be longer than 140 characters") + else: + raise TangoError("You must be authenticated to send a new direct message.") + + def destroyDirectMessage(self, id): + if self.authenticated is True: + try: + return self.opener.open("http://twitter.com/direct_messages/destroy/%s.json" % id, "") + except HTTPError, e: + raise TangoError("destroyDirectMessage() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("You must be authenticated to destroy a direct message.") + + def createFriendship(self, id = None, user_id = None, screen_name = None, follow = "false"): + if self.authenticated is True: + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/friendships/create/" + id + ".json" + "?follow=" + follow + if user_id is not None: + apiURL = "http://twitter.com/friendships/create.json?user_id=" + user_id + "&follow=" + follow + if screen_name is not None: + apiURL = "http://twitter.com/friendships/create.json?screen_name=" + screen_name + "&follow=" + follow + try: + return simplejson.load(self.opener.open(apiURL)) + except HTTPError, e: + # Rate limiting is done differently here for API reasons... + if e.code == 403: + raise TangoError("You've hit the update limit for this method. Try again in 24 hours.") + raise TangoError("createFriendship() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("createFriendship() requires you to be authenticated.") + + def destroyFriendship(self, id = None, user_id = None, screen_name = None): + if self.authenticated is True: + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/friendships/destroy/" + id + ".json" + if user_id is not None: + apiURL = "http://twitter.com/friendships/destroy.json?user_id=" + user_id + if screen_name is not None: + apiURL = "http://twitter.com/friendships/destroy.json?screen_name=" + screen_name + try: + return simplejson.load(self.opener.open(apiURL)) + except HTTPError, e: + raise TangoError("destroyFriendship() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("destroyFriendship() requires you to be authenticated.") + + def checkIfFriendshipExists(self, user_a, user_b): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/friendships/exists.json", urllib.urlencode({"user_a": user_a, "user_b": user_b}))) + except HTTPError, e: + raise TangoError("checkIfFriendshipExists() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("checkIfFriendshipExists(), oddly, requires that you be authenticated.") + + def updateDeliveryDevice(self, device_name = "none"): + if self.authenticated is True: + try: + return self.opener.open("http://twitter.com/account/update_delivery_device.json?", urllib.urlencode({"device": device_name})) + except HTTPError, e: + raise TangoError("updateDeliveryDevice() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("updateDeliveryDevice() requires you to be authenticated.") + + def updateProfileColors(self, **kwargs): + if self.authenticated is True: + try: + return self.opener.open(self.constructApiURL("http://twitter.com/account/update_profile_colors.json?", kwargs)) + except HTTPError, e: + raise TangoError("updateProfileColors() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("updateProfileColors() requires you to be authenticated.") + + def updateProfile(self, name = None, email = None, url = None, location = None, description = None): + if self.authenticated is True: + useAmpersands = False + updateProfileQueryString = "" + if name is not None: + if len(list(name)) < 20: + updateProfileQueryString += "name=" + name + useAmpersands = True + else: + raise TangoError("Twitter has a character limit of 20 for all usernames. Try again.") + if email is not None and "@" in email: + if len(list(email)) < 40: + if useAmpersands is True: + updateProfileQueryString += "&email=" + email + else: + updateProfileQueryString += "email=" + email + useAmpersands = True + else: + raise TangoError("Twitter has a character limit of 40 for all email addresses, and the email address must be valid. Try again.") + if url is not None: + if len(list(url)) < 100: + if useAmpersands is True: + updateProfileQueryString += "&" + urllib.urlencode({"url": url}) + else: + updateProfileQueryString += urllib.urlencode({"url": url}) + useAmpersands = True + else: + raise TangoError("Twitter has a character limit of 100 for all urls. Try again.") + if location is not None: + if len(list(location)) < 30: + if useAmpersands is True: + updateProfileQueryString += "&" + urllib.urlencode({"location": location}) + else: + updateProfileQueryString += urllib.urlencode({"location": location}) + useAmpersands = True + else: + raise TangoError("Twitter has a character limit of 30 for all locations. Try again.") + if description is not None: + if len(list(description)) < 160: + if useAmpersands is True: + updateProfileQueryString += "&" + urllib.urlencode({"description": description}) + else: + updateProfileQueryString += urllib.urlencode({"description": description}) + else: + raise TangoError("Twitter has a character limit of 160 for all descriptions. Try again.") + + if updateProfileQueryString != "": + try: + return self.opener.open("http://twitter.com/account/update_profile.json?", updateProfileQueryString) + except HTTPError, e: + raise TangoError("updateProfile() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("updateProfile() requires you to be authenticated.") + + def getFavorites(self, page = "1"): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/favorites.json?page=" + page)) + except HTTPError, e: + raise TangoError("getFavorites() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getFavorites() requires you to be authenticated.") + + def createFavorite(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/favorites/create/" + id + ".json", "")) + except HTTPError, e: + raise TangoError("createFavorite() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("createFavorite() requires you to be authenticated.") + + def destroyFavorite(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/favorites/destroy/" + id + ".json", "")) + except HTTPError, e: + raise TangoError("destroyFavorite() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("destroyFavorite() requires you to be authenticated.") + + def notificationFollow(self, id = None, user_id = None, screen_name = None): + if self.authenticated is True: + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/notifications/follow/" + id + ".json" + if user_id is not None: + apiURL = "http://twitter.com/notifications/follow/follow.json?user_id=" + user_id + if screen_name is not None: + apiURL = "http://twitter.com/notifications/follow/follow.json?screen_name=" + screen_name + try: + return simplejson.load(self.opener.open(apiURL, "")) + except HTTPError, e: + raise TangoError("notificationFollow() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("notificationFollow() requires you to be authenticated.") + + def notificationLeave(self, id = None, user_id = None, screen_name = None): + if self.authenticated is True: + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/notifications/leave/" + id + ".json" + if user_id is not None: + apiURL = "http://twitter.com/notifications/leave/leave.json?user_id=" + user_id + if screen_name is not None: + apiURL = "http://twitter.com/notifications/leave/leave.json?screen_name=" + screen_name + try: + return simplejson.load(self.opener.open(apiURL, "")) + except HTTPError, e: + raise TangoError("notificationLeave() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("notificationLeave() requires you to be authenticated.") + + def getFriendsIDs(self, id = None, user_id = None, screen_name = None, page = "1"): + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/friends/ids/" + id + ".json" + "?page=" + page + if user_id is not None: + apiURL = "http://twitter.com/friends/ids.json?user_id=" + user_id + "&page=" + page + if screen_name is not None: + apiURL = "http://twitter.com/friends/ids.json?screen_name=" + screen_name + "&page=" + page + try: + return simplejson.load(urllib2.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("getFriendsIDs() failed with a %s error code." % `e.code`, e.code) + + def getFollowersIDs(self, id = None, user_id = None, screen_name = None, page = "1"): + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/followers/ids/" + id + ".json" + "?page=" + page + if user_id is not None: + apiURL = "http://twitter.com/followers/ids.json?user_id=" + user_id + "&page=" + page + if screen_name is not None: + apiURL = "http://twitter.com/followers/ids.json?screen_name=" + screen_name + "&page=" + page + try: + return simplejson.load(urllib2.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("getFollowersIDs() failed with a %s error code." % `e.code`, e.code) + + def createBlock(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/blocks/create/" + id + ".json", "")) + except HTTPError, e: + raise TangoError("createBlock() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("createBlock() requires you to be authenticated.") + + def destroyBlock(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/blocks/destroy/" + id + ".json", "")) + except HTTPError, e: + raise TangoError("destroyBlock() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("destroyBlock() requires you to be authenticated.") + + def checkIfBlockExists(self, id = None, user_id = None, screen_name = None): + apiURL = "" + if id is not None: + apiURL = "http://twitter.com/blocks/exists/" + id + ".json" + if user_id is not None: + apiURL = "http://twitter.com/blocks/exists.json?user_id=" + user_id + if screen_name is not None: + apiURL = "http://twitter.com/blocks/exists.json?screen_name=" + screen_name + try: + return simplejson.load(urllib2.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("checkIfBlockExists() failed with a %s error code." % `e.code`, e.code) + + def getBlocking(self, page = "1"): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/blocks/blocking.json?page=" + page)) + except HTTPError, e: + raise TangoError("getBlocking() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getBlocking() requires you to be authenticated") + + def getBlockedIDs(self): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/blocks/blocking/ids.json")) + except HTTPError, e: + raise TangoError("getBlockedIDs() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getBlockedIDs() requires you to be authenticated.") + + def searchTwitter(self, search_query, **kwargs): + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": search_query}) + try: + return simplejson.load(urllib2.urlopen(searchURL)) + except HTTPError, e: + raise TangoError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) + + def getCurrentTrends(self, excludeHashTags = False): + apiURL = "http://search.twitter.com/trends/current.json" + if excludeHashTags is True: + apiURL += "?exclude=hashtags" + try: + return simplejson.load(urllib.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("getCurrentTrends() failed with a %s error code." % `e.code`, e.code) + + def getDailyTrends(self, date = None, exclude = False): + apiURL = "http://search.twitter.com/trends/daily.json" + questionMarkUsed = False + if date is not None: + apiURL += "?date=" + date + questionMarkUsed = True + if exclude is True: + if questionMarkUsed is True: + apiURL += "&exclude=hashtags" + else: + apiURL += "?exclude=hashtags" + try: + return simplejson.load(urllib.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("getDailyTrends() failed with a %s error code." % `e.code`, e.code) + + def getWeeklyTrends(self, date = None, exclude = False): + apiURL = "http://search.twitter.com/trends/daily.json" + questionMarkUsed = False + if date is not None: + apiURL += "?date=" + date + questionMarkUsed = True + if exclude is True: + if questionMarkUsed is True: + apiURL += "&exclude=hashtags" + else: + apiURL += "?exclude=hashtags" + try: + return simplejson.load(urllib.urlopen(apiURL)) + except HTTPError, e: + raise TangoError("getWeeklyTrends() failed with a %s error code." % `e.code`, e.code) + + def getSavedSearches(self): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/saved_searches.json")) + except HTTPError, e: + raise TangoError("getSavedSearches() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("getSavedSearches() requires you to be authenticated.") + + def showSavedSearch(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/saved_searches/show/" + id + ".json")) + except HTTPError, e: + raise TangoError("showSavedSearch() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("showSavedSearch() requires you to be authenticated.") + + def createSavedSearch(self, query): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/saved_searches/create.json?query=" + query, "")) + except HTTPError, e: + raise TangoError("createSavedSearch() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("createSavedSearch() requires you to be authenticated.") + + def destroySavedSearch(self, id): + if self.authenticated is True: + try: + return simplejson.load(self.opener.open("http://twitter.com/saved_searches/destroy/" + id + ".json", "")) + except HTTPError, e: + raise TangoError("destroySavedSearch() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("destroySavedSearch() requires you to be authenticated.") + + # The following methods are apart from the other Account methods, because they rely on a whole multipart-data posting function set. + def updateProfileBackgroundImage(self, filename, tile="true"): + if self.authenticated is True: + try: + files = [("image", filename, open(filename).read())] + fields = [] + content_type, body = self.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib2.Request("http://twitter.com/account/update_profile_background_image.json?tile=" + tile, body, headers) + return self.opener.open(r).read() + except HTTPError, e: + raise TangoError("updateProfileBackgroundImage() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("You realize you need to be authenticated to change a background image, right?") + + def updateProfileImage(self, filename): + if self.authenticated is True: + try: + files = [("image", filename, open(filename).read())] + fields = [] + content_type, body = self.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib2.Request("http://twitter.com/account/update_profile_image.json", body, headers) + return self.opener.open(r).read() + except HTTPError, e: + raise TangoError("updateProfileImage() failed with a %s error code." % `e.code`, e.code) + else: + raise TangoError("You realize you need to be authenticated to change a profile image, right?") + + def encode_multipart_formdata(self, fields, files): + BOUNDARY = mimetools.choose_boundary() + CRLF = '\r\n' + L = [] + for (key, value) in fields: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + L.append(value) + for (key, filename, value) in files: + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: %s' % self.get_content_type(filename)) + L.append('') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + + def get_content_type(self, filename): + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' diff --git a/twython/twython3k.py b/twython3k.py similarity index 99% rename from twython/twython3k.py rename to twython3k.py index fdcc081..93e2e06 100644 --- a/twython/twython3k.py +++ b/twython3k.py @@ -15,7 +15,7 @@ import http.client, urllib, urllib.request, urllib.error, urllib.parse, mimetype from urllib.error import HTTPError __author__ = "Ryan McGrath " -__version__ = "0.6" +__version__ = "0.5" try: import simplejson