def show_post(post, string, seen): if 'authorUserName' in post: user = post['authorUserName'] elif 'authorSlug' in post: user = post['authorSlug'] else: user = post['authorDisplayName'] result = ( '''<div style="border: 1px solid #B3B3B3; margin: 10px; padding: 10px; background-color: #ECF5FF;"><a href="./posts.php?id=%s">%s</a> by <a href="./users.php?userid=%s">%s</a> · %s · score: %s ''' % (post['_id'], util.htmlescape(post['title']), post['userId'], user, post['postedAt'], post['baseScore'])) if 'body' in post and string.lower() in post['body'].lower(): result += '''<pre style="font-family: Lato, Helvetica, sans-serif; word-wrap: break-word; white-space: pre-wrap; white-space: -moz-pre-wrap;">%s</pre>\n''' % highlighted_search_string( util.htmlescape(post['body']), string) else: if post['_id'] in seen: # If we didn't even match inside the body and we've seen this post # before, it's a garbage result, so return nothing return "" seen.add(post['_id']) result += "</div>\n" return result
def show_post(post, string, seen): if 'authorUserName' in post: user = post['authorUserName'] elif 'authorSlug' in post: user = post['authorSlug'] else: user = post['authorDisplayName'] url = linkpath.posts(util.safe_get(post, ['_id']), util.safe_get(post, ['slug'])) result = ( '''<div style="border: 1px solid #B3B3B3; margin: 10px; padding: 10px; background-color: %s;"><a href="%s">%s</a> by %s · %s ''' % (config.COMMENT_COLOR, url, util.htmlescape(post['title']), util.userlink(slug=util.safe_get(post, ['authorSlug']), display_name=util.safe_get( post, ['authorDisplayName'])), post['postedAt'])) matched_in_body = False if 'body' in post: for term in string.split(): # Check if any of the search terms is in the body if term.lower() in util.safe_get(post, 'body', default="").lower(): matched_in_body = True if matched_in_body: result += '''<pre style="font-family: Lato, Helvetica, sans-serif; word-wrap: break-word; white-space: pre-wrap; white-space: -moz-pre-wrap;">%s</pre>\n''' % highlighted_search_string( util.htmlescape(post['body']), string) else: if post['_id'] in seen: # If we didn't even match inside the body and we've seen this post # before, it's a garbage result, so return nothing return "" seen.add(post['_id']) result += "</div>\n" return result
def feed_for_user(username): result = ('''<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"> <channel> <title>%s</title> <description>%s</description> <language>en-us</language>\n''' % (username + " feed - " + config.TITLE, username + "’s posts and comments on the Effective Altruism Forum")) comments = get_comments_for_user(username) posts = get_posts_for_user(username) all_content = [] all_content.extend(comments) all_content.extend(posts) all_content = sorted(all_content, key=lambda x: x['postedAt'], reverse=True) for content in all_content: content_type = "post" if "title" in content else "comment" result += "<item>\n" if content_type == "post": result += " <title>%s</title>\n" % content['title'] else: if content['post'] is None: result += " <title>Comment by %s on [deleted post]</title>\n" % ( content['user']['username']) else: result += " <title>Comment by %s on %s</title>\n" % ( content['user']['username'], util.htmlescape(content['post']['title'])) result += ''' <link>%s</link>\n''' % util.official_url_to_reader( content['pageUrl']) content_body = util.htmlescape(util.cleanHtmlBody(content['htmlBody'])) result += ''' <description>%s</description>\n''' % content_body result += ''' <author>%s</author>\n''' % username result += ''' <guid>%s</guid>\n''' % content['_id'] result += ''' <pubDate>%s</pubDate>\n''' % content['postedAt'] result += "</item>\n" result += '''</channel> </rss>''' return result
def highlighted_search_string(body, string): # body is already html escaped, so we need string to be on the same level esc_str = util.htmlescape(string) highlighted = r'''<span style="background-color: #ffff00;">\1</span>''' return re.sub("(" + re.escape(esc_str) + ")", highlighted, body, flags=re.IGNORECASE)
def show_comment(comment, string): if 'authorUserName' in comment: user = util.safe_get(comment, 'authorUserName') elif 'authorSlug' in comment: user = util.safe_get(comment, 'authorSlug') else: user = util.safe_get(comment, 'authorDisplayName') result = ( '''<div style="border: 1px solid #B3B3B3; margin: 10px; padding: 10px; background-color: %s;">comment by <a href="./users.php?userid=%s">%s</a> on <a href="./posts.php?id=%s">%s</a> · <a href="./posts.php?id=%s#%s">%s</a> ''' % (config.COMMENT_COLOR, comment['userId'], user, comment['postId'], util.htmlescape(comment['postTitle']), comment['postId'], comment['_id'], comment['postedAt'])) result += '''<pre style="font-family: Lato, Helvetica, sans-serif; word-wrap: break-word; white-space: pre-wrap; white-space: -moz-pre-wrap;">%s</pre>\n''' % highlighted_search_string( util.htmlescape(comment['body']), string) result += "</div>" return result
def show_tag(tagslug, display_format): print("""<!DOCTYPE html> <html> """) run_query = False if display_format == "queries" else True tag_content = get_content_for_tag(tagslug, run_query=run_query) if display_format == "queries": result = "<pre>" result += tag_content + "\n" result += "</pre>\n" return result result = util.show_head( title=tagslug, author="", date="", publisher="LessWrong 2.0" if "lesswrong" in config.GRAPHQL_URL else "Effective Altruism Forum", widepage=True) result += "<body>\n" result += util.show_navbar(navlinks=[ '''<a href="%s" title="Show all the GraphQL queries used to generate this page">Queries</a>''' % "TODO" ]) result += '''<div id="wrapper">''' result += '''<div id="content">''' result += "<h1>" + util.htmlescape(tagslug) + "</h1>\n" result += " ·\n" result += util.cleanHtmlBody( util.substitute_alt_links( util.safe_get(tag_content, ['description', 'html']))) result += (""" </div> </div> </body> </html> """) return result
def show_post_and_comment_thread(postid, display_format): print("""<!DOCTYPE html> <html> """) run_query = False if display_format == "queries" else True post = posts.get_content_for_post(postid, run_query=run_query) comments = posts.get_comments_for_post(postid, view="postCommentsOld", run_query=run_query) if (not run_query) or util.safe_get(post, "question"): answers = query_question_answers(postid, run_query=run_query) print(""" <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> <style type="text/css"> body { font-family: arial,helvetica,sans-serif; font-size: 10pt; } blockquote { color: #789922; padding: 0; margin: 0; } blockquote:before { content: ">"; } a.reply-parent { color: #d00; } </style> </head> """) result = "<body>\n" result += util.show_navbar() result += '''<div id="wrapper">''' result += '''<div id="content">''' result += "<h1>" + util.htmlescape(post['title']) + "</h1>\n" result += " ·\n" result += '''%s ·\n''' % post['postedAt'] result += util.official_link(post['pageUrl']) + ' ·\n' result += util.gw_link(post['pageUrl']) + ' ·\n' if util.safe_get(post, "question") and util.safe_get( post, ["tableOfContents", "sections"]): result += '''<a href="#comments">''' + util.safe_get( post, ["tableOfContents", "sections"])[-1]["title"] + '</a>\n' else: result += '''<a href="#comments">''' + str( post['commentCount']) + ' comments</a>\n' if post['url'] is not None: result += (''' <p>This is a link post for <a href="%s">%s</a></p> ''' % (post['url'], post['url'])) if "question" in post and post["question"]: result += "<p>This is a question post.</p>" if "tableOfContents" in post and post[ "tableOfContents"] and "sections" in post["tableOfContents"]: if post["tableOfContents"]["sections"]: result += '''<h2>Contents</h2>\n''' result += '<pre style="font-size: 12px;">\n' for section in post["tableOfContents"]["sections"]: indent = " " * (2 * section["level"]) result += '''%s<a href="#%s">%s</a>\n''' % ( indent, section["anchor"], util.safe_get(section, "title")) result += '</pre>\n' # post['htmlBody'] is HTML without the table of contents anchors added # in, so we have to use a separate HTML provided by the # tableOfContents JSON result += util.cleanHtmlBody( util.substitute_alt_links( util.safe_get(post, ['tableOfContents', 'html']))) else: result += util.cleanHtmlBody( util.substitute_alt_links(post['htmlBody'])) if util.safe_get(post, "question"): result += '<h2 id="answers">Answers</h2>' for answer in answers: result += show_answer(answer) if util.safe_get(post, "question") and util.safe_get( post, ["tableOfContents", "sections"]): result += '''<h2 id="comments">''' + util.safe_get( post, ["tableOfContents", "sections"])[-1]["title"] + '</h2>\n' else: result += '''<h2 id="comments">''' + str( post['commentCount']) + ' comments</h2>' # map of commentId -> list of commentIds that have replied reply_graph = {} for comment in comments: commentid = util.safe_get(comment, ['_id']) parentid = util.safe_get(comment, ['parentCommentId']) if parentid: if parentid in reply_graph: reply_graph[parentid].append(commentid) else: reply_graph[parentid] = [commentid] for comment in comments: color = config.COMMENT_COLOR commentid = comment['_id'] result += "<div>" # this extra outer div will ensure that two very short comments aren't displayed side-by-side result += ( '''<div id="%s" style="border: 1px solid #B3B3B3; padding-left: 15px; padding-right: 15px; padding-bottom: 10px; padding-top: 10px; margin-left: 0px; margin-right: -1px; margin-bottom: 0px; margin-top: 10px; background-color: %s; display: inline-block;">''' % (commentid, color)) result += '<span style="color: #117743; font-weight: 700;">Anonymous</span> ' result += " ·\n" result += (('''<a href="#%s">''' % commentid) + comment['postedAt'] + "</a> · ") if "lesswrong" in config.GRAPHQL_URL: result += ('<a title="Official LessWrong 2.0 link" href="' + comment['pageUrl'] + '">LW</a> · ') else: result += ('<a title="Official EA Forum link" href="' + comment['pageUrl'] + '">EA</a> · ') result += '<a title="GreaterWrong link" href="' + util.official_url_to_gw( comment['pageUrl']) + '">GW</a>' # This comment has replies, so show their IDs if commentid in reply_graph: for reply in reply_graph[commentid]: result += ''' <a href="#%s" onmouseover="showComment(this, '%s')" onmouseout="removeComment('%s')">>>%s</a>''' % ( reply, reply, reply, reply) result += '<br/>' if util.safe_get(comment, ['parentCommentId']): result += '''<br/><a href="#%s" id="%s" class="reply-parent" onmouseover="showComment(this, '%s')" onmouseout="removeComment('%s')">>>%s</a><br/>''' % ( util.safe_get(comment, ['parentCommentId']), util.safe_get(comment, ['parentCommentId']), util.safe_get(comment, ['parentCommentId']), util.safe_get(comment, ['parentCommentId']), util.safe_get(comment, ['parentCommentId'])) result += util.cleanHtmlBody( util.substitute_alt_links(comment['htmlBody'])) result += "</div></div>" result += (""" </div> </div> <script> function showComment(pointer, commentid) { var comment = document.getElementById(commentid); var clone = comment.cloneNode(true); var rect = pointer.getBoundingClientRect(); clone.id = "cloned-" + commentid; clone.style.position = 'absolute'; clone.style.top = (window.pageYOffset + rect.top) + 'px'; if (window.innerWidth - rect.right < 100) { clone.style.right = rect.left + 'px'; } else { clone.style.left = rect.right + 'px'; } document.body.appendChild(clone); } function removeComment(commentid) { var clone = document.getElementById("cloned-" + commentid); clone.parentNode.removeChild(clone); } </script> </body> </html> """) return result
def html_page_for_user(username, display_format): run_query = False if display_format == "queries" else True comments = get_comments_for_user(username, run_query=run_query) posts = get_posts_for_user(username, run_query=run_query) user_info = query_user_info(username, run_query=run_query) if display_format == "queries": result = "<pre>" result += comments + "\n" result += posts + "\n" result += user_info + "\n" result += "</pre>\n" return result result = """<!DOCTYPE html> <html> """ result += util.show_head(username) result += "<body>" feed_link = '''<a href="%s">Feed</a>''' % linkpath.users( userslug=username, display_format="rss") result += util.show_navbar(navlinks=[ feed_link, '''<a href="%s" title="Show all the GraphQL queries used to generate this page">Queries</a>''' % linkpath.users(userslug=util.htmlescape(username), display_format="queries") ]) result += '''<div id="wrapper">''' result += '''<div id="sidebar">''' result += '<p>' if "lesswrong" in config.GRAPHQL_URL: result += ( '<a title="Official LessWrong 2.0 link" href="https://www.lesswrong.com/users/' + username + '">LW</a> · ') result += ( '<a title="GreaterWrong link" href="https://www.greaterwrong.com/users/' + username + '">GW</a>') else: result += ( '<a title="Official EA Forum link" href="https://forum.effectivealtruism.org/users/' + username + '">EA</a> · ') result += ( '<a title="GreaterWrong link" href="https://ea.greaterwrong.com/users/' + username + '">GW</a>') result += '</p>' result += '''<h2>User info</h2>''' result += ''' <dl>''' if "displayName" in user_info and user_info["displayName"]: result += ''' <dt>Display name</dt>''' result += ''' <dd>%s</dd>''' % user_info["displayName"] if "karma" in user_info and user_info["karma"]: result += ''' <dt>Karma</dt>''' result += ''' <dd>%s</dd>''' % user_info["karma"] if "location" in user_info and user_info["location"]: result += ''' <dt>Location</dt>''' result += ''' <dd>%s</dd>''' % user_info["location"] if "htmlBio" in user_info and user_info["htmlBio"]: result += ''' <dt>Biography</dt>''' result += ''' <dd>%s</dd>''' % user_info["htmlBio"] if "postCount" in user_info and user_info["postCount"]: result += ''' <dt>Post count</dt>''' result += ''' <dd>%s</dd>''' % user_info["postCount"] if "commentCount" in user_info and user_info["commentCount"]: result += ''' <dt>Comment count</dt>''' result += ''' <dd>%s</dd>''' % user_info["commentCount"] if "website" in user_info and user_info["website"]: result += ''' <dt>Website</dt>''' result += ''' <dd><a href="%s">%s</a></dd>''' % ( user_info["website"], user_info["website"]) result += ''' </dl>''' result += '''</div>''' # closes sidebar result += '''<div id="content">''' all_content = [] all_content.extend(comments) all_content.extend(posts) all_content = sorted(all_content, key=lambda x: x['postedAt'], reverse=True) for content in all_content: content_type = "post" if "title" in content else "comment" result += '''<div style="border: 1px solid #B3B3B3; margin-bottom: 15px; padding: 10px; background-color: %s;">\n''' % config.COMMENT_COLOR if content_type == "post": result += ''' <h2><a href="%s">%s</a></h2>\n''' % ( linkpath.posts(postid=content['_id'], postslug=content['slug']), util.htmlescape(content['title'])) result += ''' %s · score: %s (%s votes)\n''' % ( content['postedAt'], content['baseScore'], content['voteCount']) else: if content['post'] is None: postslug = content['pageUrl'].split('/')[-1].split('#')[0] result += ''' <a href="%s#%s">Comment</a> by <b>%s</b> on [deleted post]</b>\n''' % ( linkpath.posts(postid=content['postId'], postslug=postslug), content['_id'], content['user']['username']) result += ''' %s\n''' % content['postedAt'] else: if "lesswrong" in config.GRAPHQL_URL: official_link = '''<a href="%s" title="Official LessWrong 2.0 link">LW</a>''' % content[ 'pageUrl'] else: official_link = '''<a href="%s" title="Official EA Forum link">EA</a>''' % content[ 'pageUrl'] result += ( ''' Comment by <b>%s</b> on <a href="%s">%s</a></b> · <a href="%s#%s">%s</a> · score: %s (%s votes) · %s · <a href="%s" title="GreaterWrong link">GW</a>''' % (username, linkpath.posts(postid=content['postId'], postslug=content['post']['slug']), util.htmlescape(content['post']['title']), linkpath.posts(postid=content['postId'], postslug=content['post']['slug']), content['_id'], content['postedAt'], content['baseScore'], content['voteCount'], official_link, util.official_url_to_gw(content['pageUrl']))) content_body = util.cleanHtmlBody(content['htmlBody']) result += ''' %s\n''' % content_body result += "</div>\n" result += ''' </div> </div> </body> </html> ''' return result
def show_daily_posts(offset, view, before, after, display_format): posts = posts_list_query(offset=offset, view=view, before=before, after=after, run_query=(False if display_format == "queries" else True)) recent_comments = recent_comments_query(run_query=(False if display_format == "queries" else True)) if display_format == "queries": result = "<pre>" result += posts + "\n" # this is just the query string result += recent_comments + "\n" result += "</pre>\n" return result result = """<!DOCTYPE html> <html> """ result += util.show_head(config.TITLE) result += "<body>\n" result += util.show_navbar(navlinks=[ '''<a href="/?view=%s&offset=%s&before=%s&after=%s&format=queries" title="Show all the GraphQL queries used to generate this page">Queries</a>''' % (view, offset, before, after) ]) result += '''<div id="wrapper">''' result += ''' <div id="sidebar"> <h2>Archive</h2> <ul> ''' start_year = 2006 if "lesswrong" in config.GRAPHQL_URL else 2011 for year in range(start_year, datetime.datetime.utcnow().year + 1): result += "<li>\n" result += '''<a href="/?view=%s&before=%s&after=%s">%s</a>''' % ( view, str(year) + "-12-31", str(year) + "-01-01", year ) if str(year) == after[:4] and str(year) == before[:4]: # If we are here, it means we are viewing the "archive" for this # year (or a month within this year), so show the months in the # sidebar so that we can go inside the months. result += "<ul>" for month in range(1, 12 + 1): if month == 12: last_day = datetime.date(year + 1, 1, 1) - datetime.timedelta(days=1) else: last_day = datetime.date(year, month + 1, 1) - datetime.timedelta(days=1) result += '''<li><a href="/?view=%s&before=%s&after=%s">%s</a></li>''' % ( view, last_day.strftime("%Y-%m-%d"), str(year) + "-" + str(month).zfill(2) + "-01", datetime.date(2000, month, 1).strftime("%B") ) result += "</ul>" result += "</li>\n" result += "</ul>" result += '''<h2>Recent comments</h2>''' for comment in recent_comments: post = comment['post'] if post is None: post = {'slug': comment['pageUrl'].split('/')[-1].split('#')[0], 'title': '[deleted]'} result += (''' <a href="%s">%s</a> on <a href="%s#%s">%s</a><br/> <span style="font-size: 14px;"> %s </span> ''' % ( linkpath.users(userslug=util.strong_multiget(comment, ['user', 'slug'], "")), util.strong_multiget(comment, ['user', 'slug'], ""), linkpath.posts(postid=comment['postId'], postslug=post['slug']), comment['_id'], util.htmlescape(post['title']), comment['htmlBody'] ) ) result += "</div>" # sidebar result += '''<div id="content">''' result += """<h1><a href="/">%s</a></h1>""" % config.TITLE result += ''' View: <a href="/?view=new">New</a> · <a href="/?view=old">Old</a> · <a href="/?view=top">Top</a> <br /><br /> ''' if view == "top": result += (''' Restrict date range: <a href="/?view=top&after=%s">Today</a> · <a href="/?view=top&after=%s">This week</a> · <a href="/?view=top&after=%s">This month</a> · <a href="/?view=top&after=%s">Last three months</a> · <a href="/?view=top&after=%s">This year</a> · <a href="/?view=top">All time</a> <br /> <br /> ''' % ( # Today's posts are all posts after yesterday's date (datetime.datetime.utcnow() - datetime.timedelta(days=1)).strftime("%Y-%m-%d"), # This week (datetime.datetime.utcnow() - datetime.timedelta(days=7)).strftime("%Y-%m-%d"), # This month (datetime.datetime.utcnow() - datetime.timedelta(days=30)).strftime("%Y-%m-%d"), # Last three months (datetime.datetime.utcnow() - datetime.timedelta(days=90)).strftime("%Y-%m-%d"), # This year (datetime.datetime.utcnow() - datetime.timedelta(days=365)).strftime("%Y-%m-%d"), ) ) date_range_params = "" if before: date_range_params += "&before=%s" % before if after: date_range_params += "&after=%s" % after if offset - 50 >= 0: result += '''<a href="/?view=%s&offset=%s%s">← previous page (newer posts)</a> · ''' % (view, offset - 50, date_range_params) result += '''<a href="/?view=%s&offset=%s%s">next page (older posts) →</a>''' % (view, offset + 50, date_range_params) result += '''<br/><br/>\n''' for post in posts: post_url = linkpath.posts(postid=post['_id'], postslug=post['slug']) result += ('''<div style="margin-bottom: 15px;">\n''') if "lesswrong" in config.GRAPHQL_URL: result += "[meta] " if post['meta'] else "" else: result += "[community] " if post['meta'] else "" if "question" in post and post["question"]: result += "[question] " if "url" in post and post["url"]: result += '''[<a href="%s">link</a>] ''' % post["url"] result += ((''' <a href="%s">''' % post_url) + util.htmlescape(post['title']) + "</a><br />\n") result += util.userlink(slug=util.strong_multiget(post, ['user', 'slug']), username=util.strong_multiget(post, ['user', 'username']), display_name=util.strong_multiget(post, ['user', 'displayName'])) result += " ·\n" result += post['postedAt'] + " ·\n" result += '''score: %s (%s votes) ·\n''' % (post['baseScore'], post['voteCount']) result += (''' <a href="%s#comments">comments (%s)</a>\n''' % (post_url, post['commentsCount'])) result += ("</div>") if offset - 50 >= 0: result += '''<a href="/?view=%s&offset=%s%s">← previous page (newer posts)</a> · ''' % (view, offset - 50, date_range_params) result += '''<a href="/?view=%s&offset=%s%s">next page (older posts) →</a>''' % (view, offset + 50, date_range_params) result += """ </div> </div> </body> </html> """ return result
def show_post_and_comment_thread(postid, display_format): print("""<!DOCTYPE html> <html> """) run_query = False if display_format == "queries" else True post = get_content_for_post(postid, run_query=run_query) if run_query: post_date = util.safe_get(post, 'postedAt', default="2018-01-01") else: post_date = "2018-01-01" # Apparently post_date is sometimes the empty string, so we have to check again if not post_date: post_date = "2018-01-01" if (run_query and "lesswrong" in config.GRAPHQL_URL and datetime.datetime.strptime(post_date[:len("2018-01-01")], "%Y-%m-%d") < datetime.datetime( 2009, 2, 27)): comments = get_comments_for_post(postid, view="postCommentsOld", run_query=run_query) sorting_text = "oldest first, as this post is from before comment nesting was available (around 2009-02-27)." else: comments = get_comments_for_post(postid, run_query=run_query) sorting_text = "top scores." if (not run_query) or util.safe_get(post, "question"): answers = query_question_answers(postid, run_query=run_query) if display_format == "queries": result = "<pre>" result += post + "\n" result += comments + "\n" result += answers + "\n" result += "</pre>\n" return result if "user" in post and post["user"] and "slug" in post["user"] and post[ "user"]["slug"]: author = post['user']['slug'] else: author = None canonical_url = util.safe_get(post, 'pageUrl') if util.safe_get(post, 'canonicalSource'): canonical_url = util.safe_get(post, 'canonicalSource') result = util.show_head( title=post['title'], canonical_url=canonical_url, author=author if author is not None else "[deleted]", date=post['postedAt'], publisher="LessWrong 2.0" if "lesswrong" in config.GRAPHQL_URL else "Effective Altruism Forum") result += "<body>\n" result += util.show_navbar(navlinks=[ '''<a href="%s" title="Show all the GraphQL queries used to generate this page">Queries</a>''' % linkpath.posts(postid=util.htmlescape(postid), postslug=post['slug'], display_format="queries") ]) result += '''<div id="wrapper">''' result += '''<div id="content">''' result += "<h1>" + util.htmlescape(post['title']) + "</h1>\n" result += "post by " result += util.userlink(slug=post.get("user", {}).get("slug", None), username=post.get("user", {}).get("username", None), display_name=post.get("user", {}).get("displayName", None), bio=util.safe_get(post, ['user', 'bio'])) for coauthor in util.safe_get(post, "coauthors", []): result += ", " + util.userlink( slug=util.safe_get(coauthor, "slug"), username=util.safe_get(coauthor, "username"), display_name=util.safe_get(coauthor, "displayName")) result += " ·\n" result += '''%s ·\n''' % post['postedAt'] result += util.grouped_links(util.alt_urls(post['pageUrl'])) + " ·\n" if post['legacyId'] is not None: result += '''<a href="%s" title="Legacy link">Legacy</a> ·\n''' % util.legacy_link( post['legacyId']) if util.safe_get(post, "question") and util.safe_get( post, ["tableOfContents", "sections"]): result += '''<a href="#comments">''' + util.safe_get( post, ["tableOfContents", "sections"])[-1]["title"] + '</a>\n' else: result += '''<a href="#comments">''' + str( post['commentCount']) + ' comments</a>\n' if post['url'] is not None: result += (''' <p>This is a link post for <a href="%s">%s</a></p> ''' % (post['url'], post['url'])) if "question" in post and post["question"]: result += "<p>This is a question post.</p>" if "tableOfContents" in post and post[ "tableOfContents"] and "sections" in post["tableOfContents"]: if post["tableOfContents"]["sections"]: result += '''<h2>Contents</h2>\n''' result += '<pre style="font-size: 12px;">\n' for section in post["tableOfContents"]["sections"]: # I think I was using this to get rid of the score (which # always appeared as the first word of level 2 headings on # question posts). But doing so without checking that a post is # a question post results in erroneously removing the first # word of standard level 2 headings. As I write this # (2021-11-21), question posts don't even seem to come with # answer TOCs anymore, so this is kind of useless. ans_title = util.safe_get(section, "title") # if ans_title and section["level"] == 2: # ans_title = " ".join(ans_title.split()[1:]) indent = " " * (2 * section["level"]) result += '''%s<a href="#%s">%s</a>\n''' % ( indent, section["anchor"], ans_title) result += '</pre>\n' # post['htmlBody'] is HTML without the table of contents anchors added # in, so we have to use a separate HTML provided by the # tableOfContents JSON result += util.cleanHtmlBody( util.substitute_alt_links( util.safe_get(post, ['tableOfContents', 'html']))) else: result += util.cleanHtmlBody( util.substitute_alt_links(post['htmlBody'])) if util.safe_get(post, "question"): result += '<h2 id="answers">Answers</h2>' for answer in answers: result += show_answer(answer) if util.safe_get(post, "question") and util.safe_get( post, ["tableOfContents", "sections"]): result += '''<h2 id="comments">''' + util.safe_get( post, ["tableOfContents", "sections"])[-1]["title"] + '</h2>\n' else: result += '''<h2 id="comments">''' + str( post['commentCount']) + ' comments</h2>' result += "<p>Comments sorted by %s</p>" % sorting_text root = build_comment_thread(comments) result += show_comment(root) result += (""" </div> </div> </body> </html> """) return result