Skip to content

Commit 376106a

Browse files
author
Ethan Liou
committed
support uploadPhoto
1 parent c012110 commit 376106a

3 files changed

Lines changed: 88 additions & 7 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.pyc

ImageUtils.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import struct
2+
import imghdr
3+
4+
def getImageSize(fname):
5+
with open(fname, 'rb') as fhandle:
6+
head = fhandle.read(24)
7+
if len(head) != 24:
8+
raise RuntimeError("Invalid Header")
9+
if imghdr.what(fname) == 'png':
10+
check = struct.unpack('>i', head[4:8])[0]
11+
if check != 0x0d0a1a0a:
12+
raise RuntimeError("PNG: Invalid check")
13+
width, height = struct.unpack('>ii', head[16:24])
14+
elif imghdr.what(fname) == 'gif':
15+
width, height = struct.unpack('<HH', head[6:10])
16+
elif imghdr.what(fname) == 'jpeg':
17+
fhandle.seek(0) # Read 0xff next
18+
size = 2
19+
ftype = 0
20+
while not 0xc0 <= ftype <= 0xcf:
21+
fhandle.seek(size, 1)
22+
byte = fhandle.read(1)
23+
while ord(byte) == 0xff:
24+
byte = fhandle.read(1)
25+
ftype = ord(byte)
26+
size = struct.unpack('>H', fhandle.read(2))[0] - 2
27+
# We are at a SOFn block
28+
fhandle.seek(1, 1) # Skip `precision' byte.
29+
height, width = struct.unpack('>HH', fhandle.read(4))
30+
else:
31+
raise RuntimeError("Unsupported format")
32+
return width, height

InstagramAPI.py

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@
77
import hmac
88
import urllib
99
import uuid
10+
import time
11+
from ImageUtils import getImageSize
1012

1113
class InstagramAPI:
1214
API_URL = 'https://i.instagram.com/api/v1/'
13-
USER_AGENT = 'Instagram 9.2.0 Android (18/4.3; 320dpi; 720x1280; Xiaomi; HM 1SW; armani; qcom; en_US)'
15+
DEVICE_SETTINTS = {
16+
'manufacturer' : 'Xiaomi',
17+
'model' : 'HM 1SW',
18+
'android_version' : 18,
19+
'android_release' : '4.3'
20+
}
21+
USER_AGENT = 'Instagram 9.2.0 Android ({android_version}/{android_release}; 320dpi; 720x1280; {manufacturer}; {model}; armani; qcom; en_US)'.format(**DEVICE_SETTINTS)
1422
IG_SIG_KEY = '012a54f51c49aa8c5c322416ab1410909add32c966bbaa0fe3dc58ac43fd7ede'
1523
EXPERIMENTS = 'ig_android_progressive_jpeg,ig_creation_growth_holdout,ig_android_report_and_hide,ig_android_new_browser,ig_android_enable_share_to_whatsapp,ig_android_direct_drawing_in_quick_cam_universe,ig_android_huawei_app_badging,ig_android_universe_video_production,ig_android_asus_app_badging,ig_android_direct_plus_button,ig_android_ads_heatmap_overlay_universe,ig_android_http_stack_experiment_2016,ig_android_infinite_scrolling,ig_fbns_blocked,ig_android_white_out_universe,ig_android_full_people_card_in_user_list,ig_android_post_auto_retry_v7_21,ig_fbns_push,ig_android_feed_pill,ig_android_profile_link_iab,ig_explore_v3_us_holdout,ig_android_histogram_reporter,ig_android_anrwatchdog,ig_android_search_client_matching,ig_android_high_res_upload_2,ig_android_new_browser_pre_kitkat,ig_android_2fac,ig_android_grid_video_icon,ig_android_white_camera_universe,ig_android_disable_chroma_subsampling,ig_android_share_spinner,ig_android_explore_people_feed_icon,ig_explore_v3_android_universe,ig_android_media_favorites,ig_android_nux_holdout,ig_android_search_null_state,ig_android_react_native_notification_setting,ig_android_ads_indicator_change_universe,ig_android_video_loading_behavior,ig_android_black_camera_tab,liger_instagram_android_univ,ig_explore_v3_internal,ig_android_direct_emoji_picker,ig_android_prefetch_explore_delay_time,ig_android_business_insights_qe,ig_android_direct_media_size,ig_android_enable_client_share,ig_android_promoted_posts,ig_android_app_badging_holdout,ig_android_ads_cta_universe,ig_android_mini_inbox_2,ig_android_feed_reshare_button_nux,ig_android_boomerang_feed_attribution,ig_android_fbinvite_qe,ig_fbns_shared,ig_android_direct_full_width_media,ig_android_hscroll_profile_chaining,ig_android_feed_unit_footer,ig_android_media_tighten_space,ig_android_private_follow_request,ig_android_inline_gallery_backoff_hours_universe,ig_android_direct_thread_ui_rewrite,ig_android_rendering_controls,ig_android_ads_full_width_cta_universe,ig_video_max_duration_qe_preuniverse,ig_android_prefetch_explore_expire_time,ig_timestamp_public_test,ig_android_profile,ig_android_dv2_consistent_http_realtime_response,ig_android_enable_share_to_messenger,ig_explore_v3,ig_ranking_following,ig_android_pending_request_search_bar,ig_android_feed_ufi_redesign,ig_android_video_pause_logging_fix,ig_android_default_folder_to_camera,ig_android_video_stitching_7_23,ig_android_profanity_filter,ig_android_business_profile_qe,ig_android_search,ig_android_boomerang_entry,ig_android_inline_gallery_universe,ig_android_ads_overlay_design_universe,ig_android_options_app_invite,ig_android_view_count_decouple_likes_universe,ig_android_periodic_analytics_upload_v2,ig_android_feed_unit_hscroll_auto_advance,ig_peek_profile_photo_universe,ig_android_ads_holdout_universe,ig_android_prefetch_explore,ig_android_direct_bubble_icon,ig_video_use_sve_universe,ig_android_inline_gallery_no_backoff_on_launch_universe,ig_android_image_cache_multi_queue,ig_android_camera_nux,ig_android_immersive_viewer,ig_android_dense_feed_unit_cards,ig_android_sqlite_dev,ig_android_exoplayer,ig_android_add_to_last_post,ig_android_direct_public_threads,ig_android_prefetch_venue_in_composer,ig_android_bigger_share_button,ig_android_dv2_realtime_private_share,ig_android_non_square_first,ig_android_video_interleaved_v2,ig_android_follow_search_bar,ig_android_last_edits,ig_android_video_download_logging,ig_android_ads_loop_count_universe,ig_android_swipeable_filters_blacklist,ig_android_boomerang_layout_white_out_universe,ig_android_ads_carousel_multi_row_universe,ig_android_mentions_invite_v2,ig_android_direct_mention_qe,ig_android_following_follower_social_context'
1624
SIG_KEY_VERSION = '4'
@@ -105,7 +113,29 @@ def logout(self):
105113
# TODO Instagram.php 180-185
106114

107115
def uploadPhoto(self, photo, caption = None, upload_id = None):
108-
# TODO Instagram.php 200-290
116+
if upload_id is None:
117+
upload_id = str(int(time.time() * 1000))
118+
from requests_toolbelt import MultipartEncoder
119+
data = {
120+
'upload_id' : upload_id,
121+
'_uuid' : self.uuid,
122+
'_csrftoken' : self.token,
123+
'image_compression' : '{"lib_name":"jt","lib_version":"1.3.0","quality":"87"}',
124+
'photo' : ('pending_media_%s.jpg'%upload_id, open(photo, 'rb'), 'application/octet-stream', {'Content-Transfer-Encoding':'binary'})
125+
}
126+
m = MultipartEncoder(data, boundary=self.uuid)
127+
self.s.headers.update ({'X-IG-Capabilities' : '3Q4=',
128+
'X-IG-Connection-Type' : 'WIFI',
129+
'Cookie2' : '$Version=1',
130+
'Accept-Language' : 'en-US',
131+
'Accept-Encoding' : 'gzip, deflate',
132+
'Content-type': m.content_type,
133+
'Connection' : 'close',
134+
'User-Agent' : self.USER_AGENT})
135+
response = self.s.post(self.API_URL + "upload/photo/", data=m.to_string())
136+
if response.status_code == 200:
137+
if self.configure(upload_id, photo, caption):
138+
self.expose()
109139
return False
110140

111141
def uploadVideo(self, video, caption = None):
@@ -116,13 +146,31 @@ def direct_share(self, media_id, recipients, text = None):
116146
# TODO Instagram.php 420-490
117147
return False
118148

119-
def configureVideo(upload_id, video, caption = ''):
149+
def configureVideo(self, upload_id, video, caption = ''):
120150
# TODO Instagram.php 490-530
121151
return False
122152

123-
def configure(upload_id, photo, caption = ''):
124-
# TODO Instagram.php 530-570
125-
return False
153+
def configure(self, upload_id, photo, caption = ''):
154+
(w,h) = getImageSize(photo)
155+
data = json.dumps({
156+
'_csrftoken' : self.token,
157+
'media_folder' : 'Instagram',
158+
'source_type' : 4,
159+
'_uid' : self.username_id,
160+
'_uuid' : self.uuid,
161+
'caption' : caption,
162+
'upload_id' : upload_id,
163+
'device' : self.DEVICE_SETTINTS,
164+
'edits' : {
165+
'crop_original_size': [w * 1.0, h * 1.0],
166+
'crop_center' : [0.0, 0.0],
167+
'crop_zoom' : 1.0
168+
},
169+
'extra' : {
170+
'source_width' : w,
171+
'source_height' : h,
172+
}})
173+
return self.SendRequest('media/configure/?', self.generateSignature(data))
126174

127175
def editMedia(self, mediaId, captionText = ''):
128176
data = json.dumps({
@@ -430,7 +478,7 @@ def getLikedMedia(self,maxid=''):
430478
return self.SendRequest('feed/liked/?max_id='+str(maxid))
431479

432480
def generateSignature(self, data):
433-
return 'ig_sig_key_version=' + self.SIG_KEY_VERSION + '&signed_body=' + hmac.new(self.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + urllib.parse.quote(data)
481+
return 'ig_sig_key_version=' + self.SIG_KEY_VERSION + '&signed_body=' + hmac.new(self.IG_SIG_KEY.encode('utf-8'), data.encode('utf-8'), hashlib.sha256).hexdigest() + '.' + urllib.quote(data)
434482

435483
def generateDeviceId(self, seed):
436484
volatile_seed = "12345"

0 commit comments

Comments
 (0)