Skip to content

Instantly share code, notes, and snippets.

@wpsmith
Forked from mmueller/bitly.py
Created September 8, 2016 16:02
Show Gist options
  • Save wpsmith/cf8405de6418a95cf931803542bd83a2 to your computer and use it in GitHub Desktop.
Save wpsmith/cf8405de6418a95cf931803542bd83a2 to your computer and use it in GitHub Desktop.
Command-line bit.ly URL generator (Python)
#!/usr/bin/env python
#
# Copyright 2009 Empeeric LTD. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import urllib,urllib2
import urlparse
import string
import sys
try:
import simplejson
except ImportError:
import json as simplejson
# Register an account at bit.ly and then copy your username and key from here:
# http://bit.ly/a/account
API_USERNAME = ''
API_KEY = ''
BITLY_BASE_URL = "http://api.bit.ly/"
BITLY_API_VERSION = "2.0.1"
VERBS_PARAM = {
'shorten':'longUrl',
'expand':'shortUrl',
'info':'shortUrl',
'stats':'shortUrl',
'errors':'',
}
class BitlyError(Exception):
'''Base class for bitly errors'''
@property
def message(self):
'''Returns the first argument used to construct this error.'''
return self.args[0]
class Api(object):
""" API class for bit.ly """
def __init__(self, login, apikey):
self.login = login
self.apikey = apikey
self._urllib = urllib2
def shorten(self,longURLs,params={}):
"""
Takes either:
A long URL string and returns shortened URL string
Or a list of long URL strings and returns a list of shortened URL strings.
"""
want_result_list = True
if not isinstance(longURLs, list):
longURLs = [longURLs]
want_result_list = False
for index,url in enumerate(longURLs):
if not '://' in url:
longURLs[index] = "http://" + url
request = self._getURL("shorten",longURLs,params)
result = self._fetchUrl(request)
json = simplejson.loads(result)
self._CheckForError(json)
results = json['results']
res = [self._extract_short_url(results[url]) for url in longURLs]
if want_result_list:
return res
else:
return res[0]
def _extract_short_url(self,item):
if item['shortKeywordUrl'] == "":
return item['shortUrl']
else:
return item['shortKeywordUrl']
def expand(self,shortURL,params={}):
""" Given a bit.ly url or hash, return long source url """
request = self._getURL("expand",shortURL,params)
result = self._fetchUrl(request)
json = simplejson.loads(result)
self._CheckForError(json)
return json['results'][string.split(shortURL, '/')[-1]]['longUrl']
def info(self,shortURL,params={}):
"""
Given a bit.ly url or hash,
return information about that page,
such as the long source url
"""
request = self._getURL("info",shortURL,params)
result = self._fetchUrl(request)
json = simplejson.loads(result)
self._CheckForError(json)
return json['results'][string.split(shortURL, '/')[-1]]
def stats(self,shortURL,params={}):
""" Given a bit.ly url or hash, return traffic and referrer data. """
request = self._getURL("stats",shortURL,params)
result = self._fetchUrl(request)
json = simplejson.loads(result)
self._CheckForError(json)
return Stats.NewFromJsonDict(json['results'])
def errors(self,params={}):
""" Get a list of bit.ly API error codes. """
request = self._getURL("errors","",params)
result = self._fetchUrl(request)
json = simplejson.loads(result)
self._CheckForError(json)
return json['results']
def setUrllib(self, urllib):
'''Override the default urllib implementation.
Args:
urllib: an instance that supports the same API as the urllib2 module
'''
self._urllib = urllib
def _getURL(self,verb,paramVal,more_params={}):
if not isinstance(paramVal, list):
paramVal = [paramVal]
params = {
'version':BITLY_API_VERSION,
'format':'json',
'login':self.login,
'apiKey':self.apikey,
}
params.update(more_params)
params = params.items()
verbParam = VERBS_PARAM[verb]
if verbParam:
for val in paramVal:
params.append(( verbParam,val ))
encoded_params = urllib.urlencode(params)
return "%s%s?%s" % (BITLY_BASE_URL,verb,encoded_params)
def _fetchUrl(self,url):
'''Fetch a URL
Args:
url: The URL to retrieve
Returns:
A string containing the body of the response.
'''
# Open and return the URL
url_data = self._urllib.urlopen(url).read()
return url_data
def _CheckForError(self, data):
"""Raises a BitlyError if bitly returns an error message.
Args:
data: A python dict created from the bitly json response
Raises:
BitlyError wrapping the bitly error message if one exists.
"""
# bitly errors are relatively unlikely, so it is faster
# to check first, rather than try and catch the exception
if 'ERROR' in data or data['statusCode'] == 'ERROR':
raise BitlyError, data['errorMessage']
for key in data['results']:
if type(data['results']) is dict and type(data['results'][key]) is dict:
if 'statusCode' in data['results'][key] and data['results'][key]['statusCode'] == 'ERROR':
raise BitlyError, data['results'][key]['errorMessage']
class Stats(object):
'''A class representing the Statistics returned by the bitly api.
The Stats structure exposes the following properties:
status.user_clicks # read only
status.clicks # read only
'''
def __init__(self,user_clicks=None,total_clicks=None):
self.user_clicks = user_clicks
self.total_clicks = total_clicks
@staticmethod
def NewFromJsonDict(data):
'''Create a new instance based on a JSON dict.
Args:
data: A JSON dict, as converted from the JSON in the bitly API
Returns:
A bitly.Stats instance
'''
return Stats(user_clicks=data.get('userClicks', None),
total_clicks=data.get('clicks', None))
def main(argv):
if len(sys.argv) < 2:
print "Usage: bitly.py '[url]'"
return 1
print ''
if not API_USERNAME or not API_KEY:
print 'API_USERNAME and API_KEY must be set.'
return 1
shortUrl = Api(login=API_USERNAME, apikey=API_KEY).shorten(sys.argv[1])
print 'Short URL: ' + shortUrl
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment