Source code for instagram_private_api.compatpatch
# -*- coding: utf-8 -*-
from .endpoints.common import MediaTypes
[docs]class ClientCompatPatch(object):
"""Utility to make entities from the private api similar to the ones
from the public one by adding the necessary properties, and if required,
remove any incompatible properties (to save storage space for example).
"""
FILTERS = {
-2: 'OES',
-1: 'YUV',
0: 'Normal',
1: 'X-Pro II',
2: 'Lo-Fi',
3: 'Earlybird',
10: 'Inkwell',
14: '1977',
15: 'Nashville',
16: 'Kelvin',
17: 'Mayfair',
18: 'Sutro',
19: 'Toaster',
20: 'Walden',
21: 'Hefe',
22: 'Brannan',
23: 'Rise',
24: 'Amaro',
25: 'Valencia',
26: 'Hudson',
27: 'Sierra',
28: 'Willow',
105: 'Dogpatch',
106: 'Vesper',
107: 'Ginza',
108: 'Charmes',
109: 'Stinson',
111: 'Moon',
112: 'Clarendon',
113: 'Skyline',
114: 'Gingham',
115: 'Brooklyn',
116: 'Ashby',
117: 'Helena',
118: 'Maven',
603: 'Ludwig',
605: 'Slumber',
608: 'Perpetua',
612: 'Aden',
613: 'Juno',
614: 'Reyes',
615: 'Lark',
616: 'Crema',
640: 'BrightContrast',
642: 'CrazyColor',
643: 'SubtleColor',
}
@staticmethod
def _get_closest_size(medias, width, height=0):
"""
Try to extract a image/video object that will most match the resolution returned by the public API
:param medias: list of images/videos
:param width: desired width
:param height: desired height
:return:
"""
current = None
for media in medias:
if not current:
current = media
if (abs(media['width'] - width) < abs(current['width'] - width) or
(media['width'] == current['width'] and not height and
not media['height'] == current['width']) or
(media['width'] == current['width'] and height and
abs(media['height'] - height) < abs(current['height'] - height))):
current = media
return current
@staticmethod
def _drop_keys(obj, keys):
"""
Drop unwanted dict keys
:param obj:
:param keys:
:return:
"""
for k in keys:
obj.pop(k, None)
[docs] @classmethod
def comment(cls, comment, drop_incompat_keys=False):
"""Patch a comment object"""
comment['created_time'] = str(int(comment.get('created_at')))
from_user = {
'username': comment['user']['username'],
'profile_picture': comment['user']['profile_pic_url'],
'id': str(comment['user']['pk']),
'full_name': comment['user']['full_name'],
}
comment['from'] = from_user
comment['id'] = str(comment['pk'])
if drop_incompat_keys:
cls._drop_keys(
comment,
[
'bit_flags',
'content_type',
'created_at',
'created_at_utc',
'media_id',
'pk',
'status',
'type',
'user',
'user_id',
]
)
return comment
[docs] @classmethod
def media(cls, media, drop_incompat_keys=False):
"""Patch a media object"""
media['link'] = 'https://www.instagram.com/p/{0!s}/'.format(media['code'])
media['created_time'] = str(int(media.get('taken_at') or media.get('device_timestamp')))
if media['media_type'] == MediaTypes.PHOTO:
media['type'] = 'image'
elif media['media_type'] == MediaTypes.VIDEO:
media['type'] = 'video'
elif media['media_type'] == MediaTypes.CAROUSEL:
media['type'] = 'carousel' # will be patched over below
if media['caption']:
media['caption']['id'] = str(media['caption']['pk'])
media['caption']['created_time'] = str(int(media['caption']['created_at']))
caption_from = {
'username': media['caption']['user']['username'],
'profile_picture': media['caption']['user']['profile_pic_url'],
'id': str(media['caption']['user']['pk']),
'full_name': media['caption']['user']['full_name'],
}
media['caption']['from'] = caption_from
if drop_incompat_keys:
cls._drop_keys(
media['caption'],
[
'bit_flags',
'content_type',
'created_at',
'created_at_utc',
'has_translation',
'media_id',
'pk',
'status',
'type',
'user',
]
)
media['user'] = cls.list_user(media['user'], drop_incompat_keys=drop_incompat_keys)
if media['media_type'] == MediaTypes.CAROUSEL and media.get('carousel_media', []):
# patch carousel media
for carousel_media in media.get('carousel_media', []):
if carousel_media['media_type'] == MediaTypes.PHOTO:
carousel_media['type'] = 'image'
elif carousel_media['media_type'] == MediaTypes.VIDEO:
carousel_media['type'] = 'video'
image_versions2 = carousel_media.get('image_versions2', {}).get('candidates', [])
images = {
'low_resolution': cls._get_closest_size(image_versions2, 320),
'thumbnail': cls._get_closest_size(image_versions2, 150, 150),
'standard_resolution': cls._get_closest_size(
image_versions2, carousel_media.get('original_width', 1000)),
}
carousel_media['images'] = images
if carousel_media['media_type'] == MediaTypes.VIDEO:
video_versions = carousel_media.get('video_versions', [])
videos = {
'low_bandwidth': cls._get_closest_size(video_versions, 480),
'standard_resolution': cls._get_closest_size(
video_versions, carousel_media.get('original_width', 640)),
'low_resolution': cls._get_closest_size(video_versions, 640),
}
if drop_incompat_keys:
[cls._drop_keys(i, ['type']) for i in list(videos.values())]
carousel_media['videos'] = videos
# patch user tags
if carousel_media.get('usertags', {}).get('in', []):
usertags = carousel_media['usertags']['in']
user_tags = []
for ut in usertags:
pos = {'y': ut['position'][1], 'x': ut['position'][0]}
user = ut['user']
user['id'] = str(ut['user']['pk'])
user['profile_picture'] = ut['user']['profile_pic_url']
if drop_incompat_keys:
cls._drop_keys(user, ['profile_pic_url', 'pk', 'is_private'])
user_tags.append({
'position': pos,
'user': user,
})
carousel_media['users_in_photo'] = user_tags
# patch location
if 'location' not in carousel_media or not carousel_media['location'].get('lat'):
carousel_media['location'] = None
else:
carousel_media['location']['latitude'] = carousel_media['location']['lat']
carousel_media['location']['longitude'] = carousel_media['location']['lng']
carousel_media['location']['id'] = carousel_media['location']['pk']
first_carousel_media = media['carousel_media'][0]
media['images'] = first_carousel_media['images']
media['type'] = first_carousel_media['type']
if first_carousel_media['media_type'] == MediaTypes.VIDEO:
media['videos'] = first_carousel_media['videos']
else:
image_versions2 = media.get('image_versions2', {}).get('candidates', [])
images = {
'low_resolution': cls._get_closest_size(image_versions2, 320),
'thumbnail': cls._get_closest_size(image_versions2, 150, 150),
'standard_resolution': cls._get_closest_size(image_versions2, media.get('original_width', 1000)),
}
media['images'] = images
if media['media_type'] == MediaTypes.VIDEO:
video_versions = media.get('video_versions', [])
videos = {
'low_bandwidth': cls._get_closest_size(video_versions, 480),
'standard_resolution': cls._get_closest_size(video_versions, media.get('original_width', 640)),
'low_resolution': cls._get_closest_size(video_versions, 640),
}
if drop_incompat_keys:
[cls._drop_keys(i, ['type']) for i in list(videos.values())]
media['videos'] = videos
likes = {
'count': media.get('like_count', 0),
'data': []
}
media['likes'] = likes
comments = {
'count': media.get('comment_count', 0),
# Patch comment too
'data': [
cls.comment(c, drop_incompat_keys=drop_incompat_keys)
for c in media.get('comments', [])
]
}
media['comments'] = comments
if media.get('preview_comments'):
[
cls.comment(c, drop_incompat_keys=drop_incompat_keys)
for c in media.get('preview_comments', [])
]
media['attribution'] = None
if media.get('filter_type') is not None and media.get('filter_type') in cls.FILTERS:
media['filter'] = cls.FILTERS[media.get('filter_type')]
else:
media['filter'] = ''
media['user_has_liked'] = media.get('has_liked', False)
# Try to preserve location even if there's no lat/lng/pk
if 'location' not in media or not media['location']:
media['location'] = None
elif (media.get('location', {}).get('lat')
and media.get('location', {}).get('lng')
and media.get('location', {}).get('pk')):
media['location']['latitude'] = media['location']['lat']
media['location']['longitude'] = media['location']['lng']
media['location']['id'] = media['location']['pk']
# For stories
if (not media.get('location')
and media.get('story_locations')
and media.get('story_locations', [{}])[0].get('location')):
story_location = media['story_locations'][0]['location']
if (story_location.get('lat')
and story_location.get('lng')
and story_location.get('pk')):
media['location'] = story_location
media['tags'] = []
if media.get('usertags', {}).get('in', []):
usertags = media['usertags']['in']
user_tags = []
for ut in usertags:
pos = {'y': ut['position'][1], 'x': ut['position'][0]}
user = ut['user']
user['id'] = str(ut['user']['pk'])
user['profile_picture'] = ut['user']['profile_pic_url']
if drop_incompat_keys:
cls._drop_keys(user, ['profile_pic_url', 'pk', 'is_private'])
user_tags.append({
'position': pos,
'user': user,
})
media['users_in_photo'] = user_tags
elif media.get('reel_mentions'):
reel_mentions = media['reel_mentions']
user_tags = []
for rm in reel_mentions:
pos = {'y': rm['y'], 'x': rm['x']}
user = rm['user']
user['id'] = str(rm['user']['pk'])
user['profile_picture'] = rm['user']['profile_pic_url']
if drop_incompat_keys:
cls._drop_keys(user, ['profile_pic_id', 'profile_pic_url', 'pk', 'is_private'])
user_tags.append({
'position': pos,
'user': user,
})
media['users_in_photo'] = user_tags
else:
media['users_in_photo'] = []
if drop_incompat_keys:
cls._drop_keys(
media,
[
'can_viewer_save',
'caption_is_edited',
'client_cache_key',
'code',
'comment_count',
'comments_disabled',
'comment_likes_enabled',
'device_timestamp',
'filter_type',
'has_audio',
'has_liked',
'has_more_comments',
'image_versions2',
'is_reel_media',
'lat',
'like_count',
'lng',
'max_num_visible_preview_comments',
'media_type',
'next_max_id',
'organic_tracking_token',
'original_height',
'original_width',
'photo_of_you',
'pk',
'preview_comments',
'reel_mentions',
'saved_collection_ids',
'taken_at',
'top_likers',
'video_duration',
'video_versions',
'view_count',
'visibility',
]
)
if media['location']:
cls._drop_keys(
media['location'],
[
'address',
'city',
'external_id',
'external_source',
'facebook_places_id',
'foursquare_v2_id',
'lat',
'lng',
'pk',
'state',
]
)
return media
[docs] @classmethod
def user(cls, user, drop_incompat_keys=False):
"""Patch a user object """
user['id'] = str(user['pk'])
user['bio'] = user.get('biography', '')
user['profile_picture'] = user['profile_pic_url']
user['website'] = user.get('external_url', '')
if 'media_count' in user and 'follower_count' in user and 'following_count' in user:
counts = {
'media': user['media_count'],
'followed_by': user['follower_count'],
'follows': user['following_count']
}
user['counts'] = counts
if drop_incompat_keys:
cls._drop_keys(
user,
[
'auto_expand_chaining',
'biography',
'external_lynx_url',
'external_url',
'follower_count',
'following_count',
'geo_media_count',
'has_anonymous_profile_picture',
'has_chaining',
'hd_profile_pic_url_info',
'hd_profile_pic_versions',
'include_direct_blacklist_status',
'is_business',
'is_favorite',
'is_private',
'is_unpublished',
'is_verified',
'media_count',
'pk',
'profile_context',
'profile_pic_id',
'profile_pic_url',
'usertags_count',
]
)
return user
[docs] @classmethod
def list_user(cls, user, drop_incompat_keys=False):
"""
Patch a list user object, example in
:meth:`Client.user_following`, :meth:`Client.user_followers`, :meth:`Client.search_users`
"""
user['id'] = str(user['pk'])
user['profile_picture'] = user['profile_pic_url']
if drop_incompat_keys:
cls._drop_keys(
user,
[
'byline',
'follower_count',
'friendship_status',
'has_anonymous_profile_picture',
'has_chaining',
'is_favorite',
'is_private',
'is_unpublished',
'is_verified',
'mutual_followers_count',
'pk',
'profile_pic_url',
'social_context',
'unseen_count',
]
)
return user