From eb5541e4332ccf088f03c744014d55816ec17cc5 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Sat, 16 Oct 2010 23:37:47 -0400 Subject: [PATCH] Twython 1.3, OAuth support is now finally included and working. Ships with an example Django application to get people started with OAuth, entire library is refactored to not be a royal clusterfsck. Enjoy. --- README.markdown | 72 +- oauth_django_example/__init__.py | 0 oauth_django_example/manage.py | 11 + oauth_django_example/settings.py | 94 + oauth_django_example/templates/tweets.html | 3 + oauth_django_example/test.db | Bin 0 -> 65536 bytes oauth_django_example/twitter/__init__.py | 0 oauth_django_example/twitter/models.py | 7 + .../twitter/twitter_endpoints.py | 299 +++ oauth_django_example/twitter/twython.py | 421 ++++ oauth_django_example/twitter/views.py | 73 + oauth_django_example/urls.py | 9 + setup.py | 8 +- twython/__init__.py | 2 +- twython/core.py | 1956 ----------------- twython/oauth.py | 524 ----- twython/twitter_endpoints.py | 299 +++ twython/twyauth.py | 85 - twython/twython.py | 417 ++++ twython3k/core.py | 1896 ---------------- twython3k/twitter_endpoints.py | 299 +++ twython3k/twython.py | 417 ++++ 22 files changed, 2400 insertions(+), 4492 deletions(-) create mode 100644 oauth_django_example/__init__.py create mode 100644 oauth_django_example/manage.py create mode 100644 oauth_django_example/settings.py create mode 100644 oauth_django_example/templates/tweets.html create mode 100644 oauth_django_example/test.db create mode 100644 oauth_django_example/twitter/__init__.py create mode 100644 oauth_django_example/twitter/models.py create mode 100644 oauth_django_example/twitter/twitter_endpoints.py create mode 100644 oauth_django_example/twitter/twython.py create mode 100644 oauth_django_example/twitter/views.py create mode 100644 oauth_django_example/urls.py delete mode 100644 twython/core.py delete mode 100644 twython/oauth.py create mode 100644 twython/twitter_endpoints.py delete mode 100644 twython/twyauth.py create mode 100644 twython/twython.py delete mode 100644 twython3k/core.py create mode 100644 twython3k/twitter_endpoints.py create mode 100644 twython3k/twython.py diff --git a/README.markdown b/README.markdown index 302ac4d..8357239 100644 --- a/README.markdown +++ b/README.markdown @@ -1,52 +1,76 @@ Twython - Easy Twitter utilities in Python ========================================================================================= -I wrote Twython because I found that other Python Twitter libraries weren't that up to date. Certain -things like the Search API, OAuth, etc, don't seem to be fully covered. This is my attempt at -a library that offers more coverage. +Ah, Twitter, your API used to be so awesome, before you went and implemented the crap known +as OAuth 1.0. However, since you decided to force your entire development community over a barrel +about it, I suppose Twython has to support this. So, that said... -This is my first library I've ever written in Python, so there could be some stuff in here that'll -make a seasoned Python vet scratch his head, or possibly call me insane. It's open source, though, -and I'm open to anything that'll improve the library as a whole. +If you used this library and it all stopped working, it's because of the Authentication method change. +========================================================================================================= +Twitter recently disabled the use of "Basic Authentication", which is why, if you used Twython previously, +you probably started getting a ton of 401 errors. To fix this, we should note one thing... + +You need to change how authentication works in your program/application. If you're using a command line +application or something, you'll probably languish in hell for a bit, because OAuth wasn't really designed +for those types of use cases. Twython cannot help you with that or fix the annoying parts of OAuth. -OAuth and Streaming API support is in the works, but every other part of the Twitter API should be covered. Twython -handles both Basic (HTTP) Authentication and OAuth (Older versions (pre 0.9) of Twython need Basic Auth specified - -to override this, specify 'authtype="Basic"' in your twython.setup() call). - -Twython has Docstrings if you want function-by-function plays; otherwise, check the Twython Wiki or -Twitter's API Wiki (Twython calls mirror most of the methods listed there). +If you need OAuth, though, Twython now supports it, and ships with a skeleton Django application to get you started. +Enjoy! Requirements ----------------------------------------------------------------------------------------------------- Twython (for versions of Python before 2.6) requires a library called -"simplejson". You can grab it at the following link: +"simplejson". Depending on your flavor of package manager, you can do the following... -> http://pypi.python.org/pypi/simplejson + (pip install | easy_install) simplejson + +Twython also requires the (most excellent) OAuth2 library for handling OAuth tokens/signing/etc. Again... + + (pip install | easy_install) oauth2 Installation ----------------------------------------------------------------------------------------------------- Installing Twython is fairly easy. You can... -> easy_install twython + (pip install | easy_install) twython ...or, you can clone the repo and install it the old fashioned way. -> git clone git://github.com/ryanmcgrath/twython.git -> cd twython -> sudo python setup.py install + git clone git://github.com/ryanmcgrath/twython.git + cd twython + sudo python setup.py install Example Use ----------------------------------------------------------------------------------------------------- -> import twython -> -> twitter = twython.core.setup(username="example", password="example") -> twitter.updateStatus("See how easy this was?") + from twython import Twython + + twitter = Twython() + results = twitter.searchTwitter(q="bert") +A note about the development of Twython (specifically, 1.3) +---------------------------------------------------------------------------------------------------- +As of version 1.3, Twython has been extensively overhauled. Most API endpoint definitions are stored +in a separate Python file, and the class itself catches calls to methods that match up in said table. + +Certain functions require a bit more legwork, and get to stay in the main file, but for the most part +it's all abstracted out. + +As of Twython 1.3, the syntax has changed a bit as well. Instead of Twython.core, there's a main +Twython class to import and use. If you need to catch exceptions, import those from twython as well. + +Arguments to functions are now exact keyword matches for the Twitter API documentation - that means that +whatever query parameter arguments you read on Twitter's documentation (http://dev.twitter.com/doc) gets mapped +as a named argument to any Twitter function. + +For example: the search API looks for arguments under the name "q", so you pass q="query_here" to searchTwitter(). + +Doing this allows us to be incredibly flexible in querying the Twitter API, so changes to the API aren't held up +from you using them by this library. Twython 3k ----------------------------------------------------------------------------------------------------- There's an experimental version of Twython that's made for Python 3k. This is currently not guaranteed -to work, but it's provided so that others can grab it and hack on it. If you choose to try it out, -be aware of this. +to work (especially with regards to OAuth), but it's provided so that others can grab it and hack on it. +If you choose to try it out, be aware of this. Questions, Comments, etc? diff --git a/oauth_django_example/__init__.py b/oauth_django_example/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oauth_django_example/manage.py b/oauth_django_example/manage.py new file mode 100644 index 0000000..5e78ea9 --- /dev/null +++ b/oauth_django_example/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/oauth_django_example/settings.py b/oauth_django_example/settings.py new file mode 100644 index 0000000..8ceb7a5 --- /dev/null +++ b/oauth_django_example/settings.py @@ -0,0 +1,94 @@ +import os.path + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('Ryan McGrath', 'ryan@venodesigns.net'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': os.path.join(os.path.dirname(__file__), 'test.db'), # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + + +AUTH_PROFILE_MODULE = 'twitter.Profile' + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/New_York' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale +USE_L10N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '&!_t1t^gmenaid9mkmkuw=4nthj7f)o+!@$#ipfp*s11380t*)' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +ROOT_URLCONF = 'twython_testing.urls' + +TEMPLATE_DIRS = ( + os.path.join(os.path.dirname(__file__), 'templates'), +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'twitter', +) diff --git a/oauth_django_example/templates/tweets.html b/oauth_django_example/templates/tweets.html new file mode 100644 index 0000000..a2ec453 --- /dev/null +++ b/oauth_django_example/templates/tweets.html @@ -0,0 +1,3 @@ +{% for tweet in tweets %} + {{ tweet.text }} +{% endfor %} diff --git a/oauth_django_example/test.db b/oauth_django_example/test.db new file mode 100644 index 0000000000000000000000000000000000000000..3082a95365c31ec03e2a58e9352abe89008a2011 GIT binary patch literal 65536 zcmeI5Uu+{uTEM&P?yihyGXD~fvy+{ho@A5VC^I{5JC5yLt#&gRCzF4ki5)w(B`|Gw zJ8@#iUfYw&%q=RAf<0y#3AaYy#?s6?_{_=mLM75qQ=hvH`?kL!QMN%Sq(H_;c+7mnBH%N+HT z?2BDSLU~U&4w~;*$a%?YcUv`>qP~x{Ylz3`&Rk@Q`TANE` zm6cfbswu{-=_Sj{nGyM30!kvb5KF}3^RXm1gUJ#DsO~AVek#7So{EvIww|lXnAPNz zXX;&#>`PvIGB1;b0~@Dv^5}JpeRFf@VYZ7NQ&=||)pE00uI)KEc9#r1ifFgsr_I*3 z?kNG=6`oAlmZgmo)X4$aw>9@<)ZM*yLhmj+ad=9?zAIPIVbR9V&J`P%EGGBB%L<+q z=blzI#*}@nQQX!VZ$$i4_P92OnP(~}%f7`ckL@E<&HKdS_~?=w`=U|wFxAzEyv@)Y zK4x7fy3yH~>Oa<+ zb?57Dl^`zGcmss)J1*I`KK)p_c9%I;mOx2dHSd~YNc|;7%-E0o}Bbqa%9%t+p^`%WLr+TAPLiGoVKUwy44toTM}MNf@^G& z#Wm*YXj~b#B)Bdn!4=W?#UV+UK$qHDSTR*WBa`PXOWW-mY~?7oRhY@ibCz6N3zusv zGLy<#%Np9FCKqDr%=q;)lJGWC9Sr$Yq0k9P;%a#w&ANWts=!ruRN(p@6{ZFyA%v#e z{q2yZhB!ngPg&jK-LDcWwJk&BcJSonNlT8~(3a!sc~bFobiXY`wYDWrSQ1<#U0u4l zIvQ67ED5fQNpM9pei3(^P*zM;(8!o93D?ks120?UxVQqA7?Ug+uECVyDp=xGQ4(H9 zm)j6c0jh+>uDT^5jIKMh*t0d_s_dMrE=!v0v&#@yYUfNMi#5@w*ae8Hx04=n{f@}v z0{%DrH~5?Qckq|-&*6Qn;{<*WPvZ+%kpD&gOZkuF-;{q@eki{$=jAmyLMp%k1b_e# z00J*NfngC%p{}*rh?&`*cFgsg@0^In(EyuZ%$y4>$GOn*nmN28T|5n+7SSbyX)T%@ zVl8(qoX(q>?ONs<=p1WGW);nxvm+u>QIFNn8MA=bcPw2_dqp&jB-(}}caW*;TD_by zGkF_)A##%0Akz9g$HQ`*OOX?14zEa;7Xz%zZd!{b@p%zVppM0bY-aH~Y_%Yr5z%Gj zqLo-$^oi&?I=)VDoB6z|J=43G{aw>PqR;>2OMf_k01yBIKmZ5;fj%NYpZ{V0@1qkA zT?GO_00;m9AV3Jf^FIs&AOHk_01yBI{YL%AfdCKy0zd!= z(C2@IcLef*0|)>CAOHk_01)`p2^VuxE)HjRYg~`5)o`6vzh-AOHk_01yBIK;R`M zaK=5397_+j|KAChTX4DE!|tTm6#qoJEqzY@J?zGR44!#j=pgun{(qj^E5>s-VDB;6 z=WaFFp6_n(31hoy`jPvDc`2L-fgdVGv3%ap9dW4#onAxdN8^X8pPi`D@Bd}@hl2Y< z_xGi*OW&7&3x7i%l$YiI7CscdIuwVo@11FMc#V0%>+XL~-*j zIZ-CLUJ|S`mD*N$Z%om(meDF#jcz40KfgoVCK|-nV?v&(PqNO64PxKK1Ug!?J&*DI zzS$7q@v}Wf0WbO_1A*KHpWDYFj9lWC4o2>aAGtj|h#qtL$?uR3pPysa<Em)ujeaT!EoG6 zdx=$v8^6bkSPLS%MY1F^>k;37u`{wS8+?@SzaCx9t35K$!|(sCMF&KH01yBIKmZ8z z4FNj;58x>Q{{#LD{0;nT#P?qo$4CYoKmZ5;0U!VbfPf%eBo2i~M@PAD>ejt-t7SBJ z>O65J?Dcu6`$G0dgHhr_c=XaJ_ubt7$N#@Ud%sU{>W06b55%3l775nRNT4JZ@@BapHS-{`J z-@^Zn{|bK%|04cbk^u)000KY&2mk>f@caaZ#L-dIxojYAfPG$Mtr+mIcxe=MtrX-} z#ffp`SP(cD2hv&5JBsLp&KCvZpy>6o47wh0pBBA7mSQUb*O2JF!jks%&wl@hq;Ct- zx5e$}?6@A5N->kJ6oZM9z8G!fHbW$Tsg_PG?`Jb>=_>0~8Y zSk$ZW#oWEz&G-!uF(zhI7IqTqLT#rcBr4HZpU8ls|M&-`(Df2Xc3kyW0okd_EQ}CF}XL&{AVO zPq0QML$bt&q)J%b&jgmqAR$AunmCwKQ}x_peY;TA_gAa&eNSO8abMe9y2~oehsxQ_ zI33%&#rvUnA&{sPVs_;l3#oX94Gubr;+0}0TF>2zEhO(PsH;0$0Z+V|uC6V{)Y}`& zmF$CEa#ogi<2O??+3M2F+UC+sZZR9kZe$NU?f3tJe#e`Ev$H`p=)r$S-v9eZz#owS z4j=#ofB+Bx0zlx$n80i9HT0yFw2Rn35ssFkA+;2ojp_kCT+ky%q^RqWh!!YlGpbQA zpa1bc3FHF@5C8%|00;m9An+0qxQIkyeB3zDs`ZLNw-<6m&HewrD&ViaL>-2%00AHX z1b_e#_=zU)Gvvp6=>4BUYA6y6hJ)d2i<%bNO&IfqQmWQShGREwtu9rTTFt%r2g{*_ zVkFthB+KdLR%Cr^e``+L4zH){w^FkY79(q^mF3mhh;fk3-OeOaw;tThF0Hg?=$;$OnQfRFG8u<$es2Wcpt zV&O>^dT8iA!NLI+Vj8++7D_A>X^7k`bg>Z8`Ck}#!4NfJV6dV&q@dXKh&nMA=KzBz(@$vJeG>N0^z*sqf`^Qz%jXNu&9 z-1I!K(r%GgA7xgzpw(8lf!%@>mu-W|>i z>|2SV!PCXbrzDQJqkI-xNv^gxNmPJIpf^t@=juDZ7z{jR$^Jcb7Z>?+0RiMQ#v+>;}XU} zz1%SJbi>D<+djU1PWIiNdcuZjZ8hz^(s+8<+K}|JbNe~^>Q=jtBNT<*C)wiC+G*48pu_vyxfNZ=AwYJ^6Nj*GdKx`^mzmpjPwh%x z=rp^l^jS@BkL%;ze46e5YV0y>)piX>?*l!0ZwfYzV#8?JoH`^uQ$HrV#~!}oHLviu zA9sa!OeM#z%MpH(oLkx+6Y4ds9225fxS2`sKJfhST;4z&2mk>f00e+Qe-MEAzdz11 z^b!aF0U!VbfPj+#%>Par5C;N400;m9AkZHKVE*rq^9;QN0zd!=00AK2BmncjlLo|r z01yBIKmZ8z2LYJ>`{O)AFM$9M00KY&2sjDQ`5)nr$^JjiT!;e!AOHk_01yBIFDC(W H|DXQ@eXup& literal 0 HcmV?d00001 diff --git a/oauth_django_example/twitter/__init__.py b/oauth_django_example/twitter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oauth_django_example/twitter/models.py b/oauth_django_example/twitter/models.py new file mode 100644 index 0000000..3a07664 --- /dev/null +++ b/oauth_django_example/twitter/models.py @@ -0,0 +1,7 @@ +from django.db import models +from django.contrib.auth.models import User + +class Profile(models.Model): + user = models.ForeignKey(User) + oauth_token = models.CharField(max_length = 200) + oauth_secret = models.CharField(max_length = 200) diff --git a/oauth_django_example/twitter/twitter_endpoints.py b/oauth_django_example/twitter/twitter_endpoints.py new file mode 100644 index 0000000..42bd5a1 --- /dev/null +++ b/oauth_django_example/twitter/twitter_endpoints.py @@ -0,0 +1,299 @@ +""" + A huge map of every Twitter API endpoint to a function definition in Twython. + + Parameters that need to be embedded in the URL are treated with mustaches, e.g: + + {{version}}, etc + + When creating new endpoint definitions, keep in mind that the name of the mustache + will be replaced with the keyword that gets passed in to the function at call time. + + i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced + with 47, instead of defaulting to 1 (said defaulting takes place at conversion time). +""" + +# Base Twitter API url, no need to repeat this junk... +base_url = 'http://api.twitter.com/{{version}}' + +api_table = { + 'getRateLimitStatus': { + 'url': '/account/rate_limit_status.json', + 'method': 'GET', + }, + + # Timeline methods + 'getPublicTimeline': { + 'url': '/statuses/public_timeline.json', + 'method': 'GET', + }, + 'getHomeTimeline': { + 'url': '/statuses/home_timeline.json', + 'method': 'GET', + }, + 'getUserTimeline': { + 'url': '/statuses/user_timeline.json', + 'method': 'GET', + }, + 'getFriendsTimeline': { + 'url': '/statuses/friends_timeline.json', + 'method': 'GET', + }, + + # Interfacing with friends/followers + 'getUserMentions': { + 'url': '/statuses/mentions.json', + 'method': 'GET', + }, + 'getFriendsStatus': { + 'url': '/statuses/friends.json', + 'method': 'GET', + }, + 'getFollowersStatus': { + 'url': '/statuses/followers.json', + 'method': 'GET', + }, + 'createFriendship': { + 'url': '/friendships/create.json', + 'method': 'POST', + }, + 'destroyFriendship': { + 'url': '/friendships/destroy.json', + 'method': 'POST', + }, + 'getFriendsIDs': { + 'url': '/friends/ids.json', + 'method': 'GET', + }, + 'getFollowersIDs': { + 'url': '/followers/ids.json', + 'method': 'GET', + }, + + # Retweets + 'reTweet': { + 'url': '/statuses/retweet/{{id}}.json', + 'method': 'POST', + }, + 'getRetweets': { + 'url': '/statuses/retweets/{{id}}.json', + 'method': 'GET', + }, + 'retweetedOfMe': { + 'url': '/statuses/retweets_of_me.json', + 'method': 'GET', + }, + 'retweetedByMe': { + 'url': '/statuses/retweeted_by_me.json', + 'method': 'GET', + }, + 'retweetedToMe': { + 'url': '/statuses/retweeted_to_me.json', + 'method': 'GET', + }, + + # User methods + 'showUser': { + 'url': '/users/show.json', + 'method': 'GET', + }, + 'searchUsers': { + 'url': '/users/search.json', + 'method': 'GET', + }, + + # Status methods - showing, updating, destroying, etc. + 'showStatus': { + 'url': '/statuses/show/{{id}}.json', + 'method': 'GET', + }, + 'updateStatus': { + 'url': '/statuses/update.json', + 'method': 'POST', + }, + 'destroyStatus': { + 'url': '/statuses/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Direct Messages - getting, sending, effing, etc. + 'getDirectMessages': { + 'url': '/direct_messages.json', + 'method': 'GET', + }, + 'getSentMessages': { + 'url': '/direct_messages/sent.json', + 'method': 'GET', + }, + 'sendDirectMessage': { + 'url': '/direct_messages/new.json', + 'method': 'POST', + }, + 'destroyDirectMessage': { + 'url': '/direct_messages/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Friendship methods + 'checkIfFriendshipExists': { + 'url': '/friendships/exists.json', + 'method': 'GET', + }, + 'showFriendship': { + 'url': '/friendships/show.json', + 'method': 'GET', + }, + + # Profile methods + 'updateProfile': { + 'url': '/account/update_profile.json', + 'method': 'POST', + }, + 'updateProfileColors': { + 'url': '/account/update_profile_colors.json', + 'method': 'POST', + }, + + # Favorites methods + 'getFavorites': { + 'url': '/favorites.json', + 'method': 'GET', + }, + 'createFavorite': { + 'url': '/favorites/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyFavorite': { + 'url': '/favorites/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Blocking methods + 'createBlock': { + 'url': '/blocks/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyBlock': { + 'url': '/blocks/destroy/{{id}}.json', + 'method': 'POST', + }, + 'getBlocking': { + 'url': '/blocks/blocking.json', + 'method': 'GET', + }, + 'getBlockedIDs': { + 'url': '/blocks/blocking/ids.json', + 'method': 'GET', + }, + 'checkIfBlockExists': { + 'url': '/blocks/exists.json', + 'method': 'GET', + }, + + # Trending methods + 'getCurrentTrends': { + 'url': '/trends/current.json', + 'method': 'GET', + }, + 'getDailyTrends': { + 'url': '/trends/daily.json', + 'method': 'GET', + }, + 'getWeeklyTrends': { + 'url': '/trends/weekly.json', + 'method': 'GET', + }, + 'availableTrends': { + 'url': '/trends/available.json', + 'method': 'GET', + }, + 'trendsByLocation': { + 'url': '/trends/{{woeid}}.json', + 'method': 'GET', + }, + + # Saved Searches + 'getSavedSearches': { + 'url': '/saved_searches.json', + 'method': 'GET', + }, + 'showSavedSearch': { + 'url': '/saved_searches/show/{{id}}.json', + 'method': 'GET', + }, + 'createSavedSearch': { + 'url': '/saved_searches/create.json', + 'method': 'GET', + }, + 'destroySavedSearch': { + 'url': '/saved_searches/destroy/{{id}}.json', + 'method': 'GET', + }, + + # List API methods/endpoints. Fairly exhaustive and annoying in general. ;P + 'createList': { + 'url': '/{{username}}/lists.json', + 'method': 'POST', + }, + 'updateList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'POST', + }, + 'showLists': { + 'url': '/{{username}}/lists.json', + 'method': 'GET', + }, + 'getListMemberships': { + 'url': '/{{username}}/lists/followers.json', + 'method': 'GET', + }, + 'deleteList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'DELETE', + }, + 'getListTimeline': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'getSpecificList': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'addListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'POST', + }, + 'getListMembers': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'GET', + }, + 'deleteListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'DELETE', + }, + 'subscribeToList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'POST', + }, + 'unsubscribeFromList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'DELETE', + }, + + # The one-offs + 'notificationFollow': { + 'url': '/notifications/follow/follow.json', + 'method': 'POST', + }, + 'notificationLeave': { + 'url': '/notifications/leave/leave.json', + 'method': 'POST', + }, + 'updateDeliveryService': { + 'url': '/account/update_delivery_device.json', + 'method': 'POST', + }, + 'reportSpam': { + 'url': '/report_spam.json', + 'method': 'POST', + }, +} diff --git a/oauth_django_example/twitter/twython.py b/oauth_django_example/twitter/twython.py new file mode 100644 index 0000000..eb96be7 --- /dev/null +++ b/oauth_django_example/twitter/twython.py @@ -0,0 +1,421 @@ +#!/usr/bin/python + +""" + Twython is a library for Python that wraps the Twitter API. + It aims to abstract away all the API endpoints, so that additions to the library + and/or the Twitter API won't cause any overall problems. + + Questions, comments? ryan@venodesigns.net +""" + +__author__ = "Ryan McGrath " +__version__ = "1.3" + +import urllib +import urllib2 +import urlparse +import httplib +import httplib2 +import mimetypes +import mimetools +import re + +import oauth2 as oauth + +# Twython maps keyword based arguments to Twitter API endpoints. The endpoints +# table is a file with a dictionary of every API endpoint that Twython supports. +from twitter_endpoints import base_url, api_table + +from urllib2 import HTTPError + +# There are some special setups (like, oh, a Django application) where +# simplejson exists behind the scenes anyway. Past Python 2.6, this should +# never really cause any problems to begin with. +try: + # Python 2.6 and up + import json as simplejson +except ImportError: + try: + # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) + import simplejson + except ImportError: + try: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + except: + # Seriously wtf is wrong with you if you get this Exception. + raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + +class TwythonError(Exception): + """ + Generic error class, catch-all for most Twython issues. + Special cases are handled by APILimit and AuthError. + + Note: To use these, the syntax has changed as of Twython 1.3. To catch these, + you need to explicitly import them into your code, e.g: + + from twython import TwythonError, APILimit, AuthError + """ + 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(TwythonError): + """ + Raised when you've hit an API limit. Try to avoid these, read the API + docs if you're running into issues here, Twython does not concern itself with + this matter beyond telling you that you've done goofed. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class AuthError(TwythonError): + """ + Raised when you try to access a protected resource and it fails due to some issue with + your authentication. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class Twython(object): + def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None): + """setup(self, oauth_token = None, headers = None) + + Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + + Parameters: + twitter_token - Given to you when you register your application with Twitter. + twitter_secret - Given to you when you register your application with Twitter. + oauth_token - If you've gone through the authentication process and have a token for this user, + pass it in and it'll be used for all requests going forward. + oauth_token_secret - see oauth_token; it's the other half. + headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} + + ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. + """ + # Needed for hitting that there API. + self.request_token_url = 'http://twitter.com/oauth/request_token' + self.access_token_url = 'http://twitter.com/oauth/access_token' + self.authorize_url = 'http://twitter.com/oauth/authorize' + self.authenticate_url = 'http://twitter.com/oauth/authenticate' + self.twitter_token = twitter_token + self.twitter_secret = twitter_secret + self.oauth_token = oauth_token + self.oauth_secret = oauth_token_secret + + # If there's headers, set them, otherwise be an embarassing parent for their own good. + self.headers = headers + if self.headers is None: + headers = {'User-agent': 'Twython Python Twitter Library v1.3'} + + consumer = None + token = None + + if self.twitter_token is not None and self.twitter_secret is not None: + consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) + + if self.oauth_token is not None and self.oauth_secret is not None: + token = oauth.Token(oauth_token, oauth_token_secret) + + # Filter down through the possibilities here - if they have a token, if they're first stage, etc. + if consumer is not None and token is not None: + self.client = oauth.Client(consumer, token) + elif consumer is not None: + self.client = oauth.Client(consumer) + else: + # If they don't do authentication, but still want to request unprotected resources, we need an opener. + self.client = httplib2.Http() + + def __getattr__(self, api_call): + """ + The most magically awesome block of code you'll see in 2010. + + Rather than list out 9 million damn methods for this API, we just keep a table (see above) of + every API endpoint and their corresponding function id for this library. This pretty much gives + unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is + going to be your bottleneck... well, don't use Python. ;P + + For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). + It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, + we can take over and find the API method in our table. We then return a function that downloads and parses + what we're looking for, based on the keywords passed in. + + I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". + """ + def get(self, **kwargs): + # Go through and replace any mustaches that are in our API url. + fn = api_table[api_call] + base = re.sub( + '\{\{(?P[a-zA-Z]+)\}\}', + lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious. + base_url + fn['url'] + ) + + # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication + if fn['method'] == 'POST': + resp, content = self.client.request(base, fn['method'], urllib.urlencode(kwargs)) + else: + url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in kwargs.iteritems()]) + resp, content = self.client.request(url, fn['method']) + + return simplejson.loads(content) + + if api_call in api_table: + return get.__get__(self) + else: + raise AttributeError, api_call + + def get_authentication_tokens(self): + """ + get_auth_url(self) + + Returns an authorization URL for a user to hit. + """ + resp, content = self.client.request(self.request_token_url, "GET") + + if resp['status'] != '200': + raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content)) + + request_tokens = dict(urlparse.parse_qsl(content)) + request_tokens['auth_url'] = "%s?oauth_token=%s" % (self.authenticate_url, request_tokens['oauth_token']) + return request_tokens + + def get_authorized_tokens(self): + """ + get_authorized_tokens + + Returns authorized tokens after they go through the auth_url phase. + """ + resp, content = self.client.request(self.access_token_url, "GET") + return dict(urlparse.parse_qsl(content)) + + + @staticmethod + def shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): + """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") + + Shortens url specified by url_to_shorten. + + Parameters: + url_to_shorten - URL to shorten. + shortener - In case you want to use a url shortening service other than is.gd. + """ + try: + resp, content = self.client.request( + shortener + "?" + urllib.urlencode({query: self.unicode2utf8(url_to_shorten)}), + "GET" + ) + return content + except HTTPError, e: + raise TwythonError("shortenURL() failed with a %s error code." % `e.code`) + + def bulkUserLookup(self, ids = None, screen_names = None, version = None): + """ bulkUserLookup(self, ids = None, screen_names = None, version = None) + + A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that + contain their respective data sets. + + Statuses for the users in question will be returned inline if they exist. Requires authentication! + """ + version = version or self.apiVersion + if self.authenticated is True: + apiURL = "http://api.twitter.com/%d/users/lookup.json?lol=1" % version + if ids is not None: + apiURL += "&user_id=" + for id in ids: + apiURL += `id` + "," + if screen_names is not None: + apiURL += "&screen_name=" + for name in screen_names: + apiURL += name + "," + try: + return simplejson.load(self.opener.open(apiURL)) + except HTTPError, e: + raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) + else: + raise AuthError("bulkUserLookup() requires you to be authenticated.") + + def searchTwitter(self, **kwargs): + """searchTwitter(search_query, **kwargs) + + Returns tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) + try: + return simplejson.load(self.opener.open(searchURL)) + except HTTPError, e: + raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) + + def searchTwitterGen(self, **kwargs): + """searchTwitterGen(search_query, **kwargs) + + Returns a generator of tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) + try: + data = simplejson.load(self.opener.open(searchURL)) + except HTTPError, e: + raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) + + if not data['results']: + raise StopIteration + + for tweet in data['results']: + yield tweet + + if 'page' not in kwargs: + kwargs['page'] = 2 + else: + kwargs['page'] += 1 + + for tweet in self.searchTwitterGen(search_query, **kwargs): + yield tweet + + def isListMember(self, list_id, id, username, version = None): + """ isListMember(self, list_id, id, version) + + Check if a specified user (id) is a member of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - User who owns the list you're checking against (username) + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + version = version or self.apiVersion + try: + return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, `id`))) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + def isListSubscriber(self, list_id, id, version = None): + """ isListSubscriber(self, list_id, id, version) + + Check if a specified user (id) is a subscriber of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + version = version or self.apiVersion + try: + return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, `id`))) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + # 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", version = None): + """ updateProfileBackgroundImage(filename, tile="true") + + Updates the authenticating user's profile background image. + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. + tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. + ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + version = version or self.apiVersion + if self.authenticated is True: + try: + files = [("image", filename, open(filename, 'rb').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://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) + return self.opener.open(r).read() + except HTTPError, e: + raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) + else: + raise AuthError("You realize you need to be authenticated to change a background image, right?") + + def updateProfileImage(self, filename, version = None): + """ updateProfileImage(filename) + + Updates the authenticating user's profile image (avatar). + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + version = version or self.apiVersion + if self.authenticated is True: + try: + files = [("image", filename, open(filename, 'rb').read())] + fields = [] + content_type, body = Twython.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib2.Request("http://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) + return self.opener.open(r).read() + except HTTPError, e: + raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) + else: + raise AuthError("You realize you need to be authenticated to change a profile image, right?") + + @staticmethod + def encode_multipart_formdata(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' % Twython.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 + + @staticmethod + def get_content_type(filename): + """ get_content_type(self, filename) + + Exactly what you think it does. :D + """ + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + + @staticmethod + def unicode2utf8(text): + try: + if isinstance(text, unicode): + text = text.encode('utf-8') + except: + pass + return text diff --git a/oauth_django_example/twitter/views.py b/oauth_django_example/twitter/views.py new file mode 100644 index 0000000..14d8e69 --- /dev/null +++ b/oauth_django_example/twitter/views.py @@ -0,0 +1,73 @@ +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.models import User +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.contrib.auth.decorators import login_required + +from twython import Twython +from twitter.models import Profile + +CONSUMER_KEY = "piKE9TwKoAhJoj7KEMlwGQ" +CONSUMER_SECRET = "RA9IzvvzoLAFGOOoOndm1Cvyh94pwPWLy4Grl4dt0o" + +def twitter_logout(request): + logout(request) + return HttpResponseRedirect('/') + +def twitter_begin_auth(request): + # Instantiate Twython with the first leg of our trip. + twitter = Twython( + twitter_token = CONSUMER_KEY, + twitter_secret = CONSUMER_SECRET + ) + + # Request an authorization url to send the user to... + auth_props = twitter.get_authentication_tokens() + + # Then send them over there, durh. + request.session['request_token'] = auth_props + return HttpResponseRedirect(auth_props['auth_url']) + +def twitter_thanks(request): + # Now that we've got the magic tokens back from Twitter, we need to exchange + # for permanent ones and store them... + twitter = Twython( + twitter_token = CONSUMER_KEY, + twitter_secret = CONSUMER_SECRET, + oauth_token = request.session['request_token']['oauth_token'], + oauth_token_secret = request.session['request_token']['oauth_token_secret'] + ) + + # Retrieve the tokens we want... + authorized_tokens = twitter.get_authorized_tokens() + + # If they already exist, grab them, login and redirect to a page displaying stuff. + try: + user = User.objects.get(username = authorized_tokens['screen_name']) + except User.DoesNotExist: + # We mock a creation here; no email, password is just the token, etc. + user = User.objects.create_user(authorized_tokens['screen_name'], "fjdsfn@jfndjfn.com", authorized_tokens['oauth_token_secret']) + profile = Profile() + profile.user = user + profile.oauth_token = authorized_tokens['oauth_token'] + profile.oauth_secret = authorized_tokens['oauth_token_secret'] + profile.save() + + user = authenticate( + username = authorized_tokens['screen_name'], + password = authorized_tokens['oauth_token_secret'] + ) + login(request, user) + return HttpResponseRedirect('/timeline') + +def twitter_timeline(request): + user = request.user.get_profile() + twitter = Twython( + twitter_token = CONSUMER_KEY, + twitter_secret = CONSUMER_SECRET, + oauth_token = user.oauth_token, + oauth_token_secret = user.oauth_secret + ) + my_tweets = twitter.getHomeTimeline() + print my_tweets + return render_to_response('tweets.html', {'tweets': my_tweets}) diff --git a/oauth_django_example/urls.py b/oauth_django_example/urls.py new file mode 100644 index 0000000..9344f4d --- /dev/null +++ b/oauth_django_example/urls.py @@ -0,0 +1,9 @@ +from django.conf.urls.defaults import * +from twitter.views import twitter_begin_auth, twitter_thanks, twitter_logout, twitter_timeline + +urlpatterns = patterns('', + (r'^login/?$', twitter_begin_auth), + (r'^/logout?$', twitter_logout), + (r'^thanks/?$', twitter_thanks), # Where they're redirect to after authorizing + (r'^timeline/?$', twitter_timeline), +) diff --git a/setup.py b/setup.py index 60ea710..505eea7 100644 --- a/setup.py +++ b/setup.py @@ -4,13 +4,10 @@ import sys, os from setuptools import setup from setuptools import find_packages - __author__ = 'Ryan McGrath ' -__version__ = '1.2.1' - +__version__ = '1.3' setup( - # Basic package information. name = 'twython', version = __version__, @@ -20,7 +17,7 @@ setup( include_package_data = True, # Package dependencies. - install_requires = ['simplejson'], + install_requires = ['simplejson', 'oauth2'], # Metadata for PyPI. author = 'Ryan McGrath', @@ -38,5 +35,4 @@ setup( 'Topic :: Communications :: Chat', 'Topic :: Internet' ] - ) diff --git a/twython/__init__.py b/twython/__init__.py index 00deffe..59aac86 100644 --- a/twython/__init__.py +++ b/twython/__init__.py @@ -1 +1 @@ -import core, twyauth, streaming +from twython import Twython diff --git a/twython/core.py b/twython/core.py deleted file mode 100644 index 1d15c85..0000000 --- a/twython/core.py +++ /dev/null @@ -1,1956 +0,0 @@ -#!/usr/bin/python - -""" - 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 -import urllib -import urllib2 -import mimetypes -import mimetools - -from urlparse import urlparse -from urllib2 import HTTPError - - -__author__ = "Ryan McGrath " -__version__ = "1.2.1" - - -try: - import simplejson -except ImportError: - try: - import json as simplejson - except ImportError: - try: - from django.utils import simplejson - except: - raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") - - -class TwythonError(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(TwythonError): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return repr(self.msg) - - -class AuthError(TwythonError): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return repr(self.msg) - - -class setup: - - def __init__(self, username=None, password=None, headers=None, proxy=None, - version=1): - """setup(username = None, password = None, proxy = None, headers = None) - - Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). - - Parameters: - username - Your Twitter username, if you want Basic (HTTP) Authentication. - password - Password for your twitter account, if you want Basic (HTTP) Authentication. - headers - User agent header. - proxy - An object detailing information, in case proxy use/authentication is required. Object passed should be something like... - - proxyobj = { - "username": "fjnfsjdnfjd", - "password": "fjnfjsjdfnjd", - "host": "http://fjanfjasnfjjfnajsdfasd.com", - "port": 87 - } - - version (number) - Twitter supports a "versioned" API as of Oct. 16th, 2009 - this defaults to 1, but can be overridden on a class and function-based basis. - - ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. - """ - self.authenticated = False - self.username = username - self.apiVersion = version - self.proxy = proxy - self.headers = headers - if self.proxy is not None: - self.proxyobj = urllib2.ProxyHandler({'http': 'http://%s:%s@%s:%d' % (self.proxy["username"], self.proxy["password"], self.proxy["host"], self.proxy["port"])}) - # Check and set up authentication - if self.username is not None and password is not None: - # Assume Basic authentication ritual - self.auth_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() - self.auth_manager.add_password(None, "http://api.twitter.com", self.username, password) - self.handler = urllib2.HTTPBasicAuthHandler(self.auth_manager) - if self.proxy is not None: - self.opener = urllib2.build_opener(self.proxyobj, self.handler) - else: - self.opener = urllib2.build_opener(self.handler) - if self.headers is not None: - self.opener.addheaders = [('User-agent', self.headers)] - self.authenticated = True # Play nice, people can force-check using verifyCredentials() - else: - # Build a non-auth opener so we can allow proxy-auth and/or header swapping - if self.proxy is not None: - self.opener = urllib2.build_opener(self.proxyobj) - else: - self.opener = urllib2.build_opener() - if self.headers is not None: - self.opener.addheaders = [('User-agent', self.headers)] - - # URL Shortening function huzzah - def shortenURL(self, url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): - """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") - - Shortens url specified by url_to_shorten. - - Parameters: - url_to_shorten - URL to shorten. - shortener - In case you want to use a url shortening service other than is.gd. - """ - try: - return urllib2.urlopen(shortener + "?" + urllib.urlencode({query: self.unicode2utf8(url_to_shorten)})).read() - except HTTPError, e: - raise TwythonError("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 verifyCredentials(self, version = None): - """ verifyCredentials(self, version = None): - - Verifies the authenticity of the passed in credentials. Used to be a forced call, now made optional - (no need to waste network resources) - - Parameters: - None - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - simplejson.load(self.opener.open("http://api.twitter.com/%d/account/verify_credentials.json" % version)) - except HTTPError, e: - raise AuthError("Authentication failed with your provided credentials. Try again? (%s failure)" % `e.code`) - else: - raise AuthError("verifyCredentials() requires you to actually, y'know, pass in credentials.") - - def getRateLimitStatus(self, checkRequestingIP = True, version = None): - """getRateLimitStatus() - - Returns the remaining number of API requests available to the requesting user before the - API limit is reached for the current hour. Calls to rate_limit_status do not count against - the rate limit. If authentication credentials are provided, the rate limit status for the - authenticating user is returned. Otherwise, the rate limit status for the requesting - IP address is returned. - - Params: - checkRequestIP - Boolean, defaults to True. Set to False to check against the currently requesting IP, instead of the account level. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if checkRequestingIP is True: - return simplejson.load(urllib2.urlopen("http://api.twitter.com/%d/account/rate_limit_status.json" % version)) - else: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/account/rate_limit_status.json" % version)) - else: - raise TwythonError("You need to be authenticated to check a rate limit status on an account.") - except HTTPError, e: - raise TwythonError("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, version = None): - """getPublicTimeline() - - Returns the 20 most recent statuses from non-protected users who have set a custom user icon. - The public timeline is cached for 60 seconds, so requesting it more often than that is a waste of resources. - - Params: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/public_timeline.json" % version)) - except HTTPError, e: - raise TwythonError("getPublicTimeline() failed with a %s error code." % `e.code`) - - def getHomeTimeline(self, version = None, **kwargs): - """getHomeTimeline(**kwargs) - - Returns the 20 most recent statuses, including retweets, posted by the authenticating user - and that user's friends. This is the equivalent of /timeline/home on the Web. - - Usage note: This home_timeline is identical to statuses/friends_timeline, except it also - contains retweets, which statuses/friends_timeline does not (for backwards compatibility - reasons). In a future version of the API, statuses/friends_timeline will go away and - be replaced by home_timeline. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - homeTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/home_timeline.json" % version, kwargs) - return simplejson.load(self.opener.open(homeTimelineURL)) - except HTTPError, e: - raise TwythonError("getHomeTimeline() failed with a %s error code. (This is an upcoming feature in the Twitter API, and may not be implemented yet)" % `e.code`) - else: - raise AuthError("getHomeTimeline() requires you to be authenticated.") - - def getFriendsTimeline(self, version = None, **kwargs): - """getFriendsTimeline(**kwargs) - - Returns the 20 most recent statuses posted by the authenticating user, as well as that users friends. - This is the equivalent of /timeline/home on the Web. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - friendsTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/friends_timeline.json" % version, kwargs) - return simplejson.load(self.opener.open(friendsTimelineURL)) - except HTTPError, e: - raise TwythonError("getFriendsTimeline() failed with a %s error code." % `e.code`) - else: - raise AuthError("getFriendsTimeline() requires you to be authenticated.") - - def getUserTimeline(self, id = None, version = None, **kwargs): - """getUserTimeline(id = None, **kwargs) - - Returns the 20 most recent statuses posted from the authenticating user. It's also - possible to request another user's timeline via the id parameter. This is the - equivalent of the Web / page for your own user, or the profile page for a third party. - - Parameters: - id - Optional. Specifies the ID or screen name of the user for whom to return the user_timeline. - user_id - Optional. Specfies the ID of the user for whom to return the user_timeline. Helpful for disambiguating. - screen_name - Optional. Specfies 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) - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - 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://api.twitter.com/%d/statuses/user_timeline/%s.json" % (version, `id`), 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://api.twitter.com/%d/statuses/user_timeline/%s.json" % (version, self.username), kwargs) - else: - userTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/user_timeline.json" % version, 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(self.opener.open(userTimelineURL)) - except HTTPError, e: - raise TwythonError("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, version = None, **kwargs): - """getUserMentions(**kwargs) - - Returns the 20 most recent mentions (status containing @username) for the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - mentionsFeedURL = self.constructApiURL("http://api.twitter.com/%d/statuses/mentions.json" % version, kwargs) - return simplejson.load(self.opener.open(mentionsFeedURL)) - except HTTPError, e: - raise TwythonError("getUserMentions() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getUserMentions() requires you to be authenticated.") - - def reportSpam(self, id = None, user_id = None, screen_name = None, version = None): - """reportSpam(self, id), user_id, screen_name): - - Report a user account to Twitter as a spam account. *One* of the following parameters is required, and - this requires that you be authenticated with a user account. - - Parameters: - id - Optional. The ID or screen_name of the user you want to report as a spammer. - user_id - Optional. The ID of the user you want to report as a spammer. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. The ID or screen_name of the user you want to report as a spammer. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - # This entire block of code is stupid, but I'm far too tired to think through it at the moment. Refactor it if you care. - if id is not None or user_id is not None or screen_name is not None: - try: - apiExtension = "" - if id is not None: - apiExtension = "id=%s" % id - if user_id is not None: - apiExtension = "user_id=%s" % `user_id` - if screen_name is not None: - apiExtension = "screen_name=%s" % screen_name - return simplejson.load(self.opener.open("http://api.twitter.com/%d/report_spam.json" % version, apiExtension)) - except HTTPError, e: - raise TwythonError("reportSpam() failed with a %s error code." % `e.code`, e.code) - else: - raise TwythonError("reportSpam requires you to specify an id, user_id, or screen_name. Try again!") - else: - raise AuthError("reportSpam() requires you to be authenticated.") - - def reTweet(self, id, version = None): - """reTweet(id) - - Retweets a tweet. Requires the id parameter of the tweet you are retweeting. - - Parameters: - id - Required. The numerical ID of the tweet you are retweeting. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/retweet/%s.json" % (version, `id`), "POST")) - except HTTPError, e: - raise TwythonError("reTweet() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("reTweet() requires you to be authenticated.") - - def getRetweets(self, id, count = None, version = None): - """ getRetweets(self, id, count): - - Returns up to 100 of the first retweets of a given tweet. - - Parameters: - id - Required. The numerical ID of the tweet you want the retweets of. - count - Optional. Specifies the number of retweets to retrieve. May not be greater than 100. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/statuses/retweets/%s.json" % (version, `id`) - if count is not None: - apiURL += "?count=%s" % `count` - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getRetweets failed with a %s eroror code." % `e.code`, e.code) - else: - raise AuthError("getRetweets() requires you to be authenticated.") - - def retweetedOfMe(self, version = None, **kwargs): - """retweetedOfMe(**kwargs) - - Returns the 20 most recent tweets of the authenticated user that have been retweeted by others. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweets_of_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError, e: - raise TwythonError("retweetedOfMe() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("retweetedOfMe() requires you to be authenticated.") - - def retweetedByMe(self, version = None, **kwargs): - """retweetedByMe(**kwargs) - - Returns the 20 most recent retweets posted by the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweeted_by_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError, e: - raise TwythonError("retweetedByMe() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("retweetedByMe() requires you to be authenticated.") - - def retweetedToMe(self, version = None, **kwargs): - """retweetedToMe(**kwargs) - - Returns the 20 most recent retweets posted by the authenticating user's friends. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweeted_to_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError, e: - raise TwythonError("retweetedToMe() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("retweetedToMe() requires you to be authenticated.") - - def searchUsers(self, q, per_page = 20, page = 1, version = None): - """ searchUsers(q, per_page = None, page = None): - - Query Twitter to find a set of users who match the criteria we have. (Note: This, oddly, requires authentication - go figure) - - Parameters: - q (string) - Required. The query you wanna search against; self explanatory. ;) - per_page (number) - Optional, defaults to 20. Specify the number of users Twitter should return per page (no more than 20, just fyi) - page (number) - Optional, defaults to 1. The page of users you want to pull down. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/users/search.json?q=%s&per_page=%d&page=%d" % (version, q, per_page, page))) - except HTTPError, e: - raise TwythonError("searchUsers() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("searchUsers(), oddly, requires you to be authenticated.") - - def showUser(self, id = None, user_id = None, screen_name = None, version = None): - """showUser(id = None, user_id = None, screen_name = None) - - Returns extended information of a given user. The author's most recent status will be returned inline. - - Parameters: - ** Note: One of the following must always be specified. - id - The ID or screen name of a user. - user_id - Specfies the ID of the user to return. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Specfies the screen name of the user to return. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - - Usage Notes: - Requests for protected users without credentials from - 1) the user requested or - 2) a user that is following the protected user will omit the nested status element. - - ...will result in only publicly available data being returned. - """ - version = version or self.apiVersion - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/users/show/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/users/show.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/users/show.json?screen_name=%s" % (version, screen_name) - if apiURL != "": - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("showUser() failed with a %s error code." % `e.code`, e.code) - - def bulkUserLookup(self, ids = None, screen_names = None, version = None): - """ bulkUserLookup(self, ids = None, screen_names = None, version = None) - - A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that - contain their respective data sets. - - Statuses for the users in question will be returned inline if they exist. Requires authentication! - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/users/lookup.json?lol=1" % version - if ids is not None: - apiURL += "&user_id=" - for id in ids: - apiURL += `id` + "," - if screen_names is not None: - apiURL += "&screen_name=" - for name in screen_names: - apiURL += name + "," - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("bulkUserLookup() requires you to be authenticated.") - - def getFriendsStatus(self, id = None, user_id = None, screen_name = None, page = None, cursor="-1", version = None): - """getFriendsStatus(id = None, user_id = None, screen_name = None, page = None, cursor="-1") - - Returns a user's friends, each with current status inline. They are ordered by the order in which they were added as friends, 100 at a time. - (Please note that the result set isn't guaranteed to be 100 every time, as suspended users will be filtered out.) Use the page option to access - older friends. With no user specified, the request defaults to the authenticated users friends. - - It's also possible to request another user's friends list via the id, screen_name or user_id parameter. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, or screen_name) - id - Optional. The ID or screen name of the user for whom to request a list of friends. - user_id - Optional. Specfies the ID of the user for whom to return the list of friends. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the user for whom to return the list of friends. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page of friends to receive. - cursor - Optional. Breaks the results into pages. A single page contains 100 users. This is recommended for users who are following many users. Provide a value of -1 to begin paging. Provide values as returned to in the response body's next_cursor and previous_cursor attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends.json?screen_name=%s" % (version, screen_name) - try: - if page is not None: - return simplejson.load(self.opener.open(apiURL + "&page=%s" % `page`)) - else: - return simplejson.load(self.opener.open(apiURL + "&cursor=%s" % cursor)) - except HTTPError, e: - raise TwythonError("getFriendsStatus() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getFriendsStatus() requires you to be authenticated.") - - def getFollowersStatus(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFollowersStatus(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns the authenticating user's followers, each with current status inline. - They are ordered by the order in which they joined Twitter, 100 at a time. - (Note that the result set isn't guaranteed to be 100 every time, as suspended users will be filtered out.) - - Use the page option to access earlier followers. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Optional. The ID or screen name of the user for whom to request a list of followers. - user_id - Optional. Specfies the ID of the user for whom to return the list of followers. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the user for whom to return the list of followers. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page to retrieve. - cursor - Optional. Breaks the results into pages. A single page contains 100 users. This is recommended for users who are following many users. Provide a value of -1 to begin paging. Provide values as returned to in the response body's next_cursor and previous_cursor attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers.json?screen_name=%s" % (version, screen_name) - try: - if apiURL.find("?") == -1: - apiURL += "?" - else: - apiURL += "&" - if page is not None: - return simplejson.load(self.opener.open(apiURL + "page=%s" % page)) - else: - return simplejson.load(self.opener.open(apiURL + "cursor=%s" % cursor)) - except HTTPError, e: - raise TwythonError("getFollowersStatus() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getFollowersStatus() requires you to be authenticated.") - - def showStatus(self, id, version = None): - """showStatus(id) - - Returns a single status, specified by the id parameter below. - The status's author will be returned inline. - - Parameters: - id - Required. The numerical ID of the status to retrieve. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/show/%s.json" % (version, id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/show/%s.json" % (version, id))) - except HTTPError, e: - raise TwythonError("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, latitude = None, longitude = None, version = None): - """updateStatus(status, in_reply_to_status_id = None) - - Updates the authenticating user's status. Requires the status parameter specified below. - A status update with text identical to the authenticating users current status will be ignored to prevent duplicates. - - Parameters: - status - Required. The text of your status update. URL encode as necessary. Statuses over 140 characters will be forceably truncated. - in_reply_to_status_id - Optional. The ID of an existing status that the update is in reply to. - latitude (string) - Optional. The location's latitude that this tweet refers to. - longitude (string) - Optional. The location's longitude that this tweet refers to. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - - ** Note: in_reply_to_status_id will be ignored unless the author of the tweet this parameter references - is mentioned within the status text. Therefore, you must include @username, where username is - the author of the referenced tweet, within the update. - - ** Note: valid ranges for latitude/longitude are, for example, -180.0 to +180.0 (East is positive) inclusive. - This parameter will be ignored if outside that range, not a number, if geo_enabled is disabled, or if there not a corresponding latitude parameter with this tweet. - """ - version = version or self.apiVersion - try: - postExt = urllib.urlencode({"status": self.unicode2utf8(status)}) - if latitude is not None and longitude is not None: - postExt += "&lat=%s&long=%s" % (latitude, longitude) - if in_reply_to_status_id is not None: - postExt += "&in_reply_to_status_id=%s" % `in_reply_to_status_id` - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/update.json?" % version, postExt)) - except HTTPError, e: - raise TwythonError("updateStatus() failed with a %s error code." % `e.code`, e.code) - - def destroyStatus(self, id, version = None): - """destroyStatus(id) - - Destroys the status specified by the required ID parameter. - The authenticating user must be the author of the specified status. - - Parameters: - id - Required. The ID of the status to destroy. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/destroy/%s.json?" % (version, id), "_method=DELETE")) - except HTTPError, e: - raise TwythonError("destroyStatus() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("destroyStatus() requires you to be authenticated.") - - def endSession(self, version = None): - """endSession() - - Ends the session of the authenticating user, returning a null cookie. - Use this method to sign users out of client-facing applications (widgets, etc). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - self.opener.open("http://api.twitter.com/%d/account/end_session.json" % version, "") - self.authenticated = False - except HTTPError, e: - raise TwythonError("endSession failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("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", version = None): - """getDirectMessages(since_id = None, max_id = None, count = None, page = "1") - - Returns a list of the 20 most recent direct messages sent to the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/direct_messages.json?page=%s" % (version, `page`) - if since_id is not None: - apiURL += "&since_id=%s" % `since_id` - if max_id is not None: - apiURL += "&max_id=%s" % `max_id` - if count is not None: - apiURL += "&count=%s" % `count` - - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getDirectMessages() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getDirectMessages() requires you to be authenticated.") - - def getSentMessages(self, since_id = None, max_id = None, count = None, page = "1", version = None): - """getSentMessages(since_id = None, max_id = None, count = None, page = "1") - - Returns a list of the 20 most recent direct messages sent by the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/direct_messages/sent.json?page=%s" % (version, `page`) - if since_id is not None: - apiURL += "&since_id=%s" % `since_id` - if max_id is not None: - apiURL += "&max_id=%s" % `max_id` - if count is not None: - apiURL += "&count=%s" % `count` - - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getSentMessages() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getSentMessages() requires you to be authenticated.") - - def sendDirectMessage(self, user, text, version = None): - """sendDirectMessage(user, text) - - Sends a new direct message to the specified user from the authenticating user. Requires both the user and text parameters. - Returns the sent message in the requested format when successful. - - Parameters: - user - Required. The ID or screen name of the recipient user. - text - Required. The text of your direct message. Be sure to keep it under 140 characters. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - if len(list(text)) < 140: - try: - return self.opener.open("http://api.twitter.com/%d/direct_messages/new.json" % version, urllib.urlencode({"user": user, "text": text})) - except HTTPError, e: - raise TwythonError("sendDirectMessage() failed with a %s error code." % `e.code`, e.code) - else: - raise TwythonError("Your message must not be longer than 140 characters") - else: - raise AuthError("You must be authenticated to send a new direct message.") - - def destroyDirectMessage(self, id, version = None): - """destroyDirectMessage(id) - - Destroys the direct message specified in the required ID parameter. - The authenticating user must be the recipient of the specified direct message. - - Parameters: - id - Required. The ID of the direct message to destroy. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return self.opener.open("http://api.twitter.com/%d/direct_messages/destroy/%s.json" % (version, id), "") - except HTTPError, e: - raise TwythonError("destroyDirectMessage() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("You must be authenticated to destroy a direct message.") - - def createFriendship(self, id = None, user_id = None, screen_name = None, follow = "false", version = None): - """createFriendship(id = None, user_id = None, screen_name = None, follow = "false") - - Allows the authenticating users to follow the user specified in the ID parameter. - Returns the befriended user in the requested format when successful. Returns a - string describing the failure condition when unsuccessful. If you are already - friends with the user an HTTP 403 will be returned. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to befriend. - user_id - Required. Specfies the ID of the user to befriend. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to befriend. Helpful for disambiguating when a valid screen name is also a user ID. - follow - Optional. Enable notifications for the target user in addition to becoming friends. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if user_id is not None: - apiURL = "user_id=%s&follow=%s" %(`user_id`, follow) - if screen_name is not None: - apiURL = "screen_name=%s&follow=%s" %(screen_name, follow) - try: - if id is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/create/%s.json" % (version, id), "?follow=%s" % follow)) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/create.json" % version, apiURL)) - except HTTPError, e: - # Rate limiting is done differently here for API reasons... - if e.code == 403: - raise APILimit("You've hit the update limit for this method. Try again in 24 hours.") - raise TwythonError("createFriendship() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("createFriendship() requires you to be authenticated.") - - def destroyFriendship(self, id = None, user_id = None, screen_name = None, version = None): - """destroyFriendship(id = None, user_id = None, screen_name = None) - - Allows the authenticating users to unfollow the user specified in the ID parameter. - Returns the unfollowed user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to unfollow. - user_id - Required. Specfies the ID of the user to unfollow. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to unfollow. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if user_id is not None: - apiURL = "user_id=%s" % `user_id` - if screen_name is not None: - apiURL = "screen_name=%s" % screen_name - try: - if id is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/destroy/%s.json" % (version, `id`), "lol=1")) # Random string hack for POST reasons ;P - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/destroy.json" % version, apiURL)) - except HTTPError, e: - raise TwythonError("destroyFriendship() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("destroyFriendship() requires you to be authenticated.") - - def checkIfFriendshipExists(self, user_a, user_b, version = None): - """checkIfFriendshipExists(user_a, user_b) - - Tests for the existence of friendship between two users. - Will return true if user_a follows user_b; otherwise, it'll return false. - - Parameters: - user_a - Required. The ID or screen_name of the subject user. - user_b - Required. The ID or screen_name of the user to test for following. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - friendshipURL = "http://api.twitter.com/%d/friendships/exists.json?%s" % (version, urllib.urlencode({"user_a": user_a, "user_b": user_b})) - return simplejson.load(self.opener.open(friendshipURL)) - except HTTPError, e: - raise TwythonError("checkIfFriendshipExists() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("checkIfFriendshipExists(), oddly, requires that you be authenticated.") - - def showFriendship(self, source_id = None, source_screen_name = None, target_id = None, target_screen_name = None, version = None): - """showFriendship(source_id, source_screen_name, target_id, target_screen_name) - - Returns detailed information about the relationship between two users. - - Parameters: - ** Note: One of the following is required if the request is unauthenticated - source_id - The user_id of the subject user. - source_screen_name - The screen_name of the subject user. - - ** Note: One of the following is required at all times - target_id - The user_id of the target user. - target_screen_name - The screen_name of the target user. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/friendships/show.json?lol=1" % version # Another quick hack, look away if you want. :D - if source_id is not None: - apiURL += "&source_id=%s" % `source_id` - if source_screen_name is not None: - apiURL += "&source_screen_name=%s" % source_screen_name - if target_id is not None: - apiURL += "&target_id=%s" % `target_id` - if target_screen_name is not None: - apiURL += "&target_screen_name=%s" % target_screen_name - try: - if self.authenticated is True: - return simplejson.load(self.opener.open(apiURL)) - else: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - # Catch this for now - if e.code == 403: - raise AuthError("You're unauthenticated, and forgot to pass a source for this method. Try again!") - raise TwythonError("showFriendship() failed with a %s error code." % `e.code`, e.code) - - def updateDeliveryDevice(self, device_name = "none", version = None): - """updateDeliveryDevice(device_name = "none") - - Sets which device Twitter delivers updates to for the authenticating user. - Sending "none" as the device parameter will disable IM or SMS updates. (Simply calling .updateDeliveryService() also accomplishes this) - - Parameters: - device - Required. Must be one of: sms, im, none. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return self.opener.open("http://api.twitter.com/%d/account/update_delivery_device.json?" % version, urllib.urlencode({"device": self.unicode2utf8(device_name)})) - except HTTPError, e: - raise TwythonError("updateDeliveryDevice() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("updateDeliveryDevice() requires you to be authenticated.") - - def updateProfileColors(self, - profile_background_color = None, - profile_text_color = None, - profile_link_color = None, - profile_sidebar_fill_color = None, - profile_sidebar_border_color = None, - version = None): - """updateProfileColors() - - Sets one or more hex values that control the color scheme of the authenticating user's profile page on api.twitter.com. - - Parameters: - ** Note: One or more of the following parameters must be present. Each parameter's value must - be a valid hexidecimal value, and may be either three or six characters (ex: #fff or #ffffff). - - profile_background_color - Optional. - profile_text_color - Optional. - profile_link_color - Optional. - profile_sidebar_fill_color - Optional. - profile_sidebar_border_color - Optional. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - updateProfileColorsQueryString = "?lol=2" - - def checkValidColor(str): - if len(str) != 6: - return False - for c in str: - if c not in "1234567890abcdefABCDEF": return False - - return True - - if profile_background_color is not None: - if checkValidColor(profile_background_color): - updateProfileColorsQueryString += "profile_background_color=" + profile_background_color - else: - raise TwythonError("Invalid background color. Try an hexadecimal 6 digit number.") - if profile_text_color is not None: - if checkValidColor(profile_text_color): - updateProfileColorsQueryString += "profile_text_color=" + profile_text_color - else: - raise TwythonError("Invalid text color. Try an hexadecimal 6 digit number.") - if profile_link_color is not None: - if checkValidColor(profile_link_color): - updateProfileColorsQueryString += "profile_link_color=" + profile_link_color - else: - raise TwythonError("Invalid profile link color. Try an hexadecimal 6 digit number.") - if profile_sidebar_fill_color is not None: - if checkValidColor(profile_sidebar_fill_color): - updateProfileColorsQueryString += "profile_sidebar_fill_color=" + profile_sidebar_fill_color - else: - raise TwythonError("Invalid sidebar fill color. Try an hexadecimal 6 digit number.") - if profile_sidebar_border_color is not None: - if checkValidColor(profile_sidebar_border_color): - updateProfileColorsQueryString += "profile_sidebar_border_color=" + profile_sidebar_border_color - else: - raise TwythonError("Invalid sidebar border color. Try an hexadecimal 6 digit number.") - - try: - return self.opener.open("http://api.twitter.com/%d/account/update_profile_colors.json?" % version, updateProfileColorsQueryString) - except HTTPError, e: - raise TwythonError("updateProfileColors() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("updateProfileColors() requires you to be authenticated.") - - def updateProfile(self, name = None, email = None, url = None, location = None, description = None, version = None): - """updateProfile(name = None, email = None, url = None, location = None, description = None) - - Sets values that users are able to set under the "Account" tab of their settings page. - Only the parameters specified will be updated. - - Parameters: - One or more of the following parameters must be present. Each parameter's value - should be a string. See the individual parameter descriptions below for further constraints. - - name - Optional. Maximum of 20 characters. - email - Optional. Maximum of 40 characters. Must be a valid email address. - url - Optional. Maximum of 100 characters. Will be prepended with "http://" if not present. - location - Optional. Maximum of 30 characters. The contents are not normalized or geocoded in any way. - description - Optional. Maximum of 160 characters. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - 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 TwythonError("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 TwythonError("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": self.unicode2utf8(url)}) - else: - updateProfileQueryString += urllib.urlencode({"url": self.unicode2utf8(url)}) - useAmpersands = True - else: - raise TwythonError("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": self.unicode2utf8(location)}) - else: - updateProfileQueryString += urllib.urlencode({"location": self.unicode2utf8(location)}) - useAmpersands = True - else: - raise TwythonError("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": self.unicode2utf8(description)}) - else: - updateProfileQueryString += urllib.urlencode({"description": self.unicode2utf8(description)}) - else: - raise TwythonError("Twitter has a character limit of 160 for all descriptions. Try again.") - - if updateProfileQueryString != "": - try: - return self.opener.open("http://api.twitter.com/%d/account/update_profile.json?" % version, updateProfileQueryString) - except HTTPError, e: - raise TwythonError("updateProfile() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("updateProfile() requires you to be authenticated.") - - def getFavorites(self, page = "1", version = None): - """getFavorites(page = "1") - - Returns the 20 most recent favorite statuses for the authenticating user or user specified by the ID parameter in the requested format. - - Parameters: - page - Optional. Specifies the page of favorites to retrieve. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites.json?page=%s" % (version, `page`))) - except HTTPError, e: - raise TwythonError("getFavorites() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getFavorites() requires you to be authenticated.") - - def createFavorite(self, id, version = None): - """createFavorite(id) - - Favorites the status specified in the ID parameter as the authenticating user. Returns the favorite status when successful. - - Parameters: - id - Required. The ID of the status to favorite. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites/create/%s.json" % (version, `id`), "")) - except HTTPError, e: - raise TwythonError("createFavorite() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("createFavorite() requires you to be authenticated.") - - def destroyFavorite(self, id, version = None): - """destroyFavorite(id) - - Un-favorites the status specified in the ID parameter as the authenticating user. Returns the un-favorited status in the requested format when successful. - - Parameters: - id - Required. The ID of the status to un-favorite. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites/destroy/%s.json" % (version, `id`), "")) - except HTTPError, e: - raise TwythonError("destroyFavorite() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("destroyFavorite() requires you to be authenticated.") - - def notificationFollow(self, id = None, user_id = None, screen_name = None, version = None): - """notificationFollow(id = None, user_id = None, screen_name = None) - - Enables device notifications for updates from the specified user. Returns the specified user when successful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/follow.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/follow.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL, "")) - except HTTPError, e: - raise TwythonError("notificationFollow() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("notificationFollow() requires you to be authenticated.") - - def notificationLeave(self, id = None, user_id = None, screen_name = None, version = None): - """notificationLeave(id = None, user_id = None, screen_name = None) - - Disables notifications for updates from the specified user to the authenticating user. Returns the specified user when successful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/leave.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/leave.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL, "")) - except HTTPError, e: - raise TwythonError("notificationLeave() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("notificationLeave() requires you to be authenticated.") - - def getFriendsIDs(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFriendsIDs(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns an array of numeric IDs for every user the specified user is following. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page number of the results beginning at 1. A single page contains up to 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.) - cursor - Optional. Breaks the results into pages. A single page contains 5000 ids. This is recommended for users with large ID lists. Provide a value of -1 to begin paging. Provide values as returned to in the response body's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - breakResults = "cursor=%s" % cursor - if page is not None: - breakResults = "page=%s" % page - if id is not None: - apiURL = "http://api.twitter.com/%d/friends/ids/%s.json?%s" %(version, id, breakResults) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/friends/ids.json?user_id=%s&%s" %(version, `user_id`, breakResults) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/friends/ids.json?screen_name=%s&%s" %(version, screen_name, breakResults) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getFriendsIDs() failed with a %s error code." % `e.code`, e.code) - - def getFollowersIDs(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFollowersIDs(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns an array of numeric IDs for every user following the specified user. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page number of the results beginning at 1. A single page contains 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.) - cursor - Optional. Breaks the results into pages. A single page contains 5000 ids. This is recommended for users with large ID lists. Provide a value of -1 to begin paging. Provide values as returned to in the response body's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - breakResults = "cursor=%s" % cursor - if page is not None: - breakResults = "page=%s" % page - if id is not None: - apiURL = "http://api.twitter.com/%d/followers/ids/%s.json?%s" % (version, `id`, breakResults) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/followers/ids.json?user_id=%s&%s" %(version, `user_id`, breakResults) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/followers/ids.json?screen_name=%s&%s" %(version, screen_name, breakResults) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getFollowersIDs() failed with a %s error code." % `e.code`, e.code) - - def createBlock(self, id, version = None): - """createBlock(id) - - Blocks the user specified in the ID parameter as the authenticating user. Destroys a friendship to the blocked user if it exists. - Returns the blocked user in the requested format when successful. - - Parameters: - id - The ID or screen name of a user to block. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/create/%s.json" % (version, `id`), "")) - except HTTPError, e: - raise TwythonError("createBlock() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("createBlock() requires you to be authenticated.") - - def destroyBlock(self, id, version = None): - """destroyBlock(id) - - Un-blocks the user specified in the ID parameter for the authenticating user. - Returns the un-blocked user in the requested format when successful. - - Parameters: - id - Required. The ID or screen_name of the user to un-block - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/destroy/%s.json" % (version, `id`), "")) - except HTTPError, e: - raise TwythonError("destroyBlock() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("destroyBlock() requires you to be authenticated.") - - def checkIfBlockExists(self, id = None, user_id = None, screen_name = None, version = None): - """checkIfBlockExists(id = None, user_id = None, screen_name = None) - - Returns if the authenticating user is blocking a target user. Will return the blocked user's object if a block exists, and - error with an HTTP 404 response code otherwise. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Optional. The ID or screen_name of the potentially blocked user. - user_id - Optional. Specfies the ID of the potentially blocked user. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the potentially blocked user. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists/%s.json" % (version, `id`) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists.json?user_id=%s" % (version, `user_id`) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("checkIfBlockExists() failed with a %s error code." % `e.code`, e.code) - - def getBlocking(self, page = "1", version = None): - """getBlocking(page = "1") - - Returns an array of user objects that the authenticating user is blocking. - - Parameters: - page - Optional. Specifies the page number of the results beginning at 1. A single page contains 20 ids. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/blocking.json?page=%s" % (version, `page`))) - except HTTPError, e: - raise TwythonError("getBlocking() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getBlocking() requires you to be authenticated") - - def getBlockedIDs(self, version = None): - """getBlockedIDs() - - Returns an array of numeric user ids the authenticating user is blocking. - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/blocking/ids.json" % version)) - except HTTPError, e: - raise TwythonError("getBlockedIDs() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getBlockedIDs() requires you to be authenticated.") - - def searchTwitter(self, search_query, **kwargs): - """searchTwitter(search_query, **kwargs) - - Returns tweets that match a specified query. - - Parameters: - callback - Optional. Only available for JSON format. If supplied, the response will use the JSONP format with a callback of the given name. - lang - Optional. Restricts tweets to the given language, given by an ISO 639-1 code. - locale - Optional. Language of the query you're sending (only ja is currently effective). Intended for language-specific clients; default should work in most cases. - rpp - Optional. The number of tweets to return per page, up to a max of 100. - page - Optional. The page number (starting at 1) to return, up to a max of roughly 1500 results (based on rpp * page. Note: there are pagination limits.) - since_id - Optional. Returns tweets with status ids greater than the given id. - geocode - Optional. Returns tweets by users located within a given radius of the given latitude/longitude, where the user's location is taken from their Twitter profile. The parameter value is specified by "latitide,longitude,radius", where radius units must be specified as either "mi" (miles) or "km" (kilometers). Note that you cannot use the near operator via the API to geocode arbitrary locations; however you can use this geocode parameter to search near geocodes directly. - show_user - Optional. When true, prepends ":" to the beginning of the tweet. This is useful for readers that do not display Atom's author field. The default is false. - - Usage Notes: - Queries are limited 140 URL encoded characters. - Some users may be absent from search results. - The since_id parameter will be removed from the next_page element as it is not supported for pagination. If since_id is removed a warning will be added to alert you. - This method will return an HTTP 404 error if since_id is used and is too old to be in the search index. - - Applications must have a meaningful and unique User Agent when using this method. - An HTTP Referrer is expected but not required. Search traffic that does not include a User Agent will be rate limited to fewer API calls per hour than - applications including a User Agent string. You can set your custom UA headers by passing it as a respective argument to the setup() method. - """ - searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) - try: - return simplejson.load(self.opener.open(searchURL)) - except HTTPError, e: - raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) - - def searchTwitterGen(self, search_query, **kwargs): - """searchTwitterGen(search_query, **kwargs) - - Returns a generator of tweets that match a specified query. - - Parameters: - callback - Optional. Only available for JSON format. If supplied, the response will use the JSONP format with a callback of the given name. - lang - Optional. Restricts tweets to the given language, given by an ISO 639-1 code. - locale - Optional. Language of the query you're sending (only ja is currently effective). Intended for language-specific clients; default should work in most cases. - rpp - Optional. The number of tweets to return per page, up to a max of 100. - page - Optional. The page number (starting at 1) to return, up to a max of roughly 1500 results (based on rpp * page. Note: there are pagination limits.) - since_id - Optional. Returns tweets with status ids greater than the given id. - geocode - Optional. Returns tweets by users located within a given radius of the given latitude/longitude, where the user's location is taken from their Twitter profile. The parameter value is specified by "latitide,longitude,radius", where radius units must be specified as either "mi" (miles) or "km" (kilometers). Note that you cannot use the near operator via the API to geocode arbitrary locations; however you can use this geocode parameter to search near geocodes directly. - show_user - Optional. When true, prepends ":" to the beginning of the tweet. This is useful for readers that do not display Atom's author field. The default is false. - - Usage Notes: - Queries are limited 140 URL encoded characters. - Some users may be absent from search results. - The since_id parameter will be removed from the next_page element as it is not supported for pagination. If since_id is removed a warning will be added to alert you. - This method will return an HTTP 404 error if since_id is used and is too old to be in the search index. - - Applications must have a meaningful and unique User Agent when using this method. - An HTTP Referrer is expected but not required. Search traffic that does not include a User Agent will be rate limited to fewer API calls per hour than - applications including a User Agent string. You can set your custom UA headers by passing it as a respective argument to the setup() method. - """ - searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) - try: - data = simplejson.load(self.opener.open(searchURL)) - except HTTPError, e: - raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) - - if not data['results']: - raise StopIteration - - for tweet in data['results']: - yield tweet - - if 'page' not in kwargs: - kwargs['page'] = 2 - else: - kwargs['page'] += 1 - - for tweet in self.searchTwitterGen(search_query, **kwargs): - yield tweet - - def getCurrentTrends(self, excludeHashTags = False, version = None): - """getCurrentTrends(excludeHashTags = False, version = None) - - Returns the current top 10 trending topics on Twitter. The response includes the time of the request, the name of each trending topic, and the query used - on Twitter Search results page for that topic. - - Parameters: - excludeHashTags - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/current.json" % version - if excludeHashTags is True: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getCurrentTrends() failed with a %s error code." % `e.code`, e.code) - - def getDailyTrends(self, date = None, exclude = False, version = None): - """getDailyTrends(date = None, exclude = False, version = None) - - Returns the top 20 trending topics for each hour in a given day. - - Parameters: - date - Optional. Permits specifying a start date for the report. The date should be formatted YYYY-MM-DD. - exclude - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/daily.json" % version - questionMarkUsed = False - if date is not None: - apiURL += "?date=%s" % date - questionMarkUsed = True - if exclude is True: - if questionMarkUsed is True: - apiURL += "&exclude=hashtags" - else: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getDailyTrends() failed with a %s error code." % `e.code`, e.code) - - def getWeeklyTrends(self, date = None, exclude = False): - """getWeeklyTrends(date = None, exclude = False) - - Returns the top 30 trending topics for each day in a given week. - - Parameters: - date - Optional. Permits specifying a start date for the report. The date should be formatted YYYY-MM-DD. - exclude - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/daily.json" % version - questionMarkUsed = False - if date is not None: - apiURL += "?date=%s" % date - questionMarkUsed = True - if exclude is True: - if questionMarkUsed is True: - apiURL += "&exclude=hashtags" - else: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError, e: - raise TwythonError("getWeeklyTrends() failed with a %s error code." % `e.code`, e.code) - - def getSavedSearches(self, version = None): - """getSavedSearches() - - Returns the authenticated user's saved search queries. - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches.json" % version)) - except HTTPError, e: - raise TwythonError("getSavedSearches() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("getSavedSearches() requires you to be authenticated.") - - def showSavedSearch(self, id, version = None): - """showSavedSearch(id) - - Retrieve the data for a saved search owned by the authenticating user specified by the given id. - - Parameters: - id - Required. The id of the saved search to be retrieved. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/show/%s.json" % (version, `id`))) - except HTTPError, e: - raise TwythonError("showSavedSearch() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("showSavedSearch() requires you to be authenticated.") - - def createSavedSearch(self, query, version = None): - """createSavedSearch(query) - - Creates a saved search for the authenticated user. - - Parameters: - query - Required. The query of the search the user would like to save. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/create.json?query=%s" % (version, query), "")) - except HTTPError, e: - raise TwythonError("createSavedSearch() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("createSavedSearch() requires you to be authenticated.") - - def destroySavedSearch(self, id, version = None): - """ destroySavedSearch(id) - - Destroys a saved search for the authenticated user. - The search specified by id must be owned by the authenticating user. - - Parameters: - id - Required. The id of the saved search to be deleted. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/destroy/%s.json" % (version, `id`), "")) - except HTTPError, e: - raise TwythonError("destroySavedSearch() failed with a %s error code." % `e.code`, e.code) - else: - raise AuthError("destroySavedSearch() requires you to be authenticated.") - - def createList(self, name, mode = "public", description = "", version = None): - """ createList(self, name, mode, description, version) - - Creates a new list for the currently authenticated user. (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - name - Required. The name for the new list. - description - Optional, in the sense that you can leave it blank if you don't want one. ;) - mode - Optional. This is a string indicating "public" or "private", defaults to "public". - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists.json" % (version, self.username), - urllib.urlencode({"name": name, "mode": mode, "description": description}))) - except HTTPError, e: - raise TwythonError("createList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("createList() requires you to be authenticated.") - - def updateList(self, list_id, name, mode = "public", description = "", version = None): - """ updateList(self, list_id, name, mode, description, version) - - Updates an existing list for the authenticating user. (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - This method is a bit cumbersome for the time being; I'd personally avoid using it unless you're positive you know what you're doing. Twitter should really look - at this... - - Parameters: - list_id - Required. The name of the list (this gets turned into a slug - e.g, "Huck Hound" becomes "huck-hound"). - name - Required. The name of the list, possibly for renaming or such. - description - Optional, in the sense that you can leave it blank if you don't want one. ;) - mode - Optional. This is a string indicating "public" or "private", defaults to "public". - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s.json" % (version, self.username, list_id), - urllib.urlencode({"name": name, "mode": mode, "description": description}))) - except HTTPError, e: - raise TwythonError("updateList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("updateList() requires you to be authenticated.") - - def showLists(self, version = None): - """ showLists(self, version) - - Show all the lists for the currently authenticated user (i.e, they own these lists). - (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists.json" % (version, self.username))) - except HTTPError, e: - raise TwythonError("showLists() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("showLists() requires you to be authenticated.") - - def getListMemberships(self, version = None): - """ getListMemberships(self, version) - - Get all the lists for the currently authenticated user (i.e, they're on all the lists that are returned, the lists belong to other people) - (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/followers.json" % (version, self.username))) - except HTTPError, e: - raise TwythonError("getLists() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("getLists() requires you to be authenticated.") - - def deleteList(self, list_id, version = None): - """ deleteList(self, list_id, version) - - Deletes a list for the authenticating user. - - Parameters: - list_id - Required. The name of the list to delete - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s.json" % (version, self.username, list_id), "_method=DELETE")) - except HTTPError, e: - raise TwythonError("deleteList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("deleteList() requires you to be authenticated.") - - def getListTimeline(self, list_id, cursor = "-1", version = None, **kwargs): - """ getListTimeline(self, list_id, cursor, version, **kwargs) - - Retrieves a timeline representing everyone in the list specified. - - Parameters: - list_id - Required. The name of the list to get a timeline for - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - cursor - Optional. Breaks the results into pages. Provide a value of -1 to begin paging. - Provide values returned in the response's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - baseURL = self.constructApiURL("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id), kwargs) - return simplejson.load(self.opener.open(baseURL + "&cursor=%s" % cursor)) - except HTTPError, e: - if e.code == 404: - raise AuthError("It seems the list you're trying to access is private/protected, and you don't have access. Are you authenticated and allowed?") - raise TwythonError("getListTimeline() failed with a %d error code." % e.code, e.code) - - def getSpecificList(self, list_id, version = None): - """ getSpecificList(self, list_id, version) - - Retrieve a specific list - this only requires authentication if the list you're requesting is protected/private (if it is, you need to have access as well). - - Parameters: - list_id - Required. The name of the list to get - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id))) - except HTTPError, e: - if e.code == 404: - raise AuthError("It seems the list you're trying to access is private/protected, and you don't have access. Are you authenticated and allowed?") - raise TwythonError("getSpecificList() failed with a %d error code." % e.code, e.code) - - def addListMember(self, list_id, id, version = None): - """ addListMember(self, list_id, id, version) - - Adds a new Member (the passed in id) to the specified list. - - Parameters: - list_id - Required. The slug of the list to add the new member to. - id - Required. The ID or slug of the user that's being added to the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id), "id=%s" % (id))) - except HTTPError, e: - raise TwythonError("addListMember() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("addListMember requires you to be authenticated.") - - def getListMembers(self, list_id, version = None): - """ getListMembers(self, list_id, version = None) - - Show all members of a specified list. This method requires authentication if the list is private/protected. - - Parameters: - list_id - Required. The slug of the list to retrieve members for. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id))) - except HTTPError, e: - raise TwythonError("getListMembers() failed with a %d error code." % e.code, e.code) - - def removeListMember(self, list_id, id, version = None): - """ removeListMember(self, list_id, id, version) - - Remove the specified user (id) from the specified list (list_id). Requires you to be authenticated and in control of the list in question. - - Parameters: - list_id - Required. The slug of the list to remove the specified user from. - id - Required. The ID of the user that's being added to the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id), "_method=DELETE&id=%s" % `id`)) - except HTTPError, e: - raise TwythonError("getListMembers() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("removeListMember() requires you to be authenticated.") - - def isListMember(self, list_id, id, version = None): - """ isListMember(self, list_id, id, version) - - Check if a specified user (id) is a member of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, self.username, list_id, `id`))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, self.username, list_id, `id`))) - except HTTPError, e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - - def subscribeToList(self, list_id, version): - """ subscribeToList(self, list_id, version) - - Subscribe the authenticated user to the list provided (must be public). - - Parameters: - list_id - Required. The list to subscribe to. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following.json" % (version, self.username, list_id), "")) - except HTTPError, e: - raise TwythonError("subscribeToList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("subscribeToList() requires you to be authenticated.") - - def unsubscribeFromList(self, list_id, version): - """ unsubscribeFromList(self, list_id, version) - - Unsubscribe the authenticated user from the list in question (must be public). - - Parameters: - list_id - Required. The list to unsubscribe from. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following.json" % (version, self.username, list_id), "_method=DELETE")) - except HTTPError, e: - raise TwythonError("unsubscribeFromList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("unsubscribeFromList() requires you to be authenticated.") - - def isListSubscriber(self, list_id, id, version = None): - """ isListSubscriber(self, list_id, id, version) - - Check if a specified user (id) is a subscriber of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, self.username, list_id, `id`))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, self.username, list_id, `id`))) - except HTTPError, e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - - def availableTrends(self, latitude = None, longitude = None, version = None): - """ availableTrends(latitude, longitude, version): - - Gets all available trends, optionally filtering by geolocation based stuff. - - Note: If you choose to pass a latitude/longitude, keep in mind that you have to pass both - one won't work by itself. ;P - - Parameters: - latitude (string) - Optional. A latitude to sort by. - longitude (string) - Optional. A longitude to sort by. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if latitude is not None and longitude is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/available.json?latitude=%s&longitude=%s" % (version, latitude, longitude))) - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/available.json" % version)) - except HTTPError, e: - raise TwythonError("availableTrends() failed with a %d error code." % e.code, e.code) - - def trendsByLocation(self, woeid, version = None): - """ trendsByLocation(woeid, version): - - Gets all available trends, filtering by geolocation (woeid - see http://developer.yahoo.com/geo/geoplanet/guide/concepts.html). - - Note: If you choose to pass a latitude/longitude, keep in mind that you have to pass both - one won't work by itself. ;P - - Parameters: - woeid (string) - Required. WoeID of the area you're searching in. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/%s.json" % (version, woeid))) - except HTTPError, e: - raise TwythonError("trendsByLocation() failed with a %d error code." % e.code, e.code) - - # 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", version = None): - """ updateProfileBackgroundImage(filename, tile="true") - - Updates the authenticating user's profile background image. - - Parameters: - image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. - tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. - ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - files = [("image", filename, open(filename, 'rb').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://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) - return self.opener.open(r).read() - except HTTPError, e: - raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("You realize you need to be authenticated to change a background image, right?") - - def updateProfileImage(self, filename, version = None): - """ updateProfileImage(filename) - - Updates the authenticating user's profile image (avatar). - - Parameters: - image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - files = [("image", filename, open(filename, 'rb').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://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) - return self.opener.open(r).read() - except HTTPError, e: - raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("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): - """ get_content_type(self, filename) - - Exactly what you think it does. :D - """ - return mimetypes.guess_type(filename)[0] or 'application/octet-stream' - - def unicode2utf8(self, text): - try: - if isinstance(text, unicode): - text = text.encode('utf-8') - except: - pass - return text diff --git a/twython/oauth.py b/twython/oauth.py deleted file mode 100644 index 4bc47f5..0000000 --- a/twython/oauth.py +++ /dev/null @@ -1,524 +0,0 @@ -import cgi -import urllib -import time -import random -import urlparse -import hmac -import binascii - -VERSION = '1.0' # Hi Blaine! -HTTP_METHOD = 'GET' -SIGNATURE_METHOD = 'PLAINTEXT' - -# Generic exception class -class OAuthError(RuntimeError): - def __init__(self, message='OAuth error occured.'): - self.message = message - -# optional WWW-Authenticate header (401 error) -def build_authenticate_header(realm=''): - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} - -# url escape -def escape(s): - # escape '/' too - return urllib.quote(s, safe='~') - -# util function: current timestamp -# seconds since epoch (UTC) -def generate_timestamp(): - return int(time.time()) - -# util function: nonce -# pseudorandom number -def generate_nonce(length=8): - return ''.join([str(random.randint(0, 9)) for i in range(length)]) - -# OAuthConsumer is a data type that represents the identity of the Consumer -# via its shared secret with the Service Provider. -class OAuthConsumer(object): - key = None - secret = None - - def __init__(self, key, secret): - self.key = key - self.secret = secret - -# OAuthToken is a data type that represents an End User via either an access -# or request token. -class OAuthToken(object): - # access tokens and request tokens - key = None - secret = None - - ''' - key = the token - secret = the token secret - ''' - def __init__(self, key, secret): - self.key = key - self.secret = secret - - def to_string(self): - return urllib.urlencode({'oauth_token': self.key, 'oauth_token_secret': self.secret}) - - # return a token from something like: - # oauth_token_secret=digg&oauth_token=digg - def from_string(s): - params = cgi.parse_qs(s, keep_blank_values=False) - key = params['oauth_token'][0] - secret = params['oauth_token_secret'][0] - return OAuthToken(key, secret) - from_string = staticmethod(from_string) - - def __str__(self): - return self.to_string() - -# OAuthRequest represents the request and can be serialized -class OAuthRequest(object): - ''' - OAuth parameters: - - oauth_consumer_key - - oauth_token - - oauth_signature_method - - oauth_signature - - oauth_timestamp - - oauth_nonce - - oauth_version - ... any additional parameters, as defined by the Service Provider. - ''' - parameters = None # oauth parameters - http_method = HTTP_METHOD - http_url = None - version = VERSION - - def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None): - self.http_method = http_method - self.http_url = http_url - self.parameters = parameters or {} - - def set_parameter(self, parameter, value): - self.parameters[parameter] = value - - def get_parameter(self, parameter): - try: - return self.parameters[parameter] - except: - raise OAuthError('Parameter not found: %s' % parameter) - - def _get_timestamp_nonce(self): - return self.get_parameter('oauth_timestamp'), self.get_parameter('oauth_nonce') - - # get any non-oauth parameters - def get_nonoauth_parameters(self): - parameters = {} - for k, v in self.parameters.iteritems(): - # ignore oauth parameters - if k.find('oauth_') < 0: - parameters[k] = v - return parameters - - # serialize as a header for an HTTPAuth request - def to_header(self, realm=''): - auth_header = 'OAuth realm="%s"' % realm - # add the oauth parameters - if self.parameters: - for k, v in self.parameters.iteritems(): - if k[:6] == 'oauth_': - auth_header += ', %s="%s"' % (k, escape(str(v))) - return {'Authorization': auth_header} - - # serialize as post data for a POST request - def to_postdata(self): - return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in self.parameters.iteritems()]) - - # serialize as a url for a GET request - def to_url(self): - return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata()) - - # return a string that consists of all the parameters that need to be signed - def get_normalized_parameters(self): - params = self.parameters - try: - # exclude the signature if it exists - del params['oauth_signature'] - except: - pass - key_values = params.items() - # sort lexicographically, first after key, then after value - key_values.sort() - # combine key value pairs in string and escape - return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) for k, v in key_values]) - - # just uppercases the http method - def get_normalized_http_method(self): - return self.http_method.upper() - - # parses the url and rebuilds it to be scheme://host/path - def get_normalized_http_url(self): - parts = urlparse.urlparse(self.http_url) - url_string = '%s://%s%s' % (parts[0], parts[1], parts[2]) # scheme, netloc, path - return url_string - - # set the signature parameter to the result of build_signature - def sign_request(self, signature_method, consumer, token): - # set the signature method - self.set_parameter('oauth_signature_method', signature_method.get_name()) - # set the signature - self.set_parameter('oauth_signature', self.build_signature(signature_method, consumer, token)) - - def build_signature(self, signature_method, consumer, token): - # call the build signature method within the signature method - return signature_method.build_signature(self, consumer, token) - - def from_request(http_method, http_url, headers=None, parameters=None, query_string=None): - # combine multiple parameter sources - if parameters is None: - parameters = {} - - # headers - if headers and 'Authorization' in headers: - auth_header = headers['Authorization'] - # check that the authorization header is OAuth - if auth_header.index('OAuth') > -1: - try: - # get the parameters from the header - header_params = OAuthRequest._split_header(auth_header) - parameters.update(header_params) - except: - raise OAuthError('Unable to parse OAuth parameters from Authorization header.') - - # GET or POST query string - if query_string: - query_params = OAuthRequest._split_url_string(query_string) - parameters.update(query_params) - - # URL parameters - param_str = urlparse.urlparse(http_url)[4] # query - url_params = OAuthRequest._split_url_string(param_str) - parameters.update(url_params) - - if parameters: - return OAuthRequest(http_method, http_url, parameters) - - return None - from_request = staticmethod(from_request) - - def from_consumer_and_token(oauth_consumer, token=None, http_method=HTTP_METHOD, http_url=None, parameters=None): - if not parameters: - parameters = {} - - defaults = { - 'oauth_consumer_key': oauth_consumer.key, - 'oauth_timestamp': generate_timestamp(), - 'oauth_nonce': generate_nonce(), - 'oauth_version': OAuthRequest.version, - } - - defaults.update(parameters) - parameters = defaults - - if token: - parameters['oauth_token'] = token.key - - return OAuthRequest(http_method, http_url, parameters) - from_consumer_and_token = staticmethod(from_consumer_and_token) - - def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD, http_url=None, parameters=None): - if not parameters: - parameters = {} - - parameters['oauth_token'] = token.key - - if callback: - parameters['oauth_callback'] = callback - - return OAuthRequest(http_method, http_url, parameters) - from_token_and_callback = staticmethod(from_token_and_callback) - - # util function: turn Authorization: header into parameters, has to do some unescaping - def _split_header(header): - params = {} - parts = header.split(',') - for param in parts: - # ignore realm parameter - if param.find('OAuth realm') > -1: - continue - # remove whitespace - param = param.strip() - # split key-value - param_parts = param.split('=', 1) - # remove quotes and unescape the value - params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) - return params - _split_header = staticmethod(_split_header) - - # util function: turn url string into parameters, has to do some unescaping - def _split_url_string(param_str): - parameters = cgi.parse_qs(param_str, keep_blank_values=False) - for k, v in parameters.iteritems(): - parameters[k] = urllib.unquote(v[0]) - return parameters - _split_url_string = staticmethod(_split_url_string) - -# OAuthServer is a worker to check a requests validity against a data store -class OAuthServer(object): - timestamp_threshold = 300 # in seconds, five minutes - version = VERSION - signature_methods = None - data_store = None - - def __init__(self, data_store=None, signature_methods=None): - self.data_store = data_store - self.signature_methods = signature_methods or {} - - def set_data_store(self, oauth_data_store): - self.data_store = data_store - - def get_data_store(self): - return self.data_store - - def add_signature_method(self, signature_method): - self.signature_methods[signature_method.get_name()] = signature_method - return self.signature_methods - - # process a request_token request - # returns the request token on success - def fetch_request_token(self, oauth_request): - try: - # get the request token for authorization - token = self._get_token(oauth_request, 'request') - except OAuthError: - # no token required for the initial token request - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - self._check_signature(oauth_request, consumer, None) - # fetch a new token - token = self.data_store.fetch_request_token(consumer) - return token - - # process an access_token request - # returns the access token on success - def fetch_access_token(self, oauth_request): - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - # get the request token - token = self._get_token(oauth_request, 'request') - self._check_signature(oauth_request, consumer, token) - new_token = self.data_store.fetch_access_token(consumer, token) - return new_token - - # verify an api call, checks all the parameters - def verify_request(self, oauth_request): - # -> consumer and token - version = self._get_version(oauth_request) - consumer = self._get_consumer(oauth_request) - # get the access token - token = self._get_token(oauth_request, 'access') - self._check_signature(oauth_request, consumer, token) - parameters = oauth_request.get_nonoauth_parameters() - return consumer, token, parameters - - # authorize a request token - def authorize_token(self, token, user): - return self.data_store.authorize_request_token(token, user) - - # get the callback url - def get_callback(self, oauth_request): - return oauth_request.get_parameter('oauth_callback') - - # optional support for the authenticate header - def build_authenticate_header(self, realm=''): - return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} - - # verify the correct version request for this server - def _get_version(self, oauth_request): - try: - version = oauth_request.get_parameter('oauth_version') - except: - version = VERSION - if version and version != self.version: - raise OAuthError('OAuth version %s not supported.' % str(version)) - return version - - # figure out the signature with some defaults - def _get_signature_method(self, oauth_request): - try: - signature_method = oauth_request.get_parameter('oauth_signature_method') - except: - signature_method = SIGNATURE_METHOD - try: - # get the signature method object - signature_method = self.signature_methods[signature_method] - except: - signature_method_names = ', '.join(self.signature_methods.keys()) - raise OAuthError('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) - - return signature_method - - def _get_consumer(self, oauth_request): - consumer_key = oauth_request.get_parameter('oauth_consumer_key') - if not consumer_key: - raise OAuthError('Invalid consumer key.') - consumer = self.data_store.lookup_consumer(consumer_key) - if not consumer: - raise OAuthError('Invalid consumer.') - return consumer - - # try to find the token for the provided request token key - def _get_token(self, oauth_request, token_type='access'): - token_field = oauth_request.get_parameter('oauth_token') - token = self.data_store.lookup_token(token_type, token_field) - if not token: - raise OAuthError('Invalid %s token: %s' % (token_type, token_field)) - return token - - def _check_signature(self, oauth_request, consumer, token): - timestamp, nonce = oauth_request._get_timestamp_nonce() - self._check_timestamp(timestamp) - self._check_nonce(consumer, token, nonce) - signature_method = self._get_signature_method(oauth_request) - try: - signature = oauth_request.get_parameter('oauth_signature') - except: - raise OAuthError('Missing signature.') - # validate the signature - valid_sig = signature_method.check_signature(oauth_request, consumer, token, signature) - if not valid_sig: - key, base = signature_method.build_signature_base_string(oauth_request, consumer, token) - raise OAuthError('Invalid signature. Expected signature base string: %s' % base) - built = signature_method.build_signature(oauth_request, consumer, token) - - def _check_timestamp(self, timestamp): - # verify that timestamp is recentish - timestamp = int(timestamp) - now = int(time.time()) - lapsed = now - timestamp - if lapsed > self.timestamp_threshold: - raise OAuthError('Expired timestamp: given %d and now %s has a greater difference than threshold %d' % (timestamp, now, self.timestamp_threshold)) - - def _check_nonce(self, consumer, token, nonce): - # verify that the nonce is uniqueish - nonce = self.data_store.lookup_nonce(consumer, token, nonce) - if nonce: - raise OAuthError('Nonce already used: %s' % str(nonce)) - -# OAuthClient is a worker to attempt to execute a request -class OAuthClient(object): - consumer = None - token = None - - def __init__(self, oauth_consumer, oauth_token): - self.consumer = oauth_consumer - self.token = oauth_token - - def get_consumer(self): - return self.consumer - - def get_token(self): - return self.token - - def fetch_request_token(self, oauth_request): - # -> OAuthToken - raise NotImplementedError - - def fetch_access_token(self, oauth_request): - # -> OAuthToken - raise NotImplementedError - - def access_resource(self, oauth_request): - # -> some protected resource - raise NotImplementedError - -# OAuthDataStore is a database abstraction used to lookup consumers and tokens -class OAuthDataStore(object): - - def lookup_consumer(self, key): - # -> OAuthConsumer - raise NotImplementedError - - def lookup_token(self, oauth_consumer, token_type, token_token): - # -> OAuthToken - raise NotImplementedError - - def lookup_nonce(self, oauth_consumer, oauth_token, nonce, timestamp): - # -> OAuthToken - raise NotImplementedError - - def fetch_request_token(self, oauth_consumer): - # -> OAuthToken - raise NotImplementedError - - def fetch_access_token(self, oauth_consumer, oauth_token): - # -> OAuthToken - raise NotImplementedError - - def authorize_request_token(self, oauth_token, user): - # -> OAuthToken - raise NotImplementedError - -# OAuthSignatureMethod is a strategy class that implements a signature method -class OAuthSignatureMethod(object): - def get_name(self): - # -> str - raise NotImplementedError - - def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token): - # -> str key, str raw - raise NotImplementedError - - def build_signature(self, oauth_request, oauth_consumer, oauth_token): - # -> str - raise NotImplementedError - - def check_signature(self, oauth_request, consumer, token, signature): - built = self.build_signature(oauth_request, consumer, token) - return built == signature - -class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod): - - def get_name(self): - return 'HMAC-SHA1' - - def build_signature_base_string(self, oauth_request, consumer, token): - sig = ( - escape(oauth_request.get_normalized_http_method()), - escape(oauth_request.get_normalized_http_url()), - escape(oauth_request.get_normalized_parameters()), - ) - - key = '%s&' % escape(consumer.secret) - if token: - key += escape(token.secret) - raw = '&'.join(sig) - return key, raw - - def build_signature(self, oauth_request, consumer, token): - # build the base signature string - key, raw = self.build_signature_base_string(oauth_request, consumer, token) - - # hmac object - try: - import hashlib # 2.5 - hashed = hmac.new(key, raw, hashlib.sha1) - except: - import sha # deprecated - hashed = hmac.new(key, raw, sha) - - # calculate the digest base 64 - return binascii.b2a_base64(hashed.digest())[:-1] - -class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod): - - def get_name(self): - return 'PLAINTEXT' - - def build_signature_base_string(self, oauth_request, consumer, token): - # concatenate the consumer key and secret - sig = escape(consumer.secret) + '&' - if token: - sig = sig + escape(token.secret) - return sig - - def build_signature(self, oauth_request, consumer, token): - return self.build_signature_base_string(oauth_request, consumer, token) \ No newline at end of file diff --git a/twython/twitter_endpoints.py b/twython/twitter_endpoints.py new file mode 100644 index 0000000..42bd5a1 --- /dev/null +++ b/twython/twitter_endpoints.py @@ -0,0 +1,299 @@ +""" + A huge map of every Twitter API endpoint to a function definition in Twython. + + Parameters that need to be embedded in the URL are treated with mustaches, e.g: + + {{version}}, etc + + When creating new endpoint definitions, keep in mind that the name of the mustache + will be replaced with the keyword that gets passed in to the function at call time. + + i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced + with 47, instead of defaulting to 1 (said defaulting takes place at conversion time). +""" + +# Base Twitter API url, no need to repeat this junk... +base_url = 'http://api.twitter.com/{{version}}' + +api_table = { + 'getRateLimitStatus': { + 'url': '/account/rate_limit_status.json', + 'method': 'GET', + }, + + # Timeline methods + 'getPublicTimeline': { + 'url': '/statuses/public_timeline.json', + 'method': 'GET', + }, + 'getHomeTimeline': { + 'url': '/statuses/home_timeline.json', + 'method': 'GET', + }, + 'getUserTimeline': { + 'url': '/statuses/user_timeline.json', + 'method': 'GET', + }, + 'getFriendsTimeline': { + 'url': '/statuses/friends_timeline.json', + 'method': 'GET', + }, + + # Interfacing with friends/followers + 'getUserMentions': { + 'url': '/statuses/mentions.json', + 'method': 'GET', + }, + 'getFriendsStatus': { + 'url': '/statuses/friends.json', + 'method': 'GET', + }, + 'getFollowersStatus': { + 'url': '/statuses/followers.json', + 'method': 'GET', + }, + 'createFriendship': { + 'url': '/friendships/create.json', + 'method': 'POST', + }, + 'destroyFriendship': { + 'url': '/friendships/destroy.json', + 'method': 'POST', + }, + 'getFriendsIDs': { + 'url': '/friends/ids.json', + 'method': 'GET', + }, + 'getFollowersIDs': { + 'url': '/followers/ids.json', + 'method': 'GET', + }, + + # Retweets + 'reTweet': { + 'url': '/statuses/retweet/{{id}}.json', + 'method': 'POST', + }, + 'getRetweets': { + 'url': '/statuses/retweets/{{id}}.json', + 'method': 'GET', + }, + 'retweetedOfMe': { + 'url': '/statuses/retweets_of_me.json', + 'method': 'GET', + }, + 'retweetedByMe': { + 'url': '/statuses/retweeted_by_me.json', + 'method': 'GET', + }, + 'retweetedToMe': { + 'url': '/statuses/retweeted_to_me.json', + 'method': 'GET', + }, + + # User methods + 'showUser': { + 'url': '/users/show.json', + 'method': 'GET', + }, + 'searchUsers': { + 'url': '/users/search.json', + 'method': 'GET', + }, + + # Status methods - showing, updating, destroying, etc. + 'showStatus': { + 'url': '/statuses/show/{{id}}.json', + 'method': 'GET', + }, + 'updateStatus': { + 'url': '/statuses/update.json', + 'method': 'POST', + }, + 'destroyStatus': { + 'url': '/statuses/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Direct Messages - getting, sending, effing, etc. + 'getDirectMessages': { + 'url': '/direct_messages.json', + 'method': 'GET', + }, + 'getSentMessages': { + 'url': '/direct_messages/sent.json', + 'method': 'GET', + }, + 'sendDirectMessage': { + 'url': '/direct_messages/new.json', + 'method': 'POST', + }, + 'destroyDirectMessage': { + 'url': '/direct_messages/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Friendship methods + 'checkIfFriendshipExists': { + 'url': '/friendships/exists.json', + 'method': 'GET', + }, + 'showFriendship': { + 'url': '/friendships/show.json', + 'method': 'GET', + }, + + # Profile methods + 'updateProfile': { + 'url': '/account/update_profile.json', + 'method': 'POST', + }, + 'updateProfileColors': { + 'url': '/account/update_profile_colors.json', + 'method': 'POST', + }, + + # Favorites methods + 'getFavorites': { + 'url': '/favorites.json', + 'method': 'GET', + }, + 'createFavorite': { + 'url': '/favorites/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyFavorite': { + 'url': '/favorites/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Blocking methods + 'createBlock': { + 'url': '/blocks/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyBlock': { + 'url': '/blocks/destroy/{{id}}.json', + 'method': 'POST', + }, + 'getBlocking': { + 'url': '/blocks/blocking.json', + 'method': 'GET', + }, + 'getBlockedIDs': { + 'url': '/blocks/blocking/ids.json', + 'method': 'GET', + }, + 'checkIfBlockExists': { + 'url': '/blocks/exists.json', + 'method': 'GET', + }, + + # Trending methods + 'getCurrentTrends': { + 'url': '/trends/current.json', + 'method': 'GET', + }, + 'getDailyTrends': { + 'url': '/trends/daily.json', + 'method': 'GET', + }, + 'getWeeklyTrends': { + 'url': '/trends/weekly.json', + 'method': 'GET', + }, + 'availableTrends': { + 'url': '/trends/available.json', + 'method': 'GET', + }, + 'trendsByLocation': { + 'url': '/trends/{{woeid}}.json', + 'method': 'GET', + }, + + # Saved Searches + 'getSavedSearches': { + 'url': '/saved_searches.json', + 'method': 'GET', + }, + 'showSavedSearch': { + 'url': '/saved_searches/show/{{id}}.json', + 'method': 'GET', + }, + 'createSavedSearch': { + 'url': '/saved_searches/create.json', + 'method': 'GET', + }, + 'destroySavedSearch': { + 'url': '/saved_searches/destroy/{{id}}.json', + 'method': 'GET', + }, + + # List API methods/endpoints. Fairly exhaustive and annoying in general. ;P + 'createList': { + 'url': '/{{username}}/lists.json', + 'method': 'POST', + }, + 'updateList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'POST', + }, + 'showLists': { + 'url': '/{{username}}/lists.json', + 'method': 'GET', + }, + 'getListMemberships': { + 'url': '/{{username}}/lists/followers.json', + 'method': 'GET', + }, + 'deleteList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'DELETE', + }, + 'getListTimeline': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'getSpecificList': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'addListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'POST', + }, + 'getListMembers': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'GET', + }, + 'deleteListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'DELETE', + }, + 'subscribeToList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'POST', + }, + 'unsubscribeFromList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'DELETE', + }, + + # The one-offs + 'notificationFollow': { + 'url': '/notifications/follow/follow.json', + 'method': 'POST', + }, + 'notificationLeave': { + 'url': '/notifications/leave/leave.json', + 'method': 'POST', + }, + 'updateDeliveryService': { + 'url': '/account/update_delivery_device.json', + 'method': 'POST', + }, + 'reportSpam': { + 'url': '/report_spam.json', + 'method': 'POST', + }, +} diff --git a/twython/twyauth.py b/twython/twyauth.py deleted file mode 100644 index bf4183f..0000000 --- a/twython/twyauth.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/python - -""" - Twython-oauth (twyauth) is a separate library to handle OAuth routines with Twython. This currently doesn't work, as I never get the time to finish it. - Feel free to help out. - - Questions, comments? ryan@venodesigns.net -""" - -import httplib, urllib, urllib2, mimetypes, mimetools - -from urlparse import urlparse -from urllib2 import HTTPError - -try: - import oauth -except ImportError: - pass - -class oauth: - def __init__(self, username, consumer_key, consumer_secret, signature_method = None, headers = None, version = 1): - """oauth(username = None, consumer_secret = None, consumer_key = None, headers = None) - - Instantiates an instance of Twython with OAuth. Takes optional parameters for authentication and such (see below). - - Parameters: - username - Your Twitter username, if you want Basic (HTTP) Authentication. - consumer_secret - Consumer secret, given to you when you register your App with Twitter. - consumer_key - Consumer key (see situation with consumer_secret). - signature_method - Method for signing OAuth requests; defaults to oauth.OAuthSignatureMethod_HMAC_SHA1() - headers - User agent header. - version (number) - Twitter supports a "versioned" API as of Oct. 16th, 2009 - this defaults to 1, but can be overridden on a class and function-based basis. - """ - # OAuth specific variables below - self.request_token_url = 'https://api.twitter.com/%s/oauth/request_token' % version - self.access_token_url = 'https://api.twitter.com/%s/oauth/access_token' % version - self.authorization_url = 'http://api.twitter.com/%s/oauth/authorize' % version - self.signin_url = 'http://api.twitter.com/%s/oauth/authenticate' % version - self.consumer_key = consumer_key - self.consumer_secret = consumer_secret - self.request_token = None - self.access_token = None - self.consumer = None - self.connection = None - self.signature_method = None - self.consumer = oauth.OAuthConsumer(self.consumer_key, self.consumer_secret) - self.connection = httplib.HTTPSConnection("http://api.twitter.com") - - def getOAuthResource(self, url, access_token, params, http_method="GET"): - """getOAuthResource(self, url, access_token, params, http_method="GET") - - Returns a signed OAuth object for use in requests. - """ - newRequest = oauth.OAuthRequest.from_consumer_and_token(consumer, token=self.access_token, http_method=http_method, http_url=url, parameters=parameters) - oauth_request.sign_request(self.signature_method, consumer, access_token) - return oauth_request - - def getResponse(self, oauth_request, connection): - """getResponse(self, oauth_request, connection) - - Returns a JSON-ified list of results. - """ - url = oauth_request.to_url() - connection.request(oauth_request.http_method, url) - response = connection.getresponse() - return simplejson.load(response.read()) - - def getUnauthorisedRequestToken(self, consumer, connection, signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()): - oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, consumer, http_url=self.request_token_url) - oauth_request.sign_request(signature_method, consumer, None) - resp = fetch_response(oauth_request, connection) - return oauth.OAuthToken.from_string(resp) - - def getAuthorizationURL(self, consumer, token, signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()): - oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token=token, http_url=self.authorization_url) - oauth_request.sign_request(signature_method, consumer, token) - return oauth_request.to_url() - - def exchangeRequestTokenForAccessToken(self, consumer, connection, request_token, signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()): - # May not be needed... - self.request_token = request_token - oauth_request = oauth.OAuthRequest.from_consumer_and_token(consumer, token = request_token, http_url=self.access_token_url) - oauth_request.sign_request(signature_method, consumer, request_token) - resp = fetch_response(oauth_request, connection) - return oauth.OAuthToken.from_string(resp) diff --git a/twython/twython.py b/twython/twython.py new file mode 100644 index 0000000..4ae4daf --- /dev/null +++ b/twython/twython.py @@ -0,0 +1,417 @@ +#!/usr/bin/python + +""" + Twython is a library for Python that wraps the Twitter API. + It aims to abstract away all the API endpoints, so that additions to the library + and/or the Twitter API won't cause any overall problems. + + Questions, comments? ryan@venodesigns.net +""" + +__author__ = "Ryan McGrath " +__version__ = "1.3" + +import urllib +import urllib2 +import urlparse +import httplib +import httplib2 +import mimetypes +import mimetools +import re + +import oauth2 as oauth + +# Twython maps keyword based arguments to Twitter API endpoints. The endpoints +# table is a file with a dictionary of every API endpoint that Twython supports. +from twitter_endpoints import base_url, api_table + +from urllib2 import HTTPError + +# There are some special setups (like, oh, a Django application) where +# simplejson exists behind the scenes anyway. Past Python 2.6, this should +# never really cause any problems to begin with. +try: + # Python 2.6 and up + import json as simplejson +except ImportError: + try: + # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) + import simplejson + except ImportError: + try: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + except: + # Seriously wtf is wrong with you if you get this Exception. + raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + +class TwythonError(Exception): + """ + Generic error class, catch-all for most Twython issues. + Special cases are handled by APILimit and AuthError. + + Note: To use these, the syntax has changed as of Twython 1.3. To catch these, + you need to explicitly import them into your code, e.g: + + from twython import TwythonError, APILimit, AuthError + """ + 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(TwythonError): + """ + Raised when you've hit an API limit. Try to avoid these, read the API + docs if you're running into issues here, Twython does not concern itself with + this matter beyond telling you that you've done goofed. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class AuthError(TwythonError): + """ + Raised when you try to access a protected resource and it fails due to some issue with + your authentication. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class Twython(object): + def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None): + """setup(self, oauth_token = None, headers = None) + + Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + + Parameters: + twitter_token - Given to you when you register your application with Twitter. + twitter_secret - Given to you when you register your application with Twitter. + oauth_token - If you've gone through the authentication process and have a token for this user, + pass it in and it'll be used for all requests going forward. + oauth_token_secret - see oauth_token; it's the other half. + headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} + + ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. + """ + # Needed for hitting that there API. + self.request_token_url = 'http://twitter.com/oauth/request_token' + self.access_token_url = 'http://twitter.com/oauth/access_token' + self.authorize_url = 'http://twitter.com/oauth/authorize' + self.authenticate_url = 'http://twitter.com/oauth/authenticate' + self.twitter_token = twitter_token + self.twitter_secret = twitter_secret + self.oauth_token = oauth_token + self.oauth_secret = oauth_token_secret + + # If there's headers, set them, otherwise be an embarassing parent for their own good. + self.headers = headers + if self.headers is None: + headers = {'User-agent': 'Twython Python Twitter Library v1.3'} + + consumer = None + token = None + + if self.twitter_token is not None and self.twitter_secret is not None: + consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) + + if self.oauth_token is not None and self.oauth_secret is not None: + token = oauth.Token(oauth_token, oauth_token_secret) + + # Filter down through the possibilities here - if they have a token, if they're first stage, etc. + if consumer is not None and token is not None: + self.client = oauth.Client(consumer, token) + elif consumer is not None: + self.client = oauth.Client(consumer) + else: + # If they don't do authentication, but still want to request unprotected resources, we need an opener. + self.client = httplib2.Http() + + def __getattr__(self, api_call): + """ + The most magically awesome block of code you'll see in 2010. + + Rather than list out 9 million damn methods for this API, we just keep a table (see above) of + every API endpoint and their corresponding function id for this library. This pretty much gives + unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is + going to be your bottleneck... well, don't use Python. ;P + + For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). + It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, + we can take over and find the API method in our table. We then return a function that downloads and parses + what we're looking for, based on the keywords passed in. + + I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". + """ + def get(self, **kwargs): + # Go through and replace any mustaches that are in our API url. + fn = api_table[api_call] + base = re.sub( + '\{\{(?P[a-zA-Z]+)\}\}', + lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious. + base_url + fn['url'] + ) + + # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication + if fn['method'] == 'POST': + resp, content = self.client.request(base, fn['method'], urllib.urlencode(kwargs)) + else: + url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in kwargs.iteritems()]) + resp, content = self.client.request(url, fn['method']) + + return simplejson.loads(content) + + if api_call in api_table: + return get.__get__(self) + else: + raise AttributeError, api_call + + def get_authentication_tokens(self): + """ + get_auth_url(self) + + Returns an authorization URL for a user to hit. + """ + resp, content = self.client.request(self.request_token_url, "GET") + + if resp['status'] != '200': + raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content)) + + request_tokens = dict(urlparse.parse_qsl(content)) + request_tokens['auth_url'] = "%s?oauth_token=%s" % (self.authenticate_url, request_tokens['oauth_token']) + return request_tokens + + def get_authorized_tokens(self): + """ + get_authorized_tokens + + Returns authorized tokens after they go through the auth_url phase. + """ + resp, content = self.client.request(self.access_token_url, "GET") + return dict(urlparse.parse_qsl(content)) + + # ------------------------------------------------------------------------------------------------------------------------ + # The following methods are all different in some manner or require special attention with regards to the Twitter API. + # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, + # but it's not high on the priority list at the moment. + # ------------------------------------------------------------------------------------------------------------------------ + + @staticmethod + def shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): + """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") + + Shortens url specified by url_to_shorten. + + Parameters: + url_to_shorten - URL to shorten. + shortener - In case you want to use a url shortening service other than is.gd. + """ + try: + resp, content = self.client.request( + shortener + "?" + urllib.urlencode({query: Twython.unicode2utf8(url_to_shorten)}), + "GET" + ) + return content + except HTTPError, e: + raise TwythonError("shortenURL() failed with a %s error code." % `e.code`) + + def bulkUserLookup(self, ids = None, screen_names = None, version = None): + """ bulkUserLookup(self, ids = None, screen_names = None, version = None) + + A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that + contain their respective data sets. + + Statuses for the users in question will be returned inline if they exist. Requires authentication! + """ + apiURL = "http://api.twitter.com/1/users/lookup.json?lol=1" + if ids is not None: + apiURL += "&user_id=" + for id in ids: + apiURL += `id` + "," + if screen_names is not None: + apiURL += "&screen_name=" + for name in screen_names: + apiURL += name + "," + try: + resp, content = self.client.request(apiURL, "GET") + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("bulkUserLookup() failed with a %s error code." % `e.code`, e.code) + + def searchTwitter(self, **kwargs): + """searchTwitter(search_query, **kwargs) + + Returns tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) + try: + resp, content = self.client.request(searchURL, "GET") + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("getSearchTimeline() failed with a %s error code." % `e.code`, e.code) + + def searchTwitterGen(self, **kwargs): + """searchTwitterGen(search_query, **kwargs) + + Returns a generator of tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.urlencode({"q": self.unicode2utf8(search_query)}) + try: + resp, content = self.client.request(searchURL, "GET") + data = simplejson.loads(content) + except HTTPError, e: + raise TwythonError("searchTwitterGen() failed with a %s error code." % `e.code`, e.code) + + if not data['results']: + raise StopIteration + + for tweet in data['results']: + yield tweet + + if 'page' not in kwargs: + kwargs['page'] = 2 + else: + kwargs['page'] += 1 + + for tweet in self.searchTwitterGen(search_query, **kwargs): + yield tweet + + def isListMember(self, list_id, id, username, version = 1): + """ isListMember(self, list_id, id, version) + + Check if a specified user (id) is a member of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - User who owns the list you're checking against (username) + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + resp, content = self.client.request("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, `id`)) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + def isListSubscriber(self, list_id, id, version = 1): + """ isListSubscriber(self, list_id, id, version) + + Check if a specified user (id) is a subscriber of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + resp, content = "http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, `id`) + return simplejson.loads(content) + except HTTPError, e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + # 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", version = 1): + """ updateProfileBackgroundImage(filename, tile="true") + + Updates the authenticating user's profile background image. + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. + tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. + ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + files = [("image", filename, open(filename, 'rb').read())] + fields = [] + content_type, body = Twython.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib2.Request("http://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) + return urllib2.urlopen(r).read() + except HTTPError, e: + raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) + + def updateProfileImage(self, filename, version = 1): + """ updateProfileImage(filename) + + Updates the authenticating user's profile image (avatar). + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + files = [("image", filename, open(filename, 'rb').read())] + fields = [] + content_type, body = Twython.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib2.Request("http://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) + return urllib2.urlopen(r).read() + except HTTPError, e: + raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) + + @staticmethod + def encode_multipart_formdata(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' % Twython.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 + + @staticmethod + def get_content_type(filename): + """ get_content_type(self, filename) + + Exactly what you think it does. :D + """ + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + + @staticmethod + def unicode2utf8(text): + try: + if isinstance(text, unicode): + text = text.encode('utf-8') + except: + pass + return text \ No newline at end of file diff --git a/twython3k/core.py b/twython3k/core.py deleted file mode 100644 index d86a17f..0000000 --- a/twython3k/core.py +++ /dev/null @@ -1,1896 +0,0 @@ -#!/usr/bin/python - -""" - 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 http.client, urllib, urllib.request, urllib.error, urllib.parse, mimetypes, mimetools - -from urllib.parse import urlparse -from urllib.error import HTTPError - -__author__ = "Ryan McGrath " -__version__ = "1.1" - -"""Twython - Easy Twitter utilities in Python""" - -try: - import simplejson -except ImportError: - try: - import json as simplejson - except ImportError: - try: - from django.utils import simplejson - except: - raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") - -class TwythonError(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(TwythonError): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return repr(self.msg) - -class AuthError(TwythonError): - def __init__(self, msg): - self.msg = msg - def __str__(self): - return repr(self.msg) - -class setup: - def __init__(self, username = None, password = None, headers = None, proxy = None, version = 1): - """setup(username = None, password = None, proxy = None, headers = None) - - Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). - - Parameters: - username - Your Twitter username, if you want Basic (HTTP) Authentication. - password - Password for your twitter account, if you want Basic (HTTP) Authentication. - headers - User agent header. - proxy - An object detailing information, in case proxy use/authentication is required. Object passed should be something like... - - proxyobj = { - "username": "fjnfsjdnfjd", - "password": "fjnfjsjdfnjd", - "host": "http://fjanfjasnfjjfnajsdfasd.com", - "port": 87 - } - - version (number) - Twitter supports a "versioned" API as of Oct. 16th, 2009 - this defaults to 1, but can be overridden on a class and function-based basis. - - ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. - """ - self.authenticated = False - self.username = username - self.apiVersion = version - self.proxy = proxy - self.headers = headers - if self.proxy is not None: - self.proxyobj = urllib.request.ProxyHandler({'http': 'http://%s:%s@%s:%d' % (self.proxy["username"], self.proxy["password"], self.proxy["host"], self.proxy["port"])}) - # Check and set up authentication - if self.username is not None and password is not None: - # Assume Basic authentication ritual - self.auth_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm() - self.auth_manager.add_password(None, "http://api.twitter.com", self.username, password) - self.handler = urllib.request.HTTPBasicAuthHandler(self.auth_manager) - if self.proxy is not None: - self.opener = urllib.request.build_opener(self.proxyobj, self.handler) - else: - self.opener = urllib.request.build_opener(self.handler) - if self.headers is not None: - self.opener.addheaders = [('User-agent', self.headers)] - self.authenticated = True # Play nice, people can force-check using verifyCredentials() - else: - # Build a non-auth opener so we can allow proxy-auth and/or header swapping - if self.proxy is not None: - self.opener = urllib.request.build_opener(self.proxyobj) - else: - self.opener = urllib.request.build_opener() - if self.headers is not None: - self.opener.addheaders = [('User-agent', self.headers)] - - # URL Shortening function huzzah - def shortenURL(self, url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): - """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") - - Shortens url specified by url_to_shorten. - - Parameters: - url_to_shorten - URL to shorten. - shortener - In case you want to use a url shortening service other than is.gd. - """ - try: - return urllib.request.urlopen(shortener + "?" + urllib.urlencode({query: self.unicode2utf8(url_to_shorten)})).read() - except HTTPError, e: - raise TwythonError("shortenURL() failed with a %s error code." % repr(e.code)) - - def constructApiURL(self, base_url, params): - return base_url + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in params.items()]) - - def verifyCredentials(self, version = None): - """ verifyCredentials(self, version = None): - - Verifies the authenticity of the passed in credentials. Used to be a forced call, now made optional - (no need to waste network resources) - - Parameters: - None - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - simplejson.load(self.opener.open("http://api.twitter.com/%d/account/verify_credentials.json" % version)) - except HTTPError as e: - raise AuthError("Authentication failed with your provided credentials. Try again? (%s failure)" % repr(e.code)) - else: - raise AuthError("verifyCredentials() requires you to actually, y'know, pass in credentials.") - - def getRateLimitStatus(self, checkRequestingIP = True, version = None): - """getRateLimitStatus() - - Returns the remaining number of API requests available to the requesting user before the - API limit is reached for the current hour. Calls to rate_limit_status do not count against - the rate limit. If authentication credentials are provided, the rate limit status for the - authenticating user is returned. Otherwise, the rate limit status for the requesting - IP address is returned. - - Params: - checkRequestIP - Boolean, defaults to True. Set to False to check against the currently requesting IP, instead of the account level. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if checkRequestingIP is True: - return simplejson.load(urllib.request.urlopen("http://api.twitter.com/%d/account/rate_limit_status.json" % version)) - else: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/account/rate_limit_status.json" % version)) - else: - raise TwythonError("You need to be authenticated to check a rate limit status on an account.") - except HTTPError as e: - raise TwythonError("It seems that there's something wrong. Twitter gave you a %s error code; are you doing something you shouldn't be?" % repr(e.code), e.code) - - def getPublicTimeline(self, version = None): - """getPublicTimeline() - - Returns the 20 most recent statuses from non-protected users who have set a custom user icon. - The public timeline is cached for 60 seconds, so requesting it more often than that is a waste of resources. - - Params: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/public_timeline.json" % version)) - except HTTPError as e: - raise TwythonError("getPublicTimeline() failed with a %s error code." % repr(e.code)) - - def getHomeTimeline(self, version = None, **kwargs): - """getHomeTimeline(**kwargs) - - Returns the 20 most recent statuses, including retweets, posted by the authenticating user - and that user's friends. This is the equivalent of /timeline/home on the Web. - - Usage note: This home_timeline is identical to statuses/friends_timeline, except it also - contains retweets, which statuses/friends_timeline does not (for backwards compatibility - reasons). In a future version of the API, statuses/friends_timeline will go away and - be replaced by home_timeline. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - homeTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/home_timeline.json" % version, kwargs) - return simplejson.load(self.opener.open(homeTimelineURL)) - except HTTPError as e: - raise TwythonError("getHomeTimeline() failed with a %s error code. (This is an upcoming feature in the Twitter API, and may not be implemented yet)" % repr(e.code)) - else: - raise AuthError("getHomeTimeline() requires you to be authenticated.") - - def getFriendsTimeline(self, version = None, **kwargs): - """getFriendsTimeline(**kwargs) - - Returns the 20 most recent statuses posted by the authenticating user, as well as that users friends. - This is the equivalent of /timeline/home on the Web. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - friendsTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/friends_timeline.json" % version, kwargs) - return simplejson.load(self.opener.open(friendsTimelineURL)) - except HTTPError as e: - raise TwythonError("getFriendsTimeline() failed with a %s error code." % repr(e.code)) - else: - raise AuthError("getFriendsTimeline() requires you to be authenticated.") - - def getUserTimeline(self, id = None, version = None, **kwargs): - """getUserTimeline(id = None, **kwargs) - - Returns the 20 most recent statuses posted from the authenticating user. It's also - possible to request another user's timeline via the id parameter. This is the - equivalent of the Web / page for your own user, or the profile page for a third party. - - Parameters: - id - Optional. Specifies the ID or screen name of the user for whom to return the user_timeline. - user_id - Optional. Specfies the ID of the user for whom to return the user_timeline. Helpful for disambiguating. - screen_name - Optional. Specfies 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) - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if id is not None and ("user_id" in kwargs) is False and ("screen_name" in kwargs) is False: - userTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/user_timeline/%s.json" % (version, repr(id)), kwargs) - elif id is None and ("user_id" in kwargs) is False and ("screen_name" in kwargs) is False and self.authenticated is True: - userTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/user_timeline/%s.json" % (version, self.username), kwargs) - else: - userTimelineURL = self.constructApiURL("http://api.twitter.com/%d/statuses/user_timeline.json" % version, 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(self.opener.open(userTimelineURL)) - except HTTPError as e: - raise TwythonError("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." - % repr(e.code), e.code) - - def getUserMentions(self, version = None, **kwargs): - """getUserMentions(**kwargs) - - Returns the 20 most recent mentions (status containing @username) for the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - mentionsFeedURL = self.constructApiURL("http://api.twitter.com/%d/statuses/mentions.json" % version, kwargs) - return simplejson.load(self.opener.open(mentionsFeedURL)) - except HTTPError as e: - raise TwythonError("getUserMentions() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getUserMentions() requires you to be authenticated.") - - def reportSpam(self, id = None, user_id = None, screen_name = None, version = None): - """reportSpam(self, id), user_id, screen_name): - - Report a user account to Twitter as a spam account. *One* of the following parameters is required, and - this requires that you be authenticated with a user account. - - Parameters: - id - Optional. The ID or screen_name of the user you want to report as a spammer. - user_id - Optional. The ID of the user you want to report as a spammer. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. The ID or screen_name of the user you want to report as a spammer. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - # This entire block of code is stupid, but I'm far too tired to think through it at the moment. Refactor it if you care. - if id is not None or user_id is not None or screen_name is not None: - try: - apiExtension = "" - if id is not None: - apiExtension = "id=%s" % id - if user_id is not None: - apiExtension = "user_id=%s" % repr(user_id) - if screen_name is not None: - apiExtension = "screen_name=%s" % screen_name - return simplejson.load(self.opener.open("http://api.twitter.com/%d/report_spam.json" % version, apiExtension)) - except HTTPError as e: - raise TwythonError("reportSpam() failed with a %s error code." % repr(e.code), e.code) - else: - raise TwythonError("reportSpam requires you to specify an id, user_id, or screen_name. Try again!") - else: - raise AuthError("reportSpam() requires you to be authenticated.") - - def reTweet(self, id, version = None): - """reTweet(id) - - Retweets a tweet. Requires the id parameter of the tweet you are retweeting. - - Parameters: - id - Required. The numerical ID of the tweet you are retweeting. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/retweet/%s.json" % (version, repr(id)), "POST")) - except HTTPError as e: - raise TwythonError("reTweet() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("reTweet() requires you to be authenticated.") - - def getRetweets(self, id, count = None, version = None): - """ getRetweets(self, id, count): - - Returns up to 100 of the first retweets of a given tweet. - - Parameters: - id - Required. The numerical ID of the tweet you want the retweets of. - count - Optional. Specifies the number of retweets to retrieve. May not be greater than 100. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/statuses/retweets/%s.json" % (version, repr(id)) - if count is not None: - apiURL += "?count=%s" % repr(count) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getRetweets failed with a %s eroror code." % repr(e.code), e.code) - else: - raise AuthError("getRetweets() requires you to be authenticated.") - - def retweetedOfMe(self, version = None, **kwargs): - """retweetedOfMe(**kwargs) - - Returns the 20 most recent tweets of the authenticated user that have been retweeted by others. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweets_of_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError as e: - raise TwythonError("retweetedOfMe() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("retweetedOfMe() requires you to be authenticated.") - - def retweetedByMe(self, version = None, **kwargs): - """retweetedByMe(**kwargs) - - Returns the 20 most recent retweets posted by the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweeted_by_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError as e: - raise TwythonError("retweetedByMe() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("retweetedByMe() requires you to be authenticated.") - - def retweetedToMe(self, version = None, **kwargs): - """retweetedToMe(**kwargs) - - Returns the 20 most recent retweets posted by the authenticating user's friends. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - retweetURL = self.constructApiURL("http://api.twitter.com/%d/statuses/retweeted_to_me.json" % version, kwargs) - return simplejson.load(self.opener.open(retweetURL)) - except HTTPError as e: - raise TwythonError("retweetedToMe() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("retweetedToMe() requires you to be authenticated.") - - def searchUsers(self, q, per_page = 20, page = 1, version = None): - """ searchUsers(q, per_page = None, page = None): - - Query Twitter to find a set of users who match the criteria we have. (Note: This, oddly, requires authentication - go figure) - - Parameters: - q (string) - Required. The query you wanna search against; self explanatory. ;) - per_page (number) - Optional, defaults to 20. Specify the number of users Twitter should return per page (no more than 20, just fyi) - page (number) - Optional, defaults to 1. The page of users you want to pull down. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/users/search.json?q=%s&per_page=%d&page=%d" % (version, q, per_page, page))) - except HTTPError as e: - raise TwythonError("searchUsers() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("searchUsers(), oddly, requires you to be authenticated.") - - def showUser(self, id = None, user_id = None, screen_name = None, version = None): - """showUser(id = None, user_id = None, screen_name = None) - - Returns extended information of a given user. The author's most recent status will be returned inline. - - Parameters: - ** Note: One of the following must always be specified. - id - The ID or screen name of a user. - user_id - Specfies the ID of the user to return. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Specfies the screen name of the user to return. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - - Usage Notes: - Requests for protected users without credentials from - 1) the user requested or - 2) a user that is following the protected user will omit the nested status element. - - ...will result in only publicly available data being returned. - """ - version = version or self.apiVersion - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/users/show/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/users/show.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/users/show.json?screen_name=%s" % (version, screen_name) - if apiURL != "": - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("showUser() failed with a %s error code." % repr(e.code), e.code) - - def bulkUserLookup(self, ids = None, screen_names = None, version = None): - """ bulkUserLookup(self, ids = None, screen_names = None, version = None) - - A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that - contain their respective data sets. - - Statuses for the users in question will be returned inline if they exist. Requires authentication! - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/users/lookup.json?lol=1" % version - if ids is not None: - apiURL += "&user_id=" - for id in ids: - apiURL += repr(id) + "," - if screen_names is not None: - apiURL += "&screen_name=" - for name in screen_names: - apiURL += name + "," - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("bulkUserLookup() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("bulkUserLookup() requires you to be authenticated.") - - def getFriendsStatus(self, id = None, user_id = None, screen_name = None, page = None, cursor="-1", version = None): - """getFriendsStatus(id = None, user_id = None, screen_name = None, page = None, cursor="-1") - - Returns a user's friends, each with current status inline. They are ordered by the order in which they were added as friends, 100 at a time. - (Please note that the result set isn't guaranteed to be 100 every time, as suspended users will be filtered out.) Use the page option to access - older friends. With no user specified, the request defaults to the authenticated users friends. - - It's also possible to request another user's friends list via the id, screen_name or user_id parameter. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, or screen_name) - id - Optional. The ID or screen name of the user for whom to request a list of friends. - user_id - Optional. Specfies the ID of the user for whom to return the list of friends. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the user for whom to return the list of friends. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page of friends to receive. - cursor - Optional. Breaks the results into pages. A single page contains 100 users. This is recommended for users who are following many users. Provide a value of -1 to begin paging. Provide values as returned to in the response body's next_cursor and previous_cursor attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/statuses/friends.json?screen_name=%s" % (version, screen_name) - try: - if page is not None: - return simplejson.load(self.opener.open(apiURL + "&page=%s" % repr(page))) - else: - return simplejson.load(self.opener.open(apiURL + "&cursor=%s" % cursor)) - except HTTPError as e: - raise TwythonError("getFriendsStatus() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getFriendsStatus() requires you to be authenticated.") - - def getFollowersStatus(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFollowersStatus(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns the authenticating user's followers, each with current status inline. - They are ordered by the order in which they joined Twitter, 100 at a time. - (Note that the result set isn't guaranteed to be 100 every time, as suspended users will be filtered out.) - - Use the page option to access earlier followers. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Optional. The ID or screen name of the user for whom to request a list of followers. - user_id - Optional. Specfies the ID of the user for whom to return the list of followers. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the user for whom to return the list of followers. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page to retrieve. - cursor - Optional. Breaks the results into pages. A single page contains 100 users. This is recommended for users who are following many users. Provide a value of -1 to begin paging. Provide values as returned to in the response body's next_cursor and previous_cursor attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/statuses/followers.json?screen_name=%s" % (version, screen_name) - try: - if page is not None: - return simplejson.load(self.opener.open(apiURL + "&page=%s" % page)) - else: - return simplejson.load(self.opener.open(apiURL + "&cursor=%s" % cursor)) - except HTTPError as e: - raise TwythonError("getFollowersStatus() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getFollowersStatus() requires you to be authenticated.") - - def showStatus(self, id, version = None): - """showStatus(id) - - Returns a single status, specified by the id parameter below. - The status's author will be returned inline. - - Parameters: - id - Required. The numerical ID of the status to retrieve. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/show/%s.json" % (version, id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/show/%s.json" % (version, id))) - except HTTPError as e: - raise TwythonError("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." - % repr(e.code), e.code) - - def updateStatus(self, status, in_reply_to_status_id = None, latitude = None, longitude = None, version = None): - """updateStatus(status, in_reply_to_status_id = None) - - Updates the authenticating user's status. Requires the status parameter specified below. - A status update with text identical to the authenticating users current status will be ignored to prevent duplicates. - - Parameters: - status - Required. The text of your status update. URL encode as necessary. Statuses over 140 characters will be forceably truncated. - in_reply_to_status_id - Optional. The ID of an existing status that the update is in reply to. - latitude (string) - Optional. The location's latitude that this tweet refers to. - longitude (string) - Optional. The location's longitude that this tweet refers to. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - - ** Note: in_reply_to_status_id will be ignored unless the author of the tweet this parameter references - is mentioned within the status text. Therefore, you must include @username, where username is - the author of the referenced tweet, within the update. - - ** Note: valid ranges for latitude/longitude are, for example, -180.0 to +180.0 (East is positive) inclusive. - This parameter will be ignored if outside that range, not a number, if geo_enabled is disabled, or if there not a corresponding latitude parameter with this tweet. - """ - version = version or self.apiVersion - try: - postExt = urllib.parse.urlencode({"status": self.unicode2utf8(status)}) - if latitude is not None and longitude is not None: - postExt += "&lat=%s&long=%s" % (latitude, longitude) - if in_reply_to_status_id is not None: - postExt += "&in_reply_to_status_id=%s" % repr(in_reply_to_status_id) - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/update.json?" % version, postExt)) - except HTTPError as e: - raise TwythonError("updateStatus() failed with a %s error code." % repr(e.code), e.code) - - def destroyStatus(self, id, version = None): - """destroyStatus(id) - - Destroys the status specified by the required ID parameter. - The authenticating user must be the author of the specified status. - - Parameters: - id - Required. The ID of the status to destroy. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/statuses/destroy/%s.json?" % (version, id), "_method=DELETE")) - except HTTPError as e: - raise TwythonError("destroyStatus() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("destroyStatus() requires you to be authenticated.") - - def endSession(self, version = None): - """endSession() - - Ends the session of the authenticating user, returning a null cookie. - Use this method to sign users out of client-facing applications (widgets, etc). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - self.opener.open("http://api.twitter.com/%d/account/end_session.json" % version, "") - self.authenticated = False - except HTTPError as e: - raise TwythonError("endSession failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("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", version = None): - """getDirectMessages(since_id = None, max_id = None, count = None, page = "1") - - Returns a list of the 20 most recent direct messages sent to the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/direct_messages.json?page=%s" % (version, repr(page)) - if since_id is not None: - apiURL += "&since_id=%s" % repr(since_id) - if max_id is not None: - apiURL += "&max_id=%s" % repr(max_id) - if count is not None: - apiURL += "&count=%s" % repr(count) - - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getDirectMessages() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getDirectMessages() requires you to be authenticated.") - - def getSentMessages(self, since_id = None, max_id = None, count = None, page = "1", version = None): - """getSentMessages(since_id = None, max_id = None, count = None, page = "1") - - Returns a list of the 20 most recent direct messages sent by the authenticating user. - - Parameters: - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - page - Optional. Specifies the page of results to retrieve. Note: there are pagination limits. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "http://api.twitter.com/%d/direct_messages/sent.json?page=%s" % (version, repr(page)) - if since_id is not None: - apiURL += "&since_id=%s" % repr(since_id) - if max_id is not None: - apiURL += "&max_id=%s" % repr(max_id) - if count is not None: - apiURL += "&count=%s" % repr(count) - - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getSentMessages() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getSentMessages() requires you to be authenticated.") - - def sendDirectMessage(self, user, text, version = None): - """sendDirectMessage(user, text) - - Sends a new direct message to the specified user from the authenticating user. Requires both the user and text parameters. - Returns the sent message in the requested format when successful. - - Parameters: - user - Required. The ID or screen name of the recipient user. - text - Required. The text of your direct message. Be sure to keep it under 140 characters. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - if len(list(text)) < 140: - try: - return self.opener.open("http://api.twitter.com/%d/direct_messages/new.json" % version, urllib.parse.urlencode({"user": user, "text": text})) - except HTTPError as e: - raise TwythonError("sendDirectMessage() failed with a %s error code." % repr(e.code), e.code) - else: - raise TwythonError("Your message must not be longer than 140 characters") - else: - raise AuthError("You must be authenticated to send a new direct message.") - - def destroyDirectMessage(self, id, version = None): - """destroyDirectMessage(id) - - Destroys the direct message specified in the required ID parameter. - The authenticating user must be the recipient of the specified direct message. - - Parameters: - id - Required. The ID of the direct message to destroy. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return self.opener.open("http://api.twitter.com/%d/direct_messages/destroy/%s.json" % (version, id), "") - except HTTPError as e: - raise TwythonError("destroyDirectMessage() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("You must be authenticated to destroy a direct message.") - - def createFriendship(self, id = None, user_id = None, screen_name = None, follow = "false", version = None): - """createFriendship(id = None, user_id = None, screen_name = None, follow = "false") - - Allows the authenticating users to follow the user specified in the ID parameter. - Returns the befriended user in the requested format when successful. Returns a - string describing the failure condition when unsuccessful. If you are already - friends with the user an HTTP 403 will be returned. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to befriend. - user_id - Required. Specfies the ID of the user to befriend. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to befriend. Helpful for disambiguating when a valid screen name is also a user ID. - follow - Optional. Enable notifications for the target user in addition to becoming friends. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if user_id is not None: - apiURL = "user_id=%s&follow=%s" %(repr(user_id), follow) - if screen_name is not None: - apiURL = "screen_name=%s&follow=%s" %(screen_name, follow) - try: - if id is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/create/%s.json" % (version, id), "?follow=%s" % follow)) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/create.json" % version, apiURL)) - except HTTPError as e: - # Rate limiting is done differently here for API reasons... - if e.code == 403: - raise APILimit("You've hit the update limit for this method. Try again in 24 hours.") - raise TwythonError("createFriendship() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("createFriendship() requires you to be authenticated.") - - def destroyFriendship(self, id = None, user_id = None, screen_name = None, version = None): - """destroyFriendship(id = None, user_id = None, screen_name = None) - - Allows the authenticating users to unfollow the user specified in the ID parameter. - Returns the unfollowed user in the requested format when successful. Returns a string describing the failure condition when unsuccessful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to unfollow. - user_id - Required. Specfies the ID of the user to unfollow. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to unfollow. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if user_id is not None: - apiURL = "user_id=%s" % repr(user_id) - if screen_name is not None: - apiURL = "screen_name=%s" % screen_name - try: - if id is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/destroy/%s.json" % (version, repr(id)), "lol=1")) # Random string hack for POST reasons ;P - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/friendships/destroy.json" % version, apiURL)) - except HTTPError as e: - raise TwythonError("destroyFriendship() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("destroyFriendship() requires you to be authenticated.") - - def checkIfFriendshipExists(self, user_a, user_b, version = None): - """checkIfFriendshipExists(user_a, user_b) - - Tests for the existence of friendship between two users. - Will return true if user_a follows user_b; otherwise, it'll return false. - - Parameters: - user_a - Required. The ID or screen_name of the subject user. - user_b - Required. The ID or screen_name of the user to test for following. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - friendshipURL = "http://api.twitter.com/%d/friendships/exists.json?%s" % (version, urllib.parse.urlencode({"user_a": user_a, "user_b": user_b})) - return simplejson.load(self.opener.open(friendshipURL)) - except HTTPError as e: - raise TwythonError("checkIfFriendshipExists() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("checkIfFriendshipExists(), oddly, requires that you be authenticated.") - - def showFriendship(self, source_id = None, source_screen_name = None, target_id = None, target_screen_name = None, version = None): - """showFriendship(source_id, source_screen_name, target_id, target_screen_name) - - Returns detailed information about the relationship between two users. - - Parameters: - ** Note: One of the following is required if the request is unauthenticated - source_id - The user_id of the subject user. - source_screen_name - The screen_name of the subject user. - - ** Note: One of the following is required at all times - target_id - The user_id of the target user. - target_screen_name - The screen_name of the target user. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/friendships/show.json?lol=1" % version # Another quick hack, look away if you want. :D - if source_id is not None: - apiURL += "&source_id=%s" % repr(source_id) - if source_screen_name is not None: - apiURL += "&source_screen_name=%s" % source_screen_name - if target_id is not None: - apiURL += "&target_id=%s" % repr(target_id) - if target_screen_name is not None: - apiURL += "&target_screen_name=%s" % target_screen_name - try: - if self.authenticated is True: - return simplejson.load(self.opener.open(apiURL)) - else: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - # Catch this for now - if e.code == 403: - raise AuthError("You're unauthenticated, and forgot to pass a source for this method. Try again!") - raise TwythonError("showFriendship() failed with a %s error code." % repr(e.code), e.code) - - def updateDeliveryDevice(self, device_name = "none", version = None): - """updateDeliveryDevice(device_name = "none") - - Sets which device Twitter delivers updates to for the authenticating user. - Sending "none" as the device parameter will disable IM or SMS updates. (Simply calling .updateDeliveryService() also accomplishes this) - - Parameters: - device - Required. Must be one of: sms, im, none. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return self.opener.open("http://api.twitter.com/%d/account/update_delivery_device.json?" % version, urllib.parse.urlencode({"device": self.unicode2utf8(device_name)})) - except HTTPError as e: - raise TwythonError("updateDeliveryDevice() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("updateDeliveryDevice() requires you to be authenticated.") - - def updateProfileColors(self, - profile_background_color = None, - profile_text_color = None, - profile_link_color = None, - profile_sidebar_fill_color = None, - profile_sidebar_border_color = None, - version = None): - """updateProfileColors() - - Sets one or more hex values that control the color scheme of the authenticating user's profile page on api.twitter.com. - - Parameters: - ** Note: One or more of the following parameters must be present. Each parameter's value must - be a valid hexidecimal value, and may be either three or six characters (ex: #fff or #ffffff). - - profile_background_color - Optional. - profile_text_color - Optional. - profile_link_color - Optional. - profile_sidebar_fill_color - Optional. - profile_sidebar_border_color - Optional. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - updateProfileColorsQueryString = "?lol=2" - - def checkValidColor(str): - if len(str) != 6: - return False - for c in str: - if c not in "1234567890abcdefABCDEF": return False - - return True - - if profile_background_color is not None: - if checkValidColor(profile_background_color): - updateProfileColorsQueryString += "profile_background_color=" + profile_background_color - else: - raise TwythonError("Invalid background color. Try an hexadecimal 6 digit number.") - if profile_text_color is not None: - if checkValidColor(profile_text_color): - updateProfileColorsQueryString += "profile_text_color=" + profile_text_color - else: - raise TwythonError("Invalid text color. Try an hexadecimal 6 digit number.") - if profile_link_color is not None: - if checkValidColor(profile_link_color): - updateProfileColorsQueryString += "profile_link_color=" + profile_link_color - else: - raise TwythonError("Invalid profile link color. Try an hexadecimal 6 digit number.") - if profile_sidebar_fill_color is not None: - if checkValidColor(profile_sidebar_fill_color): - updateProfileColorsQueryString += "profile_sidebar_fill_color=" + profile_sidebar_fill_color - else: - raise TwythonError("Invalid sidebar fill color. Try an hexadecimal 6 digit number.") - if profile_sidebar_border_color is not None: - if checkValidColor(profile_sidebar_border_color): - updateProfileColorsQueryString += "profile_sidebar_border_color=" + profile_sidebar_border_color - else: - raise TwythonError("Invalid sidebar border color. Try an hexadecimal 6 digit number.") - - try: - return self.opener.open("http://api.twitter.com/%d/account/update_profile_colors.json?" % version, updateProfileColorsQueryString) - except HTTPError as e: - raise TwythonError("updateProfileColors() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("updateProfileColors() requires you to be authenticated.") - - def updateProfile(self, name = None, email = None, url = None, location = None, description = None, version = None): - """updateProfile(name = None, email = None, url = None, location = None, description = None) - - Sets values that users are able to set under the "Account" tab of their settings page. - Only the parameters specified will be updated. - - Parameters: - One or more of the following parameters must be present. Each parameter's value - should be a string. See the individual parameter descriptions below for further constraints. - - name - Optional. Maximum of 20 characters. - email - Optional. Maximum of 40 characters. Must be a valid email address. - url - Optional. Maximum of 100 characters. Will be prepended with "http://" if not present. - location - Optional. Maximum of 30 characters. The contents are not normalized or geocoded in any way. - description - Optional. Maximum of 160 characters. - - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - 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 TwythonError("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 TwythonError("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.parse.urlencode({"url": self.unicode2utf8(url)}) - else: - updateProfileQueryString += urllib.parse.urlencode({"url": self.unicode2utf8(url)}) - useAmpersands = True - else: - raise TwythonError("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.parse.urlencode({"location": self.unicode2utf8(location)}) - else: - updateProfileQueryString += urllib.parse.urlencode({"location": self.unicode2utf8(location)}) - useAmpersands = True - else: - raise TwythonError("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.parse.urlencode({"description": self.unicode2utf8(description)}) - else: - updateProfileQueryString += urllib.parse.urlencode({"description": self.unicode2utf8(description)}) - else: - raise TwythonError("Twitter has a character limit of 160 for all descriptions. Try again.") - - if updateProfileQueryString != "": - try: - return self.opener.open("http://api.twitter.com/%d/account/update_profile.json?" % version, updateProfileQueryString) - except HTTPError as e: - raise TwythonError("updateProfile() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("updateProfile() requires you to be authenticated.") - - def getFavorites(self, page = "1", version = None): - """getFavorites(page = "1") - - Returns the 20 most recent favorite statuses for the authenticating user or user specified by the ID parameter in the requested format. - - Parameters: - page - Optional. Specifies the page of favorites to retrieve. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites.json?page=%s" % (version, repr(page)))) - except HTTPError as e: - raise TwythonError("getFavorites() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getFavorites() requires you to be authenticated.") - - def createFavorite(self, id, version = None): - """createFavorite(id) - - Favorites the status specified in the ID parameter as the authenticating user. Returns the favorite status when successful. - - Parameters: - id - Required. The ID of the status to favorite. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites/create/%s.json" % (version, repr(id)), "")) - except HTTPError as e: - raise TwythonError("createFavorite() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("createFavorite() requires you to be authenticated.") - - def destroyFavorite(self, id, version = None): - """destroyFavorite(id) - - Un-favorites the status specified in the ID parameter as the authenticating user. Returns the un-favorited status in the requested format when successful. - - Parameters: - id - Required. The ID of the status to un-favorite. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/favorites/destroy/%s.json" % (version, repr(id)), "")) - except HTTPError as e: - raise TwythonError("destroyFavorite() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("destroyFavorite() requires you to be authenticated.") - - def notificationFollow(self, id = None, user_id = None, screen_name = None, version = None): - """notificationFollow(id = None, user_id = None, screen_name = None) - - Enables device notifications for updates from the specified user. Returns the specified user when successful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/follow.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/notifications/follow/follow.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL, "")) - except HTTPError as e: - raise TwythonError("notificationFollow() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("notificationFollow() requires you to be authenticated.") - - def notificationLeave(self, id = None, user_id = None, screen_name = None, version = None): - """notificationLeave(id = None, user_id = None, screen_name = None) - - Disables notifications for updates from the specified user to the authenticating user. Returns the specified user when successful. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/%s.json" % (version, id) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/leave.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/notifications/leave/leave.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL, "")) - except HTTPError as e: - raise TwythonError("notificationLeave() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("notificationLeave() requires you to be authenticated.") - - def getFriendsIDs(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFriendsIDs(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns an array of numeric IDs for every user the specified user is following. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page number of the results beginning at 1. A single page contains up to 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.) - cursor - Optional. Breaks the results into pages. A single page contains 5000 ids. This is recommended for users with large ID lists. Provide a value of -1 to begin paging. Provide values as returned to in the response body's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - breakResults = "cursor=%s" % cursor - if page is not None: - breakResults = "page=%s" % page - if id is not None: - apiURL = "http://api.twitter.com/%d/friends/ids/%s.json?%s" %(version, id, breakResults) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/friends/ids.json?user_id=%s&%s" %(version, repr(user_id), breakResults) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/friends/ids.json?screen_name=%s&%s" %(version, screen_name, breakResults) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getFriendsIDs() failed with a %s error code." % repr(e.code), e.code) - - def getFollowersIDs(self, id = None, user_id = None, screen_name = None, page = None, cursor = "-1", version = None): - """getFollowersIDs(id = None, user_id = None, screen_name = None, page = None, cursor = "-1") - - Returns an array of numeric IDs for every user following the specified user. - - Note: The previously documented page-based pagination mechanism is still in production, but please migrate to cursor-based pagination for increase reliability and performance. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Required. The ID or screen name of the user to follow with device updates. - user_id - Required. Specfies the ID of the user to follow with device updates. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Required. Specfies the screen name of the user to follow with device updates. Helpful for disambiguating when a valid screen name is also a user ID. - page - (BEING DEPRECATED) Optional. Specifies the page number of the results beginning at 1. A single page contains 5000 ids. This is recommended for users with large ID lists. If not provided all ids are returned. (Please note that the result set isn't guaranteed to be 5000 every time as suspended users will be filtered out.) - cursor - Optional. Breaks the results into pages. A single page contains 5000 ids. This is recommended for users with large ID lists. Provide a value of -1 to begin paging. Provide values as returned to in the response body's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - breakResults = "cursor=%s" % cursor - if page is not None: - breakResults = "page=%s" % page - if id is not None: - apiURL = "http://api.twitter.com/%d/followers/ids/%s.json?%s" % (version, repr(id), breakResults) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/followers/ids.json?user_id=%s&%s" %(version, repr(user_id), breakResults) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/followers/ids.json?screen_name=%s&%s" %(version, screen_name, breakResults) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getFollowersIDs() failed with a %s error code." % repr(e.code), e.code) - - def createBlock(self, id, version = None): - """createBlock(id) - - Blocks the user specified in the ID parameter as the authenticating user. Destroys a friendship to the blocked user if it exists. - Returns the blocked user in the requested format when successful. - - Parameters: - id - The ID or screen name of a user to block. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/create/%s.json" % (version, repr(id)), "")) - except HTTPError as e: - raise TwythonError("createBlock() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("createBlock() requires you to be authenticated.") - - def destroyBlock(self, id, version = None): - """destroyBlock(id) - - Un-blocks the user specified in the ID parameter for the authenticating user. - Returns the un-blocked user in the requested format when successful. - - Parameters: - id - Required. The ID or screen_name of the user to un-block - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/destroy/%s.json" % (version, repr(id)), "")) - except HTTPError as e: - raise TwythonError("destroyBlock() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("destroyBlock() requires you to be authenticated.") - - def checkIfBlockExists(self, id = None, user_id = None, screen_name = None, version = None): - """checkIfBlockExists(id = None, user_id = None, screen_name = None) - - Returns if the authenticating user is blocking a target user. Will return the blocked user's object if a block exists, and - error with an HTTP 404 response code otherwise. - - Parameters: - ** Note: One of the following is required. (id, user_id, screen_name) - id - Optional. The ID or screen_name of the potentially blocked user. - user_id - Optional. Specfies the ID of the potentially blocked user. Helpful for disambiguating when a valid user ID is also a valid screen name. - screen_name - Optional. Specfies the screen name of the potentially blocked user. Helpful for disambiguating when a valid screen name is also a user ID. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "" - if id is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists/%s.json" % (version, repr(id)) - if user_id is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists.json?user_id=%s" % (version, repr(user_id)) - if screen_name is not None: - apiURL = "http://api.twitter.com/%d/blocks/exists.json?screen_name=%s" % (version, screen_name) - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("checkIfBlockExists() failed with a %s error code." % repr(e.code), e.code) - - def getBlocking(self, page = "1", version = None): - """getBlocking(page = "1") - - Returns an array of user objects that the authenticating user is blocking. - - Parameters: - page - Optional. Specifies the page number of the results beginning at 1. A single page contains 20 ids. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/blocking.json?page=%s" % (version, repr(page)))) - except HTTPError as e: - raise TwythonError("getBlocking() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getBlocking() requires you to be authenticated") - - def getBlockedIDs(self, version = None): - """getBlockedIDs() - - Returns an array of numeric user ids the authenticating user is blocking. - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/blocks/blocking/ids.json" % version)) - except HTTPError as e: - raise TwythonError("getBlockedIDs() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getBlockedIDs() requires you to be authenticated.") - - def searchTwitter(self, search_query, **kwargs): - """searchTwitter(search_query, **kwargs) - - Returns tweets that match a specified query. - - Parameters: - callback - Optional. Only available for JSON format. If supplied, the response will use the JSONP format with a callback of the given name. - lang - Optional. Restricts tweets to the given language, given by an ISO 639-1 code. - locale - Optional. Language of the query you're sending (only ja is currently effective). Intended for language-specific clients; default should work in most cases. - rpp - Optional. The number of tweets to return per page, up to a max of 100. - page - Optional. The page number (starting at 1) to return, up to a max of roughly 1500 results (based on rpp * page. Note: there are pagination limits.) - since_id - Optional. Returns tweets with status ids greater than the given id. - geocode - Optional. Returns tweets by users located within a given radius of the given latitude/longitude, where the user's location is taken from their Twitter profile. The parameter value is specified by "latitide,longitude,radius", where radius units must be specified as either "mi" (miles) or "km" (kilometers). Note that you cannot use the near operator via the API to geocode arbitrary locations; however you can use this geocode parameter to search near geocodes directly. - show_user - Optional. When true, prepends ":" to the beginning of the tweet. This is useful for readers that do not display Atom's author field. The default is false. - - Usage Notes: - Queries are limited 140 URL encoded characters. - Some users may be absent from search results. - The since_id parameter will be removed from the next_page element as it is not supported for pagination. If since_id is removed a warning will be added to alert you. - This method will return an HTTP 404 error if since_id is used and is too old to be in the search index. - - Applications must have a meaningful and unique User Agent when using this method. - An HTTP Referrer is expected but not required. Search traffic that does not include a User Agent will be rate limited to fewer API calls per hour than - applications including a User Agent string. You can set your custom UA headers by passing it as a respective argument to the setup() method. - """ - searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.parse.urlencode({"q": self.unicode2utf8(search_query)}) - try: - return simplejson.load(self.opener.open(searchURL)) - except HTTPError as e: - raise TwythonError("getSearchTimeline() failed with a %s error code." % repr(e.code), e.code) - - def getCurrentTrends(self, excludeHashTags = False, version = None): - """getCurrentTrends(excludeHashTags = False, version = None) - - Returns the current top 10 trending topics on Twitter. The response includes the time of the request, the name of each trending topic, and the query used - on Twitter Search results page for that topic. - - Parameters: - excludeHashTags - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/current.json" % version - if excludeHashTags is True: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getCurrentTrends() failed with a %s error code." % repr(e.code), e.code) - - def getDailyTrends(self, date = None, exclude = False, version = None): - """getDailyTrends(date = None, exclude = False, version = None) - - Returns the top 20 trending topics for each hour in a given day. - - Parameters: - date - Optional. Permits specifying a start date for the report. The date should be formatted YYYY-MM-DD. - exclude - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/daily.json" % version - questionMarkUsed = False - if date is not None: - apiURL += "?date=%s" % date - questionMarkUsed = True - if exclude is True: - if questionMarkUsed is True: - apiURL += "&exclude=hashtags" - else: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getDailyTrends() failed with a %s error code." % repr(e.code), e.code) - - def getWeeklyTrends(self, date = None, exclude = False): - """getWeeklyTrends(date = None, exclude = False) - - Returns the top 30 trending topics for each day in a given week. - - Parameters: - date - Optional. Permits specifying a start date for the report. The date should be formatted YYYY-MM-DD. - exclude - Optional. Setting this equal to hashtags will remove all hashtags from the trends list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - apiURL = "http://api.twitter.com/%d/trends/daily.json" % version - questionMarkUsed = False - if date is not None: - apiURL += "?date=%s" % date - questionMarkUsed = True - if exclude is True: - if questionMarkUsed is True: - apiURL += "&exclude=hashtags" - else: - apiURL += "?exclude=hashtags" - try: - return simplejson.load(self.opener.open(apiURL)) - except HTTPError as e: - raise TwythonError("getWeeklyTrends() failed with a %s error code." % repr(e.code), e.code) - - def getSavedSearches(self, version = None): - """getSavedSearches() - - Returns the authenticated user's saved search queries. - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches.json" % version)) - except HTTPError as e: - raise TwythonError("getSavedSearches() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("getSavedSearches() requires you to be authenticated.") - - def showSavedSearch(self, id, version = None): - """showSavedSearch(id) - - Retrieve the data for a saved search owned by the authenticating user specified by the given id. - - Parameters: - id - Required. The id of the saved search to be retrieved. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/show/%s.json" % (version, repr(id)))) - except HTTPError as e: - raise TwythonError("showSavedSearch() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("showSavedSearch() requires you to be authenticated.") - - def createSavedSearch(self, query, version = None): - """createSavedSearch(query) - - Creates a saved search for the authenticated user. - - Parameters: - query - Required. The query of the search the user would like to save. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/create.json?query=%s" % (version, query), "")) - except HTTPError as e: - raise TwythonError("createSavedSearch() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("createSavedSearch() requires you to be authenticated.") - - def destroySavedSearch(self, id, version = None): - """ destroySavedSearch(id) - - Destroys a saved search for the authenticated user. - The search specified by id must be owned by the authenticating user. - - Parameters: - id - Required. The id of the saved search to be deleted. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/saved_searches/destroy/%s.json" % (version, repr(id)), "")) - except HTTPError as e: - raise TwythonError("destroySavedSearch() failed with a %s error code." % repr(e.code), e.code) - else: - raise AuthError("destroySavedSearch() requires you to be authenticated.") - - def createList(self, name, mode = "public", description = "", version = None): - """ createList(self, name, mode, description, version) - - Creates a new list for the currently authenticated user. (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - name - Required. The name for the new list. - description - Optional, in the sense that you can leave it blank if you don't want one. ;) - mode - Optional. This is a string indicating "public" or "private", defaults to "public". - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists.json" % (version, self.username), - urllib.parse.urlencode({"name": name, "mode": mode, "description": description}))) - except HTTPError as e: - raise TwythonError("createList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("createList() requires you to be authenticated.") - - def updateList(self, list_id, name, mode = "public", description = "", version = None): - """ updateList(self, list_id, name, mode, description, version) - - Updates an existing list for the authenticating user. (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - This method is a bit cumbersome for the time being; I'd personally avoid using it unless you're positive you know what you're doing. Twitter should really look - at this... - - Parameters: - list_id - Required. The name of the list (this gets turned into a slug - e.g, "Huck Hound" becomes "huck-hound"). - name - Required. The name of the list, possibly for renaming or such. - description - Optional, in the sense that you can leave it blank if you don't want one. ;) - mode - Optional. This is a string indicating "public" or "private", defaults to "public". - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s.json" % (version, self.username, list_id), - urllib.parse.urlencode({"name": name, "mode": mode, "description": description}))) - except HTTPError as e: - raise TwythonError("updateList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("updateList() requires you to be authenticated.") - - def showLists(self, version = None): - """ showLists(self, version) - - Show all the lists for the currently authenticated user (i.e, they own these lists). - (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists.json" % (version, self.username))) - except HTTPError as e: - raise TwythonError("showLists() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("showLists() requires you to be authenticated.") - - def getListMemberships(self, version = None): - """ getListMemberships(self, version) - - Get all the lists for the currently authenticated user (i.e, they're on all the lists that are returned, the lists belong to other people) - (Note: This may encounter issues if you authenticate with an email; try username (screen name) instead). - - Parameters: - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/followers.json" % (version, self.username))) - except HTTPError as e: - raise TwythonError("getLists() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("getLists() requires you to be authenticated.") - - def deleteList(self, list_id, version = None): - """ deleteList(self, list_id, version) - - Deletes a list for the authenticating user. - - Parameters: - list_id - Required. The name of the list to delete - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s.json" % (version, self.username, list_id), "_method=DELETE")) - except HTTPError as e: - raise TwythonError("deleteList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("deleteList() requires you to be authenticated.") - - def getListTimeline(self, list_id, cursor = "-1", version = None, **kwargs): - """ getListTimeline(self, list_id, cursor, version, **kwargs) - - Retrieves a timeline representing everyone in the list specified. - - Parameters: - list_id - Required. The name of the list to get a timeline for - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - since_id - Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified ID. - max_id - Optional. Returns only statuses with an ID less than (that is, older than) or equal to the specified ID. - count - Optional. Specifies the number of statuses to retrieve. May not be greater than 200. - cursor - Optional. Breaks the results into pages. Provide a value of -1 to begin paging. - Provide values returned in the response's "next_cursor" and "previous_cursor" attributes to page back and forth in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - baseURL = self.constructApiURL("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id), kwargs) - return simplejson.load(self.opener.open(baseURL + "&cursor=%s" % cursor)) - except HTTPError as e: - if e.code == 404: - raise AuthError("It seems the list you're trying to access is private/protected, and you don't have access. Are you authenticated and allowed?") - raise TwythonError("getListTimeline() failed with a %d error code." % e.code, e.code) - - def getSpecificList(self, list_id, version = None): - """ getSpecificList(self, list_id, version) - - Retrieve a specific list - this only requires authentication if the list you're requesting is protected/private (if it is, you need to have access as well). - - Parameters: - list_id - Required. The name of the list to get - this gets turned into a slug, so you can pass it as that, or hope the transformation works out alright. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/lists/%s/statuses.json" % (version, self.username, list_id))) - except HTTPError as e: - if e.code == 404: - raise AuthError("It seems the list you're trying to access is private/protected, and you don't have access. Are you authenticated and allowed?") - raise TwythonError("getSpecificList() failed with a %d error code." % e.code, e.code) - - def addListMember(self, list_id, version = None): - """ addListMember(self, list_id, id, version) - - Adds a new Member (the passed in id) to the specified list. - - Parameters: - list_id - Required. The slug of the list to add the new member to. - id - Required. The ID of the user that's being added to the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id), "id=%s" % repr(id))) - except HTTPError as e: - raise TwythonError("addListMember() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("addListMember requires you to be authenticated.") - - def getListMembers(self, list_id, version = None): - """ getListMembers(self, list_id, version = None) - - Show all members of a specified list. This method requires authentication if the list is private/protected. - - Parameters: - list_id - Required. The slug of the list to retrieve members for. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id))) - except HTTPError as e: - raise TwythonError("getListMembers() failed with a %d error code." % e.code, e.code) - - def removeListMember(self, list_id, id, version = None): - """ removeListMember(self, list_id, id, version) - - Remove the specified user (id) from the specified list (list_id). Requires you to be authenticated and in control of the list in question. - - Parameters: - list_id - Required. The slug of the list to remove the specified user from. - id - Required. The ID of the user that's being added to the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members.json" % (version, self.username, list_id), "_method=DELETE&id=%s" % repr(id))) - except HTTPError as e: - raise TwythonError("getListMembers() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("removeListMember() requires you to be authenticated.") - - def isListMember(self, list_id, id, version = None): - """ isListMember(self, list_id, id, version) - - Check if a specified user (id) is a member of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, self.username, list_id, repr(id)))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, self.username, list_id, repr(id)))) - except HTTPError as e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - - def subscribeToList(self, list_id, version): - """ subscribeToList(self, list_id, version) - - Subscribe the authenticated user to the list provided (must be public). - - Parameters: - list_id - Required. The list to subscribe to. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following.json" % (version, self.username, list_id), "")) - except HTTPError as e: - raise TwythonError("subscribeToList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("subscribeToList() requires you to be authenticated.") - - def unsubscribeFromList(self, list_id, version): - """ unsubscribeFromList(self, list_id, version) - - Unsubscribe the authenticated user from the list in question (must be public). - - Parameters: - list_id - Required. The list to unsubscribe from. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - if self.authenticated is True: - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following.json" % (version, self.username, list_id), "_method=DELETE")) - except HTTPError as e: - raise TwythonError("unsubscribeFromList() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("unsubscribeFromList() requires you to be authenticated.") - - def isListSubscriber(self, list_id, id, version = None): - """ isListSubscriber(self, list_id, id, version) - - Check if a specified user (id) is a subscriber of the list in question (list_id). - - **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. - - Parameters: - list_id - Required. The slug of the list to check against. - id - Required. The ID of the user being checked in the list. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if self.authenticated is True: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, self.username, list_id, repr(id)))) - else: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, self.username, list_id, repr(id)))) - except HTTPError as e: - raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) - - def availableTrends(self, latitude = None, longitude = None, version = None): - """ availableTrends(latitude, longitude, version): - - Gets all available trends, optionally filtering by geolocation based stuff. - - Note: If you choose to pass a latitude/longitude, keep in mind that you have to pass both - one won't work by itself. ;P - - Parameters: - latitude (string) - Optional. A latitude to sort by. - longitude (string) - Optional. A longitude to sort by. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - if latitude is not None and longitude is not None: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/available.json?latitude=%s&longitude=%s" % (version, latitude, longitude))) - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/available.json" % version)) - except HTTPError as e: - raise TwythonError("availableTrends() failed with a %d error code." % e.code, e.code) - - def trendsByLocation(self, woeid, version = None): - """ trendsByLocation(woeid, version): - - Gets all available trends, filtering by geolocation (woeid - see http://developer.yahoo.com/geo/geoplanet/guide/concepts.html). - - Note: If you choose to pass a latitude/longitude, keep in mind that you have to pass both - one won't work by itself. ;P - - Parameters: - woeid (string) - Required. WoeID of the area you're searching in. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - try: - return simplejson.load(self.opener.open("http://api.twitter.com/%d/trends/%s.json" % (version, woeid))) - except HTTPError as e: - raise TwythonError("trendsByLocation() failed with a %d error code." % e.code, e.code) - - # 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", version = None): - """ updateProfileBackgroundImage(filename, tile="true") - - Updates the authenticating user's profile background image. - - Parameters: - image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. - tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. - ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - files = [("image", filename, open(filename, 'rb').read())] - fields = [] - content_type, body = self.encode_multipart_formdata(fields, files) - headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} - r = urllib.request.Request("http://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) - return self.opener.open(r).read() - except HTTPError as e: - raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("You realize you need to be authenticated to change a background image, right?") - - def updateProfileImage(self, filename, version = None): - """ updateProfileImage(filename) - - Updates the authenticating user's profile image (avatar). - - Parameters: - image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. - version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. - """ - version = version or self.apiVersion - if self.authenticated is True: - try: - files = [("image", filename, open(filename, 'rb').read())] - fields = [] - content_type, body = self.encode_multipart_formdata(fields, files) - headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} - r = urllib.request.Request("http://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) - return self.opener.open(r).read() - except HTTPError as e: - raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) - else: - raise AuthError("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): - """ get_content_type(self, filename) - - Exactly what you think it does. :D - """ - return mimetypes.guess_type(filename)[0] or 'application/octet-stream' - - def unicode2utf8(self, text): - try: - if isinstance(text, str): - text = text.encode('utf-8') - except: - pass - return text diff --git a/twython3k/twitter_endpoints.py b/twython3k/twitter_endpoints.py new file mode 100644 index 0000000..42bd5a1 --- /dev/null +++ b/twython3k/twitter_endpoints.py @@ -0,0 +1,299 @@ +""" + A huge map of every Twitter API endpoint to a function definition in Twython. + + Parameters that need to be embedded in the URL are treated with mustaches, e.g: + + {{version}}, etc + + When creating new endpoint definitions, keep in mind that the name of the mustache + will be replaced with the keyword that gets passed in to the function at call time. + + i.e, in this case, if I pass version = 47 to any function, {{version}} will be replaced + with 47, instead of defaulting to 1 (said defaulting takes place at conversion time). +""" + +# Base Twitter API url, no need to repeat this junk... +base_url = 'http://api.twitter.com/{{version}}' + +api_table = { + 'getRateLimitStatus': { + 'url': '/account/rate_limit_status.json', + 'method': 'GET', + }, + + # Timeline methods + 'getPublicTimeline': { + 'url': '/statuses/public_timeline.json', + 'method': 'GET', + }, + 'getHomeTimeline': { + 'url': '/statuses/home_timeline.json', + 'method': 'GET', + }, + 'getUserTimeline': { + 'url': '/statuses/user_timeline.json', + 'method': 'GET', + }, + 'getFriendsTimeline': { + 'url': '/statuses/friends_timeline.json', + 'method': 'GET', + }, + + # Interfacing with friends/followers + 'getUserMentions': { + 'url': '/statuses/mentions.json', + 'method': 'GET', + }, + 'getFriendsStatus': { + 'url': '/statuses/friends.json', + 'method': 'GET', + }, + 'getFollowersStatus': { + 'url': '/statuses/followers.json', + 'method': 'GET', + }, + 'createFriendship': { + 'url': '/friendships/create.json', + 'method': 'POST', + }, + 'destroyFriendship': { + 'url': '/friendships/destroy.json', + 'method': 'POST', + }, + 'getFriendsIDs': { + 'url': '/friends/ids.json', + 'method': 'GET', + }, + 'getFollowersIDs': { + 'url': '/followers/ids.json', + 'method': 'GET', + }, + + # Retweets + 'reTweet': { + 'url': '/statuses/retweet/{{id}}.json', + 'method': 'POST', + }, + 'getRetweets': { + 'url': '/statuses/retweets/{{id}}.json', + 'method': 'GET', + }, + 'retweetedOfMe': { + 'url': '/statuses/retweets_of_me.json', + 'method': 'GET', + }, + 'retweetedByMe': { + 'url': '/statuses/retweeted_by_me.json', + 'method': 'GET', + }, + 'retweetedToMe': { + 'url': '/statuses/retweeted_to_me.json', + 'method': 'GET', + }, + + # User methods + 'showUser': { + 'url': '/users/show.json', + 'method': 'GET', + }, + 'searchUsers': { + 'url': '/users/search.json', + 'method': 'GET', + }, + + # Status methods - showing, updating, destroying, etc. + 'showStatus': { + 'url': '/statuses/show/{{id}}.json', + 'method': 'GET', + }, + 'updateStatus': { + 'url': '/statuses/update.json', + 'method': 'POST', + }, + 'destroyStatus': { + 'url': '/statuses/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Direct Messages - getting, sending, effing, etc. + 'getDirectMessages': { + 'url': '/direct_messages.json', + 'method': 'GET', + }, + 'getSentMessages': { + 'url': '/direct_messages/sent.json', + 'method': 'GET', + }, + 'sendDirectMessage': { + 'url': '/direct_messages/new.json', + 'method': 'POST', + }, + 'destroyDirectMessage': { + 'url': '/direct_messages/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Friendship methods + 'checkIfFriendshipExists': { + 'url': '/friendships/exists.json', + 'method': 'GET', + }, + 'showFriendship': { + 'url': '/friendships/show.json', + 'method': 'GET', + }, + + # Profile methods + 'updateProfile': { + 'url': '/account/update_profile.json', + 'method': 'POST', + }, + 'updateProfileColors': { + 'url': '/account/update_profile_colors.json', + 'method': 'POST', + }, + + # Favorites methods + 'getFavorites': { + 'url': '/favorites.json', + 'method': 'GET', + }, + 'createFavorite': { + 'url': '/favorites/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyFavorite': { + 'url': '/favorites/destroy/{{id}}.json', + 'method': 'POST', + }, + + # Blocking methods + 'createBlock': { + 'url': '/blocks/create/{{id}}.json', + 'method': 'POST', + }, + 'destroyBlock': { + 'url': '/blocks/destroy/{{id}}.json', + 'method': 'POST', + }, + 'getBlocking': { + 'url': '/blocks/blocking.json', + 'method': 'GET', + }, + 'getBlockedIDs': { + 'url': '/blocks/blocking/ids.json', + 'method': 'GET', + }, + 'checkIfBlockExists': { + 'url': '/blocks/exists.json', + 'method': 'GET', + }, + + # Trending methods + 'getCurrentTrends': { + 'url': '/trends/current.json', + 'method': 'GET', + }, + 'getDailyTrends': { + 'url': '/trends/daily.json', + 'method': 'GET', + }, + 'getWeeklyTrends': { + 'url': '/trends/weekly.json', + 'method': 'GET', + }, + 'availableTrends': { + 'url': '/trends/available.json', + 'method': 'GET', + }, + 'trendsByLocation': { + 'url': '/trends/{{woeid}}.json', + 'method': 'GET', + }, + + # Saved Searches + 'getSavedSearches': { + 'url': '/saved_searches.json', + 'method': 'GET', + }, + 'showSavedSearch': { + 'url': '/saved_searches/show/{{id}}.json', + 'method': 'GET', + }, + 'createSavedSearch': { + 'url': '/saved_searches/create.json', + 'method': 'GET', + }, + 'destroySavedSearch': { + 'url': '/saved_searches/destroy/{{id}}.json', + 'method': 'GET', + }, + + # List API methods/endpoints. Fairly exhaustive and annoying in general. ;P + 'createList': { + 'url': '/{{username}}/lists.json', + 'method': 'POST', + }, + 'updateList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'POST', + }, + 'showLists': { + 'url': '/{{username}}/lists.json', + 'method': 'GET', + }, + 'getListMemberships': { + 'url': '/{{username}}/lists/followers.json', + 'method': 'GET', + }, + 'deleteList': { + 'url': '/{{username}}/lists/{{list_id}}.json', + 'method': 'DELETE', + }, + 'getListTimeline': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'getSpecificList': { + 'url': '/{{username}}/lists/{{list_id}}/statuses.json', + 'method': 'GET', + }, + 'addListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'POST', + }, + 'getListMembers': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'GET', + }, + 'deleteListMember': { + 'url': '/{{username}}/{{list_id}}/members.json', + 'method': 'DELETE', + }, + 'subscribeToList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'POST', + }, + 'unsubscribeFromList': { + 'url': '/{{username}}/{{list_id}}/following.json', + 'method': 'DELETE', + }, + + # The one-offs + 'notificationFollow': { + 'url': '/notifications/follow/follow.json', + 'method': 'POST', + }, + 'notificationLeave': { + 'url': '/notifications/leave/leave.json', + 'method': 'POST', + }, + 'updateDeliveryService': { + 'url': '/account/update_delivery_device.json', + 'method': 'POST', + }, + 'reportSpam': { + 'url': '/report_spam.json', + 'method': 'POST', + }, +} diff --git a/twython3k/twython.py b/twython3k/twython.py new file mode 100644 index 0000000..5efa1e7 --- /dev/null +++ b/twython3k/twython.py @@ -0,0 +1,417 @@ +#!/usr/bin/python + +""" + Twython is a library for Python that wraps the Twitter API. + It aims to abstract away all the API endpoints, so that additions to the library + and/or the Twitter API won't cause any overall problems. + + Questions, comments? ryan@venodesigns.net +""" + +__author__ = "Ryan McGrath " +__version__ = "1.3" + +import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.error, urllib.parse +import urllib.parse +import http.client +import httplib2 +import mimetypes +import mimetools +import re + +import oauth2 as oauth + +# Twython maps keyword based arguments to Twitter API endpoints. The endpoints +# table is a file with a dictionary of every API endpoint that Twython supports. +from .twitter_endpoints import base_url, api_table + +from urllib.error import HTTPError + +# There are some special setups (like, oh, a Django application) where +# simplejson exists behind the scenes anyway. Past Python 2.6, this should +# never really cause any problems to begin with. +try: + # Python 2.6 and up + import json as simplejson +except ImportError: + try: + # Python 2.6 and below (2.4/2.5, 2.3 is not guranteed to work with this library to begin with) + import simplejson + except ImportError: + try: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + except: + # Seriously wtf is wrong with you if you get this Exception. + raise Exception("Twython requires the simplejson library (or Python 2.6) to work. http://www.undefined.org/python/") + +class TwythonError(Exception): + """ + Generic error class, catch-all for most Twython issues. + Special cases are handled by APILimit and AuthError. + + Note: To use these, the syntax has changed as of Twython 1.3. To catch these, + you need to explicitly import them into your code, e.g: + + from twython import TwythonError, APILimit, AuthError + """ + 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(TwythonError): + """ + Raised when you've hit an API limit. Try to avoid these, read the API + docs if you're running into issues here, Twython does not concern itself with + this matter beyond telling you that you've done goofed. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class AuthError(TwythonError): + """ + Raised when you try to access a protected resource and it fails due to some issue with + your authentication. + """ + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return repr(self.msg) + + +class Twython(object): + def __init__(self, twitter_token = None, twitter_secret = None, oauth_token = None, oauth_token_secret = None, headers=None): + """setup(self, oauth_token = None, headers = None) + + Instantiates an instance of Twython. Takes optional parameters for authentication and such (see below). + + Parameters: + twitter_token - Given to you when you register your application with Twitter. + twitter_secret - Given to you when you register your application with Twitter. + oauth_token - If you've gone through the authentication process and have a token for this user, + pass it in and it'll be used for all requests going forward. + oauth_token_secret - see oauth_token; it's the other half. + headers - User agent header, dictionary style ala {'User-Agent': 'Bert'} + + ** Note: versioning is not currently used by search.twitter functions; when Twitter moves their junk, it'll be supported. + """ + # Needed for hitting that there API. + self.request_token_url = 'http://twitter.com/oauth/request_token' + self.access_token_url = 'http://twitter.com/oauth/access_token' + self.authorize_url = 'http://twitter.com/oauth/authorize' + self.authenticate_url = 'http://twitter.com/oauth/authenticate' + self.twitter_token = twitter_token + self.twitter_secret = twitter_secret + self.oauth_token = oauth_token + self.oauth_secret = oauth_token_secret + + # If there's headers, set them, otherwise be an embarassing parent for their own good. + self.headers = headers + if self.headers is None: + headers = {'User-agent': 'Twython Python Twitter Library v1.3'} + + consumer = None + token = None + + if self.twitter_token is not None and self.twitter_secret is not None: + consumer = oauth.Consumer(self.twitter_token, self.twitter_secret) + + if self.oauth_token is not None and self.oauth_secret is not None: + token = oauth.Token(oauth_token, oauth_token_secret) + + # Filter down through the possibilities here - if they have a token, if they're first stage, etc. + if consumer is not None and token is not None: + self.client = oauth.Client(consumer, token) + elif consumer is not None: + self.client = oauth.Client(consumer) + else: + # If they don't do authentication, but still want to request unprotected resources, we need an opener. + self.client = httplib2.Http() + + def __getattr__(self, api_call): + """ + The most magically awesome block of code you'll see in 2010. + + Rather than list out 9 million damn methods for this API, we just keep a table (see above) of + every API endpoint and their corresponding function id for this library. This pretty much gives + unlimited flexibility in API support - there's a slight chance of a performance hit here, but if this is + going to be your bottleneck... well, don't use Python. ;P + + For those who don't get what's going on here, Python classes have this great feature known as __getattr__(). + It's called when an attribute that was called on an object doesn't seem to exist - since it doesn't exist, + we can take over and find the API method in our table. We then return a function that downloads and parses + what we're looking for, based on the keywords passed in. + + I'll hate myself for saying this, but this is heavily inspired by Ruby's "method_missing". + """ + def get(self, **kwargs): + # Go through and replace any mustaches that are in our API url. + fn = api_table[api_call] + base = re.sub( + '\{\{(?P[a-zA-Z]+)\}\}', + lambda m: "%s" % kwargs.get(m.group(1), '1'), # The '1' here catches the API version. Slightly hilarious. + base_url + fn['url'] + ) + + # Then open and load that shiiit, yo. TODO: check HTTP method and junk, handle errors/authentication + if fn['method'] == 'POST': + resp, content = self.client.request(base, fn['method'], urllib.parse.urlencode(kwargs)) + else: + url = base + "?" + "&".join(["%s=%s" %(key, value) for (key, value) in kwargs.items()]) + resp, content = self.client.request(url, fn['method']) + + return simplejson.loads(content) + + if api_call in api_table: + return get.__get__(self) + else: + raise AttributeError(api_call) + + def get_authentication_tokens(self): + """ + get_auth_url(self) + + Returns an authorization URL for a user to hit. + """ + resp, content = self.client.request(self.request_token_url, "GET") + + if resp['status'] != '200': + raise AuthError("Seems something couldn't be verified with your OAuth junk. Error: %s, Message: %s" % (resp['status'], content)) + + request_tokens = dict(urllib.parse.parse_qsl(content)) + request_tokens['auth_url'] = "%s?oauth_token=%s" % (self.authenticate_url, request_tokens['oauth_token']) + return request_tokens + + def get_authorized_tokens(self): + """ + get_authorized_tokens + + Returns authorized tokens after they go through the auth_url phase. + """ + resp, content = self.client.request(self.access_token_url, "GET") + return dict(urllib.parse.parse_qsl(content)) + + # ------------------------------------------------------------------------------------------------------------------------ + # The following methods are all different in some manner or require special attention with regards to the Twitter API. + # Because of this, we keep them separate from all the other endpoint definitions - ideally this should be change-able, + # but it's not high on the priority list at the moment. + # ------------------------------------------------------------------------------------------------------------------------ + + @staticmethod + def shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl"): + """shortenURL(url_to_shorten, shortener = "http://is.gd/api.php", query = "longurl") + + Shortens url specified by url_to_shorten. + + Parameters: + url_to_shorten - URL to shorten. + shortener - In case you want to use a url shortening service other than is.gd. + """ + try: + resp, content = self.client.request( + shortener + "?" + urllib.parse.urlencode({query: Twython.unicode2utf8(url_to_shorten)}), + "GET" + ) + return content + except HTTPError as e: + raise TwythonError("shortenURL() failed with a %s error code." % repr(e.code)) + + def bulkUserLookup(self, ids = None, screen_names = None, version = None): + """ bulkUserLookup(self, ids = None, screen_names = None, version = None) + + A method to do bulk user lookups against the Twitter API. Arguments (ids (numbers) / screen_names (strings)) should be flat Arrays that + contain their respective data sets. + + Statuses for the users in question will be returned inline if they exist. Requires authentication! + """ + apiURL = "http://api.twitter.com/1/users/lookup.json?lol=1" + if ids is not None: + apiURL += "&user_id=" + for id in ids: + apiURL += repr(id) + "," + if screen_names is not None: + apiURL += "&screen_name=" + for name in screen_names: + apiURL += name + "," + try: + resp, content = self.client.request(apiURL, "GET") + return simplejson.loads(content) + except HTTPError as e: + raise TwythonError("bulkUserLookup() failed with a %s error code." % repr(e.code), e.code) + + def searchTwitter(self, **kwargs): + """searchTwitter(search_query, **kwargs) + + Returns tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.parse.urlencode({"q": self.unicode2utf8(search_query)}) + try: + resp, content = self.client.request(searchURL, "GET") + return simplejson.loads(content) + except HTTPError as e: + raise TwythonError("getSearchTimeline() failed with a %s error code." % repr(e.code), e.code) + + def searchTwitterGen(self, **kwargs): + """searchTwitterGen(search_query, **kwargs) + + Returns a generator of tweets that match a specified query. + + Parameters: + See the documentation at http://dev.twitter.com/doc/get/search. Pass in the API supported arguments as named parameters. + + e.g x.searchTwitter(q="jjndf", page="2") + """ + searchURL = self.constructApiURL("http://search.twitter.com/search.json", kwargs) + "&" + urllib.parse.urlencode({"q": self.unicode2utf8(search_query)}) + try: + resp, content = self.client.request(searchURL, "GET") + data = simplejson.loads(content) + except HTTPError as e: + raise TwythonError("searchTwitterGen() failed with a %s error code." % repr(e.code), e.code) + + if not data['results']: + raise StopIteration + + for tweet in data['results']: + yield tweet + + if 'page' not in kwargs: + kwargs['page'] = 2 + else: + kwargs['page'] += 1 + + for tweet in self.searchTwitterGen(search_query, **kwargs): + yield tweet + + def isListMember(self, list_id, id, username, version = 1): + """ isListMember(self, list_id, id, version) + + Check if a specified user (id) is a member of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - User who owns the list you're checking against (username) + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + resp, content = self.client.request("http://api.twitter.com/%d/%s/%s/members/%s.json" % (version, username, list_id, repr(id))) + return simplejson.loads(content) + except HTTPError as e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + def isListSubscriber(self, list_id, id, version = 1): + """ isListSubscriber(self, list_id, id, version) + + Check if a specified user (id) is a subscriber of the list in question (list_id). + + **Note: This method may not work for private/protected lists, unless you're authenticated and have access to those lists. + + Parameters: + list_id - Required. The slug of the list to check against. + id - Required. The ID of the user being checked in the list. + username - Required. The username of the owner of the list that you're seeing if someone is subscribed to. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + resp, content = "http://api.twitter.com/%d/%s/%s/following/%s.json" % (version, username, list_id, repr(id)) + return simplejson.loads(content) + except HTTPError as e: + raise TwythonError("isListMember() failed with a %d error code." % e.code, e.code) + + # 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", version = 1): + """ updateProfileBackgroundImage(filename, tile="true") + + Updates the authenticating user's profile background image. + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 800 kilobytes in size. Images with width larger than 2048 pixels will be forceably scaled down. + tile - Optional (defaults to true). If set to true the background image will be displayed tiled. The image will not be tiled otherwise. + ** Note: It's sad, but when using this method, pass the tile value as a string, e.g tile="false" + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + files = [("image", filename, open(filename, 'rb').read())] + fields = [] + content_type, body = Twython.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib.request.Request("http://api.twitter.com/%d/account/update_profile_background_image.json?tile=%s" % (version, tile), body, headers) + return urllib.request.urlopen(r).read() + except HTTPError as e: + raise TwythonError("updateProfileBackgroundImage() failed with a %d error code." % e.code, e.code) + + def updateProfileImage(self, filename, version = 1): + """ updateProfileImage(filename) + + Updates the authenticating user's profile image (avatar). + + Parameters: + image - Required. Must be a valid GIF, JPG, or PNG image of less than 700 kilobytes in size. Images with width larger than 500 pixels will be scaled down. + version (number) - Optional. API version to request. Entire Twython class defaults to 1, but you can override on a function-by-function or class basis - (version=2), etc. + """ + try: + files = [("image", filename, open(filename, 'rb').read())] + fields = [] + content_type, body = Twython.encode_multipart_formdata(fields, files) + headers = {'Content-Type': content_type, 'Content-Length': str(len(body))} + r = urllib.request.Request("http://api.twitter.com/%d/account/update_profile_image.json" % version, body, headers) + return urllib.request.urlopen(r).read() + except HTTPError as e: + raise TwythonError("updateProfileImage() failed with a %d error code." % e.code, e.code) + + @staticmethod + def encode_multipart_formdata(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' % Twython.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 + + @staticmethod + def get_content_type(filename): + """ get_content_type(self, filename) + + Exactly what you think it does. :D + """ + return mimetypes.guess_type(filename)[0] or 'application/octet-stream' + + @staticmethod + def unicode2utf8(text): + try: + if isinstance(text, str): + text = text.encode('utf-8') + except: + pass + return text \ No newline at end of file