def test_is_name_a_title(): for name, content, expected in [ # simple ('this is the content', 'this is the content', False), ('This is a title', 'This is some content', True), # common case with no explicit p-name ('nonsensethe contentnonsense', 'the content', False), # ignore case, punctuation ('the content', 'ThE cONTeNT...', False), # test bytestrings (b'This is a title', b'This is some content', True), ]: assert expected == mf2util.is_name_a_title(name, content)
def activities_to_jsonfeed(activities, actor=None, title=None, feed_url=None, home_page_url=None): """Converts ActivityStreams activities to a JSON feed. Args: activities: sequence of ActivityStreams activity dicts actor: ActivityStreams actor dict, the author of the feed title: string, the feed title home_page_url: string, the home page URL feed_url: the URL of the JSON Feed, if any. Included in the feed_url field. Returns: dict, JSON Feed data, ready to be JSON-encoded """ try: iter(activities) except TypeError: raise TypeError('activities must be iterable') if isinstance(activities, (dict, basestring)): raise TypeError('activities may not be a dict or string') def image_url(obj): return util.get_first(obj, 'image', {}).get('url') def actor_name(obj): return obj.get('displayName') or obj.get('username') if not actor: actor = {} items = [] for activity in activities: obj = activity.get('object') or activity if obj.get('objectType') == 'person': continue author = obj.get('author', {}) content = microformats2.render_content( obj, include_location=True, render_attachments=True) obj_title = obj.get('title') or obj.get('displayName') item = { 'id': obj.get('id') or obj.get('url'), 'url': obj.get('url'), 'image': image_url(obj), 'title': obj_title if mf2util.is_name_a_title(obj_title, content) else None, 'summary': obj.get('summary'), 'content_html': content, 'date_published': obj.get('published'), 'date_modified': obj.get('updated'), 'author': { 'name': actor_name(author), 'url': author.get('url'), 'avatar': image_url(author), }, 'attachments': [], } for att in obj.get('attachments', []): url = (util.get_first(att, 'stream') or util.get_first(att, 'image') or att ).get('url') mime = mimetypes.guess_type(url)[0] if url else None if (att.get('objectType') in ATTACHMENT_TYPES or mime and mime.split('/')[0] in ATTACHMENT_TYPES): item['attachments'].append({ 'url': url or '', 'mime_type': mime, 'title': att.get('title'), }) if not item['content_html']: item['content_text'] = '' items.append(item) return util.trim_nulls({ 'version': 'https://jsonfeed.org/version/1', 'title': title or actor_name(actor) or 'JSON Feed', 'feed_url': feed_url, 'home_page_url': home_page_url or actor.get('url'), 'author': { 'name': actor_name(actor), 'url': actor.get('url'), 'avatar': image_url(actor), }, 'items': items, }, ignore='content_text')
def _create(self, obj, preview, include_link): """Creates or previews creating for the previous two methods. https://www.flickr.com/services/api/upload.api.html https://www.flickr.com/services/api/flickr.photos.comments.addComment.html https://www.flickr.com/services/api/flickr.favorites.add.html https://www.flickr.com/services/api/flickr.photos.people.add.html Args: obj: ActivityStreams object preview: boolean include_link: boolean Return: a CreationResult """ # photo, comment, or like type = source.object_type(obj) logging.debug('publishing object type %s to Flickr', type) content = self._content_for_create(obj) link_text = '(Originally published at: %s)' % obj.get('url') if obj.get('image') and type in ('note', 'article'): image_url = obj.get('image').get('url') name = obj.get('displayName') people = self._get_person_tags(obj) # if name does not represent an explicit title, then we'll just # use it as the title and wipe out the content if name and content and not mf2util.is_name_a_title(name, content): content = None # add original post link if include_link: content = ((content + '\n\n') if content else '') + link_text if preview: preview_content = '' if name: preview_content += '<h4>%s</h4>' % name if content: preview_content += '<div>%s</div>' % content if people: preview_content += '<div> with %s</div>' % ', '.join( ('<a href="%s">%s</a>' % ( p.get('url'), p.get('displayName') or 'User %s' % p.get('id')) for p in people)) preview_content += '<img src="%s" />' % image_url return source.creation_result( content=preview_content, description='post') params = [] if name: params.append(('title', name)) if content: params.append(('description', content)) resp = self.upload_photo(params, urllib2.urlopen(image_url)) photo_id = resp.get('id') resp.update({ 'type': 'post', 'url': self.photo_url(self.path_alias() or self.user_id(), photo_id), }) # add person tags for person_id in sorted(p.get('id') for p in people): self.call_api_method('flickr.photos.people.add', { 'photo_id': photo_id, 'user_id': person_id, }) return source.creation_result(resp) base_obj = self.base_object(obj) base_id = base_obj.get('id') base_url = base_obj.get('url') # maybe a comment on a flickr photo? if type == 'comment' or obj.get('inReplyTo'): if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to comment on.', error_html='Could not find a photo to <a href="http://indiewebcamp.com/reply">comment on</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.') if include_link: content += '\n\n' + link_text if preview: return source.creation_result( content=content, description='comment on <a href="%s">this photo</a>.' % base_url) resp = self.call_api_method('flickr.photos.comments.addComment', { 'photo_id': base_id, 'comment_text': content, }) resp = resp.get('comment', {}) resp.update({ 'type': 'comment', 'url': resp.get('permalink'), }) return source.creation_result(resp) if type == 'like': if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to favorite.', error_html='Could not find a photo to <a href="http://indiewebcamp.com/like">favorite</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/like">like-of</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.') if preview: return source.creation_result( description='favorite <a href="%s">this photo</a>.' % base_url) # this method doesn't return any data self.call_api_method('flickr.favorites.add', { 'photo_id': base_id, }) # TODO should we canonicalize the base_url (e.g. removing trailing path # info like "/in/contacts/") return source.creation_result({ 'type': 'like', 'url': '%s#favorited-by-%s' % (base_url, self.user_id()), }) return source.creation_result( abort=False, error_plain='Cannot publish type=%s to Flickr.' % type, error_html='Cannot publish type=%s to Flickr.' % type)
def activities_to_jsonfeed(activities, actor=None, title=None, feed_url=None, home_page_url=None): """Converts ActivityStreams activities to a JSON feed. Args: activities: sequence of ActivityStreams activity dicts actor: ActivityStreams actor dict, the author of the feed title: string, the feed title home_page_url: string, the home page URL feed_url: the URL of the JSON Feed, if any. Included in the feed_url field. Returns: dict, JSON Feed data, ready to be JSON-encoded """ try: iter(activities) except TypeError: raise TypeError('activities must be iterable') if isinstance(activities, (dict, basestring)): raise TypeError('activities may not be a dict or string') def image_url(obj): return util.get_first(obj, 'image', {}).get('url') def actor_name(obj): return obj.get('displayName') or obj.get('username') if not actor: actor = {} items = [] for activity in activities: obj = activity.get('object') or activity if obj.get('objectType') == 'person': continue author = obj.get('author', {}) content = obj.get('content') # The JSON Feed spec (https://jsonfeed.org/version/1#items) says that the # URL from the "image" property may also appear in "content_html", in which # case it should be interpreted as the "main, featured image" of the # post. It does not specify the behavior or semantics in the case that the # image does *not* appear in "content_html", but currently at least one # feed reader (Feedbin) will not display the image as part of the post # content unless it is explicitly included in "content_html". if content and image_url(obj): content += HTML_IMAGE_TEMPLATE.format(image_url(obj)) obj_title = obj.get('title') or obj.get('displayName') item = { 'id': obj.get('id') or obj.get('url'), 'url': obj.get('url'), 'image': image_url(obj), 'title': obj_title if mf2util.is_name_a_title(obj_title, content) else None, 'summary': obj.get('summary'), 'content_html': content, 'date_published': obj.get('published'), 'date_modified': obj.get('updated'), 'author': { 'name': actor_name(author), 'url': author.get('url'), 'avatar': image_url(author), }, 'attachments': [], } for att in obj.get('attachments', []): url = (util.get_first(att, 'stream') or util.get_first(att, 'image') or att).get('url') mime = mimetypes.guess_type(url)[0] if url else None if (att.get('objectType') in ATTACHMENT_TYPES or mime and mime.split('/')[0] in ATTACHMENT_TYPES): item['attachments'].append({ 'url': url or '', 'mime_type': mime, 'title': att.get('title'), }) if not item['content_html']: item['content_text'] = '' items.append(item) return util.trim_nulls( { 'version': 'https://jsonfeed.org/version/1', 'title': title or actor_name(actor) or 'JSON Feed', 'feed_url': feed_url, 'home_page_url': home_page_url or actor.get('url'), 'author': { 'name': actor_name(actor), 'url': actor.get('url'), 'avatar': image_url(actor), }, 'items': items, }, ignore='content_text')
def feed_parser(doc=None, url=None): """ parser to get hfeed """ if doc: if not isinstance(doc, BeautifulSoup): doc = BeautifulSoup(doc) if url: if doc is None: data = requests.get(url) # check for charater encodings and use 'correct' data if 'charset' in data.headers.get('content-type', ''): doc = BeautifulSoup(data.text) else: doc = BeautifulSoup(data.content) # find first h-feed object if any or construct it hfeed = doc.find(class_="h-feed") if hfeed: hfeed = mf2py.Parser(hfeed, url).to_dict()['items'][0] else: hfeed = {'type': ['h-feed'], 'properties': {}, 'children': []} # parse whole document for microformats parsed = mf2py.Parser(doc, url).to_dict() # construct h-entries from top-level items hfeed['children'] = [x for x in parsed['items'] if 'h-entry' in x.get('type', [])] # construct fall back properties for hfeed props = hfeed['properties'] # if no name or name is the content value, construct name from title or default from URL name = props.get('name') if name: name = name[0] content = props.get('content') if content: content = content[0] if isinstance(content, dict): content = content.get('value') if not name or not mf2util.is_name_a_title(name, content): feed_title = doc.find('title') if feed_title: hfeed['properties']['name'] = [feed_title.get_text()] elif url: hfeed['properties']['name'] = ['Feed for' + url] # construct author from rep_hcard or meta-author # construct uid from url if 'uid' not in props and 'url' not in props: if url: hfeed['properties']['uid'] = [url] # construct categories from meta-keywords if 'category' not in props: keywords = doc.find('meta', attrs= {'name': 'keywords', 'content': True}) if keywords: hfeed['properties']['category'] = keywords.get('content', '').split(',') return hfeed
def activities_to_jsonfeed(activities, actor=None, title=None, feed_url=None, home_page_url=None): """Converts ActivityStreams activities to a JSON feed. Args: activities: sequence of ActivityStreams activity dicts actor: ActivityStreams actor dict, the author of the feed title: string, the feed title home_page_url: string, the home page URL feed_url: the URL of the JSON Feed, if any. Included in the feed_url field. Returns: dict, JSON Feed data, ready to be JSON-encoded """ try: iter(activities) except TypeError: raise TypeError('activities must be iterable') if isinstance(activities, (dict, basestring)): raise TypeError('activities may not be a dict or string') def image_url(obj): return util.get_first(obj, 'image', {}).get('url') def actor_name(obj): return obj.get('displayName') or obj.get('username') if not actor: actor = {} items = [] for activity in activities: obj = activity.get('object') or activity if obj.get('objectType') == 'person': continue author = obj.get('author', {}) content = obj.get('content') obj_title = obj.get('title') or obj.get('displayName') item = { 'id': obj.get('id') or obj.get('url'), 'url': obj.get('url'), 'image': image_url(obj), 'title': obj_title if mf2util.is_name_a_title(obj_title, content) else None, 'summary': obj.get('summary'), 'content_html': content, 'date_published': obj.get('published'), 'date_modified': obj.get('updated'), 'author': { 'name': actor_name(author), 'url': author.get('url'), 'avatar': image_url(author), }, 'attachments': [], } for att in obj.get('attachments', []): url = (util.get_first(att, 'stream') or util.get_first(att, 'image') or att ).get('url') mime = mimetypes.guess_type(url)[0] if url else None if (att.get('objectType') in ATTACHMENT_TYPES or mime and mime.split('/')[0] in ATTACHMENT_TYPES): item['attachments'].append({ 'url': url or '', 'mime_type': mime, 'title': att.get('title'), }) if not item['content_html']: item['content_text'] = '' items.append(item) return util.trim_nulls({ 'version': 'https://jsonfeed.org/version/1', 'title': title or actor_name(actor) or 'JSON Feed', 'feed_url': feed_url, 'home_page_url': home_page_url or actor.get('url'), 'author': { 'name': actor_name(actor), 'url': actor.get('url'), 'avatar': image_url(actor), }, 'items': items, }, ignore='content_text')
def _create(self, obj, preview, include_link=source.OMIT_LINK, ignore_formatting=False): """Creates or previews creating for the previous two methods. https://www.flickr.com/services/api/upload.api.html https://www.flickr.com/services/api/flickr.photos.comments.addComment.html https://www.flickr.com/services/api/flickr.favorites.add.html https://www.flickr.com/services/api/flickr.photos.people.add.html Args: obj: ActivityStreams object preview: boolean include_link: string ignore_formatting: boolean Return: a CreationResult """ # photo, comment, or like type = source.object_type(obj) logging.debug('publishing object type %s to Flickr', type) link_text = '(Originally published at: %s)' % obj.get('url') image_url = util.get_first(obj, 'image', {}).get('url') video_url = util.get_first(obj, 'stream', {}).get('url') content = self._content_for_create( obj, ignore_formatting=ignore_formatting, strip_first_video_tag=bool(video_url)) if (video_url or image_url) and type in ('note', 'article'): name = obj.get('displayName') people = self._get_person_tags(obj) hashtags = [ t.get('displayName') for t in obj.get('tags', []) if t.get('objectType') == 'hashtag' and t.get('displayName') ] lat = obj.get('location', {}).get('latitude') lng = obj.get('location', {}).get('longitude') # if name does not represent an explicit title, then we'll just # use it as the title and wipe out the content if name and content and not mf2util.is_name_a_title(name, content): name = content content = None # add original post link if include_link == source.INCLUDE_LINK: content = ((content + '\n\n') if content else '') + link_text if preview: preview_content = '' if name: preview_content += '<h4>%s</h4>' % name if content: preview_content += '<div>%s</div>' % content if hashtags: preview_content += '<div> %s</div>' % ' '.join( '#' + t for t in hashtags) if people: preview_content += '<div> with %s</div>' % ', '.join( ('<a href="%s">%s</a>' % (p.get('url'), p.get('displayName') or 'User %s' % p.get('id')) for p in people)) if lat and lng: preview_content += '<div> at <a href="https://maps.google.com/maps?q=%s,%s">%s, %s</a></div>' % ( lat, lng, lat, lng) if video_url: preview_content += ( '<video controls src="%s"><a href="%s">this video' '</a></video>' % (video_url, video_url)) else: preview_content += '<img src="%s" />' % image_url return source.creation_result(content=preview_content, description='post') params = [] if name: params.append(('title', name)) if content: params.append(('description', content.encode('utf-8'))) if hashtags: params.append(('tags', ','.join( ('"%s"' % t if ' ' in t else t).encode('utf-8') for t in hashtags))) file = util.urlopen(video_url or image_url) try: resp = self.upload(params, file) except requests.exceptions.ConnectionError as e: if e.args[0].message.startswith( 'Request exceeds 10 MiB limit'): msg = 'Sorry, photos and videos must be under 10MB.' return source.creation_result(error_plain=msg, error_html=msg) else: raise photo_id = resp.get('id') resp.update({ 'type': 'post', 'url': self.photo_url(self.path_alias() or self.user_id(), photo_id), }) if video_url: resp['granary_message'] = \ "Note that videos take time to process before they're visible." # add person tags for person_id in sorted(p.get('id') for p in people): self.call_api_method('flickr.photos.people.add', { 'photo_id': photo_id, 'user_id': person_id, }) # add location if lat and lng: self.call_api_method('flickr.photos.geo.setLocation', { 'photo_id': photo_id, 'lat': lat, 'lon': lng, }) return source.creation_result(resp) base_obj = self.base_object(obj) base_id = base_obj.get('id') base_url = base_obj.get('url') # maybe a comment on a flickr photo? if type == 'comment' or obj.get('inReplyTo'): if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to comment on.', error_html= 'Could not find a photo to <a href="http://indiewebcamp.com/reply">comment on</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.' ) if include_link == source.INCLUDE_LINK: content += '\n\n' + link_text if preview: return source.creation_result( content=content, description='comment on <a href="%s">this photo</a>.' % base_url) resp = self.call_api_method( 'flickr.photos.comments.addComment', { 'photo_id': base_id, 'comment_text': content.encode('utf-8'), }) resp = resp.get('comment', {}) resp.update({ 'type': 'comment', 'url': resp.get('permalink'), }) return source.creation_result(resp) if type == 'like': if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to favorite.', error_html= 'Could not find a photo to <a href="http://indiewebcamp.com/like">favorite</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/like">like-of</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.' ) if preview: return source.creation_result( description='favorite <a href="%s">this photo</a>.' % base_url) # this method doesn't return any data self.call_api_method('flickr.favorites.add', { 'photo_id': base_id, }) # TODO should we canonicalize the base_url (e.g. removing trailing path # info like "/in/contacts/") return source.creation_result({ 'type': 'like', 'url': '%s#favorited-by-%s' % (base_url, self.user_id()), }) return source.creation_result( abort=False, error_plain='Cannot publish type=%s to Flickr.' % type, error_html='Cannot publish type=%s to Flickr.' % type)
def hentry2atom(entry_mf): """ convert microformats of a h-entry object to Atom 1.0 Args: entry_mf: python dictionary of parsed microformats of a h-entry Return: an Atom 1.0 XML version of the microformats or None if error, and error message """ # generate fall backs or errors for the non-existing required properties ones. if 'properties' in entry_mf: props = entry_mf['properties'] else: return None, 'properties of entry not found.' entry = {'title': '', 'subtitle': '', 'link': '', 'uid': '', 'published': '', 'updated': '', 'summary': '', 'content': '', 'categories': ''} ## required properties first # construct title of entry -- required - add default # if no name or name is the content value, construct name from title or default from URL name = props.get('name') if name: name = name[0] content = props.get('content') if content: content = content[0] if isinstance(content, dict): content = content.get('value') if name: # if name is generated from content truncate if not mf2util.is_name_a_title(name, content): if len(name) > 50: name = name[:50] + '...' else: name = '' entry['title'] = templates.TITLE.substitute(title = escape(name), t_type='title') # construct id of entry uid = _get_id(entry_mf) if uid: # construct id of entry -- required entry['uid'] = templates.ID.substitute(uid = escape(uid)) else: return None, 'entry does not have a valid id' # construct updated/published date of entry updated = _updated_or_published(entry_mf) # updated is -- required if updated: entry['updated'] = templates.DATE.substitute(date = escape(updated), dt_type = 'updated') else: return None, 'entry does not have valid updated date' ## optional properties entry['link'] = templates.LINK.substitute(url = escape(uid), rel='alternate') # construct published date of entry if 'published' in props: entry['published'] = templates.DATE.substitute(date = escape(props['published'][0]), dt_type = 'published') # construct subtitle for feed if 'additional-name' in props: feed['subtitle'] = templates.TITLE.substitute(title = escape(props['additional-name'][0]), t_type='subtitle') # content processing if 'content' in props: if isinstance(props['content'][0], dict): content = props['content'][0]['html'] else: content = props['content'][0] else: content = None if content: entry['content'] = templates.CONTENT.substitute(content = escape(content)) # construct summary of entry if 'featured' in props: featured = templates.FEATURED.substitute(featured = escape(props['featured'][0])) else: featured = '' if 'summary' in props: summary = templates.POST_SUMMARY.substitute(post_summary = escape(props['summary'][0])) else: summary = '' # make morelink if content does not exist if not content: morelink = templates.MORELINK.substitute(url = escape(uid), name = escape(name)) else: morelink = '' entry['summary'] = templates.SUMMARY.substitute(featured=featured, summary=summary, morelink=morelink) # construct category list of entry if 'category' in props: for category in props['category']: if isinstance(category, dict): if 'value' in category: category = category['value'] else: continue entry['categories'] += templates.CATEGORY.substitute(category=escape(category)) # construct atom of entry return templates.ENTRY.substitute(entry), 'up and Atom!'
def interpret_entry( parsed, source_url, base_href=None, hentry=None, use_rel_syndication=True, want_json=False, fetch_mf2_func=None, ): """ Given a document containing an h-entry, return a dictionary. {'type': 'entry', 'url': permalink of the document (may be different than source_url), 'published': datetime or date, 'updated': datetime or date, 'name': title of the entry, 'content': body of entry (contains HTML), 'author': { 'name': author name, 'url': author url, 'photo': author photo }, 'syndication': [ 'syndication url', ... ], 'in-reply-to': [...], 'like-of': [...], 'repost-of': [...]} :param dict parsed: the result of parsing a document containing mf2 markup :param str source_url: the URL of the parsed document, used by the authorship algorithm :param str base_href: (optional) the href value of the base tag :param dict hentry: (optional) the item in the above document representing the h-entry. if provided, we can avoid a redundant call to find_first_entry :param boolean use_rel_syndication: (optional, default True) Whether to include rel=syndication in the list of syndication sources. Sometimes useful to set this to False when parsing h-feeds that erroneously include rel=syndication on each entry. :param boolean want_json: (optional, default False) if true, the result will be pure json with datetimes as strings instead of python objects :param callable fetch_mf2_func: (optional) function to fetch mf2 parsed output for a given URL. :return: a dict with some or all of the described properties """ # find the h-entry if it wasn't provided if not hentry: hentry = util.find_first_entry(parsed, ["h-entry"]) if not hentry: return {} result = _interpret_common_properties( parsed, source_url, base_href, hentry, use_rel_syndication, want_json, fetch_mf2_func, ) if "h-cite" in hentry.get("type", []): result["type"] = "cite" else: result["type"] = "entry" # NOTE patch start if "category" in hentry["properties"]: result["category"] = hentry["properties"]["category"] if "pubkey" in hentry["properties"]: result["pubkey"] = hentry["properties"]["pubkey"] if "vote" in hentry["properties"]: result["vote"] = hentry["properties"]["vote"] # NOTE patch end title = util.get_plain_text(hentry["properties"].get("name")) if title and util.is_name_a_title(title, result.get("content-plain")): result["name"] = title for prop in ( "in-reply-to", "like-of", "repost-of", "bookmark-of", "vote-on", "comment", "like", "repost", ): # NOTE added vote-on for url_val in hentry["properties"].get(prop, []): if isinstance(url_val, dict): result.setdefault(prop, []).append( util.interpret( parsed, source_url, base_href, url_val, use_rel_syndication=False, want_json=want_json, fetch_mf2_func=fetch_mf2_func, ) ) else: result.setdefault(prop, []).append( { "url": url_val, } ) return result
def _create(self, obj, preview, include_link=False, ignore_formatting=False): """Creates or previews creating for the previous two methods. https://www.flickr.com/services/api/upload.api.html https://www.flickr.com/services/api/flickr.photos.comments.addComment.html https://www.flickr.com/services/api/flickr.favorites.add.html https://www.flickr.com/services/api/flickr.photos.people.add.html Args: obj: ActivityStreams object preview: boolean include_link: boolean Return: a CreationResult """ # photo, comment, or like type = source.object_type(obj) logging.debug('publishing object type %s to Flickr', type) link_text = '(Originally published at: %s)' % obj.get('url') image_url = util.get_first(obj, 'image', {}).get('url') video_url = util.get_first(obj, 'stream', {}).get('url') content = self._content_for_create(obj, ignore_formatting=ignore_formatting, strip_first_video_tag=bool(video_url)) if (video_url or image_url) and type in ('note', 'article'): name = obj.get('displayName') people = self._get_person_tags(obj) hashtags = [t.get('displayName') for t in obj.get('tags', []) if t.get('objectType') == 'hashtag' and t.get('displayName')] lat = obj.get('location', {}).get('latitude') lng = obj.get('location', {}).get('longitude') # if name does not represent an explicit title, then we'll just # use it as the title and wipe out the content if name and content and not mf2util.is_name_a_title(name, content): name = content content = None # add original post link if include_link: content = ((content + '\n\n') if content else '') + link_text if preview: preview_content = '' if name: preview_content += '<h4>%s</h4>' % name if content: preview_content += '<div>%s</div>' % content if hashtags: preview_content += '<div> %s</div>' % ' '.join('#' + t for t in hashtags) if people: preview_content += '<div> with %s</div>' % ', '.join( ('<a href="%s">%s</a>' % ( p.get('url'), p.get('displayName') or 'User %s' % p.get('id')) for p in people)) if lat and lng: preview_content += '<div> at <a href="https://maps.google.com/maps?q=%s,%s">%s, %s</a></div>' % (lat, lng, lat, lng) if video_url: preview_content += ('<video controls src="%s"><a href="%s">this video' '</a></video>' % (video_url, video_url)) else: preview_content += '<img src="%s" />' % image_url return source.creation_result(content=preview_content, description='post') params = [] if name: params.append(('title', name)) if content: params.append(('description', content)) if hashtags: params.append( ('tags', ','.join('"%s"' % t if ' ' in t else t for t in hashtags))) file = util.urlopen(video_url or image_url) resp = self.upload(params, file) photo_id = resp.get('id') resp.update({ 'type': 'post', 'url': self.photo_url(self.path_alias() or self.user_id(), photo_id), }) if video_url: resp['granary_message'] = \ "Note that videos take time to process before they're visible." # add person tags for person_id in sorted(p.get('id') for p in people): self.call_api_method('flickr.photos.people.add', { 'photo_id': photo_id, 'user_id': person_id, }) # add location if lat and lng: self.call_api_method('flickr.photos.geo.setLocation', { 'photo_id': photo_id, 'lat': lat, 'lon': lng, }) return source.creation_result(resp) base_obj = self.base_object(obj) base_id = base_obj.get('id') base_url = base_obj.get('url') # maybe a comment on a flickr photo? if type == 'comment' or obj.get('inReplyTo'): if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to comment on.', error_html='Could not find a photo to <a href="http://indiewebcamp.com/reply">comment on</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/comment">in-reply-to</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.') if include_link: content += '\n\n' + link_text if preview: return source.creation_result( content=content, description='comment on <a href="%s">this photo</a>.' % base_url) resp = self.call_api_method('flickr.photos.comments.addComment', { 'photo_id': base_id, 'comment_text': content, }) resp = resp.get('comment', {}) resp.update({ 'type': 'comment', 'url': resp.get('permalink'), }) return source.creation_result(resp) if type == 'like': if not base_id: return source.creation_result( abort=True, error_plain='Could not find a photo to favorite.', error_html='Could not find a photo to <a href="http://indiewebcamp.com/like">favorite</a>. ' 'Check that your post has an <a href="http://indiewebcamp.com/like">like-of</a> ' 'link to a Flickr photo or to an original post that publishes a ' '<a href="http://indiewebcamp.com/rel-syndication">rel-syndication</a> link to Flickr.') if preview: return source.creation_result( description='favorite <a href="%s">this photo</a>.' % base_url) # this method doesn't return any data self.call_api_method('flickr.favorites.add', { 'photo_id': base_id, }) # TODO should we canonicalize the base_url (e.g. removing trailing path # info like "/in/contacts/") return source.creation_result({ 'type': 'like', 'url': '%s#favorited-by-%s' % (base_url, self.user_id()), }) return source.creation_result( abort=False, error_plain='Cannot publish type=%s to Flickr.' % type, error_html='Cannot publish type=%s to Flickr.' % type)