]> jfr.im git - z_archive/twitter.git/blame_incremental - twitter/oauth.py
Compare lowercase hostnames when testing.
[z_archive/twitter.git] / twitter / oauth.py
... / ...
CommitLineData
1"""
2Visit the Twitter developer page and create a new application:
3
4 https://dev.twitter.com/apps/new
5
6This will get you a CONSUMER_KEY and CONSUMER_SECRET.
7
8When users run your application they have to authenticate your app
9with their Twitter account. A few HTTP calls to twitter are required
10to do this. Please see the twitter.oauth_dance module to see how this
11is done. If you are making a command-line app, you can use the
12oauth_dance() function directly.
13
14Performing the "oauth dance" gets you an ouath token and oauth secret
15that authenticate the user with Twitter. You should save these for
16later so that the user doesn't have to do the oauth dance again.
17
18read_token_file and write_token_file are utility methods to read and
19write OAuth token and secret key values. The values are stored as
20strings in the file. Not terribly exciting.
21
22Finally, you can use the OAuth authenticator to connect to Twitter. In
23code it all goes like this::
24
25 from twitter import *
26
27 MY_TWITTER_CREDS = os.path.expanduser('~/.my_app_credentials')
28 if not os.path.exists(MY_TWITTER_CREDS):
29 oauth_dance("My App Name", CONSUMER_KEY, CONSUMER_SECRET,
30 MY_TWITTER_CREDS)
31
32 oauth_token, oauth_secret = read_token_file(MY_TWITTER_CREDS)
33
34 twitter = Twitter(auth=OAuth(
35 oauth_token, oauth_token_secret, CONSUMER_KEY, CONSUMER_SECRET))
36
37 # Now work with Twitter
38 twitter.statuses.update(status='Hello, world!')
39
40"""
41
42from __future__ import print_function
43
44from random import getrandbits
45from time import time
46
47from .util import PY_3_OR_HIGHER
48
49try:
50 import urllib.parse as urllib_parse
51 from urllib.parse import urlencode
52except ImportError:
53 import urllib2 as urllib_parse
54 from urllib import urlencode
55
56import hashlib
57import hmac
58import base64
59
60from .auth import Auth
61
62
63def write_token_file(filename, oauth_token, oauth_token_secret):
64 """
65 Write a token file to hold the oauth token and oauth token secret.
66 """
67 oauth_file = open(filename, 'w')
68 print(oauth_token, file=oauth_file)
69 print(oauth_token_secret, file=oauth_file)
70 oauth_file.close()
71
72def read_token_file(filename):
73 """
74 Read a token file and return the oauth token and oauth token secret.
75 """
76 f = open(filename)
77 return f.readline().strip(), f.readline().strip()
78
79
80class OAuth(Auth):
81 """
82 An OAuth authenticator.
83 """
84 def __init__(self, token, token_secret, consumer_key, consumer_secret):
85 """
86 Create the authenticator. If you are in the initial stages of
87 the OAuth dance and don't yet have a token or token_secret,
88 pass empty strings for these params.
89 """
90 self.token = token
91 self.token_secret = token_secret
92 self.consumer_key = consumer_key
93 self.consumer_secret = consumer_secret
94
95 def encode_params(self, base_url, method, params):
96 params = params.copy()
97
98 if self.token:
99 params['oauth_token'] = self.token
100
101 params['oauth_consumer_key'] = self.consumer_key
102 params['oauth_signature_method'] = 'HMAC-SHA1'
103 params['oauth_version'] = '1.0'
104 params['oauth_timestamp'] = str(int(time()))
105 params['oauth_nonce'] = str(getrandbits(64))
106
107 enc_params = urlencode_noplus(sorted(params.items()))
108
109 key = self.consumer_secret + "&" + urllib_parse.quote(self.token_secret, safe='~')
110
111 message = '&'.join(
112 urllib_parse.quote(i, safe='~') for i in [method.upper(), base_url, enc_params])
113
114 signature = (base64.b64encode(hmac.new(
115 key.encode('ascii'), message.encode('ascii'), hashlib.sha1)
116 .digest()))
117 return enc_params + "&" + "oauth_signature=" + urllib_parse.quote(signature, safe='~')
118
119 def generate_headers(self):
120 return {}
121
122# apparently contrary to the HTTP RFCs, spaces in arguments must be encoded as
123# %20 rather than '+' when constructing an OAuth signature (and therefore
124# also in the request itself.)
125# So here is a specialized version which does exactly that.
126# In Python2, since there is no safe option for urlencode, we force it by hand
127def urlencode_noplus(query):
128 if not PY_3_OR_HIGHER:
129 new_query = []
130 TILDE = '____TILDE-PYTHON-TWITTER____'
131 for k,v in query:
132 if type(k) is unicode: k = k.encode('utf-8')
133 k = str(k).replace("~", TILDE)
134 if type(v) is unicode: v = v.encode('utf-8')
135 v = str(v).replace("~", TILDE)
136 new_query.append((k, v))
137 query = new_query
138 return urlencode(query).replace(TILDE, "~").replace("+", "%20")
139
140 return urlencode(query, safe='~').replace("+", "%20")