def user_to_actor(self, user): """Converts a user to an actor. Args: user: python_instagram.models.Comment Returns: an ActivityStreams actor dict, ready to be JSON-encoded """ if not user: return {} id = getattr(user, "id", None) username = getattr(user, "username", None) if not id or not username: return {"id": self.tag_uri(id or username), "username": username} url = getattr(user, "website", None) if not url: url = "http://instagram.com/" + username actor = { "displayName": user.full_name, "image": {"url": user.profile_picture}, "id": self.tag_uri(username), "url": url, "username": username, "description": getattr(user, "bio", None), } return util.trim_nulls(actor)
def build_user_json(me, resp=None): """user_json contains an h-card, rel-me links, and "me" Args: me: string, URL of the user, returned by resp: requests.Response (optional), re-use response if it's already been fetched Return: dict, with 'me', the URL for this person; 'h-card', the representative h-card for this page; 'rel-me', a list of rel-me URLs found at this page """ user_json = {'me': me} resp = resp or util.requests_get(me) if resp.status_code // 100 != 2: logging.warning( 'could not fetch user url "%s". got response code: %d', me, resp.status_code) return user_json # Requests doesn't look at the HTML body to find <meta charset> # tags, so if the character encoding isn't given in a header, then # we pass on the raw bytes and let BS4 deal with it. p = mf2py.parse(doc=resp.text if 'charset' in resp.headers.get('content-type', '') else resp.content, url=me) user_json['rel-me'] = p.get('rels', {}).get('me') user_json['h-card'] = mf2util.representative_hcard(p, me) logging.debug('built user-json %r', user_json) return util.trim_nulls(user_json)
def get(self): # lookup the request token token_key = self.request.get('oauth_token') token = TumblrOAuthRequestToken.get_by_key_name(token_key) if token is None: raise exc.HTTPBadRequest('Invalid oauth_token: %s' % token_key) # generate and store the final token tp = tumblpy.Tumblpy(app_key=TUMBLR_APP_KEY, app_secret=TUMBLR_APP_SECRET, oauth_token=token_key, oauth_token_secret=token.secret) auth_token = tp.get_authorized_tokens( self.request.params['oauth_verifier']) final_token = auth_token['oauth_token'] final_secret = auth_token['oauth_token_secret'] TumblrOAuthFinalToken.new(final_token, final_secret) # get the user's blogs # http://www.tumblr.com/docs/en/api/v2#user-methods tp = tumblpy.Tumblpy(app_key=TUMBLR_APP_KEY, app_secret=TUMBLR_APP_SECRET, oauth_token=final_token, oauth_token_secret=final_secret) resp = tp.post('user/info') logging.debug(resp) user = resp['user'] hostnames = [util.domain_from_link(b['url']) for b in user['blogs']] hostnames = util.trim_nulls(hostnames) # titles = [b[title] for b in user['blogs']] # redirect so that refreshing the page doesn't try to regenerate the oauth # token, which won't work. self.redirect('/?' + urllib.urlencode( { 'tumblr_username': user['name'], 'tumblr_hostnames': hostnames, # 'tumblr_titles': titles, 'oauth_token': auth_token['oauth_token'], }, True))
def user_to_actor(self, user): """Converts a user to an actor. Args: user: dict, a decoded JSON Facebook user Returns: an ActivityStreams actor dict, ready to be JSON-encoded """ if not user: return {} id = user.get("id") username = user.get("username") handle = username or id if not handle: return {} url = user.get("link") if not url: url = "http://facebook.com/" + handle # facebook implements this as a 302 redirect image_url = "http://graph.facebook.com/%s/picture?type=large" % handle actor = { "displayName": user.get("name"), "image": {"url": image_url}, "id": self.tag_uri(handle), "updated": util.maybe_iso8601_to_rfc3339(user.get("updated_time")), "url": url, "username": username, "description": user.get("bio"), } location = user.get("location") if location: actor["location"] = {"id": location.get("id"), "displayName": location.get("name")} return util.trim_nulls(actor)
def get(self): # lookup the request token token_key = self.request.get('oauth_token') token = TumblrOAuthRequestToken.get_by_key_name(token_key) if token is None: raise exc.HTTPBadRequest('Invalid oauth_token: %s' % token_key) # generate and store the final token tp = tumblpy.Tumblpy(app_key=TUMBLR_APP_KEY, app_secret=TUMBLR_APP_SECRET, oauth_token=token_key, oauth_token_secret=token.secret) auth_token = tp.get_authorized_tokens(self.request.params['oauth_verifier']) final_token = auth_token['oauth_token'] final_secret = auth_token['oauth_token_secret'] TumblrOAuthFinalToken.new(final_token, final_secret) # get the user's blogs # http://www.tumblr.com/docs/en/api/v2#user-methods tp = tumblpy.Tumblpy(app_key=TUMBLR_APP_KEY, app_secret=TUMBLR_APP_SECRET, oauth_token=final_token, oauth_token_secret=final_secret) resp = tp.post('user/info') logging.debug(resp) user = resp['user'] hostnames = [util.domain_from_link(b['url']) for b in user['blogs']] hostnames = util.trim_nulls(hostnames) # titles = [b[title] for b in user['blogs']] # redirect so that refreshing the page doesn't try to regenerate the oauth # token, which won't work. self.redirect('/?' + urllib.urlencode({ 'tumblr_username': user['name'], 'tumblr_hostnames': hostnames, # 'tumblr_titles': titles, 'oauth_token': auth_token['oauth_token'], }, True))
def user_to_actor(self, user): """Converts a tweet to an activity. Args: user: dict, a decoded JSON Twitter user Returns: an ActivityStreams actor dict, ready to be JSON-encoded """ username = user.get('screen_name') if not username: return {} return util.trim_nulls({ 'displayName': user.get('name'), 'image': {'url': user.get('profile_image_url')}, 'id': self.tag_uri(username) if username else None, 'published': self.rfc2822_to_iso8601(user.get('created_at')), 'url': self.user_url(username), 'location': {'displayName': user.get('location')}, 'username': username, 'description': user.get('description'), })
def postprocess_activity(self, activity): """Does source-independent post-processing of an activity, in place. Right now just populates the title field. Args: activity: activity dict """ # maps object type to human-readable name to use in title TYPE_DISPLAY_NAMES = {'image': 'photo', 'product': 'gift'} # maps verb to human-readable verb DISPLAY_VERBS = {'like': 'likes', 'listen': 'listened to', 'play': 'watched', 'read': 'read', 'give': 'gave'} activity = util.trim_nulls(activity) content = activity.get('object', {}).get('content') actor_name = self.actor_name(activity.get('actor')) object = activity.get('object') if 'title' not in activity: if content: activity['title'] = '%s%s%s' % ( actor_name + ': ' if actor_name else '', content[:TITLE_LENGTH], '...' if len(content) > TITLE_LENGTH else '') elif object: app = activity.get('generator', {}).get('displayName') obj_name = object.get('displayName') obj_type = TYPE_DISPLAY_NAMES.get(object.get('objectType'), 'unknown') activity['title'] = '%s %s %s%s.' % ( actor_name, DISPLAY_VERBS.get(activity['verb'], 'posted'), obj_name if obj_name else 'a %s' % obj_type, ' on %s' % app if app else '') return activity
def post_to_object(self, post): """Converts a post to an object. Args: post: dict, a decoded JSON post Returns: an ActivityStreams object dict, ready to be JSON-encoded """ object = {} id = post.get("id") if not id: return {} post_type = post.get("type") status_type = post.get("status_type") url = "http://facebook.com/" + id.replace("_", "/posts/") picture = post.get("picture") message = post.get("message") if not message: message = post.get("story") display_name = None data = post.get("data", {}) for field in ("object", "song"): obj = data.get(field) if obj: id = obj.get("id") post_type = obj.get("type") url = obj.get("url") display_name = obj.get("title") object_type = OBJECT_TYPES.get(post_type) author = self.user_to_actor(post.get("from")) link = post.get("link", "") if link.startswith("/gifts/"): object_type = "product" if not object_type: if picture and not message: object_type = "image" else: object_type = "note" object = { "id": self.tag_uri(str(id)), "objectType": object_type, "published": util.maybe_iso8601_to_rfc3339(post.get("created_time")), "updated": util.maybe_iso8601_to_rfc3339(post.get("updated_time")), "author": author, "content": message, # FB post ids are of the form USERID_POSTID "url": url, "image": {"url": picture}, "displayName": display_name, } # tags tags = itertools.chain( post.get("to", {}).get("data", []), post.get("with_tags", {}).get("data", []), *post.get("message_tags", {}).values() ) object["tags"] = [ { "objectType": OBJECT_TYPES.get(t.get("type"), "person"), "id": self.tag_uri(t.get("id")), "url": "http://facebook.com/%s" % t.get("id"), "displayName": t.get("name"), "startIndex": t.get("offset"), "length": t.get("length"), } for t in tags ] # is there an attachment? prefer to represent it as a picture (ie image # object), but if not, fall back to a link. att = { "url": link if link else url, "image": {"url": picture}, "displayName": post.get("name"), "summary": post.get("caption"), "content": post.get("description"), } if picture and picture.endswith("_s.jpg") and (post_type == "photo" or status_type == "added_photos"): # a picture the user posted. get a larger size. att.update({"objectType": "image", "image": {"url": picture[:-6] + "_o.jpg"}}) object["attachments"] = [att] elif link and not link.startswith("/gifts/"): att["objectType"] = "article" object["attachments"] = [att] # location place = post.get("place") if place: id = place.get("id") object["location"] = {"displayName": place.get("name"), "id": id, "url": "http://facebook.com/" + id} location = place.get("location", None) if isinstance(location, dict): lat = location.get("latitude") lon = location.get("longitude") if lat and lon: object["location"].update( { "latitude": lat, "longitude": lon, # ISO 6709 location string. details: http://en.wikipedia.org/wiki/ISO_6709 "position": "%+f%+f/" % (lat, lon), } ) # comments go in the replies field, according to the "Responses for # Activity Streams" extension spec: # http://activitystrea.ms/specs/json/replies/1.0/ comments = post.get("comments", {}).get("data") if comments: items = [self.comment_to_object(c) for c in comments] object["replies"] = {"items": items, "totalItems": len(items)} return util.trim_nulls(object)
def media_to_object(self, media): """Converts a media to an object. Args: media: python_instagram.models.Media Returns: an ActivityStreams object dict, ready to be JSON-encoded """ # TODO: location # http://instagram.com/developer/endpoints/locations/ id = media.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("image", "photo"), "published": media.created_time.isoformat("T"), "author": self.user_to_actor(media.user), "content": media.caption.text if media.caption else None, "url": media.link, "attachments": [ {"objectType": "image", "image": {"url": image.url, "width": image.width, "height": image.height}} for image in media.images.values() ], # 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.link) for c in media.comments], "totalItems": media.comment_count, }, "tags": [ { "objectType": "hashtag", "id": self.tag_uri(tag.name), "displayName": tag.name, # TODO: url } for tag in getattr(media, "tags", []) ] + [ { "objectType": "person", "id": self.tag_uri(user.user.username), "displayName": user.user.full_name, "url": "http://instagram.com/" + user.user.username, "image": {"url": user.user.profile_picture}, } for user in getattr(media, "users_in_photo", []) ], } for version in ("standard_resolution", "low_resolution", "thumbnail"): image = media.images.get(version) if image: object["image"] = {"url": image.url} break return util.trim_nulls(object)
def tweet_to_object(self, tweet): """Converts a tweet to an object. Args: tweet: dict, a decoded JSON tweet Returns: an ActivityStreams object dict, ready to be JSON-encoded """ object = {} id = tweet.get('id') if not id: return {} object = { 'objectType': 'note', 'published': self.rfc2822_to_iso8601(tweet.get('created_at')), # don't linkify embedded URLs. (they'll all be t.co URLs.) instead, use # url entities below to replace them with the real URLs, and then linkify. 'content': tweet.get('text'), 'attachments': [], } user = tweet.get('user') if user: object['author'] = self.user_to_actor(user) username = object['author'].get('username') if username: object['id'] = self.tag_uri(id) object['url'] = self.status_url(username, id) entities = tweet.get('entities', {}) # currently the media list will only have photos. if that changes, though, # we'll need to make this conditional on media.type. # https://dev.twitter.com/docs/tweet-entities media_url = entities.get('media', [{}])[0].get('media_url') if media_url: object['image'] = {'url': media_url} object['attachments'].append({ 'objectType': 'image', 'image': {'url': media_url}, }) # tags object['tags'] = [ {'objectType': 'person', 'id': self.tag_uri(t.get('screen_name')), 'url': self.user_url(t.get('screen_name')), 'displayName': t.get('name'), 'indices': t.get('indices') } for t in entities.get('user_mentions', []) ] + [ {'objectType': 'hashtag', 'url': 'https://twitter.com/search?q=%23' + t.get('text'), 'indices': t.get('indices'), } for t in entities.get('hashtags', []) ] + [ # TODO: links are both tags and attachments right now. should they be one # or the other? # file:///home/ryanb/docs/activitystreams_schema_spec_1.0.html#tags-property # file:///home/ryanb/docs/activitystreams_json_spec_1.0.html#object {'objectType': 'article', 'url': t.get('expanded_url'), # TODO: elide full URL? 'indices': t.get('indices'), } for t in entities.get('urls', []) ] for t in object['tags']: indices = t.get('indices') if indices: t.update({ 'startIndex': indices[0], 'length': indices[1] - indices[0], }) del t['indices'] # location place = tweet.get('place') if place: object['location'] = { 'displayName': place.get('full_name'), 'id': place.get('id'), } # place['url'] is a JSON API url, not useful for end users. get the # lat/lon from geo instead. geo = tweet.get('geo') if geo: coords = geo.get('coordinates') if coords: object['location']['url'] = ('https://maps.google.com/maps?q=%s,%s' % tuple(coords)) return util.trim_nulls(object)