1
+ #!/usr/bin/python2.4
2
+ #
3
+ # Copyright 2009 Empeeric LTD. All Rights Reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import simplejson
18
+ import urllib ,urllib2
19
+ import urlparse
20
+ import string
21
+
22
+ BITLY_BASE_URL = "http://api.bit.ly/"
23
+ BITLY_API_VERSION = "2.0.1"
24
+
25
+ VERBS_PARAM = {
26
+ 'shorten' :'longUrl' ,
27
+ 'expand' :'shortUrl' ,
28
+ 'info' :'shortUrl' ,
29
+ 'stats' :'shortUrl' ,
30
+ 'errors' :'' ,
31
+ }
32
+
33
+ class BitlyError (Exception ):
34
+ '''Base class for bitly errors'''
35
+
36
+ @property
37
+ def message (self ):
38
+ '''Returns the first argument used to construct this error.'''
39
+ return self .args [0 ]
40
+
41
+ class Api (object ):
42
+ """ API class for bit.ly """
43
+ def __init__ (self , login , apikey ):
44
+ self .login = login
45
+ self .apikey = apikey
46
+ self ._urllib = urllib2
47
+
48
+ def shorten (self ,longURLs ,params = {}):
49
+ """
50
+ Takes either:
51
+ A long URL string and returns shortened URL string
52
+ Or a list of long URL strings and returns a list of shortened URL strings.
53
+ """
54
+ want_result_list = True
55
+ if not isinstance (longURLs , list ):
56
+ longURLs = [longURLs ]
57
+ want_result_list = False
58
+
59
+ for index ,url in enumerate (longURLs ):
60
+ if not '://' in url :
61
+ longURLs [index ] = "http://" + url
62
+
63
+ request = self ._getURL ("shorten" ,longURLs ,params )
64
+ result = self ._fetchUrl (request )
65
+ json = simplejson .loads (result )
66
+ self ._CheckForError (json )
67
+
68
+ results = json ['results' ]
69
+ res = [self ._extract_short_url (results [url ]) for url in longURLs ]
70
+
71
+ if want_result_list :
72
+ return res
73
+ else :
74
+ return res [0 ]
75
+
76
+ def _extract_short_url (self ,item ):
77
+ if item ['shortKeywordUrl' ] == "" :
78
+ return item ['shortUrl' ]
79
+ else :
80
+ return item ['shortKeywordUrl' ]
81
+
82
+ def expand (self ,shortURL ,params = {}):
83
+ """ Given a bit.ly url or hash, return long source url """
84
+ request = self ._getURL ("expand" ,shortURL ,params )
85
+ result = self ._fetchUrl (request )
86
+ json = simplejson .loads (result )
87
+ self ._CheckForError (json )
88
+ return json ['results' ][string .split (shortURL , '/' )[- 1 ]]['longUrl' ]
89
+
90
+ def info (self ,shortURL ,params = {}):
91
+ """
92
+ Given a bit.ly url or hash,
93
+ return information about that page,
94
+ such as the long source url
95
+ """
96
+ request = self ._getURL ("info" ,shortURL ,params )
97
+ result = self ._fetchUrl (request )
98
+ json = simplejson .loads (result )
99
+ self ._CheckForError (json )
100
+ return json ['results' ][string .split (shortURL , '/' )[- 1 ]]
101
+
102
+ def stats (self ,shortURL ,params = {}):
103
+ """ Given a bit.ly url or hash, return traffic and referrer data. """
104
+ request = self ._getURL ("stats" ,shortURL ,params )
105
+ result = self ._fetchUrl (request )
106
+ json = simplejson .loads (result )
107
+ self ._CheckForError (json )
108
+ return Stats .NewFromJsonDict (json ['results' ])
109
+
110
+ def errors (self ,params = {}):
111
+ """ Get a list of bit.ly API error codes. """
112
+ request = self ._getURL ("errors" ,"" ,params )
113
+ result = self ._fetchUrl (request )
114
+ json = simplejson .loads (result )
115
+ self ._CheckForError (json )
116
+ return json ['results' ]
117
+
118
+ def setUrllib (self , urllib ):
119
+ '''Override the default urllib implementation.
120
+
121
+ Args:
122
+ urllib: an instance that supports the same API as the urllib2 module
123
+ '''
124
+ self ._urllib = urllib
125
+
126
+ def _getURL (self ,verb ,paramVal ,more_params = {}):
127
+ if not isinstance (paramVal , list ):
128
+ paramVal = [paramVal ]
129
+
130
+ params = {
131
+ 'version' :BITLY_API_VERSION ,
132
+ 'format' :'json' ,
133
+ 'login' :self .login ,
134
+ 'apiKey' :self .apikey ,
135
+ }
136
+
137
+ params .update (more_params )
138
+ params = params .items ()
139
+
140
+ verbParam = VERBS_PARAM [verb ]
141
+ if verbParam :
142
+ for val in paramVal :
143
+ params .append (( verbParam ,val ))
144
+
145
+ encoded_params = urllib .urlencode (params )
146
+ return "%s%s?%s" % (BITLY_BASE_URL ,verb ,encoded_params )
147
+
148
+ def _fetchUrl (self ,url ):
149
+ '''Fetch a URL
150
+
151
+ Args:
152
+ url: The URL to retrieve
153
+
154
+ Returns:
155
+ A string containing the body of the response.
156
+ '''
157
+
158
+ # Open and return the URL
159
+ url_data = self ._urllib .urlopen (url ).read ()
160
+ return url_data
161
+
162
+ def _CheckForError (self , data ):
163
+ """Raises a BitlyError if bitly returns an error message.
164
+
165
+ Args:
166
+ data: A python dict created from the bitly json response
167
+ Raises:
168
+ BitlyError wrapping the bitly error message if one exists.
169
+ """
170
+ # bitly errors are relatively unlikely, so it is faster
171
+ # to check first, rather than try and catch the exception
172
+ if 'ERROR' in data or data ['statusCode' ] == 'ERROR' :
173
+ raise BitlyError , data ['errorMessage' ]
174
+ for key in data ['results' ]:
175
+ if type (data ['results' ]) is dict and type (data ['results' ][key ]) is dict :
176
+ if 'statusCode' in data ['results' ][key ] and data ['results' ][key ]['statusCode' ] == 'ERROR' :
177
+ raise BitlyError , data ['results' ][key ]['errorMessage' ]
178
+
179
+ class Stats (object ):
180
+ '''A class representing the Statistics returned by the bitly api.
181
+
182
+ The Stats structure exposes the following properties:
183
+ status.user_clicks # read only
184
+ status.clicks # read only
185
+ '''
186
+
187
+ def __init__ (self ,user_clicks = None ,total_clicks = None ):
188
+ self .user_clicks = user_clicks
189
+ self .total_clicks = total_clicks
190
+
191
+ @staticmethod
192
+ def NewFromJsonDict (data ):
193
+ '''Create a new instance based on a JSON dict.
194
+
195
+ Args:
196
+ data: A JSON dict, as converted from the JSON in the bitly API
197
+ Returns:
198
+ A bitly.Stats instance
199
+ '''
200
+ return Stats (user_clicks = data .get ('userClicks' , None ),
201
+ total_clicks = data .get ('clicks' , None ))
202
+
203
+
204
+ if __name__ == '__main__' :
205
+ testURL1 = "www.yahoo.com"
206
+ testURL2 = "www.cnn.com"
207
+ a = Api (login = "pythonbitly" ,apikey = "R_06871db6b7fd31a4242709acaf1b6648" )
208
+ short = a .shorten (testURL1 )
209
+ print "Short URL = %s" % short
210
+ short = a .shorten (testURL1 ,{'history' :1 })
211
+ print "Short URL with history = %s" % short
212
+ urlList = [testURL1 ,testURL2 ]
213
+ shortList = a .shorten (urlList )
214
+ print "Short URL list = %s" % shortList
215
+ long = a .expand (short )
216
+ print "Expanded URL = %s" % long
217
+ info = a .info (short )
218
+ print "Info: %s" % info
219
+ stats = a .stats (short )
220
+ print "User clicks %s, total clicks: %s" % (stats .user_clicks ,stats .total_clicks )
221
+ errors = a .errors ()
222
+ print "Errors: %s" % errors
223
+ testURL3 = ["www.google.com" ]
224
+ short = a .shorten (testURL3 )
225
+ print "Short url in list = %s" % short
0 commit comments