async def get_following(account: str, start: str, follow_type: str, limit: int): """Get all accounts `account` follows. (EOL)""" following = cursor.get_following( valid_account(account), valid_account(start or '', allow_empty=True), _follow_type_to_int(follow_type), valid_limit(limit, 1000)) return [_legacy_follower(account, name, follow_type) for name in following]
async def get_followers(account: str, start: str, follow_type: str, limit: int): """Get all accounts following `account`. (EOL)""" assert follow_type != 'ignore', 'no index for ignored-by' followers = cursor.get_followers( valid_account(account), valid_account(start or '', allow_empty=True), _follow_type_to_int(follow_type), valid_limit(limit, 1000)) return [_legacy_follower(name, account, follow_type) for name in followers]
async def get_content(author: str, permlink: str): """Get a single post object.""" valid_account(author) valid_permlink(permlink) post_id = get_post_id(author, permlink) if not post_id: return {'id': 0, 'author': '', 'permlink': ''} return load_posts([post_id])[0]
async def get_discussions_by_blog(tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Retrieve account's blog posts.""" ids = cursor.pids_by_blog( valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 20)) return load_posts(ids, truncate_body=truncate_body)
async def get_content_replies(parent: str, parent_permlink: str): """Get a list of post objects based on parent.""" valid_account(parent) valid_permlink(parent_permlink) parent_id = get_post_id(parent, parent_permlink) if parent_id: child_ids = get_child_ids(parent_id) if child_ids: return load_posts(child_ids) return []
async def get_discussions_by_blog(context, tag=None, start_author='', start_permlink='', limit=20): """Retrieve account's blog posts, including reblogs.""" assert tag, '`tag` cannot be blank' ids = await cursor.pids_by_blog( 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(context['db'], ids)
async def get_discussions_by_feed(context, tag=None, start_author='', start_permlink='', limit=20): """Retrieve account's personalized feed.""" assert tag, '`tag` cannot be blank' 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)
async def get_discussions_by_feed(tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Retrieve account's personalized feed.""" res = cursor.pids_by_feed_with_reblog( valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return load_posts_reblogs(res, truncate_body=truncate_body)
async def get_content_replies(context, author: str, permlink: str): """Get a list of post objects based on parent.""" db = context['db'] valid_account(author) valid_permlink(permlink) parent_id = await get_post_id(db, author, permlink) if parent_id: child_ids = await get_child_ids(db, parent_id) if child_ids: return await load_posts(db, child_ids) return []
async def get_content(context, author: str, permlink: str): """Get a single post object.""" db = context['db'] valid_account(author) valid_permlink(permlink) post_id = await get_post_id(db, author, permlink) if not post_id: return {'id': 0, 'author': '', 'permlink': ''} posts = await load_posts(db, [post_id]) assert posts, 'post was not found in cache' return posts[0]
def _get_blog(account: str, start_index: int, limit: int = None): """Get posts for an author's blog (w/ reblogs), paged by index/limit. Examples: (acct, 2) = returns blog entries 0 up to 2 (3 oldest) (acct, 0) = returns all blog entries (limit 0 means return all?) (acct, 2, 1) = returns 1 post starting at idx 2 (acct, 2, 3) = returns 3 posts: idxs (2,1,0) """ if not limit: limit = start_index + 1 ids = cursor.pids_by_blog_by_index(valid_account(account), valid_offset(start_index), valid_limit(limit, 500)) out = [] idx = int(start_index) for post in load_posts(ids): reblog = post['author'] != account reblog_on = post['created'] if reblog else "1970-01-01T00" out.append({ "blog": account, "entry_id": idx, "comment": post, "reblog_on": reblog_on }) idx -= 1 return out
async def get_follow_count(context, account: str): """Get follow count stats. (EOL)""" count = await cursor.get_follow_counts(context['db'], valid_account(account)) return dict(account=account, following_count=count['following'], follower_count=count['followers'])
async def get_discussions_by_blog(context, tag: str = None, 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 tag, '`tag` cannot be blank' assert not filter_tags, 'filter_tags not supported' ids = await cursor.pids_by_blog( 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(context['db'], ids, truncate_body=truncate_body)
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_comments(start_author: str, start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Get comments by made by author.""" ids = cursor.pids_by_account_comments( valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 20)) return load_posts(ids, truncate_body=truncate_body)
async def get_replies_by_last_update(start_author: str, start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Get all replies made to any of author's posts.""" ids = cursor.pids_by_replies_to_account( valid_account(start_author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 50)) return load_posts(ids, truncate_body=truncate_body)
async def get_replies_by_last_update(context, start_author=None, start_permlink='', limit=20): """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)
async def get_discussions_by_comments(context, start_author=None, start_permlink='', limit=20): """Get comments by made by author.""" assert start_author, '`start_author` cannot be blank' 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)
async def get_discussions_by_created(context, start_author='', start_permlink='', limit=20, tag=None): """Query posts, sorted by creation date.""" 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)
async def get_discussions_by_created(start_author: str = '', start_permlink: str = '', limit: int = 20, tag: str = None, truncate_body: int = 0): """Query posts, sorted by creation date.""" ids = cursor.pids_by_query( '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 load_posts(ids, truncate_body=truncate_body)
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_discussions_by_feed(tag: str, start_author: str = '', start_permlink: str = '', limit: int = 20, truncate_body: int = 0): """Retrieve account's personalized feed.""" res = cursor.pids_by_feed_with_reblog( valid_account(tag), valid_account(start_author, allow_empty=True), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 20)) reblogged_by = dict(res) posts = load_posts([r[0] for r in res], truncate_body=truncate_body) # Merge reblogged_by data into result set for post in posts: rby = set(reblogged_by[post['post_id']].split(',')) rby.discard(post['author']) if rby: post['reblogged_by'] = list(rby) return posts
def split_url(url, allow_empty=False): """Validate and split a post url into author/permlink.""" if not url: assert allow_empty, 'url must be specified' return None assert isinstance(url, str), 'url must be a string' parts = url.split('/') assert len(parts) == 2, 'invalid url parts' author = valid_account(parts[0]) permlink = valid_permlink(parts[1]) return (author, permlink)
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_hot(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 hot score.""" assert not filter_tags, 'filter_tags not supported' ids = await cursor.pids_by_query( context['db'], 'hot', 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_author_before_date(author: str, 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 ids = cursor.pids_by_blog_without_reblog( valid_account(author), valid_permlink(start_permlink, allow_empty=True), valid_limit(limit, 100)) return load_posts(ids)
async def get_reblogged_by(context, author: str, permlink: str): """Get all rebloggers of a post.""" return await cursor.get_reblogged_by(context['db'], valid_account(author), valid_permlink(permlink))
async def get_state(context, path: str): """`get_state` reimplementation. See: https://github.com/steemit/steem/blob/06e67bd4aea73391123eca99e1a22a8612b0c47e/libraries/app/database_api.cpp#L1937 """ (path, part) = _normalize_path(path) db = context['db'] state = { 'feed_price': await _get_feed_price(db), 'props': await _get_props_lite(db), 'tags': {}, 'accounts': {}, 'content': {}, 'tag_idx': { 'trending': [] }, 'discussion_idx': { "": {} } } # account - `/@account/tab` (feed, blog, comments, replies) if part[0] and part[0][0] == '@': assert not part[1] == 'transfers', 'transfers API not served here' assert not part[2], 'unexpected account path[2] %s' % path if part[1] == '': part[1] = 'blog' account = valid_account(part[0][1:]) state['accounts'][account] = await _load_account(db, account) if part[1] in ACCOUNT_TAB_KEYS: key = ACCOUNT_TAB_KEYS[part[1]] posts = await _get_account_discussion_by_key(db, account, key) state['content'] = _keyed_posts(posts) state['accounts'][account][key] = list(state['content'].keys()) elif part[1] in ACCOUNT_TAB_IGNORE: pass # condenser no-op URLs else: # invalid/undefined case; probably requesting `@user/permlink`, # but condenser still relies on a valid response for redirect. state['error'] = 'invalid get_state account path %s' % path # discussion - `/category/@account/permlink` elif part[1] and part[1][0] == '@': author = valid_account(part[1][1:]) permlink = valid_permlink(part[2]) state['content'] = await _load_discussion(db, author, permlink) state['accounts'] = await _load_content_accounts(db, state['content']) # ranked posts - `/sort/category` elif part[0] in POST_LIST_SORTS: assert not part[2], "unexpected discussion path part[2] %s" % path sort = valid_sort(part[0]) tag = valid_tag(part[1].lower(), allow_empty=True) pids = await cursor.pids_by_query(db, sort, '', '', 20, tag) state['content'] = _keyed_posts(await load_posts(db, pids)) state['discussion_idx'] = {tag: {sort: list(state['content'].keys())}} state['tag_idx'] = { 'trending': await get_top_trending_tags_summary(context) } # tag "explorer" - `/tags` elif part[0] == "tags": assert not part[1] and not part[2], 'invalid /tags request' for tag in await get_trending_tags(context): state['tag_idx']['trending'].append(tag['name']) state['tags'][tag['name']] = tag elif part[0] in CONDENSER_NOOP_URLS: assert not part[1] and not part[2] else: raise ApiError('unhandled path: /%s' % path) return state
async def get_state(path: str): """`get_state` reimplementation. See: https://github.com/steemit/steem/blob/06e67bd4aea73391123eca99e1a22a8612b0c47e/libraries/app/database_api.cpp#L1937 """ (path, part) = _normalize_path(path) state = { 'feed_price': _get_feed_price(), 'props': _get_props_lite(), 'tags': {}, 'accounts': {}, 'content': {}, 'tag_idx': { 'trending': [] }, 'discussion_idx': { "": {} } } # account - `/@account/tab` (feed, blog, comments, replies) if part[0] and part[0][0] == '@': assert not part[1] == 'transfers', 'transfers API not served here' assert not part[1] == 'blog', 'canonical blog route is `/@account`' assert not part[2], 'unexpected account path[2] %s' % path account = valid_account(part[0][1:]) state['accounts'][account] = _load_account(account) if part[1] not in ACCOUNT_TAB_IGNORE: assert part[1] in ACCOUNT_TAB_KEYS, "invalid acct path %s" % path key = ACCOUNT_TAB_KEYS[part[1]] posts = await _get_account_discussion_by_key(account, key) state['content'] = _keyed_posts(posts) state['accounts'][account][key] = list(state['content'].keys()) # discussion - `/category/@account/permlink` elif part[1] and part[1][0] == '@': author = valid_account(part[1][1:]) permlink = valid_permlink(part[2]) post_id = get_post_id(author, permlink) state['content'] = _load_posts_recursive([post_id]) if post_id else {} state['accounts'] = _load_content_accounts(state['content']) # ranked posts - `/sort/category` elif part[0] in ['trending', 'promoted', 'hot', 'created']: assert not part[2], "unexpected discussion path part[2] %s" % path sort = valid_sort(part[0]) tag = valid_tag(part[1].lower(), allow_empty=True) posts = load_posts(cursor.pids_by_query(sort, '', '', 20, tag)) state['content'] = _keyed_posts(posts) state['discussion_idx'] = {tag: {sort: list(state['content'].keys())}} state['tag_idx'] = {'trending': await get_top_trending_tags_summary()} # tag "explorer" - `/tags` elif part[0] == "tags": assert not part[1] and not part[2], 'invalid /tags request' for tag in await get_trending_tags(): state['tag_idx']['trending'].append(tag['name']) state['tags'][tag['name']] = tag elif part[0] == 'witnesses' or part[0] == '~witnesses': assert not part[1] and not part[2] raise Exception("not implemented: /%s" % path) elif part[0] in CONDENSER_NOOP_URLS: assert not part[1] and not part[2] else: print('unhandled path /%s' % path) return state