forked from LBRYCommunity/lbry-sdk
new comment and reactions api
This commit is contained in:
parent
eba8856261
commit
9dc4559aba
4 changed files with 259 additions and 32 deletions
|
@ -611,7 +611,7 @@ class Config(CLIConfig):
|
|||
('lbrynet4.lbry.com', 4444) # ASIA
|
||||
])
|
||||
|
||||
comment_server = String("Comment server API URL", "https://comments.lbry.com/api")
|
||||
comment_server = String("Comment server API URL", "https://comments.lbry.com/api/v2")
|
||||
|
||||
# blockchain
|
||||
blockchain_name = String("Blockchain name - lbrycrd_main, lbrycrd_regtest, or lbrycrd_testnet", 'lbrycrd_main')
|
||||
|
|
|
@ -52,11 +52,21 @@ def sign_comment(comment: dict, channel: Output, abandon=False):
|
|||
'signing_ts': timestamp
|
||||
})
|
||||
|
||||
def sign_reaction(reaction: dict, channel: Output):
|
||||
timestamp = str(int(time.time()))
|
||||
signing_field = reaction['channel_name']
|
||||
pieces = [timestamp.encode(), channel.claim_hash, signing_field.encode()]
|
||||
digest = sha256(b''.join(pieces))
|
||||
signature = channel.private_key.sign_digest_deterministic(digest, hashfunc=hashlib.sha256)
|
||||
reaction.update({
|
||||
'signature': binascii.hexlify(signature).decode(),
|
||||
'signing_ts': timestamp
|
||||
})
|
||||
|
||||
async def jsonrpc_post(url: str, method: str, params: dict = None, **kwargs) -> any:
|
||||
params = params or {}
|
||||
params.update(kwargs)
|
||||
json_body = {'jsonrpc': '2.0', 'id': None, 'method': method, 'params': params}
|
||||
json_body = {'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params}
|
||||
async with utils.aiohttp_request('POST', url, json=json_body) as response:
|
||||
try:
|
||||
result = await response.json()
|
||||
|
|
|
@ -5048,8 +5048,9 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
if hidden ^ visible:
|
||||
result = await comment_client.jsonrpc_post(
|
||||
self.conf.comment_server,
|
||||
'get_claim_hidden_comments',
|
||||
'comment.List',
|
||||
claim_id=claim_id,
|
||||
visible=visible,
|
||||
hidden=hidden,
|
||||
page=page,
|
||||
page_size=page_size
|
||||
|
@ -5057,7 +5058,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
else:
|
||||
result = await comment_client.jsonrpc_post(
|
||||
self.conf.comment_server,
|
||||
'get_claim_comments',
|
||||
'comment.List',
|
||||
claim_id=claim_id,
|
||||
parent_id=parent_id,
|
||||
page=page,
|
||||
|
@ -5132,7 +5133,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
}
|
||||
comment_client.sign_comment(comment_body, channel)
|
||||
|
||||
response = await comment_client.jsonrpc_post(self.conf.comment_server, 'create_comment', comment_body)
|
||||
response = await comment_client.jsonrpc_post(self.conf.comment_server, 'comment.Create', comment_body)
|
||||
response.update({
|
||||
'is_claim_signature_valid': comment_client.is_comment_signed_by_channel(response, channel)
|
||||
})
|
||||
|
@ -5169,7 +5170,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
"""
|
||||
channel = await comment_client.jsonrpc_post(
|
||||
self.conf.comment_server,
|
||||
'get_channel_from_comment_id',
|
||||
'comment.GetChannelFromCommentID',
|
||||
comment_id=comment_id
|
||||
)
|
||||
if 'error' in channel:
|
||||
|
@ -5186,7 +5187,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
}
|
||||
comment_client.sign_comment(edited_comment, channel_claim)
|
||||
return await comment_client.jsonrpc_post(
|
||||
self.conf.comment_server, 'edit_comment', edited_comment
|
||||
self.conf.comment_server, 'comment.Edit', edited_comment
|
||||
)
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
|
@ -5212,7 +5213,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
|
||||
abandon_comment_body = {'comment_id': comment_id}
|
||||
channel = await comment_client.jsonrpc_post(
|
||||
self.conf.comment_server, 'get_channel_from_comment_id', comment_id=comment_id
|
||||
self.conf.comment_server, 'comment.GetChannelFromCommentID', comment_id=comment_id
|
||||
)
|
||||
if 'error' in channel:
|
||||
return {comment_id: {'abandoned': False}}
|
||||
|
@ -5222,7 +5223,7 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
'channel_name': channel.claim_name,
|
||||
})
|
||||
comment_client.sign_comment(abandon_comment_body, channel, abandon=True)
|
||||
return await comment_client.jsonrpc_post(self.conf.comment_server, 'abandon_comment', abandon_comment_body)
|
||||
return await comment_client.jsonrpc_post(self.conf.comment_server, 'comment.Abandon', abandon_comment_body)
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
async def jsonrpc_comment_hide(self, comment_ids: typing.Union[str, list], wallet_id=None):
|
||||
|
@ -5268,7 +5269,96 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
piece = {'comment_id': comment['comment_id']}
|
||||
comment_client.sign_comment(piece, channel, abandon=True)
|
||||
pieces.append(piece)
|
||||
return await comment_client.jsonrpc_post(self.conf.comment_server, 'hide_comments', pieces=pieces)
|
||||
return await comment_client.jsonrpc_post(self.conf.comment_server, 'comment.Hide', pieces=pieces)
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
async def jsonrpc_comment_react(self, comment_id, channel_name=None, channel_id=None,
|
||||
channel_account_id=None, remove=False, clear_types=False, react_type=None, wallet_id=None):
|
||||
"""
|
||||
Create and associate a reaction emoji with a comment using your channel identity.
|
||||
|
||||
Usage:
|
||||
comment_react (<comment_id> | --comment_id=<comment_id>)
|
||||
(--channel_id=<channel_id>)
|
||||
(--channel_name=<channel_name>)
|
||||
(--react_type=<react_type>)
|
||||
[(--remove) | (--clear_types=<clear_types>)]
|
||||
[--channel_account_id=<channel_account_id>...] [--wallet_id=<wallet_id>]
|
||||
|
||||
Options:
|
||||
--comment_id=<comment_id> : (str) The comment id reacted to
|
||||
--channel_id=<claim_id> : (str) The ID of channel reacting
|
||||
--channel_name=<claim_name> : (str) The name of the channel reacting
|
||||
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
|
||||
--react_type=<react_type> : (str) name of reaction type
|
||||
--remove : (bool) remove specified react_type
|
||||
--clear_types=<clear_types> : (str) types to clear when adding another type
|
||||
|
||||
|
||||
Returns:
|
||||
(dict) Reaction object if successfully made, (None) otherwise
|
||||
{
|
||||
"hidden": (list) IDs of hidden comments.
|
||||
"visible": (list) IDs of visible comments.
|
||||
}
|
||||
"""
|
||||
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
|
||||
channel = await self.get_channel_or_error(
|
||||
wallet, channel_account_id, channel_id, channel_name, for_signing=True
|
||||
)
|
||||
|
||||
print(channel)
|
||||
|
||||
react_body = {
|
||||
'comment_ids': comment_id,
|
||||
'channel_id': channel_id,
|
||||
'channel_name': channel.claim_name,
|
||||
'type': react_type,
|
||||
}
|
||||
comment_client.sign_reaction(react_body, channel)
|
||||
|
||||
response = await comment_client.jsonrpc_post(self.conf.comment_server, 'reaction.React', react_body)
|
||||
|
||||
return response
|
||||
|
||||
@requires(WALLET_COMPONENT)
|
||||
async def jsonrpc_comment_react_list(self, comment_id, channel_name=None, channel_id=None,
|
||||
channel_account_id=None, react_type=None, wallet_id=None):
|
||||
"""
|
||||
List reactions emoji with a claim using your channel identity.
|
||||
|
||||
Usage:
|
||||
comment_react_list (<comment_id> | --comment_id=<comment_id>)
|
||||
|
||||
Options:
|
||||
--comment_id=<comment_id> : (str) The comment id reacted to
|
||||
--channel_id=<claim_id> : (str) The ID of channel reacting
|
||||
--channel_name=<claim_name> : (str) The name of the channel reacting
|
||||
--wallet_id=<wallet_id> : (str) restrict operation to specific wallet
|
||||
--react_type=<react_type> : (str) name of reaction type
|
||||
|
||||
Returns:
|
||||
(dict) Comment object if successfully made, (None) otherwise
|
||||
{
|
||||
"hidden": (list) IDs of hidden comments.
|
||||
"visible": (list) IDs of visible comments.
|
||||
}
|
||||
"""
|
||||
wallet = self.wallet_manager.get_wallet_or_default(wallet_id)
|
||||
channel = await self.get_channel_or_error(
|
||||
wallet, channel_account_id, channel_id, channel_name, for_signing=True
|
||||
)
|
||||
|
||||
react_list_body = {
|
||||
'comment_id': comment_id,
|
||||
'channel_id': channel_id,
|
||||
'channel_name': channel.claim_name,
|
||||
'type': react_type,
|
||||
}
|
||||
comment_client.sign_reaction(react_list_body, channel)
|
||||
|
||||
response = await comment_client.jsonrpc_post(self.conf.comment_server, 'reaction.List', react_list_body)
|
||||
return response
|
||||
|
||||
async def broadcast_or_release(self, tx, blocking=False):
|
||||
await self.wallet_manager.broadcast_or_release(tx, blocking)
|
||||
|
|
|
@ -32,6 +32,15 @@ class MockedCommentServer:
|
|||
'is_hidden': False,
|
||||
}
|
||||
|
||||
REACT_SCHEMA = {
|
||||
'comment_id': None,
|
||||
'reaction_type': None,
|
||||
'timestamp': None,
|
||||
'signature': None,
|
||||
'channel_id': None,
|
||||
'channel_name': None
|
||||
}
|
||||
|
||||
def __init__(self, port=2903):
|
||||
self.port = port
|
||||
self.app = web.Application(debug=True)
|
||||
|
@ -39,7 +48,9 @@ class MockedCommentServer:
|
|||
self.runner = None
|
||||
self.server = None
|
||||
self.comments = []
|
||||
self.reacts = []
|
||||
self.comment_id = 0
|
||||
self.react_id = 0
|
||||
|
||||
@classmethod
|
||||
def _create_comment(cls, **kwargs):
|
||||
|
@ -47,6 +58,12 @@ class MockedCommentServer:
|
|||
schema.update(**kwargs)
|
||||
return schema
|
||||
|
||||
@classmethod
|
||||
def _react(cls, **kwargs):
|
||||
schema = cls.REACT_SCHEMA.copy()
|
||||
schema.update(**kwargs)
|
||||
return schema
|
||||
|
||||
def create_comment(self, claim_id=None, parent_id=None, channel_name=None, channel_id=None, **kwargs):
|
||||
comment_id = self.comment_id
|
||||
channel_url = 'lbry://' + channel_name + '#' + channel_id if channel_id else None
|
||||
|
@ -117,27 +134,18 @@ class MockedCommentServer:
|
|||
'visible': list(comment_ids - set(hidden))
|
||||
}
|
||||
|
||||
def get_claim_comments(self, claim_id, page=1, page_size=50,**kwargs):
|
||||
def get_claim_comments(self, claim_id, hidden=False, visible=False, page=1, page_size=50,**kwargs):
|
||||
comments = list(filter(lambda c: c['claim_id'] == claim_id, self.comments))
|
||||
if hidden ^ visible:
|
||||
comments = list(filter(lambda c: c['is_hidden'] == hidden, comments))
|
||||
|
||||
return {
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'total_pages': ceil(len(comments)/page_size),
|
||||
'total_items': len(comments),
|
||||
'items': [self.clean(c) for c in (comments[::-1])[(page - 1) * page_size: page * page_size]],
|
||||
'has_hidden_comments': bool(list(filter(lambda x: x['is_hidden'], comments)))
|
||||
}
|
||||
|
||||
def get_claim_hidden_comments(self, claim_id, hidden=True, page=1, page_size=50):
|
||||
comments = list(filter(lambda c: c['claim_id'] == claim_id, self.comments))
|
||||
select_comments = list(filter(lambda c: c['is_hidden'] == hidden, comments))
|
||||
return {
|
||||
'page': page,
|
||||
'page_size': page_size,
|
||||
'total_pages': ceil(len(select_comments) / page_size),
|
||||
'total_items': len(select_comments),
|
||||
'items': [self.clean(c) for c in (select_comments[::-1])[(page - 1) * page_size: page * page_size]],
|
||||
'has_hidden_comments': bool(list(filter(lambda c: c['is_hidden'], comments)))
|
||||
'has_hidden_comments': bool(list(filter(lambda x: x['is_hidden'], self.comments)))
|
||||
}
|
||||
|
||||
def get_comment_channel_by_id(self, comment_id: int, **kwargs):
|
||||
|
@ -157,15 +165,83 @@ class MockedCommentServer:
|
|||
'has_hidden_comments': bool({c for c in comments if c['is_hidden']})
|
||||
}
|
||||
|
||||
def react(
|
||||
self,
|
||||
comment_ids=None,
|
||||
channel_name=None,
|
||||
channel_id=None,
|
||||
remove=None,
|
||||
clear_types=None,
|
||||
type=None,
|
||||
signing_ts=None,
|
||||
**kwargs
|
||||
):
|
||||
comment_batch = comment_ids.split(',')
|
||||
for item in comment_batch:
|
||||
c_id = item.strip()
|
||||
reacts_for_comment_id = [r for r in self.reacts if r['comment_id'] == c_id]
|
||||
channels_reacts_for_comment_id = [r for r in reacts_for_comment_id if ['channel_id'] == channel_id]
|
||||
|
||||
if remove:
|
||||
matching_react = None
|
||||
for reaction in channels_reacts_for_comment_id:
|
||||
if reaction['reaction_type'] == type:
|
||||
matching_react = reaction
|
||||
break
|
||||
if matching_react:
|
||||
self.reacts.pop(matching_react['id'])
|
||||
else:
|
||||
if clear_types:
|
||||
for r_type in clear_types:
|
||||
for reaction in channels_reacts_for_comment_id:
|
||||
if reaction['reaction_type'] == r_type:
|
||||
self.reacts.pop(reaction['id'])
|
||||
react = self._react(
|
||||
comment_id=str(c_id),
|
||||
channel_name=channel_name,
|
||||
channel_id=channel_id,
|
||||
reaction_type=type,
|
||||
timestamp=str(int(time.time())),
|
||||
**kwargs
|
||||
)
|
||||
self.reacts.append(react)
|
||||
self.react_id += 1
|
||||
return self.clean(react)
|
||||
|
||||
def list_reacts(self, comment_id, channel_id, **kwargs):
|
||||
reacts_for_comment = list(filter(lambda c: c['comment_id'] == comment_id, self.reacts))
|
||||
own_reacts_for_comment = list(filter(lambda c: c['channel_id'] == channel_id, reacts_for_comment))
|
||||
other_reacts_for_comment = list(filter(lambda c: c['channel_id'] != channel_id, reacts_for_comment))
|
||||
print('seriously wtf', own_reacts_for_comment)
|
||||
own_counts = {}
|
||||
if own_reacts_for_comment:
|
||||
for react in own_reacts_for_comment:
|
||||
own_counts[react['reaction_type']] = own_counts[react['reaction_type']] + 1 if react['reaction_type'] in own_counts else 1
|
||||
print('eh??')
|
||||
other_counts = {}
|
||||
if other_reacts_for_comment:
|
||||
for react in other_reacts_for_comment:
|
||||
other_counts[react['reaction_type']] = other_counts[react['reaction_type']] + 1 if react['reaction_type'] in other_counts else 1
|
||||
|
||||
return {
|
||||
'my_reactions': {
|
||||
comment_id: own_counts,
|
||||
},
|
||||
'others_reactions': {
|
||||
comment_id: other_counts,
|
||||
}
|
||||
}
|
||||
|
||||
methods = {
|
||||
'get_claim_comments': get_claim_comments,
|
||||
'comment.List': get_claim_comments,
|
||||
'get_comments_by_id': get_comments_by_id,
|
||||
'create_comment': create_comment,
|
||||
'abandon_comment': abandon_comment,
|
||||
'get_channel_from_comment_id': get_comment_channel_by_id,
|
||||
'get_claim_hidden_comments': get_claim_hidden_comments,
|
||||
'hide_comments': hide_comments,
|
||||
'edit_comment': edit_comment,
|
||||
'comment.Create': create_comment,
|
||||
'comment.Abandon': abandon_comment,
|
||||
'comment.GetChannelFromCommentID': get_comment_channel_by_id,
|
||||
'comment.Hide': hide_comments,
|
||||
'comment.Edit': edit_comment,
|
||||
'reaction.React': react,
|
||||
'reaction.List': list_reacts,
|
||||
}
|
||||
|
||||
def process_json(self, body) -> dict:
|
||||
|
@ -450,7 +526,6 @@ class CommentCommands(CommandTestCase):
|
|||
channel_id=bee['claim_id']
|
||||
)
|
||||
all_comments = [other_comment, owner_comment, hidden_comment]
|
||||
|
||||
normal_list = await self.daemon.jsonrpc_comment_list(claim_id)
|
||||
self.assertEqual(
|
||||
{'items', 'page', 'page_size', 'has_hidden_comments', 'total_items', 'total_pages'},
|
||||
|
@ -532,3 +607,55 @@ class CommentCommands(CommandTestCase):
|
|||
comment='If you see it and you mean then you know you have to go',
|
||||
comment_id=original_cid
|
||||
)
|
||||
|
||||
async def test06_reactions(self):
|
||||
moth = (await self.channel_create('@InconspicuousMoth'))['outputs'][0]
|
||||
bee = (await self.channel_create('@LazyBumblebee'))['outputs'][0]
|
||||
stream = await self.stream_create('Cool_Lamps_to_Sit_On', channel_id=moth['claim_id'])
|
||||
claim_id = stream['outputs'][0]['claim_id']
|
||||
|
||||
first_comment = await self.daemon.jsonrpc_comment_create(
|
||||
comment='Go away you yellow freak',
|
||||
claim_id=claim_id,
|
||||
channel_id=moth['claim_id'],
|
||||
)
|
||||
second_comment = await self.daemon.jsonrpc_comment_create(
|
||||
comment='I got my swim trunks and my flippy-floppies',
|
||||
claim_id=claim_id,
|
||||
channel_id=bee['claim_id']
|
||||
)
|
||||
all_comments = [second_comment, first_comment]
|
||||
comment_list = await self.daemon.jsonrpc_comment_list(claim_id)
|
||||
self.assertEqual(
|
||||
{'items', 'page', 'page_size', 'has_hidden_comments', 'total_items', 'total_pages'},
|
||||
set(comment_list)
|
||||
)
|
||||
self.assertEqual(comment_list['total_items'], 2)
|
||||
|
||||
# bee likes [0]
|
||||
# moth likes [0,1]
|
||||
# moth smiles [0,1,2]
|
||||
# bee dislikes (cleartypes) [1,2,3]
|
||||
# bee undislikes (remove) [1,2]
|
||||
# list likes
|
||||
|
||||
bee_like_reaction = await self.daemon.jsonrpc_comment_react(
|
||||
comment_id=first_comment['comment_id'],
|
||||
channel_id=bee['claim_id'],
|
||||
channel_name=bee['name'],
|
||||
react_type='dislike',
|
||||
)
|
||||
|
||||
bee_like_reaction = await self.daemon.jsonrpc_comment_react(
|
||||
comment_id=first_comment['comment_id'],
|
||||
channel_id=moth['claim_id'],
|
||||
channel_name=moth['name'],
|
||||
react_type='like',
|
||||
)
|
||||
|
||||
reactions = await self.daemon.jsonrpc_comment_react_list(
|
||||
comment_id=first_comment['comment_id'],
|
||||
channel_id=moth['claim_id'],
|
||||
channel_name=moth['name'],
|
||||
)
|
||||
# test assertions
|
||||
|
|
Loading…
Reference in a new issue