|
|
@@ -0,0 +1,368 @@
|
|
|
+# https://github.com/tapanpandita/pocket/blob/master/pocket.py
|
|
|
+
|
|
|
+import requests
|
|
|
+import json
|
|
|
+from functools import wraps
|
|
|
+
|
|
|
+
|
|
|
+class PocketException(Exception):
|
|
|
+ '''
|
|
|
+ Base class for all pocket exceptions
|
|
|
+ http://getpocket.com/developer/docs/errors
|
|
|
+
|
|
|
+ '''
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class InvalidQueryException(PocketException):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class AuthException(PocketException):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class RateLimitException(PocketException):
|
|
|
+ '''
|
|
|
+ http://getpocket.com/developer/docs/rate-limits
|
|
|
+
|
|
|
+ '''
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class ServerMaintenanceException(PocketException):
|
|
|
+ pass
|
|
|
+
|
|
|
+EXCEPTIONS = {
|
|
|
+ 400: InvalidQueryException,
|
|
|
+ 401: AuthException,
|
|
|
+ 403: RateLimitException,
|
|
|
+ 503: ServerMaintenanceException,
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+def method_wrapper(fn):
|
|
|
+
|
|
|
+ @wraps(fn)
|
|
|
+ def wrapped(self, *args, **kwargs):
|
|
|
+ arg_names = list(fn.__code__.co_varnames)
|
|
|
+ arg_names.remove('self')
|
|
|
+ kwargs.update(dict(zip(arg_names, args)))
|
|
|
+
|
|
|
+ url = self.api_endpoints[fn.__name__]
|
|
|
+ payload = dict([
|
|
|
+ (k, v) for k, v in kwargs.items()
|
|
|
+ if v is not None
|
|
|
+ ])
|
|
|
+ payload.update(self.get_payload())
|
|
|
+
|
|
|
+ return self.make_request(url, payload)
|
|
|
+
|
|
|
+ return wrapped
|
|
|
+
|
|
|
+
|
|
|
+def bulk_wrapper(fn):
|
|
|
+
|
|
|
+ @wraps(fn)
|
|
|
+ def wrapped(self, *args, **kwargs):
|
|
|
+ arg_names = list(fn.__code__.co_varnames)
|
|
|
+ arg_names.remove('self')
|
|
|
+ kwargs.update(dict(zip(arg_names, args)))
|
|
|
+
|
|
|
+ wait = kwargs.get('wait', True)
|
|
|
+ query = dict(
|
|
|
+ [(k, v) for k, v in kwargs.items() if v is not None]
|
|
|
+ )
|
|
|
+ # TODO: Fix this hack
|
|
|
+ query['action'] = 'add' if fn.__name__ == 'bulk_add' else fn.__name__
|
|
|
+
|
|
|
+ if wait:
|
|
|
+ self.add_bulk_query(query)
|
|
|
+ return self
|
|
|
+ else:
|
|
|
+ url = self.api_endpoints['send']
|
|
|
+ payload = {
|
|
|
+ 'actions': [query],
|
|
|
+ }
|
|
|
+ payload.update(self.get_payload())
|
|
|
+ return self.make_request(
|
|
|
+ url,
|
|
|
+ json.dumps(payload),
|
|
|
+ headers={'content-type': 'application/json'},
|
|
|
+ )
|
|
|
+
|
|
|
+ return wrapped
|
|
|
+
|
|
|
+
|
|
|
+class Pocket(object):
|
|
|
+ '''
|
|
|
+ This class implements a basic python wrapper around the pocket api. For a
|
|
|
+ detailed documentation of the methods and what they do please refer the
|
|
|
+ official pocket api documentation at
|
|
|
+ http://getpocket.com/developer/docs/overview
|
|
|
+
|
|
|
+ '''
|
|
|
+ api_endpoints = dict(
|
|
|
+ (method, 'https://getpocket.com/v3/%s' % method)
|
|
|
+ for method in "add,send,get".split(",")
|
|
|
+ )
|
|
|
+
|
|
|
+ statuses = {
|
|
|
+ 200: 'Request was successful',
|
|
|
+ 400: 'Invalid request, please make sure you follow the '
|
|
|
+ 'documentation for proper syntax',
|
|
|
+ 401: 'Problem authenticating the user',
|
|
|
+ 403: 'User was authenticated, but access denied due to lack of '
|
|
|
+ 'permission or rate limiting',
|
|
|
+ 503: 'Pocket\'s sync server is down for scheduled maintenance.',
|
|
|
+ }
|
|
|
+
|
|
|
+ def __init__(self, consumer_key, access_token):
|
|
|
+ self.consumer_key = consumer_key
|
|
|
+ self.access_token = access_token
|
|
|
+ self._bulk_query = []
|
|
|
+
|
|
|
+ self._payload = {
|
|
|
+ 'consumer_key': self.consumer_key,
|
|
|
+ 'access_token': self.access_token,
|
|
|
+ }
|
|
|
+
|
|
|
+ def get_payload(self):
|
|
|
+ return self._payload
|
|
|
+
|
|
|
+ def add_bulk_query(self, query):
|
|
|
+ self._bulk_query.append(query)
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _post_request(url, payload, headers):
|
|
|
+ r = requests.post(url, data=payload, headers=headers)
|
|
|
+ return r
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def _make_request(cls, url, payload, headers=None):
|
|
|
+ r = cls._post_request(url, payload, headers)
|
|
|
+
|
|
|
+ if r.status_code > 399:
|
|
|
+ error_msg = cls.statuses.get(r.status_code)
|
|
|
+ extra_info = r.headers.get('X-Error')
|
|
|
+ raise EXCEPTIONS.get(r.status_code, PocketException)(
|
|
|
+ '%s. %s' % (error_msg, extra_info)
|
|
|
+ )
|
|
|
+
|
|
|
+ return r.json() or r.text, r.headers
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def make_request(cls, url, payload, headers=None):
|
|
|
+ return cls._make_request(url, payload, headers)
|
|
|
+
|
|
|
+ @method_wrapper
|
|
|
+ def add(self, url, title=None, tags=None, tweet_id=None):
|
|
|
+ '''
|
|
|
+ This method allows you to add a page to a user's list.
|
|
|
+ In order to use the /v3/add endpoint, your consumer key must have the
|
|
|
+ "Add" permission.
|
|
|
+ http://getpocket.com/developer/docs/v3/add
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @method_wrapper
|
|
|
+ def get(
|
|
|
+ self, state=None, favorite=None, tag=None, contentType=None,
|
|
|
+ sort=None, detailType=None, search=None, domain=None, since=None,
|
|
|
+ count=None, offset=None
|
|
|
+ ):
|
|
|
+ '''
|
|
|
+ This method allows you to retrieve a user's list. It supports
|
|
|
+ retrieving items changed since a specific time to allow for syncing.
|
|
|
+ http://getpocket.com/developer/docs/v3/retrieve
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @method_wrapper
|
|
|
+ def send(self, actions):
|
|
|
+ '''
|
|
|
+ This method allows you to make changes to a user's list. It supports
|
|
|
+ adding new pages, marking pages as read, changing titles, or updating
|
|
|
+ tags. Multiple changes to items can be made in one request.
|
|
|
+ http://getpocket.com/developer/docs/v3/modify
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def bulk_add(
|
|
|
+ self, item_id, ref_id=None, tags=None, time=None, title=None,
|
|
|
+ url=None, wait=True
|
|
|
+ ):
|
|
|
+ '''
|
|
|
+ Add a new item to the user's list
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_add
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def archive(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Move an item to the user's archive
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_archive
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def readd(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Re-add (unarchive) an item to the user's list
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_readd
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def favorite(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Mark an item as a favorite
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_favorite
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def unfavorite(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Remove an item from the user's favorites
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_unfavorite
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def delete(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Permanently remove an item from the user's account
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_delete
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def tags_add(self, item_id, tags, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Add one or more tags to an item
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_tags_add
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def tags_remove(self, item_id, tags, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Remove one or more tags from an item
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_tags_remove
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def tags_replace(self, item_id, tags, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Replace all of the tags for an item with one or more provided tags
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_tags_replace
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def tags_clear(self, item_id, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Remove all tags from an item.
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_tags_clear
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ @bulk_wrapper
|
|
|
+ def tag_rename(self, item_id, old_tag, new_tag, time=None, wait=True):
|
|
|
+ '''
|
|
|
+ Rename a tag. This affects all items with this tag.
|
|
|
+ http://getpocket.com/developer/docs/v3/modify#action_tag_rename
|
|
|
+
|
|
|
+ '''
|
|
|
+
|
|
|
+ def commit(self):
|
|
|
+ '''
|
|
|
+ This method executes the bulk query, flushes stored queries and
|
|
|
+ returns the response
|
|
|
+
|
|
|
+ '''
|
|
|
+ url = self.api_endpoints['send']
|
|
|
+ payload = {
|
|
|
+ 'actions': self._bulk_query,
|
|
|
+ }
|
|
|
+ payload.update(self._payload)
|
|
|
+ self._bulk_query = []
|
|
|
+
|
|
|
+ return self._make_request(
|
|
|
+ url,
|
|
|
+ json.dumps(payload),
|
|
|
+ headers={'content-type': 'application/json'},
|
|
|
+ )
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_request_token(
|
|
|
+ cls, consumer_key, redirect_uri='http://example.com/', state=None
|
|
|
+ ):
|
|
|
+ '''
|
|
|
+ Returns the request token that can be used to fetch the access token
|
|
|
+
|
|
|
+ '''
|
|
|
+ headers = {
|
|
|
+ 'X-Accept': 'application/json',
|
|
|
+ }
|
|
|
+ url = 'https://getpocket.com/v3/oauth/request'
|
|
|
+ payload = {
|
|
|
+ 'consumer_key': consumer_key,
|
|
|
+ 'redirect_uri': redirect_uri,
|
|
|
+ }
|
|
|
+
|
|
|
+ if state:
|
|
|
+ payload['state'] = state
|
|
|
+
|
|
|
+ return cls._make_request(url, payload, headers)[0]['code']
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_credentials(cls, consumer_key, code):
|
|
|
+ '''
|
|
|
+ Fetches access token from using the request token and consumer key
|
|
|
+
|
|
|
+ '''
|
|
|
+ headers = {
|
|
|
+ 'X-Accept': 'application/json',
|
|
|
+ }
|
|
|
+ url = 'https://getpocket.com/v3/oauth/authorize'
|
|
|
+ payload = {
|
|
|
+ 'consumer_key': consumer_key,
|
|
|
+ 'code': code,
|
|
|
+ }
|
|
|
+
|
|
|
+ return cls._make_request(url, payload, headers)[0]
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_access_token(cls, consumer_key, code):
|
|
|
+ return cls.get_credentials(consumer_key, code)['access_token']
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def get_auth_url(cls, code, redirect_uri='http://example.com'):
|
|
|
+ auth_url = ('https://getpocket.com/auth/authorize'
|
|
|
+ '?request_token=%s&redirect_uri=%s' % (code, redirect_uri))
|
|
|
+ return auth_url
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def auth(
|
|
|
+ cls, consumer_key, redirect_uri='http://example.com/', state=None,
|
|
|
+ ):
|
|
|
+ '''
|
|
|
+ This is a test method for verifying if oauth worked
|
|
|
+ http://getpocket.com/developer/docs/authentication
|
|
|
+
|
|
|
+ '''
|
|
|
+ code = cls.get_request_token(consumer_key, redirect_uri, state)
|
|
|
+
|
|
|
+ auth_url = 'https://getpocket.com/auth/authorize?request_token='\
|
|
|
+ '%s&redirect_uri=%s' % (code, redirect_uri)
|
|
|
+ raw_input(
|
|
|
+ 'Please open %s in your browser to authorize the app and '
|
|
|
+ 'press enter:' % auth_url
|
|
|
+ )
|
|
|
+
|
|
|
+ return cls.get_access_token(consumer_key, code)
|