--languages, --locations fully support all attributes, add BTC
This commit is contained in:
parent
4e6bca1389
commit
a3bd335f7f
8 changed files with 644 additions and 111 deletions
|
@ -1757,8 +1757,9 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
|
||||
Usage:
|
||||
channel_create (<name> | --name=<name>) (<bid> | --bid=<bid>)
|
||||
[--tags=<tags>...] [--allow_duplicate_name=<allow_duplicate_name>]
|
||||
[--title=<title>] [--description=<description>] [--language=<language>]
|
||||
[--allow_duplicate_name=<allow_duplicate_name>]
|
||||
[--title=<title>] [--description=<description>]
|
||||
[--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...]
|
||||
[--contact_email=<contact_email>]
|
||||
[--homepage_url=<homepage_url>] [--thumbnail_url=<thumbnail_url>] [--cover_url=<cover_url>]
|
||||
[--account_id=<account_id>] [--claim_address=<claim_address>] [--preview]
|
||||
|
@ -1768,10 +1769,47 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
--allow_duplicate_name=<allow_duplicate_name> : (bool) create new channel even if one already exists with
|
||||
given name. default: false.
|
||||
--bid=<bid> : (decimal) amount to back the claim
|
||||
--tags=<tags> : (list) content tags
|
||||
--title=<title> : (str) title of the publication
|
||||
--description=<description> : (str) description of the publication
|
||||
--language=<language> : (str) primary language of the channel
|
||||
--tags=<tags> : (list) content tags
|
||||
--languages=<languages> : (list) languages used by the channel,
|
||||
using RFC 5646 format, eg:
|
||||
for English `--languages=en`
|
||||
for Spanish (Spain) `--languages=es-ES`
|
||||
for Spanish (Mexican) `--languages=es-MX`
|
||||
for Chinese (Simplified) `--languages=zh-Hans`
|
||||
for Chinese (Traditional) `--languages=zh-Hant`
|
||||
--locations=<locations> : (list) locations of the channel, consisting of 2 letter
|
||||
`country` code and a `state`, `city` and a postal
|
||||
`code` along with a `latitude` and `longitude`.
|
||||
for JSON RPC: pass a dictionary with aforementioned
|
||||
attributes as keys, eg:
|
||||
...
|
||||
"locations": [{'country': 'US', 'state': 'NH'}]
|
||||
...
|
||||
for command line: pass a colon delimited list
|
||||
with values in the following order:
|
||||
|
||||
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
|
||||
|
||||
making sure to include colon for blank values, for
|
||||
example to provide only the city:
|
||||
|
||||
--locations"::Manchester"
|
||||
|
||||
with all values set:
|
||||
|
||||
--locations="US:NH:Manchester:03101:42.990605:-71.460989"
|
||||
|
||||
optionally, you can just pass the "LATITUDE:LONGITUDE":
|
||||
|
||||
--locations="42.990605:-71.460989"
|
||||
|
||||
finally, you can also pass JSON string of dictionary
|
||||
on the command line as you would via JSON RPC
|
||||
|
||||
--locations="{'country': 'US', 'state': 'NH'}"
|
||||
|
||||
--contact_email=<contact_email>: (str) email of channel owner
|
||||
--homepage_url=<homepage_url> : (str) homepage url
|
||||
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
|
||||
|
@ -1825,19 +1863,61 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
|
||||
Usage:
|
||||
channel_update (<claim_id> | --claim_id=<claim_id>) [<bid> | --bid=<bid>]
|
||||
[--tags=<tags>...] [--clear_tags] [--title=<title>] [--description=<description>]
|
||||
[--language=<language>] [--contact_email=<contact_email>]
|
||||
[--title=<title>] [--description=<description>]
|
||||
[--tags=<tags>...] [--clear_tags]
|
||||
[--languages=<languages>...] [--clear_languages]
|
||||
[--locations=<locations>...] [--clear_locations]
|
||||
[--contact_email=<contact_email>]
|
||||
[--homepage_url=<homepage_url>] [--thumbnail_url=<thumbnail_url>] [--cover_url=<cover_url>]
|
||||
[--account_id=<account_id>] [--claim_address=<claim_address>] [--new_signing_key] [--preview]
|
||||
|
||||
Options:
|
||||
--claim_id=<claim_id> : (str) claim_id of the channel to update
|
||||
--bid=<bid> : (decimal) amount to back the claim
|
||||
--tags=<tags> : (list) add content tags
|
||||
--clear_tags : (bool) clear existing tags (prior to adding new ones)
|
||||
--title=<title> : (str) title of the publication
|
||||
--description=<description> : (str) description of the publication
|
||||
--language=<language> : (str) primary language of the channel
|
||||
--clear_tags : (bool) clear existing tags (prior to adding new ones)
|
||||
--tags=<tags> : (list) add content tags
|
||||
--clear_languages : (bool) clear existing languages (prior to adding new ones)
|
||||
--languages=<languages> : (list) languages used by the channel,
|
||||
using RFC 5646 format, eg:
|
||||
for English `--languages=en`
|
||||
for Spanish (Spain) `--languages=es-ES`
|
||||
for Spanish (Mexican) `--languages=es-MX`
|
||||
for Chinese (Simplified) `--languages=zh-Hans`
|
||||
for Chinese (Traditional) `--languages=zh-Hant`
|
||||
--clear_locations : (bool) clear existing locations (prior to adding new ones)
|
||||
--locations=<locations> : (list) locations of the channel, consisting of 2 letter
|
||||
`country` code and a `state`, `city` and a postal
|
||||
`code` along with a `latitude` and `longitude`.
|
||||
for JSON RPC: pass a dictionary with aforementioned
|
||||
attributes as keys, eg:
|
||||
...
|
||||
"locations": [{'country': 'US', 'state': 'NH'}]
|
||||
...
|
||||
for command line: pass a colon delimited list
|
||||
with values in the following order:
|
||||
|
||||
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
|
||||
|
||||
making sure to include colon for blank values, for
|
||||
example to provide only the city:
|
||||
|
||||
--locations"::Manchester"
|
||||
|
||||
with all values set:
|
||||
|
||||
--locations="US:NH:Manchester:03101:42.990605:-71.460989"
|
||||
|
||||
optionally, you can just pass the "LATITUDE:LONGITUDE":
|
||||
|
||||
--locations="42.990605:-71.460989"
|
||||
|
||||
finally, you can also pass JSON string of dictionary
|
||||
on the command line as you would via JSON RPC
|
||||
|
||||
--locations="{'country': 'US', 'state': 'NH'}"
|
||||
|
||||
--contact_email=<contact_email>: (str) email of channel owner
|
||||
--homepage_url=<homepage_url> : (str) homepage url
|
||||
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
|
||||
|
@ -1968,7 +2048,10 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
|
||||
Usage:
|
||||
publish (<name> | --name=<name>) [--bid=<bid>] [--file_path=<file_path>]
|
||||
[<stream_type> | --stream_type=<stream_type>] [--tags=<tags>...]
|
||||
[<stream_type> | --stream_type=<stream_type>]
|
||||
[--tags=<tags>...] [--clear_tags]
|
||||
[--languages=<languages>...] [--clear_languages]
|
||||
[--locations=<locations>...] [--clear_locations]
|
||||
[--fee_currency=<fee_currency>] [--fee_amount=<fee_amount>] [--fee_address=<fee_address>]
|
||||
[--title=<title>] [--description=<description>] [--author=<author>] [--language=<language>]
|
||||
[--license=<license>] [--license_url=<license_url>] [--thumbnail_url=<thumbnail_url>]
|
||||
|
@ -1984,7 +2067,6 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
--bid=<bid> : (decimal) amount to back the claim
|
||||
--file_path=<file_path> : (str) path to file to be associated with name.
|
||||
--stream_type=<stream_type> : (str) type of stream
|
||||
--tags=<tags> : (list) content tags
|
||||
--fee_currency=<fee_currency> : (string) specify fee currency
|
||||
--fee_amount=<fee_amount> : (decimal) content download fee
|
||||
--fee_address=<fee_address> : (str) address where to send fee payments, will use
|
||||
|
@ -1996,7 +2078,48 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
who is not the publisher and is not represented by the channel. For
|
||||
example, a pdf file of 'The Odyssey' has an author of 'Homer' but may
|
||||
by published to a channel such as '@classics', or to no channel at all
|
||||
--language=<language> : (str) language of the publication
|
||||
--clear_tags : (bool) clear existing tags (prior to adding new ones)
|
||||
--tags=<tags> : (list) add content tags
|
||||
--clear_languages : (bool) clear existing languages (prior to adding new ones)
|
||||
--languages=<languages> : (list) languages used by the channel,
|
||||
using RFC 5646 format, eg:
|
||||
for English `--languages=en`
|
||||
for Spanish (Spain) `--languages=es-ES`
|
||||
for Spanish (Mexican) `--languages=es-MX`
|
||||
for Chinese (Simplified) `--languages=zh-Hans`
|
||||
for Chinese (Traditional) `--languages=zh-Hant`
|
||||
--clear_locations : (bool) clear existing locations (prior to adding new ones)
|
||||
--locations=<locations> : (list) locations of the channel, consisting of 2 letter
|
||||
`country` code and a `state`, `city` and a postal
|
||||
`code` along with a `latitude` and `longitude`.
|
||||
for JSON RPC: pass a dictionary with aforementioned
|
||||
attributes as keys, eg:
|
||||
...
|
||||
"locations": [{'country': 'US', 'state': 'NH'}]
|
||||
...
|
||||
for command line: pass a colon delimited list
|
||||
with values in the following order:
|
||||
|
||||
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
|
||||
|
||||
making sure to include colon for blank values, for
|
||||
example to provide only the city:
|
||||
|
||||
--locations"::Manchester"
|
||||
|
||||
with all values set:
|
||||
|
||||
--locations="US:NH:Manchester:03101:42.990605:-71.460989"
|
||||
|
||||
optionally, you can just pass the "LATITUDE:LONGITUDE":
|
||||
|
||||
--locations="42.990605:-71.460989"
|
||||
|
||||
finally, you can also pass JSON string of dictionary
|
||||
on the command line as you would via JSON RPC
|
||||
|
||||
--locations="{'country': 'US', 'state': 'NH'}"
|
||||
|
||||
--license=<license> : (str) publication license
|
||||
--license_url=<license_url> : (str) publication license url
|
||||
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
|
||||
|
@ -2049,9 +2172,10 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
Usage:
|
||||
stream_create (<name> | --name=<name>) (<bid> | --bid=<bid>)
|
||||
(<file_path> | --file_path=<file_path>) [<stream_type> | --stream_type=<stream_type>]
|
||||
[--tags=<tags>...] [--allow_duplicate_name=<allow_duplicate_name>]
|
||||
[--allow_duplicate_name=<allow_duplicate_name>]
|
||||
[--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...]
|
||||
[--fee_currency=<fee_currency>] [--fee_amount=<fee_amount>] [--fee_address=<fee_address>]
|
||||
[--title=<title>] [--description=<description>] [--author=<author>] [--language=<language>]
|
||||
[--title=<title>] [--description=<description>] [--author=<author>]
|
||||
[--license=<license>] [--license_url=<license_url>] [--thumbnail_url=<thumbnail_url>]
|
||||
[--release_time=<release_time>]
|
||||
[--video_width=<video_width>] [--video_height=<video_height>] [--video_duration=<video_duration>]
|
||||
|
@ -2067,7 +2191,6 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
--bid=<bid> : (decimal) amount to back the claim
|
||||
--file_path=<file_path> : (str) path to file to be associated with name.
|
||||
--stream_type=<stream_type> : (str) type of stream
|
||||
--tags=<tags> : (list) content tags
|
||||
--fee_currency=<fee_currency> : (string) specify fee currency
|
||||
--fee_amount=<fee_amount> : (decimal) content download fee
|
||||
--fee_address=<fee_address> : (str) address where to send fee payments, will use
|
||||
|
@ -2079,7 +2202,45 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
who is not the publisher and is not represented by the channel. For
|
||||
example, a pdf file of 'The Odyssey' has an author of 'Homer' but may
|
||||
by published to a channel such as '@classics', or to no channel at all
|
||||
--language=<language> : (str) language of the publication
|
||||
--tags=<tags> : (list) add content tags
|
||||
--languages=<languages> : (list) languages used by the channel,
|
||||
using RFC 5646 format, eg:
|
||||
for English `--languages=en`
|
||||
for Spanish (Spain) `--languages=es-ES`
|
||||
for Spanish (Mexican) `--languages=es-MX`
|
||||
for Chinese (Simplified) `--languages=zh-Hans`
|
||||
for Chinese (Traditional) `--languages=zh-Hant`
|
||||
--locations=<locations> : (list) locations of the channel, consisting of 2 letter
|
||||
`country` code and a `state`, `city` and a postal
|
||||
`code` along with a `latitude` and `longitude`.
|
||||
for JSON RPC: pass a dictionary with aforementioned
|
||||
attributes as keys, eg:
|
||||
...
|
||||
"locations": [{'country': 'US', 'state': 'NH'}]
|
||||
...
|
||||
for command line: pass a colon delimited list
|
||||
with values in the following order:
|
||||
|
||||
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
|
||||
|
||||
making sure to include colon for blank values, for
|
||||
example to provide only the city:
|
||||
|
||||
--locations"::Manchester"
|
||||
|
||||
with all values set:
|
||||
|
||||
--locations="US:NH:Manchester:03101:42.990605:-71.460989"
|
||||
|
||||
optionally, you can just pass the "LATITUDE:LONGITUDE":
|
||||
|
||||
--locations="42.990605:-71.460989"
|
||||
|
||||
finally, you can also pass JSON string of dictionary
|
||||
on the command line as you would via JSON RPC
|
||||
|
||||
--locations="{'country': 'US', 'state': 'NH'}"
|
||||
|
||||
--license=<license> : (str) publication license
|
||||
--license_url=<license_url> : (str) publication license url
|
||||
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
|
||||
|
@ -2155,8 +2316,10 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
Update an existing stream claim and if a new file is provided announce it to lbrynet.
|
||||
|
||||
Usage:
|
||||
stream_update (<claim_id> | --claim_id=<claim_id>)
|
||||
[--bid=<bid>] [--file_path=<file_path>] [--tags=<tags>...] [--clear_tags]
|
||||
stream_update (<claim_id> | --claim_id=<claim_id>) [--bid=<bid>] [--file_path=<file_path>]
|
||||
[--tags=<tags>...] [--clear_tags]
|
||||
[--languages=<languages>...] [--clear_languages]
|
||||
[--locations=<locations>...] [--clear_locations]
|
||||
[--fee_currency=<fee_currency>] [--fee_amount=<fee_amount>] [--fee_address=<fee_address>]
|
||||
[--title=<title>] [--description=<description>] [--author=<author>] [--language=<language>]
|
||||
[--license=<license>] [--license_url=<license_url>] [--thumbnail_url=<thumbnail_url>]
|
||||
|
@ -2171,8 +2334,6 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
--claim_id=<claim_id> : (str) id of the stream claim to update
|
||||
--bid=<bid> : (decimal) amount to back the claim
|
||||
--file_path=<file_path> : (str) path to file to be associated with name.
|
||||
--tags=<tags> : (list) content tags
|
||||
--clear_tags : (bool) clear existing tags (prior to adding new ones)
|
||||
--fee_currency=<fee_currency> : (string) specify fee currency
|
||||
--fee_amount=<fee_amount> : (decimal) content download fee
|
||||
--fee_address=<fee_address> : (str) address where to send fee payments, will use
|
||||
|
@ -2184,7 +2345,48 @@ class Daemon(metaclass=JSONRPCServerType):
|
|||
who is not the publisher and is not represented by the channel. For
|
||||
example, a pdf file of 'The Odyssey' has an author of 'Homer' but may
|
||||
by published to a channel such as '@classics', or to no channel at all
|
||||
--language=<language> : (str) language of the publication
|
||||
--clear_tags : (bool) clear existing tags (prior to adding new ones)
|
||||
--tags=<tags> : (list) add content tags
|
||||
--clear_languages : (bool) clear existing languages (prior to adding new ones)
|
||||
--languages=<languages> : (list) languages used by the channel,
|
||||
using RFC 5646 format, eg:
|
||||
for English `--languages=en`
|
||||
for Spanish (Spain) `--languages=es-ES`
|
||||
for Spanish (Mexican) `--languages=es-MX`
|
||||
for Chinese (Simplified) `--languages=zh-Hans`
|
||||
for Chinese (Traditional) `--languages=zh-Hant`
|
||||
--clear_locations : (bool) clear existing locations (prior to adding new ones)
|
||||
--locations=<locations> : (list) locations of the channel, consisting of 2 letter
|
||||
`country` code and a `state`, `city` and a postal
|
||||
`code` along with a `latitude` and `longitude`.
|
||||
for JSON RPC: pass a dictionary with aforementioned
|
||||
attributes as keys, eg:
|
||||
...
|
||||
"locations": [{'country': 'US', 'state': 'NH'}]
|
||||
...
|
||||
for command line: pass a colon delimited list
|
||||
with values in the following order:
|
||||
|
||||
"COUNTRY:STATE:CITY:CODE:LATITUDE:LONGITUDE"
|
||||
|
||||
making sure to include colon for blank values, for
|
||||
example to provide only the city:
|
||||
|
||||
--locations"::Manchester"
|
||||
|
||||
with all values set:
|
||||
|
||||
--locations="US:NH:Manchester:03101:42.990605:-71.460989"
|
||||
|
||||
optionally, you can just pass the "LATITUDE:LONGITUDE":
|
||||
|
||||
--locations="42.990605:-71.460989"
|
||||
|
||||
finally, you can also pass JSON string of dictionary
|
||||
on the command line as you would via JSON RPC
|
||||
|
||||
--locations="{'country': 'US', 'state': 'NH'}"
|
||||
|
||||
--license=<license> : (str) publication license
|
||||
--license_url=<license_url> : (str) publication license url
|
||||
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
|
||||
|
|
|
@ -28,10 +28,17 @@ class JSONResponseEncoder(JSONEncoder):
|
|||
claim_dict = obj.to_dict()
|
||||
if obj.is_stream:
|
||||
claim_dict['stream']['sd_hash'] = obj.stream.sd_hash
|
||||
if 'fee' in claim_dict['stream'] and 'address' in claim_dict['stream']['fee']:
|
||||
claim_dict['stream']['fee']['address'] = obj.stream.fee.address
|
||||
fee = claim_dict['stream'].get('fee', {})
|
||||
if 'address' in fee:
|
||||
fee['address'] = obj.stream.fee.address
|
||||
if 'amount' in fee:
|
||||
fee['amount'] = obj.stream.fee.amount
|
||||
if 'languages' in claim_dict['stream']:
|
||||
claim_dict['stream']['languages'] = obj.stream.langtags
|
||||
elif obj.is_channel:
|
||||
claim_dict['channel']['public_key'] = obj.channel.public_key
|
||||
if 'languages' in claim_dict['channel']:
|
||||
claim_dict['channel']['languages'] = obj.channel.langtags
|
||||
return claim_dict
|
||||
if isinstance(obj, datetime):
|
||||
return obj.strftime("%Y%m%dT%H:%M:%S")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import os.path
|
||||
from typing import List, Tuple
|
||||
import json
|
||||
from string import ascii_letters
|
||||
from typing import List, Tuple, Iterator, TypeVar, Generic
|
||||
from decimal import Decimal
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
|
@ -87,6 +89,39 @@ class Claim(Signable):
|
|||
return claim
|
||||
|
||||
|
||||
I = TypeVar('I')
|
||||
|
||||
|
||||
class BaseMessageList(Generic[I]):
|
||||
|
||||
__slots__ = 'message',
|
||||
|
||||
item_class = None
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def add(self) -> I:
|
||||
return self.item_class(self.message.add())
|
||||
|
||||
def extend(self, values: List[str]):
|
||||
for value in values:
|
||||
self.append(value)
|
||||
|
||||
def append(self, value: str):
|
||||
raise NotImplemented
|
||||
|
||||
def __len__(self):
|
||||
return len(self.message)
|
||||
|
||||
def __iter__(self) -> Iterator[I]:
|
||||
for lang in self.message:
|
||||
yield self.item_class(lang)
|
||||
|
||||
def __getitem__(self, item) -> I:
|
||||
return self.item_class(self.message[item])
|
||||
|
||||
|
||||
class Dimmensional:
|
||||
|
||||
__slots__ = ()
|
||||
|
@ -215,6 +250,8 @@ class Fee:
|
|||
def amount(self) -> Decimal:
|
||||
if self.currency == 'LBC':
|
||||
return self.lbc
|
||||
if self.currency == 'BTC':
|
||||
return self.btc
|
||||
if self.currency == 'USD':
|
||||
return self.usd
|
||||
|
||||
|
@ -241,6 +278,29 @@ class Fee:
|
|||
self._fee.amount = amount
|
||||
self._fee.currency = FeeMessage.LBC
|
||||
|
||||
SATOSHIES = Decimal(COIN)
|
||||
|
||||
@property
|
||||
def btc(self) -> Decimal:
|
||||
if self._fee.currency != FeeMessage.BTC:
|
||||
raise ValueError('BTC can only be returned for BTC fees.')
|
||||
return Decimal(self._fee.amount / self.SATOSHIES)
|
||||
|
||||
@btc.setter
|
||||
def btc(self, amount: Decimal):
|
||||
self.satoshis = int(amount * self.SATOSHIES)
|
||||
|
||||
@property
|
||||
def satoshis(self) -> int:
|
||||
if self._fee.currency != FeeMessage.BTC:
|
||||
raise ValueError('Satoshies can only be returned for BTC fees.')
|
||||
return self._fee.amount
|
||||
|
||||
@satoshis.setter
|
||||
def satoshis(self, amount: int):
|
||||
self._fee.amount = amount
|
||||
self._fee.currency = FeeMessage.BTC
|
||||
|
||||
PENNIES = Decimal(100.0)
|
||||
|
||||
@property
|
||||
|
@ -265,6 +325,176 @@ class Fee:
|
|||
self._fee.currency = FeeMessage.USD
|
||||
|
||||
|
||||
class Language:
|
||||
|
||||
__slots__ = 'message',
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
@property
|
||||
def langtag(self) -> str:
|
||||
langtag = []
|
||||
if self.language:
|
||||
langtag.append(self.language)
|
||||
if self.script:
|
||||
langtag.append(self.script)
|
||||
if self.region:
|
||||
langtag.append(self.region)
|
||||
return '-'.join(langtag)
|
||||
|
||||
@langtag.setter
|
||||
def langtag(self, langtag: str):
|
||||
parts = langtag.split('-')
|
||||
self.language = parts.pop(0)
|
||||
if parts and len(parts[0]) == 4:
|
||||
self.script = parts.pop(0)
|
||||
if parts and len(parts[0]) == 2:
|
||||
self.region = parts.pop(0)
|
||||
assert not parts, f"Failed to parse language tag: {langtag}"
|
||||
|
||||
@property
|
||||
def language(self) -> str:
|
||||
if self.message.language:
|
||||
return LanguageMessage.Language.Name(self.message.language)
|
||||
|
||||
@language.setter
|
||||
def language(self, language: str):
|
||||
self.message.language = LanguageMessage.Language.Value(language)
|
||||
|
||||
@property
|
||||
def script(self) -> str:
|
||||
if self.message.script:
|
||||
return LanguageMessage.Script.Name(self.message.script)
|
||||
|
||||
@script.setter
|
||||
def script(self, script: str):
|
||||
self.message.script = LanguageMessage.Script.Value(script)
|
||||
|
||||
@property
|
||||
def region(self) -> str:
|
||||
if self.message.region:
|
||||
return LocationMessage.Country.Name(self.message.region)
|
||||
|
||||
@region.setter
|
||||
def region(self, region: str):
|
||||
self.message.region = LocationMessage.Country.Value(region)
|
||||
|
||||
|
||||
class LanguageList(BaseMessageList[Language]):
|
||||
__slots__ = ()
|
||||
item_class = Language
|
||||
|
||||
def append(self, value: str):
|
||||
self.add().langtag = value
|
||||
|
||||
|
||||
class Location:
|
||||
|
||||
__slots__ = 'message',
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def from_value(self, value):
|
||||
if isinstance(value, str) and value.startswith('{'):
|
||||
value = json.loads(value)
|
||||
|
||||
if isinstance(value, dict):
|
||||
for key, val in value.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
elif isinstance(value, str):
|
||||
parts = value.split(':')
|
||||
if len(parts) > 2 or (parts[0] and parts[0][0] in ascii_letters):
|
||||
country = parts and parts.pop(0)
|
||||
if country:
|
||||
self.country = country
|
||||
state = parts and parts.pop(0)
|
||||
if state:
|
||||
self.state = state
|
||||
city = parts and parts.pop(0)
|
||||
if city:
|
||||
self.city = city
|
||||
code = parts and parts.pop(0)
|
||||
if code:
|
||||
self.code = code
|
||||
latitude = parts and parts.pop(0)
|
||||
if latitude:
|
||||
self.latitude = latitude
|
||||
longitude = parts and parts.pop(0)
|
||||
if longitude:
|
||||
self.longitude = longitude
|
||||
|
||||
else:
|
||||
raise ValueError(f'Could not parse country value: {value}')
|
||||
|
||||
@property
|
||||
def country(self) -> str:
|
||||
if self.message.country:
|
||||
return LocationMessage.Country.Name(self.message.country)
|
||||
|
||||
@country.setter
|
||||
def country(self, country: str):
|
||||
self.message.country = LocationMessage.Country.Value(country)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
return self.message.state
|
||||
|
||||
@state.setter
|
||||
def state(self, state: str):
|
||||
self.message.state = state
|
||||
|
||||
@property
|
||||
def city(self) -> str:
|
||||
return self.message.city
|
||||
|
||||
@city.setter
|
||||
def city(self, city: str):
|
||||
self.message.city = city
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
return self.message.code
|
||||
|
||||
@code.setter
|
||||
def code(self, code: str):
|
||||
self.message.code = code
|
||||
|
||||
GPS_PRECISION = Decimal('10000000')
|
||||
|
||||
@property
|
||||
def latitude(self) -> str:
|
||||
if self.message.latitude:
|
||||
return str(Decimal(self.message.latitude) / self.GPS_PRECISION)
|
||||
|
||||
@latitude.setter
|
||||
def latitude(self, latitude: str):
|
||||
latitude = Decimal(latitude)
|
||||
assert -90 <= latitude <= 90, "Latitude must be between -90 and 90 degrees."
|
||||
self.message.latitude = int(latitude * self.GPS_PRECISION)
|
||||
|
||||
@property
|
||||
def longitude(self) -> str:
|
||||
if self.message.longitude:
|
||||
return str(Decimal(self.message.longitude) / self.GPS_PRECISION)
|
||||
|
||||
@longitude.setter
|
||||
def longitude(self, longitude: str):
|
||||
longitude = Decimal(longitude)
|
||||
assert -180 <= longitude <= 180, "Longitude must be between -180 and 180 degrees."
|
||||
self.message.longitude = int(longitude * self.GPS_PRECISION)
|
||||
|
||||
|
||||
class LocationList(BaseMessageList[Location]):
|
||||
__slots__ = ()
|
||||
item_class = Location
|
||||
|
||||
def append(self, value):
|
||||
self.add().from_value(value)
|
||||
|
||||
|
||||
class BaseClaimSubType:
|
||||
|
||||
__slots__ = 'claim', 'message'
|
||||
|
@ -272,10 +502,6 @@ class BaseClaimSubType:
|
|||
def __init__(self, claim: Claim):
|
||||
self.claim = claim or Claim()
|
||||
|
||||
@property
|
||||
def tags(self) -> List:
|
||||
return self.message.tags
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
return self.message.title
|
||||
|
@ -301,54 +527,36 @@ class BaseClaimSubType:
|
|||
self.message.thumbnail_url = thumbnail_url
|
||||
|
||||
@property
|
||||
def language(self) -> str:
|
||||
if len(self.languages) > 0:
|
||||
return LanguageMessage.Language.Name(self.languages[0].language)
|
||||
|
||||
@language.setter
|
||||
def language(self, language: str):
|
||||
value = LanguageMessage.Language.Value(language)
|
||||
if len(self.languages) > 0:
|
||||
self.languages[0].language = value
|
||||
else:
|
||||
self.languages.add().language = value
|
||||
def tags(self) -> List:
|
||||
return self.message.tags
|
||||
|
||||
@property
|
||||
def languages(self):
|
||||
return self.message.languages
|
||||
def languages(self) -> LanguageList:
|
||||
return LanguageList(self.message.languages)
|
||||
|
||||
@property
|
||||
def location_country(self) -> str:
|
||||
if len(self.locations) > 0:
|
||||
return LocationMessage.Country.Name(self.locations[0].country)
|
||||
|
||||
@location_country.setter
|
||||
def location_country(self, country: str):
|
||||
value = LocationMessage.Country.Value(country)
|
||||
if len(self.locations) > 0:
|
||||
self.locations[0].location = value
|
||||
else:
|
||||
self.locations.add().location = value
|
||||
def langtags(self) -> List[str]:
|
||||
return [l.langtag for l in self.languages]
|
||||
|
||||
@property
|
||||
def locations(self):
|
||||
return self.message.locations
|
||||
def locations(self) -> LocationList:
|
||||
return LocationList(self.message.locations)
|
||||
|
||||
def to_dict(self):
|
||||
return MessageToDict(self.message, preserving_proto_field_name=True)
|
||||
|
||||
def update(self, tags=None, clear_tags=False, **kwargs):
|
||||
|
||||
if clear_tags:
|
||||
self.message.ClearField('tags')
|
||||
|
||||
if tags is not None:
|
||||
if isinstance(tags, str):
|
||||
self.tags.append(tags)
|
||||
elif isinstance(tags, list):
|
||||
self.tags.extend(tags)
|
||||
else:
|
||||
raise ValueError(f"Unknown tag type: {tags}")
|
||||
def update(self, **kwargs):
|
||||
for l in ('tags', 'languages', 'locations'):
|
||||
if kwargs.pop(f'clear_{l}', False):
|
||||
self.message.ClearField('tags')
|
||||
items = kwargs.pop(l, None)
|
||||
if items is not None:
|
||||
if isinstance(items, str):
|
||||
getattr(self, l).append(items)
|
||||
elif isinstance(items, list):
|
||||
getattr(self, l).extend(items)
|
||||
else:
|
||||
raise ValueError(f"Unknown {l} value: {items}")
|
||||
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
@ -454,6 +662,8 @@ class Stream(BaseClaimSubType):
|
|||
self.fee.address = fee_address
|
||||
if fee_currency.lower() == 'lbc':
|
||||
self.fee.lbc = Decimal(fee_amount)
|
||||
elif fee_currency.lower() == 'btc':
|
||||
self.fee.btc = Decimal(fee_amount)
|
||||
elif fee_currency.lower() == 'usd':
|
||||
self.fee.usd = Decimal(fee_amount)
|
||||
else:
|
||||
|
|
|
@ -2,7 +2,6 @@ import json
|
|||
from decimal import Decimal
|
||||
|
||||
from lbrynet.schema.types.v1.legacy_claim_pb2 import Claim as OldClaimMessage
|
||||
from lbrynet.schema.types.v1.metadata_pb2 import Metadata as MetadataMessage
|
||||
from lbrynet.schema.types.v1.certificate_pb2 import KeyType
|
||||
from lbrynet.schema.types.v1.fee_pb2 import Fee as FeeMessage
|
||||
|
||||
|
@ -22,7 +21,7 @@ def from_old_json_schema(claim, payload: bytes):
|
|||
if language.lower() == 'english':
|
||||
language = 'en'
|
||||
try:
|
||||
stream.language = language
|
||||
stream.languages.append(language)
|
||||
except:
|
||||
pass
|
||||
stream.sd_hash = value['sources']['lbry_sd_hash']
|
||||
|
@ -35,6 +34,8 @@ def from_old_json_schema(claim, payload: bytes):
|
|||
stream.fee.lbc = Decimal(fee[currency]['amount'])
|
||||
elif currency == 'USD':
|
||||
stream.fee.usd = Decimal(fee[currency]['amount'])
|
||||
elif currency == 'BTC':
|
||||
stream.fee.btc = Decimal(fee[currency]['amount'])
|
||||
else:
|
||||
raise ValueError(f'Unknown currency: {currency}')
|
||||
stream.fee.address = fee[currency]['address']
|
||||
|
@ -53,7 +54,7 @@ def from_types_v1(claim, payload: bytes):
|
|||
stream.license_url = old.stream.metadata.licenseUrl
|
||||
stream.thumbnail_url = old.stream.metadata.thumbnail
|
||||
if old.stream.metadata.HasField('language'):
|
||||
stream.languages.add().language = old.stream.metadata.language
|
||||
stream.languages.add().message.language = old.stream.metadata.language
|
||||
stream.media_type = old.stream.source.contentType
|
||||
stream.sd_hash_bytes = old.stream.source.source
|
||||
if old.stream.metadata.nsfw:
|
||||
|
@ -66,6 +67,8 @@ def from_types_v1(claim, payload: bytes):
|
|||
stream.fee.lbc = Decimal(fee.amount)
|
||||
elif currency == 'USD':
|
||||
stream.fee.usd = Decimal(fee.amount)
|
||||
elif currency == 'BTC':
|
||||
stream.fee.btc = Decimal(fee.amount)
|
||||
else:
|
||||
raise ValueError(f'Unsupported currency: {currency}')
|
||||
if old.HasField('publisherSignature'):
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,7 @@
|
|||
import hashlib
|
||||
import tempfile
|
||||
from binascii import unhexlify
|
||||
from decimal import Decimal
|
||||
|
||||
import ecdsa
|
||||
|
||||
|
@ -73,14 +74,15 @@ class ChannelCommands(CommandTestCase):
|
|||
'title': "Cool Channel",
|
||||
'description': "Best channel on LBRY.",
|
||||
'thumbnail_url': "https://co.ol/thumbnail.png",
|
||||
'language': "en",
|
||||
'languages': ["en-US"],
|
||||
'locations': ['US::Manchester'],
|
||||
'contact_email': "human@email.com",
|
||||
'homepage_url': "https://co.ol",
|
||||
'cover_url': "https://co.ol/cover.png",
|
||||
}
|
||||
fixed_values = values.copy()
|
||||
del fixed_values['language']
|
||||
fixed_values['languages'] = [{'language': 'en'}]
|
||||
fixed_values['languages'] = ['en-US']
|
||||
fixed_values['locations'] = [{'country': 'US', 'city': 'Manchester'}]
|
||||
|
||||
# create new channel with all fields set
|
||||
tx = await self.out(self.channel_create('@bigchannel', **values))
|
||||
|
@ -259,13 +261,14 @@ class StreamCommands(CommandTestCase):
|
|||
'hovercraft3', channel_id=baz_id, channel_account_id=[account1_id]
|
||||
)
|
||||
|
||||
async def test_setting_claim_fields(self):
|
||||
async def test_setting_stream_fields(self):
|
||||
values = {
|
||||
'tags': ["cool", "awesome"],
|
||||
'title': "Cool Content",
|
||||
'description': "Best content on LBRY.",
|
||||
'thumbnail_url': "https://co.ol/thumbnail.png",
|
||||
'language': "en",
|
||||
'languages': ["en"],
|
||||
'locations': ['{"country": "UA"}'],
|
||||
|
||||
'author': "Jules Verne",
|
||||
'license': 'Public Domain',
|
||||
|
@ -280,8 +283,8 @@ class StreamCommands(CommandTestCase):
|
|||
'video_height': 600
|
||||
}
|
||||
fixed_values = values.copy()
|
||||
del fixed_values['language']
|
||||
fixed_values['languages'] = [{'language': 'en'}]
|
||||
fixed_values['languages'] = ['en']
|
||||
fixed_values['locations'] = [{'country': 'UA'}]
|
||||
|
||||
# create new channel with all fields set
|
||||
tx = await self.out(self.stream_create('big', **values))
|
||||
|
@ -293,7 +296,7 @@ class StreamCommands(CommandTestCase):
|
|||
fixed_values['release_time'] = str(values['release_time'])
|
||||
fixed_values['fee'] = {
|
||||
'address': fixed_values.pop('fee_address'),
|
||||
'amount': fixed_values.pop('fee_amount').replace('.', ''),
|
||||
'amount': float(fixed_values.pop('fee_amount')),
|
||||
'currency': fixed_values.pop('fee_currency').upper()
|
||||
}
|
||||
fixed_values['video'] = {
|
||||
|
|
|
@ -20,7 +20,7 @@ class TestOldJSONSchemaCompatibility(TestCase):
|
|||
self.assertEqual(stream.description, '10MB test file to measure download speed on Lbry p2p-network.')
|
||||
self.assertEqual(stream.license, 'None')
|
||||
self.assertEqual(stream.author, 'root')
|
||||
self.assertEqual(stream.language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
self.assertEqual(stream.media_type, 'application/octet-stream')
|
||||
self.assertEqual(stream.thumbnail_url, '/home/robert/lbry/speed.jpg')
|
||||
self.assertEqual(
|
||||
|
@ -51,7 +51,7 @@ class TestOldJSONSchemaCompatibility(TestCase):
|
|||
self.assertEqual(stream.license, 'Creative Commons Attribution 3.0 United States')
|
||||
self.assertEqual(stream.license_url, 'https://creativecommons.org/licenses/by/3.0/us/legalcode')
|
||||
self.assertEqual(stream.author, 'Mii')
|
||||
self.assertEqual(stream.language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
self.assertEqual(stream.media_type, 'application/x-msdownload')
|
||||
self.assertEqual(
|
||||
stream.sd_hash,
|
||||
|
@ -77,7 +77,7 @@ class TestOldJSONSchemaCompatibility(TestCase):
|
|||
self.assertEqual(stream.description, 'asd')
|
||||
self.assertEqual(stream.license, 'Creative Commons Attribution 4.0 International')
|
||||
self.assertEqual(stream.author, 'sgb')
|
||||
self.assertEqual(stream.language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
self.assertEqual(stream.media_type, 'video/mp4')
|
||||
self.assertEqual(
|
||||
stream.sd_hash,
|
||||
|
@ -120,7 +120,7 @@ class TestTypesV1Compatibility(TestCase):
|
|||
)
|
||||
self.assertEqual(stream.license, 'Copyrighted (contact author)')
|
||||
self.assertEqual(stream.author, 'The Linux Gamer')
|
||||
self.assertEqual(stream.language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
self.assertEqual(stream.media_type, 'video/mp4')
|
||||
self.assertEqual(stream.thumbnail_url, 'https://berk.ninja/thumbnails/FrTdBCOS_fc')
|
||||
self.assertEqual(
|
||||
|
@ -157,7 +157,7 @@ class TestTypesV1Compatibility(TestCase):
|
|||
self.assertEqual(stream.description, 'midi')
|
||||
self.assertEqual(stream.license, 'Creative Commons Attribution 4.0 International')
|
||||
self.assertEqual(stream.author, 'rpg midi')
|
||||
self.assertEqual(stream.language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
self.assertEqual(stream.media_type, 'application/x-zip-compressed')
|
||||
self.assertEqual(
|
||||
stream.sd_hash,
|
||||
|
|
|
@ -61,3 +61,103 @@ class TestFee(TestCase):
|
|||
print(stream.fee.lbc)
|
||||
with self.assertRaisesRegex(ValueError, 'Dewies can only be returned for LBC fees.'):
|
||||
print(stream.fee.dewies)
|
||||
|
||||
|
||||
class TestLanguages(TestCase):
|
||||
|
||||
def test_language_successful_parsing(self):
|
||||
stream = Stream()
|
||||
|
||||
stream.languages.append('en')
|
||||
self.assertEqual(stream.languages[0].langtag, 'en')
|
||||
self.assertEqual(stream.languages[0].language, 'en')
|
||||
self.assertEqual(stream.langtags, ['en'])
|
||||
|
||||
stream.languages.append('en-US')
|
||||
self.assertEqual(stream.languages[1].langtag, 'en-US')
|
||||
self.assertEqual(stream.languages[1].language, 'en')
|
||||
self.assertEqual(stream.languages[1].region, 'US')
|
||||
self.assertEqual(stream.langtags, ['en', 'en-US'])
|
||||
|
||||
stream.languages.append('en-Latn-US')
|
||||
self.assertEqual(stream.languages[2].langtag, 'en-Latn-US')
|
||||
self.assertEqual(stream.languages[2].language, 'en')
|
||||
self.assertEqual(stream.languages[2].script, 'Latn')
|
||||
self.assertEqual(stream.languages[2].region, 'US')
|
||||
self.assertEqual(stream.langtags, ['en', 'en-US', 'en-Latn-US'])
|
||||
|
||||
stream = Stream()
|
||||
stream.languages.extend(['en-Latn-US', 'es-ES', 'de-DE'])
|
||||
self.assertEqual(stream.languages[0].language, 'en')
|
||||
self.assertEqual(stream.languages[1].language, 'es')
|
||||
self.assertEqual(stream.languages[2].language, 'de')
|
||||
|
||||
def test_language_error_parsing(self):
|
||||
stream = Stream()
|
||||
with self.assertRaisesRegex(ValueError, 'Language has no value defined for name zz'):
|
||||
stream.languages.append('zz')
|
||||
with self.assertRaisesRegex(ValueError, 'Script has no value defined for name Zabc'):
|
||||
stream.languages.append('en-Zabc')
|
||||
with self.assertRaisesRegex(ValueError, 'Country has no value defined for name ZZ'):
|
||||
stream.languages.append('en-Zzzz-ZZ')
|
||||
with self.assertRaisesRegex(AssertionError, 'Failed to parse language tag: en-Zzz-US'):
|
||||
stream.languages.append('en-Zzz-US')
|
||||
|
||||
|
||||
class TestLocations(TestCase):
|
||||
|
||||
def test_location_successful_parsing(self):
|
||||
# from simple string
|
||||
stream = Stream()
|
||||
stream.locations.append('US')
|
||||
self.assertEqual(stream.locations[0].country, 'US')
|
||||
|
||||
# from full string
|
||||
stream = Stream()
|
||||
stream.locations.append('US:NH:Manchester:03101:42.990605:-71.460989')
|
||||
self.assertEqual(stream.locations[0].country, 'US')
|
||||
self.assertEqual(stream.locations[0].state, 'NH')
|
||||
self.assertEqual(stream.locations[0].city, 'Manchester')
|
||||
self.assertEqual(stream.locations[0].code, '03101')
|
||||
self.assertEqual(stream.locations[0].latitude, '42.990605')
|
||||
self.assertEqual(stream.locations[0].longitude, '-71.460989')
|
||||
|
||||
# from partial string
|
||||
stream = Stream()
|
||||
stream.locations.append('::Manchester:03101:')
|
||||
self.assertEqual(stream.locations[0].country, None)
|
||||
self.assertEqual(stream.locations[0].state, '')
|
||||
self.assertEqual(stream.locations[0].city, 'Manchester')
|
||||
self.assertEqual(stream.locations[0].code, '03101')
|
||||
self.assertEqual(stream.locations[0].latitude, None)
|
||||
self.assertEqual(stream.locations[0].longitude, None)
|
||||
|
||||
# from partial string lat/long
|
||||
stream = Stream()
|
||||
stream.locations.append('::::42.990605:-71.460989')
|
||||
self.assertEqual(stream.locations[0].country, None)
|
||||
self.assertEqual(stream.locations[0].state, '')
|
||||
self.assertEqual(stream.locations[0].city, '')
|
||||
self.assertEqual(stream.locations[0].code, '')
|
||||
self.assertEqual(stream.locations[0].latitude, '42.990605')
|
||||
self.assertEqual(stream.locations[0].longitude, '-71.460989')
|
||||
|
||||
# from short circuit lat/long
|
||||
stream = Stream()
|
||||
stream.locations.append('42.990605:-71.460989')
|
||||
self.assertEqual(stream.locations[0].country, None)
|
||||
self.assertEqual(stream.locations[0].state, '')
|
||||
self.assertEqual(stream.locations[0].city, '')
|
||||
self.assertEqual(stream.locations[0].code, '')
|
||||
self.assertEqual(stream.locations[0].latitude, '42.990605')
|
||||
self.assertEqual(stream.locations[0].longitude, '-71.460989')
|
||||
|
||||
# from json string
|
||||
stream = Stream()
|
||||
stream.locations.append('{"country": "ES"}')
|
||||
self.assertEqual(stream.locations[0].country, 'ES')
|
||||
|
||||
# from dict
|
||||
stream = Stream()
|
||||
stream.locations.append({"country": "UA"})
|
||||
self.assertEqual(stream.locations[0].country, 'UA')
|
||||
|
|
Loading…
Reference in a new issue