diff --git a/.gitignore b/.gitignore index 298a1fb..bd3907a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ config.json dist build imgurpython.egg-info +runner.py +examples/auth.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cd12a9d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: python +script: + - python -m compileall +deploy: + provider: pypi + user: imgurops + password: + secure: DBBXzMOm037T4XUmfo0Gu9mAytw2DCYJT8i0KgihKYxS+uslF+dwHf2clBEWDLUE0xkXhqXetq+sNgfshovGKIqZanASYZ/6Zf5ikg10ApgaBidObv2XMYNyuQxL8Gqv9l2tdlWqdUoOJzRBMV2Nh0B3BJ9hG7V5NFMDcfG/qyo= + on: + tags: true + repo: Imgur/imgurpython diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..2ec2cb7 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,85 @@ +Examples +------------ + +## Anonymous Usage without user authorization + +### Print links in a Gallery +Output links from gallery could be a GalleyImage or GalleryAlbum + +#### Default +By default, this will return links to items on the first page (0) with section 'hot' sorted by 'viral', date range is 'day' and show_viral is set to True + +```python +items = client.gallery() +for item in items: + print(item.link) + +``` + +**Output** + + + http://i.imgur.com/dRMIpvS.png + http://imgur.com/a/uxKYS + http://i.imgur.com/jYvaxQ1.jpg + http://i.imgur.com/ZWQJSXp.jpg + http://i.imgur.com/arP5ZwL.jpg + http://i.imgur.com/BejpKnz.jpg + http://i.imgur.com/4FJF0Vt.jpg + http://i.imgur.com/MZSBjTP.jpg + http://i.imgur.com/EbeztS2.jpg + http://i.imgur.com/DuwnhKO.jpg + ... + +#### With Specific Parameters +In this example, return links to items on the fourth page (3) with section 'top' sorted by 'time', date range is 'week' and show_viral is set to False + +```python +items = client.gallery(section='top', sort='time', page=3, window='week', show_viral=False) +for item in items: + print(item.link) + +``` + +**Output** + + + http://i.imgur.com/ls7OPx7.gif + http://i.imgur.com/FI7yPWo.png + http://imgur.com/a/8QKvH + http://i.imgur.com/h4IDMyK.gif + http://i.imgur.com/t4NpfCT.jpg + http://i.imgur.com/kyCP6q9.jpg + http://imgur.com/a/CU11w + http://i.imgur.com/q4rJFbR.jpg + http://i.imgur.com/gWaNC22.jpg + http://i.imgur.com/YEQomCd.gif + ... + +#### Getting the authenticated user's albums + +For endpoints that require usernames, once a user is authenticated we can use the keyword 'me' to pull their information. Here's how to pull one of their albums: + +```python +for album in client.get_account_albums('me'): +album_title = album.title if album.title else 'Untitled' +print('Album: {0} ({1})'.format(album_title, album.id)) + +for image in client.get_album_images(album.id): + image_title = image.title if image.title else 'Untitled' + print('\t{0}: {1}'.format(image_title, image.link)) + +# Save some API credits by not getting all albums +break +``` + +***Output*** + + + Album: Qittens! (LPNnY) + Untitled: http://i.imgur.com/b9rL7ew.jpg + Untitled: http://i.imgur.com/Ymg3obW.jpg + Untitled: http://i.imgur.com/kMzbu0S.jpg + ... + + diff --git a/README.md b/README.md index 20dab4e..c89b846 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# The imgurpython project is no longer supported. + imgurpython =========== @@ -22,9 +24,7 @@ Our developer documentation can be found [here](https://api.imgur.com/). Community --------- -The best way to reach out to Imgur for API support would be our -[Google Group](https://groups.google.com/forum/#!forum/imgur), [Twitter](https://twitter.com/imgurapi), or via - api@imgur.com. +The best way to reach out to Imgur for API support is emailing us at api@imgur.com. Installation ------------ @@ -49,7 +49,7 @@ client = ImgurClient(client_id, client_secret) # Example request items = client.gallery() -for item in items +for item in items: print(item.link) ``` @@ -93,6 +93,8 @@ Error types * ImgurClientError - General error handler, access message and status code via ```python +from imgurpython.helpers.error import ImgurClientError + try ... except ImgurClientError as e @@ -102,6 +104,22 @@ except ImgurClientError as e * ImgurClientRateLimitError - Rate limit error +### Credits + +To view client and user credit information, use the `credits` attribute of `ImgurClient`. +`credits` holds a dictionary with the following keys: +* UserLimit +* UserRemaining +* UserReset +* ClientLimit +* ClientRemaining + +For more information about rate-limiting, please see the note in our [docs](http://api.imgur.com/#limits)! + +Examples +------------ +Examples can be found [here](EXAMPLES.md) + ## ImgurClient Functions ### Account @@ -138,7 +156,6 @@ except ImgurClientError as e ### Comment * `get_comment(comment_id)` * `delete_comment(comment_id)` -* `create_album(fields)` * `get_comment_replies(comment_id)` * `post_comment_reply(comment_id, image_id, comment)` * `comment_vote(comment_id, vote='up')` @@ -206,3 +223,14 @@ except ImgurClientError as e ### Memegen * `default_memes()` + +Imgur entry points +================== +| entry point | content | +|-------------------------------------|--------------------------------| +| imgur.com/{image_id} | image | +| imgur.com/{image_id}.extension | direct link to image (no html) | +| imgur.com/a/{album_id} | album | +| imgur.com/a/{album_id}#{image_id} | single image from an album | +| imgur.com/gallery/{gallery_post_id} | gallery | + diff --git a/examples/auth.ini b/examples/auth.ini new file mode 100644 index 0000000..8eb90dc --- /dev/null +++ b/examples/auth.ini @@ -0,0 +1,4 @@ +[credentials] +client_id=YOUR ID HERE +client_secret=YOUR SECRET HERE +refresh_token= \ No newline at end of file diff --git a/examples/auth.py b/examples/auth.py new file mode 100755 index 0000000..2f0b1f4 --- /dev/null +++ b/examples/auth.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +''' + Here's how you go about authenticating yourself! The important thing to + note here is that this script will be used in the other examples so + set up a test user with API credentials and set them up in auth.ini. +''' + +from imgurpython import ImgurClient +from helpers import get_input, get_config + +def authenticate(): + # Get client ID and secret from auth.ini + config = get_config() + config.read('auth.ini') + client_id = config.get('credentials', 'client_id') + client_secret = config.get('credentials', 'client_secret') + + client = ImgurClient(client_id, client_secret) + + # Authorization flow, pin example (see docs for other auth types) + authorization_url = client.get_auth_url('pin') + + print("Go to the following URL: {0}".format(authorization_url)) + + # Read in the pin, handle Python 2 or 3 here. + pin = get_input("Enter pin code: ") + + # ... redirect user to `authorization_url`, obtain pin (or code or token) ... + credentials = client.authorize(pin, 'pin') + client.set_user_auth(credentials['access_token'], credentials['refresh_token']) + + print("Authentication successful! Here are the details:") + print(" Access token: {0}".format(credentials['access_token'])) + print(" Refresh token: {0}".format(credentials['refresh_token'])) + + return client + +# If you want to run this as a standalone script, so be it! +if __name__ == "__main__": + authenticate() \ No newline at end of file diff --git a/examples/helpers.py b/examples/helpers.py new file mode 100644 index 0000000..7c9a0e8 --- /dev/null +++ b/examples/helpers.py @@ -0,0 +1,20 @@ +''' + These functions have nothing to do with the API, they just help ease + issues between Python 2 and 3 +''' + +def get_input(string): + ''' Get input from console regardless of python 2 or 3 ''' + try: + return raw_input(string) + except: + return input(string) + +def get_config(): + ''' Create a config parser for reading INI files ''' + try: + import ConfigParser + return ConfigParser.ConfigParser() + except: + import configparser + return configparser.ConfigParser() \ No newline at end of file diff --git a/examples/upload.py b/examples/upload.py new file mode 100755 index 0000000..3014d6f --- /dev/null +++ b/examples/upload.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +''' + Here's how you upload an image. For this example, put the cutest picture + of a kitten you can find in this script's folder and name it 'Kitten.jpg' + + For more details about images and the API see here: + https://api.imgur.com/endpoints/image +''' + +# Pull authentication from the auth example (see auth.py) +from auth import authenticate + +from datetime import datetime + +album = None # You can also enter an album ID here +image_path = 'Kitten.jpg' + +def upload_kitten(client): + ''' + Upload a picture of a kitten. We don't ship one, so get creative! + ''' + + # Here's the metadata for the upload. All of these are optional, including + # this config dict itself. + config = { + 'album': album, + 'name': 'Catastrophe!', + 'title': 'Catastrophe!', + 'description': 'Cute kitten being cute on {0}'.format(datetime.now()) + } + + print("Uploading image... ") + image = client.upload_from_path(image_path, config=config, anon=False) + print("Done") + print() + + return image + + +# If you want to run this as a standalone script +if __name__ == "__main__": + client = authenticate() + image = upload_kitten(client) + + print("Image was posted! Go check your images you sexy beast!") + print("You can find it here: {0}".format(image['link'])) \ No newline at end of file diff --git a/imgurpython/client.py b/imgurpython/client.py index e08923e..9c41ea6 100644 --- a/imgurpython/client.py +++ b/imgurpython/client.py @@ -17,9 +17,10 @@ from .imgur.models.account_settings import AccountSettings API_URL = 'https://api.imgur.com/' +MASHAPE_URL = 'https://imgur-apiv3.p.mashape.com/' -class AuthWrapper: +class AuthWrapper(object): def __init__(self, access_token, refresh_token, client_id, client_secret): self.current_access_token = access_token @@ -55,7 +56,7 @@ def refresh(self): self.current_access_token = response_data['access_token'] -class ImgurClient: +class ImgurClient(object): allowed_album_fields = { 'ids', 'title', 'description', 'privacy', 'layout', 'cover' } @@ -72,20 +73,26 @@ class ImgurClient: 'album', 'name', 'title', 'description' } - def __init__(self, client_id, client_secret, access_token=None, refresh_token=None): + def __init__(self, client_id, client_secret, access_token=None, refresh_token=None, mashape_key=None): self.client_id = client_id self.client_secret = client_secret self.auth = None + self.mashape_key = mashape_key if refresh_token is not None: self.auth = AuthWrapper(access_token, refresh_token, client_id, client_secret) + self.credits = self.get_credits() + def set_user_auth(self, access_token, refresh_token): self.auth = AuthWrapper(access_token, refresh_token, self.client_id, self.client_secret) def get_client_id(self): return self.client_id + def get_credits(self): + return self.make_request('GET', 'credits', None, True) + def get_auth_url(self, response_type='pin'): return '%soauth2/authorize?client_id=%s&response_type=%s' % (API_URL, self.client_id, response_type) @@ -94,24 +101,31 @@ def authorize(self, response, grant_type='pin'): 'client_id': self.client_id, 'client_secret': self.client_secret, 'grant_type': grant_type, - grant_type: response + 'code' if grant_type == 'authorization_code' else grant_type: response }, True) def prepare_headers(self, force_anon=False): + headers = {} if force_anon or self.auth is None: if self.client_id is None: raise ImgurClientError('Client credentials not found!') else: - return {'Authorization': 'Client-ID %s' % self.get_client_id()} + headers['Authorization'] = 'Client-ID %s' % self.get_client_id() else: - return {'Authorization': 'Bearer %s' % self.auth.get_current_access_token()} + headers['Authorization'] = 'Bearer %s' % self.auth.get_current_access_token() + + if self.mashape_key is not None: + headers['X-Mashape-Key'] = self.mashape_key + + return headers + def make_request(self, method, route, data=None, force_anon=False): method = method.lower() method_to_call = getattr(requests, method) header = self.prepare_headers(force_anon) - url = API_URL + ('3/%s' % route if 'oauth2' not in route else route) + url = (MASHAPE_URL if self.mashape_key is not None else API_URL) + ('3/%s' % route if 'oauth2' not in route else route) if method in ('delete', 'get'): response = method_to_call(url, headers=header, params=data, data=data) @@ -126,6 +140,14 @@ def make_request(self, method, route, data=None, force_anon=False): else: response = method_to_call(url, headers=header, data=data) + self.credits = { + 'UserLimit': response.headers.get('X-RateLimit-UserLimit'), + 'UserRemaining': response.headers.get('X-RateLimit-UserRemaining'), + 'UserReset': response.headers.get('X-RateLimit-UserReset'), + 'ClientLimit': response.headers.get('X-RateLimit-ClientLimit'), + 'ClientRemaining': response.headers.get('X-RateLimit-ClientRemaining') + } + # Rate-limit check if response.status_code == 429: raise ImgurClientRateLimitError() @@ -162,15 +184,15 @@ def get_account(self, username): account_data['pro_expiration'], ) - def get_gallery_favorites(self, username): + def get_gallery_favorites(self, username, page=0): self.validate_user_context(username) - gallery_favorites = self.make_request('GET', 'account/%s/gallery_favorites' % username) + gallery_favorites = self.make_request('GET', 'account/%s/gallery_favorites/%d' % (username, page)) return build_gallery_images_and_albums(gallery_favorites) - def get_account_favorites(self, username): + def get_account_favorites(self, username, page=0): self.validate_user_context(username) - favorites = self.make_request('GET', 'account/%s/favorites' % username) + favorites = self.make_request('GET', 'account/%s/favorites/%d' % (username, page)) return build_gallery_images_and_albums(favorites) @@ -558,22 +580,26 @@ def get_image(self, image_id): return Image(image) def upload_from_path(self, path, config=None, anon=True): - if not config: config = dict() + with open(path, 'rb') as fd: + self.upload(fd, config, anon) + + def upload(self, fd, config=None, anon=True): + if not config: + config = dict() - fd = open(path, 'rb') contents = fd.read() b64 = base64.b64encode(contents) - data = { 'image': b64, 'type': 'base64', } - data.update({meta: config[meta] for meta in set(self.allowed_image_fields).intersection(config.keys())}) + return self.make_request('POST', 'upload', data, anon) def upload_from_url(self, url, config=None, anon=True): - if not config: config = dict() + if not config: + config = dict() data = { 'image': url, diff --git a/imgurpython/helpers/format.py b/imgurpython/helpers/format.py index b36536c..70cb39f 100644 --- a/imgurpython/helpers/format.py +++ b/imgurpython/helpers/format.py @@ -71,11 +71,11 @@ def build_notifications(response): def build_notification(item): notification = Notification( - item['id'], - item['account_id'], - item['viewed'], - item['content'] - ) + item['id'], + item['account_id'], + item['viewed'], + item['content'] + ) if 'comment' in notification.content: notification.content = format_comment_tree(item['content']) diff --git a/imgurpython/imgur/models/account.py b/imgurpython/imgur/models/account.py index 4f1f9a7..01a798a 100644 --- a/imgurpython/imgur/models/account.py +++ b/imgurpython/imgur/models/account.py @@ -1,9 +1,9 @@ -class Account: +class Account(object): - def __init__(self, id, url, bio, reputation, created, pro_expiration): - self.id = id + def __init__(self, account_id, url, bio, reputation, created, pro_expiration): + self.id = account_id self.url = url self.bio = bio self.reputation = reputation - self.created = created, + self.created = created self.pro_expiration = pro_expiration diff --git a/imgurpython/imgur/models/account_settings.py b/imgurpython/imgur/models/account_settings.py index 93d2da5..045aaf2 100644 --- a/imgurpython/imgur/models/account_settings.py +++ b/imgurpython/imgur/models/account_settings.py @@ -1,4 +1,4 @@ -class AccountSettings: +class AccountSettings(object): def __init__(self, email, high_quality, public_images, album_privacy, pro_expiration, accepted_gallery_terms, active_emails, messaging_enabled, blocked_users): diff --git a/imgurpython/imgur/models/album.py b/imgurpython/imgur/models/album.py index 6c49507..414acc2 100644 --- a/imgurpython/imgur/models/album.py +++ b/imgurpython/imgur/models/album.py @@ -1,4 +1,4 @@ -class Album: +class Album(object): # See documentation at https://api.imgur.com/ for available fields def __init__(self, *initial_data, **kwargs): diff --git a/imgurpython/imgur/models/comment.py b/imgurpython/imgur/models/comment.py index 49e343c..29e4a9f 100644 --- a/imgurpython/imgur/models/comment.py +++ b/imgurpython/imgur/models/comment.py @@ -1,4 +1,4 @@ -class Comment: +class Comment(object): # See documentation at https://api.imgur.com/ for available fields def __init__(self, *initial_data, **kwargs): diff --git a/imgurpython/imgur/models/conversation.py b/imgurpython/imgur/models/conversation.py index 9d14554..335196c 100644 --- a/imgurpython/imgur/models/conversation.py +++ b/imgurpython/imgur/models/conversation.py @@ -1,10 +1,10 @@ from .message import Message -class Conversation: +class Conversation(object): - def __init__(self, id, last_message_preview, datetime, with_account_id, with_account, message_count, messages=None, + def __init__(self, conversation_id, last_message_preview, datetime, with_account_id, with_account, message_count, messages=None, done=None, page=None): - self.id = id + self.id = conversation_id self.last_message_preview = last_message_preview self.datetime = datetime self.with_account_id = with_account_id diff --git a/imgurpython/imgur/models/custom_gallery.py b/imgurpython/imgur/models/custom_gallery.py index 2752764..912ba6d 100644 --- a/imgurpython/imgur/models/custom_gallery.py +++ b/imgurpython/imgur/models/custom_gallery.py @@ -2,10 +2,10 @@ from .gallery_image import GalleryImage -class CustomGallery: +class CustomGallery(object): - def __init__(self, id, name, datetime, account_url, link, tags, item_count=None, items=None): - self.id = id + def __init__(self, custom_gallery_id, name, datetime, account_url, link, tags, item_count=None, items=None): + self.id = custom_gallery_id self.name = name self.datetime = datetime self.account_url = account_url diff --git a/imgurpython/imgur/models/gallery_album.py b/imgurpython/imgur/models/gallery_album.py index e947410..1622c99 100644 --- a/imgurpython/imgur/models/gallery_album.py +++ b/imgurpython/imgur/models/gallery_album.py @@ -1,4 +1,4 @@ -class GalleryAlbum: +class GalleryAlbum(object): # See documentation at https://api.imgur.com/ for available fields def __init__(self, *initial_data, **kwargs): diff --git a/imgurpython/imgur/models/gallery_image.py b/imgurpython/imgur/models/gallery_image.py index 73cc7ff..88faf19 100644 --- a/imgurpython/imgur/models/gallery_image.py +++ b/imgurpython/imgur/models/gallery_image.py @@ -1,4 +1,4 @@ -class GalleryImage: +class GalleryImage(object): # See documentation at https://api.imgur.com/ for available fields def __init__(self, *initial_data, **kwargs): diff --git a/imgurpython/imgur/models/image.py b/imgurpython/imgur/models/image.py index bd02b0c..18f257e 100644 --- a/imgurpython/imgur/models/image.py +++ b/imgurpython/imgur/models/image.py @@ -1,4 +1,4 @@ -class Image: +class Image(object): # See documentation at https://api.imgur.com/ for available fields def __init__(self, *initial_data, **kwargs): diff --git a/imgurpython/imgur/models/message.py b/imgurpython/imgur/models/message.py index 185a0f4..0f98f5e 100644 --- a/imgurpython/imgur/models/message.py +++ b/imgurpython/imgur/models/message.py @@ -1,7 +1,7 @@ -class Message: +class Message(object): - def __init__(self, id, from_user, account_id, sender_id, body, conversation_id, datetime): - self.id = id + def __init__(self, message_id, from_user, account_id, sender_id, body, conversation_id, datetime): + self.id = message_id self.from_user = from_user self.account_id = account_id self.sender_id = sender_id diff --git a/imgurpython/imgur/models/notification.py b/imgurpython/imgur/models/notification.py index 3da26b6..7966953 100644 --- a/imgurpython/imgur/models/notification.py +++ b/imgurpython/imgur/models/notification.py @@ -1,7 +1,7 @@ -class Notification: +class Notification(object): - def __init__(self, id, account_id, viewed, content): - self.id = id + def __init__(self, notification_id, account_id, viewed, content): + self.id = notification_id self.account_id = account_id self.viewed = viewed self.content = content diff --git a/imgurpython/imgur/models/tag.py b/imgurpython/imgur/models/tag.py index 60a02ce..d9f8547 100644 --- a/imgurpython/imgur/models/tag.py +++ b/imgurpython/imgur/models/tag.py @@ -2,7 +2,7 @@ from .gallery_image import GalleryImage -class Tag: +class Tag(object): def __init__(self, name, followers, total_items, following, items): self.name = name diff --git a/imgurpython/imgur/models/tag_vote.py b/imgurpython/imgur/models/tag_vote.py index 6ac444f..eb1a995 100644 --- a/imgurpython/imgur/models/tag_vote.py +++ b/imgurpython/imgur/models/tag_vote.py @@ -1,4 +1,4 @@ -class TagVote: +class TagVote(object): def __init__(self, ups, downs, name, author): self.ups = ups diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/setup.py b/setup.py index 1c84d09..300e786 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # http://packaging.python.org/en/latest/tutorial.html#version - version='1.1.1', + version='1.1.7', description='Official Imgur python library with OAuth2 and samples', long_description='',