def __init__(self): # Get the access tokens saved in settings.json file info = plistlib.readPlist("info.plist") bundleid = info["bundleid"] settings_path = os.path.join( os.path.expanduser("~/Library/Application Support/Alfred 2/Workflow Data/"), bundleid, "settings.json" ) with open(settings_path, "rb") as settings_file: settings = json.load(settings_file) primary_access_token = settings.get("primary_access_token", None) secondary_access_tokens = settings.get("secondary_access_tokens", []) # Do not initialize without a primary_access_token if primary_access_token: self._num_secondary_tokens = len(secondary_access_tokens) self.api_calls_remaining = [None for i in range(self._num_secondary_tokens)] self.api_last_call = [None for i in range(self._num_secondary_tokens)] # Instantiate primary api self.primary_api = InstagramAPI( access_token=primary_access_token, client_secret=MyInstagramAPI.client_secret ) # Instantiate secondary api's self.secondary_apis = [] for secondary_token in secondary_access_tokens: self.secondary_apis.append( InstagramAPI(access_token=secondary_token, client_secret=MyInstagramAPI.client_secret) ) self.test_api(self.primary_api) self.primary_follows = self.get_primary_follows() self.secondary_idx = -1 # Will be incremented to 0 in new_secondary_api self.secondary_api_gen = self.new_secondary_api() self.secondary_api = self.secondary_api_gen.next() self.secondary_follows = self.get_secondary_follows() self.api_error_handler = HandleAPIErrors(self) self.last_used_api = None
class MyInstagramAPI(object): """InstagramAPI wrapper, allows for multiple access tokens""" # primary_access_token = '1977306979.6104a3c.d49ec03bfe484e46a7be8f35bcd997ed' # secondary_access_tokens = ['1593955519.6104a3c.91152da00e96457eb5abad054d45b0af', # '1699042.6104a3c.834088edd4a6474e8d28fdf8bb1ab58b'] client_secret = "16cf9a1467b740d295631397e17512e5" def __init__(self): # Get the access tokens saved in settings.json file info = plistlib.readPlist("info.plist") bundleid = info["bundleid"] settings_path = os.path.join( os.path.expanduser("~/Library/Application Support/Alfred 2/Workflow Data/"), bundleid, "settings.json" ) with open(settings_path, "rb") as settings_file: settings = json.load(settings_file) primary_access_token = settings.get("primary_access_token", None) secondary_access_tokens = settings.get("secondary_access_tokens", []) # Do not initialize without a primary_access_token if primary_access_token: self._num_secondary_tokens = len(secondary_access_tokens) self.api_calls_remaining = [None for i in range(self._num_secondary_tokens)] self.api_last_call = [None for i in range(self._num_secondary_tokens)] # Instantiate primary api self.primary_api = InstagramAPI( access_token=primary_access_token, client_secret=MyInstagramAPI.client_secret ) # Instantiate secondary api's self.secondary_apis = [] for secondary_token in secondary_access_tokens: self.secondary_apis.append( InstagramAPI(access_token=secondary_token, client_secret=MyInstagramAPI.client_secret) ) self.test_api(self.primary_api) self.primary_follows = self.get_primary_follows() self.secondary_idx = -1 # Will be incremented to 0 in new_secondary_api self.secondary_api_gen = self.new_secondary_api() self.secondary_api = self.secondary_api_gen.next() self.secondary_follows = self.get_secondary_follows() self.api_error_handler = HandleAPIErrors(self) self.last_used_api = None ###################################################################### ################### InstagramAPI Wrappers ####################### ###################################################################### @api_type("search", "public") def media_popular(self, *args, **kwargs): """Return currently popular media. (up to count=64) Accepts parameters: count, max_id """ return self.last_used_api.media_popular(*args, **kwargs) @api_type("search", "public") def media_search(self, lat, lng, *args, **kwargs): """Return recent media matching a search query and optionally a location Accepts parameters: lat, lng, min_timestamp, max_timestamp, distance, count """ return self.last_used_api.media_search(lat, lng, *args, **kwargs) @api_type("media", "public") def media_shortcode(self, shortcode, *args, **kwargs): """Return a media using its corresponding shortcode Accepts parameter: shortcode """ return self.last_used_api.media_shortcode(shortcode) @api_type("media", "private") def media_likes(self, media_id, *args, **kwargs): """Return a list of likes for a media given its media_id Accepts parameter: media_id """ return self.last_used_api.media_likes(media_id) @api_type("media", "personal") def like_media(self, media_id, *args, **kwargs): """Submit a like to an Instagram post given its media_id Accepts parameter: media_id """ return self.last_used_api.like_media(media_id) @api_type("media", "personal") def unlike_media(self, media_id, *args, **kwargs): """Remove a like from an Instagram post Accepts parameter: media_id """ return self.last_used_api.unlike_media(media_id) @api_type("media", "personal") def create_media_comment(self, media_id, text, *args, **kwargs): """Post a comment to an Instagram photo given its media_id Accepts parameters: media_id, text """ return self.last_used_api.create_media_comment(media_id, text) @api_type("media", "personal") def delete_comment(self, media_id, comment_id, *args, **kwargs): """Delete a comment from an Instagram photo given its media_id and comment_id Accepts parameters: media_id, comment_id """ return self.last_used_api.delete_comment(media_id, comment_id) @api_type("media", "private") def media_comments(self, media_id, *args, **kwargs): """Return comments on an Instagram post given its media_id Accepts parameter: media_id """ return self.last_used_api.media_comments(media_id) @api_type("media", "private") def media(self, media_id, *args, **kwargs): """Return the information about a single instagram post given its media_id Accepts parameters: media_id """ return self.last_used_api.media(media_id) @api_type("search", "personal") def user_media_feed(self, *args, **kwargs): """Return a list of most recent media from user's followers Accepts parameters: max_id, min_id, count Paginates: True """ return self.last_used_api.user_media_feed(*args, **kwargs) @api_type("search", "personal") def user_liked_media(self, *args, **kwargs): """Return media liked by self Accepts parameters: max_like_id, count Paginates: True """ return self.last_used_api.user_liked_media(*args, **kwargs) @api_type("user", "private") def user_recent_media(self, user_id, *args, **kwargs): """Return recent media of another user given their user_id Accepts parameters: user_id, max_id, min_id, count, max_timestamp, min_timestamp Paginates: True """ return self.last_used_api.user_recent_media(user_id, *args, **kwargs) @api_type("search", "public") def user_search(self, q, *args, **kwargs): """Search for a user by username Accepts parameters: q, count """ return self.last_used_api.user_search(q, *args, **kwargs) @api_type("user", "private") def user_follows(self, user_id, *args, **kwargs): """Return the 50 most recent follows of a user as well as a pagination url Accepts parameter: user_id Paginates: True """ return self.last_used_api.user_follows(user_id, **kwargs) @api_type("user", "private") def user_followed_by(self, user_id, *args, **kwargs): """Get the users that follow a particular user given their user_id Accepts parameters: user_id Paginates: True """ return self.last_used_api.user_followed_by(user_id, **kwargs) @api_type("user", "private") def user(self, user_id, *args, **kwargs): """Return user object given user_id Accepts parameter: user_id """ return self.last_used_api.user(user_id) @api_type("search", "public") def location_recent_media(self, location_id, *args, **kwargs): """Get a list of recent media objects from a given locaiton given its location_id Accepts parameters: location_id, max_id, min_id, min_timestamp, max_timestamp Paginates: True """ return self.last_used_api.location_recent_media(location_id, *args, **kwargs) @api_type("search", "public") def location_search(self, *args, **kwargs): """ Search for a location by geographic coordinates or foursquare id Accepts parameters: lat, lng, foursquare_v2_id, distance Required parameters: (lat AND lng) OR foursquare_v2_id """ return self.last_used_api.location_search(*args, **kwargs) @api_type("search", "public") def location(self, location_id, *args, **kwargs): """ Get information about a location given its location_id Accepts parameters: location_id Required parameters: location_id """ return self.last_used_api.location(location_id) @api_type("search", "personal") def geography_recent_media(self, geography_id, *args, **kwargs): """ Get recent media from a geography subscription that you created Accepts parameters: geography_id, min_id, count Required parameters: geography_id Paginates: True """ return self.last_used_api.geography_recent_media(geography_id, *args, **kwargs) @api_type("search", "public") def tag_recent_media(self, tag_name, *args, **kwargs): """ Get recent media by tag Accepts parameters: tag_name, count, max_tag_id, min_tag_id Required parameters: tag_name Paginates: True """ return self.last_used_api.tag_recent_media(tag_name, *args, **kwargs) @api_type("search", "public") def tag_search(self, q, *args, **kwargs): """ Search for tags by name (without a leading '#') Accepts parameters: q Required parameters: q """ return self.last_used_api.tag_search(q) @api_type("search", "public") def tag(self, tag_name, *args, **kwargs): """ Get information about a tag (without leading '#') Accepts parameters: tag_name Required parameters: tag_name """ return self.last_used_api.tag(tag_name) @api_type("search", "personal") def user_incoming_requests(self, *args, **kwargs): """ Get incoming requests Accepts parameters: None Required parameters: None """ return self.last_used_api.user_incoming_requests() @api_type("user", "personal") def user_relationship(self, user_id, *args, **kwargs): """ Get the status of a relationship with another user Accepts parameters: user_id Required parameters: user_id """ return self.last_used_api.user_relationship(user_id) @api_type("user", "personal") def change_user_relationship(self, user_id, action, *args, **kwargs): """ Modify a relationship with another user. Possible actions are follow, unfollow, approve, ignore, block, unblock Accepts parameters: user_id, action action={follow, unfollow, approve, ignore, block, unblock} Required paramters: user_id, action """ return self.last_used_api.change_user_relationship(user_id, action) ############# Change user relationship action shortcuts ############## def _make_relationship_shortcut(action): def _inner(self, *args, **kwargs): if args: user_id = args[0] else: user_id = kwargs.pop("user_id") return self.change_user_relationship(user_id=user_id, action=action) return _inner follow_user = _make_relationship_shortcut("follow") unfollow_user = _make_relationship_shortcut("unfollow") block_user = _make_relationship_shortcut("block") unblock_user = _make_relationship_shortcut("unblock") approve_user_request = _make_relationship_shortcut("approve") ignore_user_request = _make_relationship_shortcut("ignore") ###################################################################### ################ Instagram Generator Wrappers ################### ###################################################################### def _make_paginator(func, results_per_page, max_pages, **outer_kwargs): def _paginator_wrapper(self, *args, **kwargs): maximum_pages = kwargs.pop("max_pages", None) or max_pages params = {"as_generator": True, "max_pages": maximum_pages} kwargs.update(params) kwargs.update(outer_kwargs) # Initialize update message message_template = kwargs.pop("message_template", None) update_after_pages = kwargs.pop("update_after_pages", None) if message_template: message_gen = self.print_update_message(maximum_pages, update_after_pages=update_after_pages) message_gen.send(None) func_gen = func(self, *args, **kwargs) content = [] pages_read = 0 while True: try: # Iterate over function until successful or unrecoverable failure while self.primary_api.is_good: # and self.secondary_api.is_good: try: content_chunk, _ = func_gen.next() except InstagramAPIError as err: self.api_error_handler.handle_error(err) else: break self.update_api() except StopIteration: # Print final update message if message_template: # Set pages_read to max_pages and it will force the message to print message_gen.send((max_pages, content_len, message_template)) break else: content += content_chunk content_len = len(content) pages_read += 1 if message_template: message_gen.send((pages_read, content_len, message_template)) return content doc = "Return muliple pages of content from api function: {}\n\n".format(func.__name__) doc += "Results per page:{}\n".format(results_per_page) doc += "Default max_pages: {}\n".format(max_pages) _paginator_wrapper.__doc__ = doc return _paginator_wrapper def print_update_message(self, max_pages, update_interval=5, update_after_pages=None): """Display updates of the information every 5 seconds""" start = time.time() interval_start = start previous_content_len = None if update_after_pages: # Send a debug update message after a certain number of pages are read while True: (pages_read, content_len, message_template) = yield now = time.time() interval = now - interval_start do_not_reprint = True if (content_len == previous_content_len) else False if not do_not_reprint and ((pages_read % update_after_pages == 0) or (pages_read == max_pages)): previous_content_len = content_len print message_template.format(running_time="{0:0.2f}".format(now - start), len_content=content_len) else: # Send an update message after a time interval while True: (idx, message_template) = yield now = time.time() interval = now - interval_start if interval > update_interval or interval_start is start or (idx + 1) == list_length: print message_template.substitute(running_time="{0:0.2f}".format(now - start)) interval_start = now api_results_per_page = { "user_liked_media": 20, "user_recent_media": 20, "user_media_feed": 20, "user_follows": 50, "user_followed_by": 50, "location_recent_media": 20, "geography_recent_media": 20, "tag_recent_media": 20, } all_user_liked_media = _make_paginator( func=user_liked_media, results_per_page=api_results_per_page["user_liked_media"], max_pages=10 ) all_user_recent_media = _make_paginator( func=user_recent_media, results_per_page=api_results_per_page["user_recent_media"], max_pages=10, update_after_pages=5, ) all_user_media_feed = _make_paginator( func=user_media_feed, results_per_page=api_results_per_page["user_media_feed"], max_pages=3 ) all_user_follows = _make_paginator( func=user_follows, results_per_page=api_results_per_page["user_follows"], max_pages=20 ) all_user_followed_by = _make_paginator( func=user_followed_by, results_per_page=api_results_per_page["user_followed_by"], max_pages=20 ) all_location_recent_media = _make_paginator( func=location_recent_media, results_per_page=api_results_per_page["location_recent_media"], max_pages=10 ) all_geography_recent_media = _make_paginator( func=geography_recent_media, results_per_page=api_results_per_page["geography_recent_media"], max_pages=10 ) all_tag_recent_media = _make_paginator( func=tag_recent_media, results_per_page=api_results_per_page["tag_recent_media"], max_pages=5 ) ###################################################################### ###################### Convenience Methods ###################### ###################################################################### def get_primary_follows(self): """Get follows of the primary account""" params = {"as_generator": True, "max_pages": 50} follows_gen = self.primary_api.user_follows("self", **params) primary_follows = [] for follows_chunk, url in follows_gen: primary_follows += follows_chunk return UserSet(primary_follows) def get_secondary_follows(self): """Get the follows of the secondary accounts""" num_secondary_accounts = len(self.secondary_apis) secondary_follows = [None for i in range(num_secondary_accounts)] for idx in range(num_secondary_accounts): follows = self.all_user_follows("self", max_pages=50, api=self.secondary_apis[idx]) secondary_follows[idx] = UserSet(follows) return secondary_follows ###################################################################### ###################### Instance Methods ######################### ###################################################################### def new_secondary_api(self): num_secondary_tokens = len(self.secondary_apis) hour = 60 * 60 new_api = False while 1: while 2: for i in range(num_secondary_tokens): self.secondary_idx = (self.secondary_idx + 1) % num_secondary_tokens num_remaining = self.api_calls_remaining[self.secondary_idx] last_call = self.api_last_call[self.secondary_idx] if (num_remaining == None) or (num_remaining >= 1000): new_api = True # break for loop through secondary access tokens break elif time.time() - last_call >= hour: new_api = True # break for loop through secondary access tokens break if new_api: secondary_api = self.secondary_apis[self.secondary_idx] # Test api user_self = self.test_api(secondary_api) if user_self: yield secondary_api # break (while 2), enter final statements of (while 1) break else: # continue (while 2) through secondary access tokens continue else: # No new api found yield False # Re-enter (while 2) def test_api(self, api): try: user = api.user("self") except InstagramAPIError as err: api.is_good = False return False else: api.is_good = True self.update_api(api) return user def update_api(self, api=None): if not api: api = self.last_used_api if api is self.primary_api: self.primary_api.api_last_call = time.time() else: # api is secondary_api calls_remaining = api.x_ratelimit_remaining if calls_remaining is not None: self.api_calls_remaining[self.secondary_idx] = int(calls_remaining) self.api_last_call[self.secondary_idx] = time.time() def api_for_media(self, media): if media.user.id in self.primary_follows: return self.primary_api else: return self.secondary_api