def feed(self, environ, request, uri): conf = self.isso.conf.section("rss") if not conf.get('base'): raise NotFound args = { 'uri': uri, 'order_by': 'id', 'asc': 0, 'limit': conf.getint('limit') } try: args['limit'] = max(int(request.args.get('limit')), args['limit']) except TypeError: pass except ValueError: return BadRequest("limit should be integer") comments = self.comments.fetch(**args) base = conf.get('base').rstrip('/') hostname = urlparse(base).netloc # Let's build an Atom feed. # RFC 4287: https://tools.ietf.org/html/rfc4287 # RFC 4685: https://tools.ietf.org/html/rfc4685 (threading extensions) # For IDs: http://web.archive.org/web/20110514113830/http://diveintomark.org/archives/2004/05/28/howto-atom-id feed = ET.Element('feed', { 'xmlns': 'http://www.w3.org/2005/Atom', 'xmlns:thr': 'http://purl.org/syndication/thread/1.0' }) # For feed ID, we would use thread ID, but we may not have # one. Therefore, we use the URI. We don't have a year # either... id = ET.SubElement(feed, 'id') id.text = 'tag:{hostname},2018:/isso/thread{uri}'.format( hostname=hostname, uri=uri) # For title, we don't have much either. Be pretty generic. title = ET.SubElement(feed, 'title') title.text = 'Comments for {hostname}{uri}'.format( hostname=hostname, uri=uri) comment0 = None for comment in comments: if comment0 is None: comment0 = comment entry = ET.SubElement(feed, 'entry') # We don't use a real date in ID either to help with # threading. id = ET.SubElement(entry, 'id') id.text = 'tag:{hostname},2018:/isso/{tid}/{id}'.format( hostname=hostname, tid=comment['tid'], id=comment['id']) title = ET.SubElement(entry, 'title') title.text = 'Comment #{}'.format(comment['id']) updated = ET.SubElement(entry, 'updated') updated.text = '{}Z'.format(datetime.fromtimestamp( comment['modified'] or comment['created']).isoformat()) author = ET.SubElement(entry, 'author') name = ET.SubElement(author, 'name') name.text = comment['author'] ET.SubElement(entry, 'link', { 'href': '{base}{uri}#isso-{id}'.format( base=base, uri=uri, id=comment['id']) }) content = ET.SubElement(entry, 'content', { 'type': 'html', }) content.text = self.isso.render(comment['text']) if comment['parent']: ET.SubElement(entry, 'thr:in-reply-to', { 'ref': 'tag:{hostname},2018:/isso/{tid}/{id}'.format( hostname=hostname, tid=comment['tid'], id=comment['parent']), 'href': '{base}{uri}#isso-{id}'.format( base=base, uri=uri, id=comment['parent']) }) # Updated is mandatory. If we have comments, we use the date # of last modification of the first one (which is the last # one). Otherwise, we use a fixed date. updated = ET.Element('updated') if comment0 is None: updated.text = '1970-01-01T01:00:00Z' else: updated.text = datetime.fromtimestamp( comment0['modified'] or comment0['created']).isoformat() updated.text += 'Z' feed.insert(0, updated) output = StringIO() ET.ElementTree(feed).write(output, encoding='utf-8', xml_declaration=True) response = XML(output.getvalue(), 200) # Add an etag/last-modified value for caching purpose if comment0 is None: response.set_etag('empty') response.last_modified = 0 else: response.set_etag('{tid}-{id}'.format(**comment0)) response.last_modified = comment0['modified'] or comment0['created'] return response.make_conditional(request)