From 42de18c4a79984845e5f31934cd263a21200ec63 Mon Sep 17 00:00:00 2001 From: belikor Date: Wed, 25 Aug 2021 17:02:26 -0500 Subject: [PATCH 1/3] Daemon.jsonrpc_get: add `claim_id` option for downloading content The `get` method normally expects a canonical url or permanent url. ``` lbrynet get lbry://@channel/something ``` However, since this URL can have Unicode characters (Romance language accents, Cyrilic, Chinese ideograms, etc.) it may not be easy to type with all keyboard layouts. A more reliable way to share "addresses" and download content is using the `'claim_id'` of the particular claim. This is a 40-character alphanumeric string so it only has the basic Latin alphabet and numbers. It is easy to type with any keyboard layout. ``` lbrynet get 70dfefa510ca6eee7023a2a927e34d385b5a18bd --claim_id ``` --- lbry/extras/daemon/daemon.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index b6a9260798..8d5fb53908 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -1079,14 +1079,15 @@ async def jsonrpc_resolve(self, urls: typing.Union[str, list], wallet_id=None, * @requires(WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT, DATABASE_COMPONENT, FILE_MANAGER_COMPONENT) async def jsonrpc_get( - self, uri, file_name=None, download_directory=None, timeout=None, save_file=None, wallet_id=None): + self, uri, file_name=None, download_directory=None, timeout=None, save_file=None, wallet_id=None, + claim_id=False): """ Download stream from a LBRY name. Usage: get [ | --file_name=] [ | --download_directory=] [ | --timeout=] - [--save_file=] [--wallet_id=] + [--save_file=] [--wallet_id=] [--claim_id] Options: @@ -1096,12 +1097,24 @@ async def jsonrpc_get( --timeout= : (int) download timeout in number of seconds --save_file= : (bool) save the file to the downloads directory --wallet_id= : (str) wallet to check for claim purchase receipts + --claim_id : (bool) treat as a claim_id, that is, download by claim_id + instead of canonical URL Returns: {File} """ - wallet = self.wallet_manager.get_wallet_or_default(wallet_id) if download_directory and not os.path.isdir(download_directory): - return {"error": f"specified download directory \"{download_directory}\" does not exist"} + return {"error": f'specified download directory "{download_directory}" does not exist'} + + if claim_id: + out = await self.jsonrpc_claim_search(claim_id=uri, wallet_id=wallet_id) + if out["total_items"] < 1: + return {"error": + f'No item found with specified claim_id "{uri}"'} + + txo = out["items"][-1] + uri = txo.meta["canonical_url"] + + wallet = self.wallet_manager.get_wallet_or_default(wallet_id) try: stream = await self.file_manager.download_from_uri( uri, self.exchange_rate_manager, timeout, file_name, download_directory, From 2712dda43e39c9082dadc74a08cabaf11545abb8 Mon Sep 17 00:00:00 2001 From: belikor Date: Thu, 26 Aug 2021 22:29:36 -0500 Subject: [PATCH 2/3] Daemon.jsonrpc_get: add `download_repost` option for downloading reposts Normally, if a particular claim is in fact a repost, it will not be downloaded, as it is not a stream. ``` lbrynet get reposted-content ``` Will result in ``` { "error": "Claim is not a stream." } ``` To resolve the repost and download the actual claim we can use the option `--download_repost`. ``` lbrynet get reposted-content --download_repost ``` --- lbry/extras/daemon/daemon.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index 8d5fb53908..2b73aa5f88 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -1080,14 +1080,14 @@ async def jsonrpc_resolve(self, urls: typing.Union[str, list], wallet_id=None, * FILE_MANAGER_COMPONENT) async def jsonrpc_get( self, uri, file_name=None, download_directory=None, timeout=None, save_file=None, wallet_id=None, - claim_id=False): + claim_id=False, download_repost=False): """ Download stream from a LBRY name. Usage: get [ | --file_name=] [ | --download_directory=] [ | --timeout=] - [--save_file=] [--wallet_id=] [--claim_id] + [--save_file=] [--wallet_id=] [--claim_id] [--download_repost] Options: @@ -1099,20 +1099,28 @@ async def jsonrpc_get( --wallet_id= : (str) wallet to check for claim purchase receipts --claim_id : (bool) treat as a claim_id, that is, download by claim_id instead of canonical URL + --download_repost : (bool) resolve the claim, and if it is a repost, download the original claim Returns: {File} """ if download_directory and not os.path.isdir(download_directory): return {"error": f'specified download directory "{download_directory}" does not exist'} - if claim_id: - out = await self.jsonrpc_claim_search(claim_id=uri, wallet_id=wallet_id) - if out["total_items"] < 1: - return {"error": - f'No item found with specified claim_id "{uri}"'} + if claim_id or download_repost: + if claim_id: + out = await self.jsonrpc_claim_search(claim_id=uri, wallet_id=wallet_id) + if out["total_items"] < 1: + return {"error": + f'No item found with specified claim_id "{uri}"'} + + txo = out["items"][-1] + uri = txo.meta["canonical_url"] + else: + out = await self.jsonrpc_resolve(uri, wallet_id=wallet_id) + txo = out[uri] - txo = out["items"][-1] - uri = txo.meta["canonical_url"] + if download_repost and txo.reposted_claim: + uri = txo.reposted_claim.meta["canonical_url"] wallet = self.wallet_manager.get_wallet_or_default(wallet_id) try: From 1e308f392a091438fb491be87901443387e81c92 Mon Sep 17 00:00:00 2001 From: belikor Date: Wed, 25 Aug 2021 23:03:53 -0500 Subject: [PATCH 3/3] Daemon.jsonrpc_getch: download the latest claims from a channel This new method will download the latest claims from a particular channel. It will pick those claims that have a `'source '`, which means it will ignore livestreams. Download the 10 newest claims, not including livestreams nor reposts. ``` lbrynet getch @MyChannel --number=10 ``` If a particular claim is in fact a repost, it won't be downloaded. To resolve the reposts, and download the original claims, we can use the option `--download_repost`. Download the 20 newest items, including any reposts. ``` lbrynet getch @MyChannel --number=20 --download_repost ``` Other options are those of the `get` method, such as `--download_directory`, `--timeout`, `--save_file`, and `--wallet_id`. --- lbry/extras/cli.py | 2 +- lbry/extras/daemon/daemon.py | 63 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/lbry/extras/cli.py b/lbry/extras/cli.py index c263a84d90..b985e4d048 100644 --- a/lbry/extras/cli.py +++ b/lbry/extras/cli.py @@ -208,7 +208,7 @@ def get_argument_parser(): group_parser = sub.add_parser(group_name, group_name=group_name, help=api['groups'][group_name]) groups[group_name] = group_parser.add_subparsers(metavar='COMMAND') - nicer_order = ['stop', 'get', 'publish', 'resolve'] + nicer_order = ['stop', 'get', 'getch', 'publish', 'resolve'] for command_name in sorted(api['commands']): if command_name not in nicer_order: nicer_order.append(command_name) diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index 2b73aa5f88..567d538730 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -1136,6 +1136,69 @@ async def jsonrpc_get( return {"error": str(e)} return stream + @requires(WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT, DATABASE_COMPONENT, + FILE_MANAGER_COMPONENT) + async def jsonrpc_getch(self, + channel, number=1, + download_directory=None, timeout=None, save_file=False, wallet_id=None, + download_repost=False): + """ + Download the latest claims from a LBRY channel. + + Usage: + getch [ | --number=] + [ | --download_directory=] [ | --timeout=] + [--save_file] [--wallet_id=] [--download_repost] + + + Options: + --channel= : (str) channel from which content will be downloaded + --number= : (int) number of latest claims to download from the channel + (default 1) + --download_directory= : (str) full path to the directory to download into + --timeout= : (int) download timeout in number of seconds + --save_file : (bool) save the file to the downloads directory + --wallet_id= : (str) wallet to check for claim purchase receipts + --download_repost : (bool) if a claim is a repost, download the original claim + + Returns: {File} + """ + if not channel.startswith("@"): + channel = "@" + channel + + # If the claim is a livestream it will not have 'source' + # and won't be able to be downloaded, thus we want `has_source=True` + out = await self.jsonrpc_claim_search(channel=channel, + page_size=number, + order_by="release_time", + has_source=True, + wallet_id=wallet_id) + if out["total_items"] < 1: + return {"error": + f'No items found with specified channel "{channel}"'} + + txos = out["items"] + + streams = [] + for txo in txos: + uri = txo.meta["canonical_url"] + + # file_name=None because we don't want to rename all streams + # claim_id=False because we are sure these are URLs not IDs + stream = await self.jsonrpc_get(uri, file_name=None, + download_directory=download_directory, + timeout=timeout, save_file=save_file, + wallet_id=wallet_id, + claim_id=False, + download_repost=download_repost) + if isinstance(stream, dict) and "error" in stream: + typ = txo.claim.claim_type + stream = {"error": stream["error"] + f" ({typ})" + " " + uri} + streams.append(stream) + + return {"items": streams, + "total_items": len(streams)} + SETTINGS_DOC = """ Settings management. """