├── .gitignore ├── .travis.yml ├── HISTORY.rst ├── LICENSE ├── MANIFEST ├── MANIFEST.in ├── README.rst ├── bin └── instapaper.py ├── instapaperlib ├── __init__.py └── instapaperlib.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | dist* 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - '2.6' 5 | - '2.7' 6 | script: python instapaperlib/instapaperlib.py -v 7 | deploy: 8 | provider: pypi 9 | user: mrtazz 10 | password: 11 | secure: m32BfYp2HRD6DKYpQZQFkyJDPhqJRdaciirLDjihm3Eut1L7fd4oQGkgaQpFYCVh/xlks5W4phTSSO36bCvpDHLkWF5NZChZ/LXdjmIj5C9IUtEhr9bYebRFRkElZ3wbZ0gavjF2oQBGQV5Zix6iac5YIHSCJHMLtxYP51Y+8oo= 12 | on: 13 | tags: true 14 | repo: mrtazz/InstapaperLibrary 15 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======== 3 | 4 | 0.5.1 (2016-01-13) 5 | ------------------ 6 | * fix travis.yml misconfig that only ran tests on master branch 7 | 8 | 0.5.0 (2016-01-13) 9 | ------------------ 10 | * add support for new instapaper API 11 | 12 | 0.4.0 (2010-09-06) 13 | ------------------ 14 | * add support for JSONP 15 | * add support for redirect=close 16 | 17 | 0.3.1 (2010-06-05) 18 | ------------------- 19 | * add doctest unit test 20 | * catch exception in auth because of new query method 21 | 22 | 0.3.0 (2010-05-24) 23 | ------------------- 24 | * Choose between HTTP/HTTPS (HTTPS standard) 25 | * Set selection for added URL 26 | * proper use of auto-title parameter 27 | * Response headers (Content-Location, X-Instapaper-Title) are returned 28 | 29 | 0.2.0 (2010-05-20) 30 | ------------------- 31 | * Restructured as python package 32 | * PyPi compatibility 33 | * Add CLI client 34 | 35 | 0.1.0 (2010-05-01) 36 | ------------------- 37 | * Basic library working 38 | * Working add_item 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Daniel Schauenberg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | HISTORY.rst 2 | README.rst 3 | setup.py 4 | bin/instapaper.py 5 | instapaperlib/__init__.py 6 | instapaperlib/instapaperlib.py 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst README.rst 2 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | instapaperlib.py 3 | ================= 4 | 5 | .. image:: https://travis-ci.org/mrtazz/InstapaperLibrary.svg?branch=master 6 | :target: https://travis-ci.org/mrtazz/InstapaperLibrary 7 | 8 | Overview 9 | --------- 10 | 11 | Python library for the instapaper.com API. http://instapaper.com 12 | 13 | Usage 14 | ------ 15 | 16 | Use the library directly:: 17 | 18 | import instapaperlib 19 | 20 | instapaperlib.auth("username", "password") 21 | instapaperlib.add_item("username", "password", "URL", "title") 22 | # with selection test set 23 | instapaperlib.add_item("username", "password", "URL", "title", "selection") 24 | 25 | Create an instance to work with:: 26 | 27 | from instapaperlib import Instapaper 28 | 29 | i = Instapaper("username", "password") 30 | i.auth() 31 | 32 | Catch the return codes to work with:: 33 | 34 | from instapaperlib import Instapaper 35 | 36 | i = Instapaper("username", "password") 37 | (statuscode, statusmessage) = i.add_item("URL", "title") 38 | 39 | Also catch the response header:: 40 | 41 | from instapaperlib import Instapaper 42 | 43 | i = Instapaper("username", "password") 44 | (statuscode, statusmessage, title, location) = i.add_item("URL", "title", response_info=True) 45 | 46 | Or use the cli client:: 47 | 48 | instapaper.py -u user -p password -t title URL 49 | 50 | If you have set your username:password in ~/.instapaperrc:: 51 | 52 | instapaper.py URL 53 | 54 | Installation 55 | ------------- 56 | Install via pip:: 57 | 58 | pip install instapaperlib 59 | 60 | Or, if you must:: 61 | 62 | easy_install instapaperlib 63 | 64 | Contributing 65 | ------------- 66 | - fork the repo 67 | - add your changes and tests so I don't accidentally break anything in the future 68 | - run the tests: :code:`python instapaperlib/instapaperlib.py` 69 | - open a pull request (also runs the tests via Travis CI) 70 | - high-five yourself, you're awesome 71 | 72 | Meta 73 | ----- 74 | :Project: http://github.com/mrtazz/InstapaperLibrary 75 | 76 | :Issues: http://github.com/mrtazz/InstapaperLibrary/issues 77 | 78 | -------------------------------------------------------------------------------- /bin/instapaper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | """ 4 | instapaper.com cli client 5 | """ 6 | 7 | import re 8 | import sys 9 | import os 10 | import instapaperlib 11 | from optparse import OptionParser 12 | from getpass import getpass 13 | 14 | def main(): 15 | """ 16 | main method 17 | """ 18 | # initialize parser 19 | usage = "usage: %prog [-u USER] [-p PASSWORD] [-t TITLE] [-s selection] url" 20 | parser = OptionParser(usage, version="%prog "+instapaperlib.__version__) 21 | parser.add_option("-u", "--user", action="store", dest="user", 22 | metavar="USER", help="instapaper username") 23 | parser.add_option("-p", "--password", action="store", dest="password", 24 | metavar="USER", help="instapaper password") 25 | parser.add_option("-t", "--title", action="store", dest="title", 26 | metavar="TITLE", help="title of the link to add") 27 | parser.add_option("-s", "--selection", action="store", dest="selection", 28 | metavar="SELECTION", help="short text for description") 29 | 30 | (options, args) = parser.parse_args() 31 | 32 | if not len(args) > 0: 33 | parser.error("What do you want to read later?") 34 | 35 | if not options.user: 36 | # auth regex 37 | login = re.compile("(.+?):(.+)") 38 | try: 39 | config = open(os.path.expanduser("~") + "/.instapaperrc") 40 | for line in config: 41 | matches = login.match(line) 42 | if matches: 43 | user = matches.group(1).strip() 44 | password = matches.group(2).strip() 45 | except IOError: 46 | parser.error("No login information present.") 47 | sys.exit(-1) 48 | else: 49 | user = options.user 50 | # make sure all parameters are present 51 | if not options.password: 52 | password = getpass() 53 | else: 54 | password = options.password 55 | 56 | (status, text) = instapaperlib.add_item(user, password, args[0], 57 | options.title, options.selection) 58 | print text 59 | 60 | if __name__ == "__main__": 61 | main() 62 | -------------------------------------------------------------------------------- /instapaperlib/__init__.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from instapaperlib import Instapaper 4 | 5 | __author__ = "Daniel Schauenberg" 6 | __version__ = "0.5.1" 7 | __license__ = "MIT" 8 | 9 | def auth(user='', password=''): 10 | return Instapaper(user, password).auth() 11 | 12 | def add_item(user='', password='', url=None, 13 | title=None, selection=None, jsonp=None, 14 | redirect=None, response_info=False): 15 | return Instapaper(user, password).add_item(url,title=title, 16 | selection=selection, jsonp=jsonp, 17 | redirect=redirect, 18 | response_info=response_info) 19 | -------------------------------------------------------------------------------- /instapaperlib/instapaperlib.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | ''' 3 | instapaperlib.py -- brief simple library to use instapaper 4 | 5 | >>> Instapaper("instapaperlib", "").auth() 6 | (200, 'OK.') 7 | 8 | >>> Instapaper("instapaperlib", "dd").auth() 9 | (200, 'OK.') 10 | 11 | >>> Instapaper("instapaperlibi", "").auth() 12 | (403, 'Invalid username or password.') 13 | 14 | >>> Instapaper("instapaperlib", "").add_item("google.com") 15 | (201, 'URL successfully added.') 16 | 17 | >>> Instapaper("instapaperlib", "").add_item("google.com", "google") 18 | (201, 'URL successfully added.') 19 | 20 | >>> Instapaper("instapaperlib", "").add_item("google.com", "google", response_info=True) 21 | (201, 'URL successfully added.', 'google', 'http://www.google.com/') 22 | 23 | >>> Instapaper("instapaperlib", "").add_item("google.com", "google", selection="google page", response_info=True) 24 | (201, 'URL successfully added.', 'google', 'http://www.google.com/') 25 | 26 | >>> Instapaper("instapaperlib", "").add_item("google.com", "google", selection="google page", jsonp="callBack", response_info=True) 27 | 'callBack({"status":201,"text":"{\\\\"bookmark_id\\\\": 45744013}"})' 28 | 29 | >>> Instapaper("instapaperlib", "").add_item("google.com", jsonp="callBack") 30 | 'callBack({"status":201,"text":"{\\\\"bookmark_id\\\\": 45744013}"})' 31 | 32 | >>> Instapaper("instapaperlib", "").auth(jsonp="callBack") 33 | 'callBack({"status":200})' 34 | 35 | >>> Instapaper("instapaperlib", "dd").auth(jsonp="callBack") 36 | 'callBack({"status":200})' 37 | 38 | >>> Instapaper("instapaperlibi", "").auth(jsonp="callBack") 39 | 'callBack({"status":403})' 40 | 41 | >>> Instapaper("instapaperlib", "").add_item("google.com", "google", redirect="close") 42 | (201, 'URL successfully added.') 43 | 44 | ''' 45 | 46 | import urllib 47 | import urllib2 48 | 49 | class Instapaper: 50 | """ This class provides the structure for the connection object """ 51 | 52 | def __init__(self, user, password, https=True): 53 | self.user = user 54 | self.password = password 55 | if https: 56 | self.authurl = "https://www.instapaper.com/api/authenticate" 57 | self.addurl = "https://www.instapaper.com/api/add" 58 | else: 59 | self.authurl = "http://www.instapaper.com/api/authenticate" 60 | self.addurl = "http://www.instapaper.com/api/add" 61 | 62 | self.add_status_codes = { 63 | 201 : "URL successfully added.", 64 | 400 : "Bad Request.", 65 | 403 : "Invalid username or password.", 66 | 500 : "Service error. Try again later." 67 | } 68 | 69 | self.auth_status_codes = { 70 | 200 : "OK.", 71 | 403 : "Invalid username or password.", 72 | 500 : "Service error. Try again later." 73 | } 74 | 75 | def add_item(self, url, title=None, selection=None, 76 | jsonp=None, redirect=None, response_info=False): 77 | """ Method to add a new item to a instapaper account 78 | 79 | Parameters: url -> URL to add 80 | title -> optional title for the URL 81 | Returns: (status as int, status error message) 82 | """ 83 | parameters = { 84 | 'username' : self.user, 85 | 'password' : self.password, 86 | 'url' : url, 87 | } 88 | # look for optional parameters title and selection 89 | if title is not None: 90 | parameters['title'] = title 91 | else: 92 | parameters['auto-title'] = 1 93 | if selection is not None: 94 | parameters['selection'] = selection 95 | if redirect is not None: 96 | parameters['redirect'] = redirect 97 | if jsonp is not None: 98 | parameters['jsonp'] = jsonp 99 | 100 | # make query with the chosen parameters 101 | status, headers = self._query(self.addurl, parameters) 102 | # return the callback call if we want jsonp 103 | if jsonp is not None: 104 | return status 105 | statustxt = self.add_status_codes[int(status)] 106 | # if response headers are desired, return them also 107 | if response_info: 108 | return (int(status), statustxt, headers['title'], headers['location']) 109 | else: 110 | return (int(status), statustxt) 111 | 112 | def auth(self, user=None, password=None, jsonp=None): 113 | """ authenticate with the instapaper.com service 114 | 115 | Parameters: user -> username 116 | password -> password 117 | Returns: (status as int, status error message) 118 | """ 119 | if not user: 120 | user = self.user 121 | if not password: 122 | password = self.password 123 | parameters = { 124 | 'username' : self.user, 125 | 'password' : self.password 126 | } 127 | if jsonp is not None: 128 | parameters['jsonp'] = jsonp 129 | status, headers = self._query(self.authurl, parameters) 130 | # return the callback call if we want jsonp 131 | if jsonp is not None: 132 | return status 133 | return (int(status), self.auth_status_codes[int(status)]) 134 | 135 | def _query(self, url=None, params=""): 136 | """ method to query a URL with the given parameters 137 | 138 | Parameters: 139 | url -> URL to query 140 | params -> dictionary with parameter values 141 | 142 | Returns: HTTP response code, headers 143 | If an exception occurred, headers fields are None 144 | """ 145 | if url is None: 146 | raise NoUrlError("No URL was provided.") 147 | # return values 148 | headers = {'location': None, 'title': None} 149 | headerdata = urllib.urlencode(params) 150 | try: 151 | request = urllib2.Request(url, headerdata) 152 | response = urllib2.urlopen(request) 153 | # return numeric HTTP status code unless JSONP was requested 154 | if 'jsonp' in params: 155 | status = response.read() 156 | else: 157 | status = response.getcode() 158 | info = response.info() 159 | 160 | try: 161 | headers['location'] = info['Content-Location'] 162 | except KeyError: 163 | pass 164 | try: 165 | headers['title'] = info['X-Instapaper-Title'] 166 | except KeyError: 167 | pass 168 | return (status, headers) 169 | except urllib2.HTTPError as exception: 170 | # handle API not returning JSONP response on 403 171 | if 'jsonp' in params: 172 | return ('%s({"status":%d})' % (params['jsonp'], exception.code), headers) 173 | else: 174 | return (exception.code, headers) 175 | except IOError as exception: 176 | return (exception.code, headers) 177 | 178 | # instapaper specific exceptions 179 | class NoUrlError(Exception): 180 | """ exception to raise if no URL is given. 181 | """ 182 | def __init__(self, arg): 183 | self.arg = arg 184 | def __str__(self): 185 | return repr(self.arg) 186 | 187 | 188 | 189 | if __name__ == '__main__': 190 | import doctest 191 | doctest.testmod() 192 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | ''' 4 | setup.py - distutils script 5 | ''' 6 | import os 7 | import sys 8 | import instapaperlib 9 | 10 | from distutils.core import setup 11 | 12 | def publish(): 13 | """ Publish to PyPi""" 14 | os.system("python setup.py sdist upload") 15 | 16 | if sys.argv[-1] == "publish": 17 | publish() 18 | sys.exit() 19 | 20 | setup(name = "instapaperlib", 21 | version = instapaperlib.__version__, 22 | description = "Python library for the instapaper.com API", 23 | long_description = (open("README.rst").read() + "\n\n" + open("HISTORY.rst").read()), 24 | author = "Daniel Schauenberg", 25 | author_email = "d@unwiredcouch.com", 26 | url = "http://github.com/mrtazz/InstapaperLibrary", 27 | packages = ["instapaperlib"], 28 | scripts=["bin/instapaper.py"], 29 | license = "MIT", 30 | classifiers = ( 31 | "Development Status :: 4 - Beta", 32 | "License :: OSI Approved :: MIT License", 33 | "Programming Language :: Python", 34 | "Programming Language :: Python :: 2.6", 35 | "Programming Language :: Python :: 2.7", 36 | ) 37 | ) 38 | --------------------------------------------------------------------------------