def comment_to_object(self, comment, photo_id): """Convert a Flickr comment json object to an ActivityStreams comment. Args: comment: dict, the comment object from Flickr photo_id: string, the Flickr ID of the photo that this comment belongs to Returns: dict, an ActivityStreams object """ obj = { 'objectType': 'comment', 'url': comment.get('permalink'), 'id': self.tag_uri(comment.get('id')), 'inReplyTo': [{'id': self.tag_uri(photo_id)}], 'content': comment.get('_content', ''), 'published': util.maybe_timestamp_to_rfc3339(comment.get('datecreate')), 'updated': util.maybe_timestamp_to_rfc3339(comment.get('datecreate')), 'author': { 'objectType': 'person', 'displayName': comment.get('realname') or comment.get('authorname'), 'username': comment.get('authorname'), 'id': self.tag_uri(comment.get('author')), 'url': self.user_url(comment.get('path_alias') or comment.get('author')), 'image': { 'url': self.get_user_image(comment.get('iconfarm'), comment.get('iconserver'), comment.get('author')), }, } } self.postprocess_object(obj) return obj
def like_to_object(self, person, photo_activity): """Convert a Flickr favorite into an ActivityStreams like tag. Args: person: dict, the person object from Flickr photo_activity: dict, the ActivityStreams object representing the photo this like belongs to Returns: dict, an ActivityStreams object """ return { 'author': { 'objectType': 'person', 'displayName': person.get('realname') or person.get('username'), 'username': person.get('username'), 'id': self.tag_uri(person.get('nsid')), 'image': { 'url': self.get_user_image(person.get('iconfarm'), person.get('iconserver'), person.get('nsid')), }, }, 'created': util.maybe_timestamp_to_rfc3339(photo_activity.get('favedate')), 'url': u'{}#liked-by-{}'.format( photo_activity.get('url'), person.get('nsid')), 'object': {'url': photo_activity.get('url')}, 'id': self.tag_uri(u'{}_liked_by_{}'.format( photo_activity.get('flickr_id'), person.get('nsid'))), 'objectType': 'activity', 'verb': 'like', }
def comment_to_object(self, comment, media_id, media_url): """Converts a comment to an object. Args: comment: JSON object retrieved from the Instagram API media_id: string media_url: string Returns: an ActivityStreams object dict, ready to be JSON-encoded """ return self.postprocess_object({ 'objectType': 'comment', 'id': self.tag_uri(comment.get('id')), 'inReplyTo': [{ 'id': self.tag_uri(media_id) }], 'url': '%s#comment-%s' % (media_url, comment.get('id')) if media_url else None, # TODO: add PST time zone 'published': util.maybe_timestamp_to_rfc3339(comment.get('created_time')), 'content': comment.get('text'), 'author': self.user_to_actor(comment.get('from')), 'to': [{ 'objectType': 'group', 'alias': '@public' }], })
def comment_to_object(self, comment, media_id, media_url): """Converts a comment to an object. Args: comment: JSON object retrieved from the Instagram API media_id: string media_url: string Returns: an ActivityStreams object dict, ready to be JSON-encoded """ return self.postprocess_object({ 'objectType': 'comment', 'id': self.tag_uri(comment.get('id')), 'inReplyTo': [{'id': self.tag_uri(media_id)}], 'url': '%s#comment-%s' % (media_url, comment.get('id')) if media_url else None, # TODO: add PST time zone 'published': util.maybe_timestamp_to_rfc3339(comment.get('created_time')), 'content': comment.get('text'), 'author': self.user_to_actor(comment.get('from')), 'to': [{'objectType': 'group', 'alias': '@public'}], })
def media_to_object(self, media): """Converts a media to an object. Args: media: JSON object retrieved from the Instagram API Returns: an ActivityStreams object dict, ready to be JSON-encoded """ id = media.get('id') user = media.get('user', {}) content = xml.sax.saxutils.escape( media.get('caption', {}).get('text', '')) object = { 'id': self.tag_uri(id), # TODO: detect videos. (the type field is in the JSON respose but not # propagated into the Media object.) 'objectType': OBJECT_TYPES.get(media.get('type', 'image'), 'photo'), 'published': util.maybe_timestamp_to_rfc3339(media.get('created_time')), 'author': self.user_to_actor(user), 'content': content, 'url': media.get('link'), 'to': [{ 'objectType': 'group', 'alias': '@private' if user.get('is_private') else '@public', }], 'attachments': [{ 'objectType': 'video' if 'videos' in media else 'image', 'url': media.get('link'), # ActivityStreams 2.0 allows image to be a JSON array. # http://jasnell.github.io/w3c-socialwg-activitystreams/activitystreams2.html#link 'image': sorted( media.get('images', {}).values(), # sort by size, descending, since atom.py # uses the first image in the list. key=operator.itemgetter('width'), reverse=True), # video object defined in # http://activitystrea.ms/head/activity-schema.html#rfc.section.4.18 'stream': sorted(media.get('videos', {}).values(), key=operator.itemgetter('width'), reverse=True), }], # comments go in the replies field, according to the "Responses for # Activity Streams" extension spec: # http://activitystrea.ms/specs/json/replies/1.0/ 'replies': { 'items': [ self.comment_to_object(c, id, media.get('link')) for c in media.get('comments', {}).get('data', []) ], 'totalItems': media.get('comments', {}).get('count'), }, 'tags': [ { 'objectType': 'hashtag', 'id': self.tag_uri(tag), 'displayName': tag, # TODO: url } for tag in media.get('tags', []) ] + [ self.user_to_actor(u.get('user')) for u in media.get('users_in_photo', []) ] + [ self.like_to_object(u, id, media.get('link')) for u in media.get('likes', {}).get('data', []) ] + self._mention_tags_from_content(content) } # alt text # https://instagram-press.com/blog/2018/11/28/creating-a-more-accessible-instagram/ alt = media.get('accessibility_caption') if alt and not any( alt.startswith(prefix) for prefix in AUTO_ALT_TEXT_PREFIXES): for att in object['attachments']: for img in att.get('image', []): img['displayName'] = alt for version in ('standard_resolution', 'low_resolution', 'thumbnail'): image = media.get('images', {}).get(version) if image: object['image'] = {'url': image.get('url')} break for version in ('standard_resolution', 'low_resolution', 'low_bandwidth'): video = media.get('videos', {}).get(version) if video: object['stream'] = {'url': video.get('url')} break # http://instagram.com/developer/endpoints/locations/ if 'location' in media: media_loc = media.get('location', {}) object['location'] = { 'id': self.tag_uri(media_loc.get('id')), 'displayName': media_loc.get('name'), 'latitude': media_loc.get('point', {}).get('latitude'), 'longitude': media_loc.get('point', {}).get('longitude'), 'address': { 'formatted': media_loc.get('street_address') }, 'url': (media_loc.get('id') and 'https://instagram.com/explore/locations/%s/' % media_loc.get('id')), } return self.postprocess_object(object)
def photo_to_activity(self, photo): """Convert a Flickr photo to an ActivityStreams object. Takes either data in the expanded form returned by flickr.photos.getInfo or the abbreviated form returned by flickr.people.getPhotos. Args: photo: dict response from Flickr Returns: dict, an ActivityStreams object """ owner = photo.get('owner') if isinstance(owner, dict): owner_id = owner.get('nsid') path_alias = owner.get('path_alias') else: owner_id = owner path_alias = photo.get('pathalias') created = photo.get('dates', {}).get('taken') or photo.get('datetaken') published = util.maybe_timestamp_to_rfc3339( photo.get('dates', {}).get('posted') or photo.get('dateupload')) # TODO replace owner_id with path_alias? photo_permalink = self.photo_url(path_alias or owner_id, photo.get('id')) title = photo.get('title') if isinstance(title, dict): title = title.get('_content', '') public = (photo.get('visibility') or photo).get('ispublic') activity = { 'id': self.tag_uri(photo.get('id')), 'flickr_id': photo.get('id'), 'url': photo_permalink, 'actor': { 'numeric_id': owner_id, }, 'object': { 'displayName': title, 'url': photo_permalink, 'id': self.tag_uri(photo.get('id')), 'image': { 'url': u'https://farm{}.staticflickr.com/{}/{}_{}_{}.jpg'.format( photo.get('farm'), photo.get('server'), photo.get('id'), photo.get('secret'), 'b'), }, 'content': '\n'.join(( title, photo.get('description', {}).get('_content', ''))), 'objectType': 'photo', 'created': created, 'published': published, 'to': [{'objectType': 'group', 'alias': '@public' if public else '@private'}], }, 'verb': 'post', 'created': created, 'published': published, } if isinstance(owner, dict): activity['object']['author'] = { 'objectType': 'person', 'displayName': owner.get('realname') or owner.get('username'), 'username': owner.get('username'), 'id': self.tag_uri(owner.get('username')), 'image': { 'url': self.get_user_image(owner.get('iconfarm'), owner.get('iconserver'), owner.get('nsid')), }, } if isinstance(photo.get('tags'), dict): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'id': self.tag_uri(tag.get('id')), 'url': u'https://www.flickr.com/search?tags={}'.format( tag.get('_content')), 'displayName': tag.get('raw'), } for tag in photo.get('tags', {}).get('tag', [])] elif isinstance(photo.get('tags'), basestring): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'url': u'https://www.flickr.com/search?tags={}'.format( tag.strip()), 'displayName': tag.strip(), } for tag in photo.get('tags').split(' ') if tag.strip()] self.postprocess_activity(activity) return activity
def photo_to_activity(self, photo): """Convert a Flickr photo to an ActivityStreams object. Takes either data in the expanded form returned by flickr.photos.getInfo or the abbreviated form returned by flickr.people.getPhotos. Args: photo: dict response from Flickr Returns: dict, an ActivityStreams object """ owner = photo.get('owner') if isinstance(owner, dict): owner_id = owner.get('nsid') path_alias = owner.get('path_alias') else: owner_id = owner path_alias = photo.get('pathalias') created = photo.get('dates', {}).get('taken') or photo.get('datetaken') published = util.maybe_timestamp_to_rfc3339( photo.get('dates', {}).get('posted') or photo.get('dateupload')) # TODO replace owner_id with path_alias? photo_permalink = self.photo_url(path_alias or owner_id, photo.get('id')) title = photo.get('title') if isinstance(title, dict): title = title.get('_content', '') public = (photo.get('visibility') or photo).get('ispublic') activity = { 'id': self.tag_uri(photo.get('id')), 'flickr_id': photo.get('id'), 'url': photo_permalink, 'actor': { 'numeric_id': owner_id, }, 'object': { 'displayName': title, 'url': photo_permalink, 'id': self.tag_uri(photo.get('id')), 'image': { 'url': u'https://farm{}.staticflickr.com/{}/{}_{}_{}.jpg'.format( photo.get('farm'), photo.get('server'), photo.get('id'), photo.get('secret'), 'b'), }, 'content': '\n'.join((title, photo.get('description', {}).get('_content', ''))), 'objectType': 'photo', 'created': created, 'published': published, 'to': [{ 'objectType': 'group', 'alias': '@public' if public else '@private' }], }, 'verb': 'post', 'created': created, 'published': published, } if isinstance(owner, dict): username = owner.get('username') # Flickr API is evidently inconsistent in what it puts in realname vs # username vs path_alias. if username has spaces, it's probably actually # real name, so use path alias instead. # https://github.com/snarfed/bridgy/issues/687 # https://www.flickr.com/groups/51035612836@N01/discuss/72157625945600254 if not username or ' ' in username: username = owner.get('path_alias') activity['object']['author'] = { 'objectType': 'person', 'displayName': owner.get('realname') or username, 'username': username, 'id': self.tag_uri(username), 'image': { 'url': self.get_user_image(owner.get('iconfarm'), owner.get('iconserver'), owner.get('nsid')), }, } if isinstance(photo.get('tags'), dict): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'id': self.tag_uri(tag.get('id')), 'url': u'https://www.flickr.com/search?tags={}'.format( tag.get('_content')), 'displayName': tag.get('raw'), } for tag in photo.get('tags', {}).get('tag', [])] elif isinstance(photo.get('tags'), basestring): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'url': u'https://www.flickr.com/search?tags={}'.format(tag.strip()), 'displayName': tag.strip(), } for tag in photo.get('tags').split(' ') if tag.strip()] # location is represented differently in a list of photos vs a # single photo info lat = photo.get('latitude') or photo.get('location', {}).get('latitude') lng = photo.get('longitude') or photo.get('location', {}).get('longitude') if lat and lng and float(lat) != 0 and float(lng) != 0: activity['object']['location'] = { 'objectType': 'place', 'latitude': float(lat), 'longitude': float(lng), } self.postprocess_object(activity['object']) self.postprocess_activity(activity) return activity
def media_to_object(self, media): """Converts a media to an object. Args: media: JSON object retrieved from the Instagram API Returns: an ActivityStreams object dict, ready to be JSON-encoded """ id = media.get('id') object = { 'id': self.tag_uri(id), # TODO: detect videos. (the type field is in the JSON respose but not # propagated into the Media object.) 'objectType': OBJECT_TYPES.get(media.get('type', 'image'), 'photo'), 'published': util.maybe_timestamp_to_rfc3339(media.get('created_time')), 'author': self.user_to_actor(media.get('user')), 'content': xml.sax.saxutils.escape( media.get('caption', {}).get('text', '')), 'url': media.get('link'), 'to': [{'objectType': 'group', 'alias': '@public'}], 'attachments': [{ 'objectType': 'video' if 'videos' in media else 'image', # ActivityStreams 2.0 allows image to be a JSON array. # http://jasnell.github.io/w3c-socialwg-activitystreams/activitystreams2.html#link 'image': sorted( media.get('images', {}).values(), # sort by size, descending, since atom.py # uses the first image in the list. key=operator.itemgetter('width'), reverse=True), # video object defined in # http://activitystrea.ms/head/activity-schema.html#rfc.section.4.18 'stream': sorted( media.get('videos', {}).values(), key=operator.itemgetter('width'), reverse=True), }], # comments go in the replies field, according to the "Responses for # Activity Streams" extension spec: # http://activitystrea.ms/specs/json/replies/1.0/ 'replies': { 'items': [self.comment_to_object(c, id, media.get('link')) for c in media.get('comments', {}).get('data', [])], 'totalItems': media.get('comments', {}).get('count'), }, 'tags': [{ 'objectType': 'hashtag', 'id': self.tag_uri(tag), 'displayName': tag, # TODO: url } for tag in media.get('tags', [])] + [self.user_to_actor(user.get('user')) for user in media.get('users_in_photo', [])] + [self.like_to_object(user, id, media.get('link')) for user in media.get('likes', {}).get('data', [])], } for version in ('standard_resolution', 'low_resolution', 'thumbnail'): image = media.get('images').get(version) if image: object['image'] = {'url': image.get('url')} break for version in ('standard_resolution', 'low_resolution', 'low_bandwidth'): video = media.get('videos', {}).get(version) if video: object['stream'] = {'url': video.get('url')} break # http://instagram.com/developer/endpoints/locations/ if 'location' in media: media_loc = media.get('location', {}) object['location'] = { 'id': media_loc.get('id'), 'displayName': media_loc.get('name'), 'latitude': media_loc.get('point', {}).get('latitude'), 'longitude': media_loc.get('point', {}).get('longitude'), 'address': {'formatted': media_loc.get('street_address')}, 'url': (media_loc.get('id') and 'https://instagram.com/explore/locations/%s/' % media_loc.get('id')), } return self.postprocess_object(object)
def photo_to_activity(self, photo): """Convert a Flickr photo to an ActivityStreams object. Takes either data in the expanded form returned by flickr.photos.getInfo or the abbreviated form returned by flickr.people.getPhotos. Args: photo: dict response from Flickr Returns: dict, an ActivityStreams object """ owner = photo.get('owner') owner_id = owner.get('nsid') if isinstance(owner, dict) else owner created = photo.get('dates', {}).get('taken') or photo.get('datetaken') published = util.maybe_timestamp_to_rfc3339( photo.get('dates', {}).get('posted') or photo.get('dateupload')) photo_permalink = 'https://www.flickr.com/photos/{}/{}/'.format( owner_id, photo.get('id')) title = photo.get('title') if isinstance(title, dict): title = title.get('_content', '') public = (photo.get('visibility') or photo).get('ispublic') activity = { 'id': self.tag_uri(photo.get('id')), 'flickr_id': photo.get('id'), 'url': photo_permalink, 'actor': { 'numeric_id': owner_id, }, 'object': { 'displayName': title, 'url': photo_permalink, 'id': self.tag_uri(photo.get('id')), 'image': { 'url': 'https://farm{}.staticflickr.com/{}/{}_{}_{}.jpg'.format( photo.get('farm'), photo.get('server'), photo.get('id'), photo.get('secret'), 'b'), }, 'content': '\n'.join((title, photo.get('description', {}).get('_content', ''))), 'objectType': 'photo', 'created': created, 'published': published, 'to': [{ 'objectType': 'group', 'alias': '@public' if public else '@private' }], }, 'verb': 'post', 'created': created, 'published': published, } if isinstance(owner, dict): activity['object']['author'] = { 'objectType': 'person', 'displayName': owner.get('realname') or owner.get('username'), 'username': owner.get('username'), 'id': self.tag_uri(owner.get('username')), 'image': { 'url': self.get_user_image(owner.get('iconfarm'), owner.get('iconserver'), owner.get('nsid')), }, } if isinstance(photo.get('tags'), dict): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'id': self.tag_uri(tag.get('id')), 'url': 'https://www.flickr.com/search?tags={}'.format( tag.get('_content')), 'displayName': tag.get('raw'), } for tag in photo.get('tags', {}).get('tag', [])] elif isinstance(photo.get('tags'), basestring): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'url': 'https://www.flickr.com/search?tags={}'.format(tag.strip()), 'displayName': tag.strip(), } for tag in photo.get('tags').split(' ') if tag.strip()] self.postprocess_activity(activity) return activity
def photo_to_activity(self, photo): """Convert a Flickr photo to an ActivityStreams object. Takes either data in the expanded form returned by flickr.photos.getInfo or the abbreviated form returned by flickr.people.getPhotos. Args: photo: dict response from Flickr Returns: dict, an ActivityStreams object """ owner = photo.get('owner') if isinstance(owner, dict): owner_id = owner.get('nsid') path_alias = owner.get('path_alias') else: owner_id = owner path_alias = photo.get('pathalias') created = photo.get('dates', {}).get('taken') or photo.get('datetaken') published = util.maybe_timestamp_to_rfc3339( photo.get('dates', {}).get('posted') or photo.get('dateupload')) # TODO replace owner_id with path_alias? photo_permalink = self.photo_url(path_alias or owner_id, photo.get('id')) title = photo.get('title') if isinstance(title, dict): title = title.get('_content', '') public = (photo.get('visibility') or photo).get('ispublic') activity = { 'id': self.tag_uri(photo.get('id')), 'flickr_id': photo.get('id'), 'url': photo_permalink, 'actor': { 'numeric_id': owner_id, }, 'object': { 'displayName': title, 'url': photo_permalink, 'id': self.tag_uri(photo.get('id')), 'image': { 'url': 'https://farm{}.staticflickr.com/{}/{}_{}_{}.jpg'.format( photo.get('farm'), photo.get('server'), photo.get('id'), photo.get('secret'), 'b'), }, 'content': '\n'.join(( title, photo.get('description', {}).get('_content', ''))), 'objectType': 'photo', 'created': created, 'published': published, 'to': [{'objectType': 'group', 'alias': '@public' if public else '@private'}], }, 'verb': 'post', 'created': created, 'published': published, } if isinstance(owner, dict): username = owner.get('username') # Flickr API is evidently inconsistent in what it puts in realname vs # username vs path_alias. if username has spaces, it's probably actually # real name, so use path alias instead. # https://github.com/snarfed/bridgy/issues/687 # https://www.flickr.com/groups/51035612836@N01/discuss/72157625945600254 if not username or ' ' in username: username = owner.get('path_alias') activity['object']['author'] = { 'objectType': 'person', 'displayName': owner.get('realname') or username, 'username': username, 'id': self.tag_uri(username), 'image': { 'url': self.get_user_image(owner.get('iconfarm'), owner.get('iconserver'), owner.get('nsid')), }, } if isinstance(photo.get('tags'), dict): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'id': self.tag_uri(tag.get('id')), 'url': 'https://www.flickr.com/search?tags={}'.format( tag.get('_content')), 'displayName': tag.get('raw'), } for tag in photo.get('tags', {}).get('tag', [])] elif isinstance(photo.get('tags'), basestring): activity['object']['tags'] = [{ 'objectType': 'hashtag', 'url': 'https://www.flickr.com/search?tags={}'.format( tag.strip()), 'displayName': tag.strip(), } for tag in photo.get('tags').split(' ') if tag.strip()] # location is represented differently in a list of photos vs a # single photo info lat = photo.get('latitude') or photo.get('location', {}).get('latitude') lng = photo.get('longitude') or photo.get('location', {}).get('longitude') if lat and lng and float(lat) != 0 and float(lng) != 0: activity['object']['location'] = { 'objectType': 'place', 'latitude': float(lat), 'longitude': float(lng), } self.postprocess_object(activity['object']) self.postprocess_activity(activity) return activity