513 lines
18 KiB
Python
Executable File
513 lines
18 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import urllib2
|
|
import json
|
|
from base64 import b64encode
|
|
from getpass import getpass
|
|
from urllib import urlencode
|
|
from cookielib import CookieJar
|
|
from os.path import expanduser, exists
|
|
from pprint import pprint
|
|
from collections import OrderedDict
|
|
from argparse import ArgumentParser
|
|
|
|
USERNAME = "rbotara"
|
|
PASSWORD = "cigareta"
|
|
APIKEY_AUTH = "acfe292a432ded08e576f019fb4e34d104df16436"
|
|
APIKEY_ANON = "641ff5860e0a7e26e97e48d8ee80d162"
|
|
DEBUG = False
|
|
|
|
|
|
class ImgurError(Exception):
|
|
pass
|
|
|
|
|
|
class Imgur(object):
|
|
"""
|
|
This class can be used to upload images and manage an account on imgur.com
|
|
"""
|
|
|
|
base = "api.imgur.com"
|
|
connected = False
|
|
urls = {
|
|
# Anon
|
|
"upload": "/2/upload",
|
|
"image": "/2/image/%s",
|
|
"delete": "/2/delete/%s",
|
|
# Auth
|
|
"signin": "/2/signin",
|
|
"account": "/2/account",
|
|
"account_images": "/2/account/images",
|
|
"account_image": "/2/account/images/%s",
|
|
"account_images_count": "/2/account/images_count",
|
|
"albums": "/2/account/albums",
|
|
"album": "/2/account/albums/%s",
|
|
"albums_count": "/2/account/albums_count",
|
|
"albums_order": "/2/account/albums_order",
|
|
"album_order": "/2/account/albums_order/%s",
|
|
# Other
|
|
"stats": "/2/stats",
|
|
"credits": "/2/credits",
|
|
}
|
|
|
|
def __init__(self, apikey_auth=None, apikey_anon=None, print_results=True):
|
|
self.apikey_auth = apikey_auth
|
|
self.apikey_anon = apikey_anon
|
|
self.print_results = print_results
|
|
self.cj = CookieJar()
|
|
|
|
def _handle_response(fun):
|
|
def new_f(self, *args, **kwargs):
|
|
try:
|
|
resp = fun(self, *args, **kwargs)
|
|
except urllib2.HTTPError, err:
|
|
raise ImgurError(err)
|
|
else:
|
|
if self.print_results:
|
|
content = resp.read()
|
|
try:
|
|
#pprint(json.loads(content))
|
|
json_print(json.loads(content))
|
|
except:
|
|
pprint(content)
|
|
return resp
|
|
return new_f
|
|
|
|
def connection_required(fun):
|
|
""" A decorator that checks if the user is authenticated """
|
|
def new_f(self, *args, **kwargs):
|
|
if not self.connected:
|
|
raise ImgurError("You must be connected to perform this"
|
|
"operation")
|
|
return fun(self, *args, **kwargs)
|
|
return new_f
|
|
|
|
def connect(self, username=None, password=None):
|
|
"""
|
|
A cookie is created for the authentication
|
|
"""
|
|
response = self.request("POST", "signin",
|
|
username=username, password=password)
|
|
debug("Connected.")
|
|
self.connected = True
|
|
|
|
def _json(self, data):
|
|
try:
|
|
return json.loads(data)
|
|
except ValueError:
|
|
raise ImgurError("Invalid data")
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _account(self):
|
|
return self.request("GET", "account")
|
|
|
|
@_handle_response
|
|
def _upload(self, filename, url, name, title, caption):
|
|
if filename:
|
|
with open(expanduser(filename), "rb") as f:
|
|
img = b64encode(f.read())
|
|
t = "base64"
|
|
else:
|
|
img = url
|
|
t = "url"
|
|
url = "account_images" if self.connected else "upload"
|
|
return self.request("POST", url, image=img,
|
|
type=t, name=name, title=title, caption=caption)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _imgedit(self, hash, title="", caption=""):
|
|
url = self.urls["account_image"] % hash
|
|
return self.request("POST", url=url, title=title, caption=caption)
|
|
|
|
@_handle_response
|
|
def _imginfos(self, hash):
|
|
c = self.connected
|
|
url = self.urls["account_image" if c else "image"] % hash
|
|
return self.request("GET", url=url)
|
|
|
|
@_handle_response
|
|
def _imgdelete(self, hash):
|
|
c = self.connected
|
|
url = self.urls["account_image" if c else "delete"] % hash
|
|
return self.request("DELETE", url=url)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _imgcount(self):
|
|
return self.request("GET", "account_images_count")
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _list(self):
|
|
return self.request("GET", "account_images")
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albums(self, title="", description="", privacy="", layout="",
|
|
count="", page="", create=True):
|
|
if create:
|
|
return self.request("POST", "albums", title=title,
|
|
description=description, privacy=privacy, layout=layout)
|
|
else:
|
|
return self.request("GET", "albums", count=count, page=page)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmlist(self, album_id):
|
|
url = self.urls["album"] % album_id
|
|
return self.request("GET", url=url)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmdelete(self, album_id):
|
|
url = self.urls["album"] % album_id
|
|
return self.request("DELETE", url=url)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmedit(self, album_id, title="", description="", cover="",
|
|
privacy="", layout="", images="", add_images="", del_images=""):
|
|
url = self.urls["album"] % album_id
|
|
datas = {}
|
|
for k in ("url", "title", "description", "cover", "privacy", "layout",
|
|
"images", "add_images", "del_images"):
|
|
datas[k] = locals()[k]
|
|
return self.request("POST", **datas)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmcount(self):
|
|
return self.request("GET", "albums_count")
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmord(self, album_id, hashes):
|
|
url = self.urls["album_order"] % album_id
|
|
return self.request("POST", url=url, hashes=hashes)
|
|
|
|
@_handle_response
|
|
@connection_required
|
|
def _albmords(self, album_ids):
|
|
return self.request("POST", "albums_order", album_ids=album_ids)
|
|
|
|
@_handle_response
|
|
def _limits(self):
|
|
return self.request("GET", "credits")
|
|
|
|
def _stats(self, view):
|
|
return self.request("GET", "stats", view=view)
|
|
|
|
def request(self, method, key="", url="", headers=None, **params):
|
|
# Send an http request.
|
|
# if key is set, use the url from self.urls[key]
|
|
# else use 'url'
|
|
if not headers:
|
|
headers = {}
|
|
url = "http://" + self.base + (self.urls[key] if key else url) + ".json"
|
|
params["key"] = self.apikey_auth if self.connected else self.apikey_anon
|
|
data = None
|
|
if method in ("POST", "DELETE"):
|
|
for k in params.keys():
|
|
if not params[k]:
|
|
del params[k]
|
|
data = urlencode(params)
|
|
elif method in ("GET", "DELETE"):
|
|
url = "%s?%s" % (url, urlencode(params))
|
|
|
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
|
|
urllib2.install_opener(opener)
|
|
debug(url, params)
|
|
req = urllib2.Request(url, data, headers)
|
|
if not method in ("POST", "GET"):
|
|
req.get_method = lambda: method
|
|
return urllib2.urlopen(req)
|
|
|
|
def _get_json(self, fun_name, *args, **kwargs):
|
|
return self._json(getattr(self, fun_name)(*args, **kwargs).read())
|
|
|
|
def _infos(self, img):
|
|
""" Print the infos of an image """
|
|
return dict_print(OrderedDict(img["image"].items() +
|
|
img["links"].items()))
|
|
|
|
def _liststr(self, datas):
|
|
""" Converts a list into "(foo,bar,baz)" """
|
|
if isinstance(datas, (list, tuple)):
|
|
datas = "(%s)" % (",".join(str(e) for e in datas))
|
|
elif not datas.startswith("("):
|
|
datas = "(%s)" % datas
|
|
return datas
|
|
|
|
def upload_image(self, image="", name="", title="", caption="", hashes=False):
|
|
"""
|
|
Upload an image
|
|
'image' can be a path or an url
|
|
if hashes is True, return only the hashes
|
|
"""
|
|
url, file = "", ""
|
|
if not exists(image):
|
|
url = image
|
|
else:
|
|
file = image
|
|
datas = self._get_json("_upload", file, url, name, title, caption)
|
|
infos = datas["images" if self.connected else "upload"]
|
|
if hashes:
|
|
return "Hash: %s\nDelete: %s" % (infos["image"]["hash"],
|
|
infos["image"]["deletehash"])
|
|
else:
|
|
return self._infos(infos)
|
|
|
|
def account(self):
|
|
""" Get account infos """
|
|
datas = self._get_json("_account")
|
|
return dict_print(datas["account"])
|
|
|
|
def list_images(self):
|
|
""" List all images in account """
|
|
datas = self._get_json("_list")
|
|
imgs = (self._infos(img) for img in datas["images"])
|
|
return "\n *** \n".join(imgs)
|
|
|
|
def infos_image(self, hash):
|
|
""" Retrieve informations from an image """
|
|
datas = self._get_json("_imginfos", hash)
|
|
return self._infos(datas["images" if self.connected else "image"])
|
|
|
|
def delete_image(self, hash):
|
|
""" Delete an image """
|
|
datas = self._get_json("_imgdelete", hash)
|
|
return datas["images"]["message"] if self.connected else ""
|
|
|
|
def edit_image(self, hash, new_title="", new_caption=""):
|
|
""" Edit an image """
|
|
datas = self._get_json("_imgedit", hash, new_title, new_caption)
|
|
return self._infos(datas["images"])
|
|
|
|
def count_images(self):
|
|
""" Count all images in the account """
|
|
return self._get_json("_imgcount")["images_count"]["count"]
|
|
|
|
def list_albums(self, count=30, page=1):
|
|
"""
|
|
List all albums in the account
|
|
"""
|
|
datas = self._get_json("_albums", count=count, page=page,
|
|
create=False)
|
|
return "\n *** \n".join(dict_print(alb) for alb in datas["albums"])
|
|
|
|
def create_album(self, title="", description="", privacy="", layout=""):
|
|
"""
|
|
All values are optional.
|
|
privacy: public/hidden/secret
|
|
layout: blog/horizontal/vertical/grid
|
|
"""
|
|
datas = self._get_json("_albums", title, description, privacy, layout)
|
|
return dict_print(datas["albums"])
|
|
|
|
def list_album(self, album_id):
|
|
""" List all images in an album """
|
|
datas = self._get_json("_albmlist", album_id)
|
|
imgs = (self._infos(img) for img in datas["albums"])
|
|
return "\n *** \n".join(imgs)
|
|
|
|
def delete_album(self, album_id):
|
|
""" Delete an album """
|
|
datas = self._get_json("_albmdelete", album_id)
|
|
return datas["albums"]["message"]
|
|
|
|
def edit_album(self, album_id, title="", description="", cover="",
|
|
privacy="", layout="", images="", add_images="", del_images=""):
|
|
"""
|
|
Edit an album
|
|
privacy: public/hidden/secret
|
|
layout: blog/horizontal/vertical/grid
|
|
cover: a hash of an image in the account
|
|
'images' replaces all images in the album
|
|
|
|
images, add_images and del_images must be a list of hashes,
|
|
or a string formatted like (hash1,hash2,...)
|
|
"""
|
|
for k in ("images", "add_images", "del_images"):
|
|
locals()[k] = self._liststr(locals()[k])
|
|
datas = self._get_json("_albmedit", album_id, title, description,
|
|
cover, privacy, layout, images, add_images, del_images)
|
|
imgs = (self._infos(img) for img in datas["albums"])
|
|
return "\n *** \n".join(imgs)
|
|
|
|
def count_albums(self):
|
|
""" Return the number of albums. """
|
|
datas = self._get_json("_albmcount")
|
|
return datas["albums_count"]["count"]
|
|
|
|
def order_album(self, album_id, hashes):
|
|
hashes = self._liststr(hashes)
|
|
datas = self._get_json("_albmord", album_id, hashes)
|
|
return "\n *** \n".join(dict_print(alb) for alb in datas["albums_order"])
|
|
|
|
def order_albums(self, album_ids):
|
|
album_ids = self._liststr(album_ids)
|
|
datas = self._get_json("_albmords", album_ids)
|
|
imgs = (self._infos(img) for img in datas["albums_order"])
|
|
return "\n *** \n".join(imgs)
|
|
|
|
def stats(self, view="month"):
|
|
"""
|
|
View imgur's stats
|
|
view: today|week|month
|
|
"""
|
|
return dict_print(self._get_json("_stats", view)["stats"])
|
|
|
|
|
|
def debug(*datas):
|
|
if DEBUG:
|
|
print "DEBUG:", ", ".join(str(d) for d in datas)
|
|
|
|
def json_print(data, tab=0):
|
|
if isinstance(data, (list, tuple)):
|
|
print
|
|
for elem in data:
|
|
json_print(elem, tab)
|
|
print
|
|
elif isinstance(data, dict):
|
|
print
|
|
for e, d in data.items():
|
|
print "\t"*tab, e,
|
|
json_print(d, tab+1)
|
|
else:
|
|
print "\t"*tab, data
|
|
|
|
def dict_print(data):
|
|
def align(l, r, w=80):
|
|
return "%s: %s%s" % (l, (w-len(l))* " ", r)
|
|
w = max(len(e) for e in data.keys())
|
|
return "\n".join(align(k, v, w) for k, v in data.items())
|
|
|
|
def main():
|
|
i = Imgur(APIKEY_AUTH, APIKEY_ANON, False)
|
|
|
|
# Parser definition
|
|
parser = ArgumentParser(description="A command-line utility for imgur.com")
|
|
parser.add_argument("--user")
|
|
parser.add_argument("--password")
|
|
parser.add_argument("--ask-pass", action="store_true",
|
|
help="Prompt for a password, so that it will never be displayed.")
|
|
parser.add_argument("--anon", action="store_true", default=False,
|
|
help="Do not authenticate.")
|
|
sparsers = parser.add_subparsers(metavar="ACTION", dest='sp_name',
|
|
help="Use '%(prog)s action -h' for more help")
|
|
|
|
up_parser = sparsers.add_parser("upload", help="Upload a file or an url")
|
|
up_parser.add_argument("image", help="A path or an url")
|
|
up_parser.add_argument("-t", "--title")
|
|
up_parser.add_argument("-n", "--name")
|
|
up_parser.add_argument("-c", "--caption")
|
|
up_parser.add_argument("-s", "--hashes", action="store_true",
|
|
help="Only print the hashes instead of all the informations")
|
|
|
|
imgd_parser = sparsers.add_parser("delete", help="Delete an image")
|
|
imgd_parser.add_argument("hash")
|
|
|
|
imgi_parser = sparsers.add_parser("infos", help="Get infos on the image")
|
|
imgi_parser.add_argument("hash")
|
|
|
|
imge_parser = sparsers.add_parser("edit", help="Edit an image")
|
|
imge_parser.add_argument("hash")
|
|
imge_parser.add_argument("-t", "--title")
|
|
imge_parser.add_argument("-c", "--caption")
|
|
|
|
list_parser = sparsers.add_parser("list", help="List all your images")
|
|
|
|
acc_parser = sparsers.add_parser("account", help="Account infos")
|
|
|
|
albc_parser = sparsers.add_parser("create-album", help="Create a new album")
|
|
albc_parser.add_argument("-t", "--title")
|
|
albc_parser.add_argument("-d", "--description")
|
|
albc_parser.add_argument("-p", "--privacy", choices=["public", "hidden",
|
|
"secret"])
|
|
albc_parser.add_argument("-l", "--layout", choices=["blog", "horizontal",
|
|
"vertical", "grid"])
|
|
|
|
albe_parser = sparsers.add_parser("edit-album", help="Edit an album")
|
|
albe_parser.add_argument("album_id")
|
|
albe_parser.add_argument("-t", "--title")
|
|
albe_parser.add_argument("-d", "--description")
|
|
albe_parser.add_argument("-p", "--privacy", choices=["public", "hidden",
|
|
"secret"])
|
|
albe_parser.add_argument("-l", "--layout", choices=["blog", "horizontal",
|
|
"vertical", "grid"])
|
|
albe_parser.add_argument("-c", "--cover", help="The hash of an image to use "
|
|
"as cover for the album", metavar="hash")
|
|
albe_parser.add_argument("-i", "--images", metavar="hash1,hash2,..,hashN",
|
|
help="Replace all images in an album.")
|
|
albe_parser.add_argument("-a", "--add", metavar="hash1,hash2,..,hashN",
|
|
help="Add images to the album.")
|
|
albe_parser.add_argument("-r", "--delete", metavar="hash1,hash2,..,hashN",
|
|
help="Delete images from the album.")
|
|
|
|
albl_parser = sparsers.add_parser("list-albums",
|
|
help="List all albums.")
|
|
albl_parser.add_argument("-c", "--count", help="The number of albums to return")
|
|
albl_parser.add_argument("-p", "--page", help="The pages of albums to return")
|
|
|
|
albs_parser = sparsers.add_parser("list-album",
|
|
help="List all images in an album")
|
|
albs_parser.add_argument("album_id")
|
|
|
|
albco_parser = sparsers.add_parser("count-albums",
|
|
help="Get the number of albums")
|
|
|
|
albd_parser = sparsers.add_parser("delete-album", help="Delete an album")
|
|
albd_parser.add_argument("album_id")
|
|
|
|
albos_parser = sparsers.add_parser("order-albums", help="Order all albums in the account")
|
|
albos_parser.add_argument("album_ids", metavar="id1,id2,...,idn", help="The new list of album IDs")
|
|
|
|
albo_parser = sparsers.add_parser("order-album", help="Order all images in an album")
|
|
albo_parser.add_argument("album_id")
|
|
albo_parser.add_argument("hashes", metavar="hash1,hash2,...,hashn", help="The new list of hashes")
|
|
|
|
stats_parser = sparsers.add_parser("stats", help="Imgur statistics")
|
|
stats_parser.add_argument("-v", "--view",
|
|
choices=["today", "month", "week"])
|
|
|
|
# Authentification
|
|
args = parser.parse_args()
|
|
u, p, ap = args.user, args.password, args.ask_pass
|
|
if u and not p:
|
|
if ap:
|
|
i.connect(u, getpass())
|
|
else:
|
|
i.connect(u, PASSWORD)
|
|
elif p and not u:
|
|
i.connect(USERNAME, p)
|
|
elif u and p:
|
|
i.connect(u, p)
|
|
elif USERNAME and (not PASSWORD or ap) and not args.anon:
|
|
i.connect(USERNAME, getpass())
|
|
elif USERNAME and PASSWORD and not args.anon:
|
|
i.connect(USERNAME, PASSWORD)
|
|
|
|
# Preparation
|
|
opts = vars(args)
|
|
spname = args.sp_name
|
|
del opts["user"], opts["password"], opts["anon"], opts["ask_pass"]
|
|
if spname in ("upload", "infos", "delete", "edit", "list", "count"):
|
|
fun_name = spname + "_image" if hasattr(Imgur,
|
|
spname + "_image") else spname + "_images"
|
|
elif spname.rstrip("s").endswith("album"):
|
|
fun_name = spname.replace("-", "_")
|
|
else:
|
|
fun_name = spname
|
|
del opts["sp_name"]
|
|
fun = getattr(Imgur, fun_name)
|
|
|
|
# And action.
|
|
print fun(i, **opts)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|