├── LICENSE ├── README.md └── instagram.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Luke Cyca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***Important: this library no longer works. The API calls we were using have been deprecated by Instagram. Sorry.*** 2 | 3 | # python-instagram-upload # 4 | 5 | Upload and post photos to [Instagram](http://instagram.com) with Python! 6 | 7 | This is inspired and roughly ported from the PHP implementation by 8 | [Lance Newman](http://lancenewman.me/posting-a-photo-to-instagram-without-a-phone/). 9 | 10 | Usage Example: 11 | 12 | filepath = "/tmp/square.jpg" 13 | print "Uploading " + filepath 14 | insta = InstagramSession() 15 | if insta.login(USERNAME, PASSWORD): 16 | media_id = insta.upload_photo("/tmp/small.jpg") 17 | print media_id 18 | if media_id is not None: 19 | insta.configure_photo(media_id, "") 20 | 21 | Note that photos must be square to be uploaded. You can convert your 22 | photo using ImageMagick with this command: 23 | 24 | convert -define jpeg:size=1280x1280 -resize 640x640^ \ 25 | -extent 640x640 /tmp/original.jpg /tmp/square.jpg 26 | -------------------------------------------------------------------------------- /instagram.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import hmac 3 | import random 4 | import uuid 5 | import urllib 6 | import json 7 | import hashlib 8 | import time 9 | 10 | try: 11 | # python 2 12 | urllib_quote_plus = urllib.quote 13 | except: 14 | # python 3 15 | urllib_quote_plus = urllib.parse.quote_plus 16 | 17 | def _generate_signature(data): 18 | return hmac.new('b4a23f5e39b5929e0666ac5de94c89d1618a2916'.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() 19 | 20 | 21 | def _generate_user_agent(): 22 | resolutions = ['720x1280', '320x480', '480x800', '1024x768', '1280x720', '768x1024', '480x320'] 23 | versions = ['GT-N7000', 'SM-N9000', 'GT-I9220', 'GT-I9100'] 24 | dpis = ['120', '160', '320', '240'] 25 | 26 | ver = random.choice(versions) 27 | dpi = random.choice(dpis) 28 | res = random.choice(resolutions) 29 | 30 | return ( 31 | 'Instagram 4.{}.{} ' 32 | 'Android ({}/{}.{}.{}; {}; {}; samsung; {}; {}; smdkc210; en_US)' 33 | ).format( 34 | random.randint(1, 2), 35 | random.randint(0, 2), 36 | random.randint(10, 11), 37 | random.randint(1, 3), 38 | random.randint(3, 5), 39 | random.randint(0, 5), 40 | dpi, 41 | res, 42 | ver, 43 | ver, 44 | ) 45 | 46 | 47 | class InstagramSession(object): 48 | 49 | def __init__(self): 50 | self.guid = str(uuid.uuid1()) 51 | self.device_id = 'android-{}'.format(self.guid) 52 | self.session = requests.Session() 53 | self.session.headers.update({'User-Agent': _generate_user_agent()}) 54 | 55 | def login(self, username, password): 56 | 57 | data = json.dumps({ 58 | "device_id": self.device_id, 59 | "guid": self.guid, 60 | "username": username, 61 | "password": password, 62 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 63 | }) 64 | print(data) 65 | 66 | sig = _generate_signature(data) 67 | 68 | payload = 'signed_body={}.{}&ig_sig_key_version=4'.format( 69 | sig, 70 | urllib_quote_plus(data) 71 | ) 72 | 73 | r = self.session.post("https://instagram.com/api/v1/accounts/login/", payload) 74 | r_json = r.json() 75 | print(r_json) 76 | 77 | if r_json.get('status') != "ok": 78 | return False 79 | 80 | return True 81 | 82 | def upload_photo(self, filename): 83 | data = { 84 | "device_timestamp": time.time(), 85 | } 86 | files = { 87 | "photo": open(filename, 'rb'), 88 | } 89 | 90 | r = self.session.post("https://instagram.com/api/v1/media/upload/", data, files=files) 91 | r_json = r.json() 92 | print(r_json) 93 | 94 | return r_json.get('media_id') 95 | 96 | def configure_photo(self, media_id, caption): 97 | data = json.dumps({ 98 | "device_id": self.device_id, 99 | "guid": self.guid, 100 | "media_id": media_id, 101 | "caption": caption, 102 | "device_timestamp": time.time(), 103 | "source_type": "5", 104 | "filter_type": "0", 105 | "extra": "{}", 106 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" 107 | }) 108 | print(data) 109 | 110 | sig = _generate_signature(data) 111 | 112 | payload = 'signed_body={}.{}&ig_sig_key_version=4'.format( 113 | sig, 114 | urllib_quote_plus(data) 115 | ) 116 | 117 | r = self.session.post("https://instagram.com/api/v1/media/configure/", payload) 118 | r_json = r.json() 119 | print(r_json) 120 | 121 | if r_json.get('status') != "ok": 122 | return False 123 | 124 | return True 125 | 126 | 127 | --------------------------------------------------------------------------------