async def get_discussions_by_blog(context, tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Retrieve account's blog posts, including reblogs.""" assert not filter_tags, 'filter_tags not supported' tag = valid_account(tag) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) limit = valid_limit(limit, 100, 20) truncate_body = valid_truncate(truncate_body) sql = "SELECT * FROM bridge_get_account_posts_by_blog( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER, False )" db = context['db'] result = await db.query_all(sql, account=tag, author=start_author, permlink=start_permlink, limit=limit) posts_by_id = [] for row in result: row = dict(row) post = _condenser_post_object(row, truncate_body=truncate_body) post['active_votes'] = await find_votes_impl( db, post['author'], post['permlink'], VotesPresentation.CondenserApi) posts_by_id.append(post) return posts_by_id
async def list_communities(context, last='', limit=100, query=None, sort='rank', observer=None): """List all communities, paginated. Returns lite community list.""" # pylint: disable=too-many-arguments, too-many-locals last = valid_community(last, True) limit = valid_limit(limit, 100, 100) supported_sort_list = ['rank', 'new', 'subs'] assert sort in supported_sort_list, "Unsupported sort, valid sorts: {}".format( ", ".join(supported_sort_list)) observer = valid_account(observer, True) search = query db = context['db'] sql = "SELECT * FROM bridge_list_communities_by_" + \ sort + "( (:observer)::VARCHAR, (:last)::VARCHAR, (:search)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, observer=observer, last=last, search=search, limit=limit) return remove_empty_admins_field(rows)
async def list_pop_communities(context, limit: int = 25): """List communities by new subscriber count. Returns lite community list.""" limit = valid_limit(limit, 25, 25) sql = "SELECT * FROM bridge_list_pop_communities( (:limit)::INT )" out = await context['db'].query_all(sql, limit=limit) return [(r[0], r[1]) for r in out]
async def get_discussions_by_comments(context, start_author: str, start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Get comments by made by author.""" assert not filter_tags, 'filter_tags not supported' start_author = valid_account(start_author) start_permlink = valid_permlink(start_permlink, allow_empty=True) limit = valid_limit(limit, 100, 20) truncate_body = valid_truncate(truncate_body) posts = [] db = context['db'] sql = "SELECT * FROM bridge_get_account_posts_by_comments( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" result = await db.query_all(sql, account=start_author, author=start_author if start_permlink else '', permlink=start_permlink, limit=limit) for row in result: row = dict(row) post = _condenser_post_object(row, truncate_body=truncate_body) post['active_votes'] = await find_votes_impl( db, post['author'], post['permlink'], VotesPresentation.CondenserApi) posts.append(post) return posts
async def get_payout_stats(context, limit=250): """Get payout stats for building treemap.""" db = context['db'] limit = valid_limit(limit, 250, 250) sql = """ SELECT hc.name, hc.title, author, payout, posts, authors FROM payout_stats_view LEFT JOIN hive_communities hc ON hc.id = community_id WHERE (community_id IS NULL AND author IS NOT NULL) OR (community_id IS NOT NULL AND author IS NULL) ORDER BY payout DESC LIMIT :limit """ rows = await db.query_all(sql, limit=limit) items = list(map(_row, rows)) sql = """SELECT SUM(payout) FROM payout_stats_view WHERE author IS NULL""" total = await db.query_one(sql) sql = """SELECT SUM(payout) FROM payout_stats_view WHERE community_id IS NULL AND author IS NULL""" blog_ttl = await db.query_one(sql) return dict(items=items, total=float(total if total is not None else 0.), blogs=float(blog_ttl if blog_ttl is not None else 0.))
async def list_subscribers(context, community, last='', limit=100): """Lists subscribers of `community`.""" community = valid_community(community) last = valid_account(last, True) limit = valid_limit(limit, 100, 100) db = context['db'] sql = "SELECT * FROM bridge_list_subscribers( (:community)::VARCHAR, (:last)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, community=community, last=last, limit=limit) return [(r[0], r[1], r[2], json_date(r[3])) for r in rows]
async def get_replies_by_last_update(context, start_author: str = None, start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Get all replies made to any of author's posts.""" assert start_author, '`start_author` cannot be blank' ids = await cursor.pids_by_replies_to_account( context['db'], valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts(context['db'], ids, truncate_body=truncate_body)
async def get_account_reputations(context, account_lower_bound: str = None, limit: int = None): """List account reputations""" return { 'reputations': await cursor.get_account_reputations(context['db'], account_lower_bound, valid_limit(limit, 1000)) }
async def list_community_roles(context, community, last='', limit=50): """List community account-roles (anyone with non-guest status).""" db = context['db'] community = valid_community(community) last = valid_account(last, True) limit = valid_limit(limit, 1000, 50) sql = "SELECT * FROM bridge_list_community_roles( (:community)::VARCHAR, (:last)::VARCHAR, (:limit)::INT )" rows = await db.query_all(sql, community=community, last=last, limit=limit) return [(r['name'], r['role'], r['title']) for r in rows]
async def get_post_discussions_by_payout(context, start_author: str = '', start_permlink: str = '', limit: int = 20, tag: str = None, truncate_body: int = 0): """Query top-level posts, sorted by payout.""" ids = await cursor.pids_by_query( context['db'], 'payout', valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100), valid_tag(tag, allow_empty=True)) return await load_posts(context['db'], ids, truncate_body=truncate_body)
async def get_account_posts(context, sort, account, start_author='', start_permlink='', limit=20, observer=None): """Get posts for an account -- blog, feed, comments, or replies.""" valid_sorts = ['blog', 'feed', 'posts', 'comments', 'replies', 'payout'] assert sort in valid_sorts, 'invalid account sort' assert account, 'account is required' db = context['db'] account = valid_account(account) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) start = (start_author, start_permlink) limit = valid_limit(limit, 100) # pylint: disable=unused-variable observer_id = await get_account_id(db, observer) if observer else None # TODO if sort == 'blog': ids = await cursor.pids_by_blog(db, account, *start, limit) posts = await load_posts(context['db'], ids) for post in posts: if post['author'] != account: post['reblogged_by'] = [account] return posts elif sort == 'feed': res = await cursor.pids_by_feed_with_reblog(db, account, *start, limit) return await load_posts_reblogs(context['db'], res) elif sort == 'posts': start = start if start_permlink else (account, None) assert account == start[ 0], 'comments - account must match start author' ids = await cursor.pids_by_posts(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'comments': start = start if start_permlink else (account, None) assert account == start[ 0], 'comments - account must match start author' ids = await cursor.pids_by_comments(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'replies': start = start if start_permlink else (account, None) ids = await cursor.pids_by_replies(db, *start, limit) return await load_posts(context['db'], ids) elif sort == 'payout': start = start if start_permlink else (account, None) ids = await cursor.pids_by_payout(db, account, *start, limit) return await load_posts(context['db'], ids)
async def get_discussions_by_comments(context, start_author: str = None, start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Get comments by made by author.""" assert start_author, '`start_author` cannot be blank' assert not filter_tags, 'filter_tags not supported' ids = await cursor.pids_by_account_comments( context['db'], valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts(context['db'], ids, truncate_body=truncate_body)
async def get_discussions_by_created(context, start_author: str = '', start_permlink: str = '', limit: int = 20, tag: str = None, truncate_body: int = 0, filter_tags: list = None): """Query posts, sorted by creation date.""" assert not filter_tags, 'filter_tags not supported' ids = await cursor.pids_by_query( context['db'], 'created', valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100), valid_tag(tag, allow_empty=True)) return await load_posts(context['db'], ids, truncate_body=truncate_body)
async def get_discussions_by_feed(context, tag: str = None, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None): """Retrieve account's personalized feed.""" assert tag, '`tag` cannot be blank' assert not filter_tags, 'filter_tags not supported' res = await cursor.pids_by_feed_with_reblog( context['db'], valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts_reblogs(context['db'], res, truncate_body=truncate_body)
async def list_top_communities(context, limit=25): """List top communities. Returns lite community list.""" limit = valid_limit(limit, 100, 25) sql = """SELECT hc.name, hc.title FROM hive_communities hc WHERE hc.rank > 0 ORDER BY hc.rank LIMIT :limit""" #ABW: restored older version since hardcoded id is out of the question #sql = """SELECT name, title FROM hive_communities # WHERE id = 1344247 OR rank > 0 # ORDER BY (CASE WHEN id = 1344247 THEN 0 ELSE rank END) # LIMIT :limit""" out = await context['db'].query_all(sql, limit=limit) return [(r[0], r[1]) for r in out]
async def get_account_posts(context, sort:str, account:str, start_author:str='', start_permlink:str='', limit:int=20, observer:str=None): """Get posts for an account -- blog, feed, comments, or replies.""" supported_sort_list = ['blog', 'feed', 'posts', 'comments', 'replies', 'payout'] assert sort in supported_sort_list, "Unsupported sort, valid sorts: {}".format(", ".join(supported_sort_list)) db = context['db'] account = valid_account(account) start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) observer = valid_account(observer, allow_empty=True) limit = valid_limit(limit, 100, 20) sql = None account_posts = True # set when only posts (or reblogs) of given account are supposed to be in results if sort == 'blog': sql = "SELECT * FROM bridge_get_account_posts_by_blog( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER, True )" elif sort == 'feed': sql = "SELECT * FROM bridge_get_by_feed_with_reblog((:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::INTEGER)" elif sort == 'posts': sql = "SELECT * FROM bridge_get_account_posts_by_posts( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" elif sort == 'comments': sql = "SELECT * FROM bridge_get_account_posts_by_comments( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" elif sort == 'replies': account_posts = False sql = "SELECT * FROM bridge_get_account_posts_by_replies( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT, True )" elif sort == 'payout': sql = "SELECT * FROM bridge_get_account_posts_by_payout( (:account)::VARCHAR, (:author)::VARCHAR, (:permlink)::VARCHAR, (:limit)::SMALLINT )" sql_result = await db.query_all(sql, account=account, author=start_author, permlink=start_permlink, limit=limit ) posts = [] for row in sql_result: post = _bridge_post_object(row) post['active_votes'] = await find_votes_impl(db, row['author'], row['permlink'], VotesPresentation.BridgeApi) if sort == 'blog': if post['author'] != account: post['reblogged_by'] = [account] elif sort == 'feed': reblogged_by = set(row['reblogged_by']) reblogged_by.discard(row['author']) # Eliminate original author of reblogged post if reblogged_by: reblogged_by_list = list(reblogged_by) reblogged_by_list.sort() post['reblogged_by'] = reblogged_by_list post = append_statistics_to_post(post, row, False if account_posts else row['is_pinned']) posts.append(post) return posts
async def get_following(context, account: str, start: str, follow_type: str = None, limit: int = None, **kwargs): """Get all accounts `account` follows. (EOL)""" # `type` reserved word workaround if not follow_type and 'type' in kwargs: follow_type = kwargs['type'] if not follow_type: follow_type = 'blog' following = await cursor.get_following( context['db'], valid_account(account), valid_account(start, allow_empty=True), valid_follow_type(follow_type), valid_limit(limit, 1000)) return [_legacy_follower(account, name, follow_type) for name in following]
async def get_discussions_by_feed(context, tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0, filter_tags: list = None, observer: str = None): """Retrieve account's personalized feed.""" assert not filter_tags, 'filter_tags not supported' return await get_discussions_by_feed_impl( context['db'], valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100, 20), valid_truncate(truncate_body), observer)
async def get_discussions_by_author_before_date(context, author: str = None, start_permlink: str = '', before_date: str = '', limit: int = 10): """Retrieve account's blog posts, without reblogs. NOTE: before_date is completely ignored, and it appears to be broken and/or completely ignored in steemd as well. This call is similar to get_discussions_by_blog but does NOT serve reblogs. """ # pylint: disable=invalid-name,unused-argument assert author, '`author` cannot be blank' ids = await cursor.pids_by_blog_without_reblog( context['db'], valid_account(author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return await load_posts(context['db'], ids)
async def get_trending_topics(context, limit:int=10, observer:str=None): """Return top trending topics across pending posts.""" # pylint: disable=unused-argument #db = context['db'] #observer_id = await get_account_id(db, observer) if observer else None #assert not observer, 'observer not supported' limit = valid_limit(limit, 25, 10) out = [] cells = await list_top_communities(context, limit) for name, title in cells: out.append((name, title or name)) for tag in ('photography', 'travel', 'gaming', 'crypto', 'newsteem', 'music', 'food'): if len(out) < limit: out.append((tag, '#' + tag)) return out
async def get_replies_by_last_update(context, start_author: str, start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Get all replies made to any of author's posts.""" # despite the name time of last edit is not used, posts ranked by creation time (that is, their id) # note that in this call start_author has dual meaning: # - when there is only start_author it means account that authored posts that we seek replies to # - when there is also start_permlink it points to one of replies (last post of previous page) and # we'll be getting account like above in form of author of parent post to the post pointed by # given start_author+start_permlink return await cursor.get_by_replies_to_account( context['db'], valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100, 20), valid_truncate(truncate_body))
async def get_blog(context, account: str, start_entry_id: int = 0, limit: int = None): """Get posts for an author's blog (w/ reblogs), paged by index/limit. Equivalent to get_discussions_by_blog, but uses offset-based pagination. Examples: (ABW: old description and examples were misleading as in many cases code worked differently, also now more cases actually work that gave error earlier) (acct, -1, limit) for limit 1..500 - returns latest (no more than) limit posts (acct, 0) - returns latest single post (ABW: this is a bug but I left it here because I'm afraid it was actively used - it should return oldest post) (acct, 0, limit) for limit 1..500 - same as (acct, -1, limit) - see above (acct, last_idx) for positive last_idx - returns last_idx oldest posts, or posts in range [last_idx..last_idx-500) when last_idx >= 500 (acct, last_idx, limit) for positive last_idx and limit 1..500 - returns posts in range [last_idx..last_idx-limit) """ db = context['db'] account = valid_account(account) if not start_entry_id: start_entry_id = -1 start_entry_id = valid_offset(start_entry_id) if not limit: limit = max(start_entry_id + 1, 1) limit = min(limit, 500) limit = valid_limit(limit, 500, None) sql = "SELECT * FROM condenser_get_blog(:account, :last, :limit)" result = await db.query_all(sql, account=account, last=start_entry_id, limit=limit) out = [] for row in result: row = dict(row) post = _condenser_post_object(row) post['active_votes'] = await find_votes_impl( db, row['author'], row['permlink'], VotesPresentation.CondenserApi) out.append({ "blog": account, "entry_id": row['entry_id'], "comment": post, "reblogged_on": json_date(row['reblogged_at']) }) return list(reversed(out))
async def _get_account_reputations_impl(db, fat_node_style, account_lower_bound, limit): """Enumerate account reputations.""" if not account_lower_bound: account_lower_bound = '' assert isinstance(account_lower_bound, str), "invalid account_lower_bound type" limit = valid_limit(limit, 1000, 1000) sql = "SELECT * FROM condenser_get_account_reputations( (:start)::VARCHAR, :limit )" rows = await db.query_all(sql, start=account_lower_bound, limit=limit) if fat_node_style: return [dict(account=r[0], reputation=r[1]) for r in rows] else: return { 'reputations': [dict(name=r[0], reputation=r[1]) for r in rows] }
async def get_trending_tags(context, start_tag: str = '', limit: int = 250): """Get top 250 trending tags among pending posts, with stats.""" limit = valid_limit(limit, 250, 250) start_tag = valid_tag(start_tag, allow_empty=True) sql = "SELECT * FROM condenser_get_trending_tags( (:tag)::VARCHAR, :limit )" out = [] for row in await context['db'].query_all(sql, limit=limit, tag=start_tag): out.append({ 'name': row['category'], 'comments': row['total_posts'] - row['top_posts'], 'top_posts': row['top_posts'], 'total_payouts': "%.3f HBD" % row['total_payouts']}) return out
async def list_votes(context, start: list, limit: int = 1000, order: str = None): """ Returns all votes, starting with the specified voter and/or author and permlink. """ supported_order_list = ["by_comment_voter", "by_voter_comment"] assert not order is None, "missing a required argument: 'order'" assert order in supported_order_list, "Unsupported order, valid orders: {}".format( ", ".join(supported_order_list)) limit = valid_limit(limit, 1000, 1000) db = context['db'] if order == "by_voter_comment": assert len( start ) == 3, "Expecting 3 arguments in 'start' array: voter, optional page start author and permlink" voter = start[0] valid_account(voter) start_post_author = start[1] valid_account(start_post_author, allow_empty=True) start_post_permlink = start[2] valid_permlink(start_post_permlink, allow_empty=True) sql = "SELECT * FROM list_votes_by_voter_comment(:voter,:author,:permlink,:limit)" rows = await db.query_all(sql, voter=voter, author=start_post_author, permlink=start_post_permlink, limit=limit) else: assert len( start ) == 3, "Expecting 3 arguments in 'start' array: post author and permlink, optional page start voter" author = start[0] valid_account(author) permlink = start[1] valid_permlink(permlink) start_voter = start[2] valid_account(start_voter, allow_empty=True) sql = "SELECT * FROM list_votes_by_comment_voter(:voter,:author,:permlink,:limit)" rows = await db.query_all(sql, voter=start_voter, author=author, permlink=permlink, limit=limit) return {'votes': api_vote_info(rows, VotesPresentation.DatabaseApi)}
async def account_notifications(context, account, min_score=25, last_id=None, limit=100): """Load notifications for named account.""" db = context['db'] valid_account(account) min_score = valid_score(min_score, 100, 25) last_id = valid_number(last_id, 0, "last_id") limit = valid_limit(limit, 100, 100) sql_query = "SELECT * FROM account_notifications( (:account)::VARCHAR, (:min_score)::SMALLINT, (:last_id)::BIGINT, (:limit)::SMALLINT )" rows = await db.query_all(sql_query, account=account, min_score=min_score, last_id=last_id, limit=limit) return [_render(row) for row in rows]
async def get_trending_tags(context, start_tag: str = '', limit: int = 250): """Get top 250 trending tags among pending posts, with stats.""" limit = valid_limit(limit, ubound=250) start_tag = valid_tag(start_tag or '', allow_empty=True) if start_tag: seek = """ HAVING SUM(payout) <= ( SELECT SUM(payout) FROM hive_posts_cache WHERE is_paidout = '0' AND category = :start_tag) """ else: seek = '' sql = """ SELECT category, COUNT(*) AS total_posts, SUM(CASE WHEN depth = 0 THEN 1 ELSE 0 END) AS top_posts, SUM(payout) AS total_payouts FROM hive_posts_cache WHERE is_paidout = '0' GROUP BY category %s ORDER BY SUM(payout) DESC LIMIT :limit """ % seek out = [] for row in await context['db'].query_all(sql, limit=limit, start_tag=start_tag): out.append({ 'name': row['category'], 'comments': row['total_posts'] - row['top_posts'], 'top_posts': row['top_posts'], 'total_payouts': "%.3f SBD" % row['total_payouts'] }) return out
async def get_ranked_posts(context, sort, start_author='', start_permlink='', limit=20, tag=None, observer=None): """Query posts, sorted by given method.""" db = context['db'] observer_id = await get_account_id(db, observer) if observer else None assert sort in [ 'trending', 'hot', 'created', 'promoted', 'payout', 'payout_comments', 'muted' ], 'invalid sort' ids = await cursor.pids_by_ranked( context['db'], sort, valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100), valid_tag(tag, allow_empty=True), observer_id) return await load_posts(context['db'], ids)
async def get_blog_entries(context, account: str, start_entry_id: int = 0, limit: int = None): """Get 'entries' for an author's blog (w/ reblogs), paged by index/limit. Interface identical to get_blog, but returns minimalistic post references. """ db = context['db'] account = valid_account(account) if not start_entry_id: start_entry_id = -1 start_entry_id = valid_offset(start_entry_id) if not limit: limit = max(start_entry_id + 1, 1) limit = min(limit, 500) limit = valid_limit(limit, 500, None) sql = "SELECT * FROM condenser_get_blog_entries(:account, :last, :limit)" result = await db.query_all(sql, account=account, last=start_entry_id, limit=limit) out = [] for row in result: row = dict(row) out.append({ "blog": account, "entry_id": row['entry_id'], "author": row['author'], "permlink": row['permlink'], "reblogged_on": json_date(row['reblogged_at']) }) return list(reversed(out))
async def get_ranked_posts(context, sort:str, start_author:str='', start_permlink:str='', limit:int=20, tag:str='', observer:str=''): """Query posts, sorted by given method.""" supported_sort_list = ['trending', 'hot', 'created', 'promoted', 'payout', 'payout_comments', 'muted'] assert sort in supported_sort_list, "Unsupported sort, valid sorts: {}".format(", ".join(supported_sort_list)) db = context['db'] async def process_query_results( sql_result ): posts = [] for row in sql_result: post = _bridge_post_object(row) post['active_votes'] = await find_votes_impl(db, row['author'], row['permlink'], VotesPresentation.BridgeApi) post = append_statistics_to_post(post, row, row['is_pinned']) posts.append(post) return posts start_author = valid_account(start_author, allow_empty=True) start_permlink = valid_permlink(start_permlink, allow_empty=True) limit = valid_limit(limit, 100, 20) tag = valid_tag(tag, allow_empty=True) observer = valid_account(observer, allow_empty=(tag != "my")) if tag == "my": result = await _get_ranked_posts_for_observer_communities(db, sort, start_author, start_permlink, limit, observer) return await process_query_results(result) if tag and check_community(tag): result = await _get_ranked_posts_for_communities(db, sort, tag, start_author, start_permlink, limit, observer) return await process_query_results(result) if ( tag and tag != "all" ): result = await _get_ranked_posts_for_tag(db, sort, tag, start_author, start_permlink, limit, observer) return await process_query_results(result) result = await _get_ranked_posts_for_all(db, sort, start_author, start_permlink, limit, observer) return await process_query_results(result)