diff --git a/lbry/lbry/schema/attrs.py b/lbry/lbry/schema/attrs.py index da9125716..0cd41c33f 100644 --- a/lbry/lbry/schema/attrs.py +++ b/lbry/lbry/schema/attrs.py @@ -13,6 +13,7 @@ from torba.client.constants import COIN from lbry.schema.mime_types import guess_media_type from lbry.schema.base import Metadata, BaseMessageList +from lbry.schema.tags import clean_tags, normalize_tag from lbry.schema.types.v2.claim_pb2 import ( Fee as FeeMessage, Location as LocationMessage, @@ -533,3 +534,22 @@ class LocationList(BaseMessageList[Location]): def append(self, value): self.add().from_value(value) + + +class TagList(BaseMessageList[str]): + __slots__ = () + item_class = str + + def __init__(self, message): + message[:] = clean_tags(message) + self.message = message + + def append(self, tag: str): + tag = normalize_tag(tag) + if tag not in self.message: + self.message.append(tag) + + def extend(self, tags: List[str]): + tags = clean_tags(tags) + tags = list(set(tags).difference(set(self.message))) + self.message.extend(tags) diff --git a/lbry/lbry/schema/base.py b/lbry/lbry/schema/base.py index 343fd1736..a60f017cb 100644 --- a/lbry/lbry/schema/base.py +++ b/lbry/lbry/schema/base.py @@ -119,3 +119,6 @@ class BaseMessageList(Metadata, Generic[I]): def __delitem__(self, key): del self._message[key] + + def __eq__(self, values: List[str]) -> bool: + return self._message == values diff --git a/lbry/lbry/schema/claim.py b/lbry/lbry/schema/claim.py index 4a74fafc7..2a8033333 100644 --- a/lbry/lbry/schema/claim.py +++ b/lbry/lbry/schema/claim.py @@ -13,7 +13,7 @@ from lbry.schema.base import Signable from lbry.schema.mime_types import guess_media_type, guess_stream_type from lbry.schema.attrs import ( Source, Playable, Dimmensional, Fee, Image, Video, Audio, - LanguageList, LocationList, ClaimList, ClaimReference + LanguageList, LocationList, ClaimList, ClaimReference, TagList ) from lbry.schema.types.v2.claim_pb2 import Claim as ClaimMessage @@ -164,8 +164,8 @@ class BaseClaim: return Source(self.claim.message.thumbnail) @property - def tags(self) -> List: - return self.claim.message.tags + def tags(self) -> List[str]: + return TagList(self.claim.message.tags) @property def languages(self) -> LanguageList: diff --git a/lbry/tests/integration/test_claim_commands.py b/lbry/tests/integration/test_claim_commands.py index 04e12b196..bcc79ed41 100644 --- a/lbry/tests/integration/test_claim_commands.py +++ b/lbry/tests/integration/test_claim_commands.py @@ -523,6 +523,19 @@ class ChannelCommands(CommandTestCase): self.assertEqual(result[0]['amount'], '3.0') await self.channel_abandon(self.get_claim_id(channel)) + async def test_tag_normalization(self): + tx1 = await self.channel_create('@abc', '1.0', tags=['aBc', ' ABC ', 'xYZ ', 'xyz']) + claim_id = self.get_claim_id(tx1) + self.assertCountEqual(tx1['outputs'][0]['value']['tags'], ['abc', 'xyz']) + + tx2 = await self.channel_update(claim_id, tags=[' pqr', 'PQr ']) + self.assertCountEqual(tx2['outputs'][0]['value']['tags'], ['abc', 'xyz', 'pqr']) + + tx3 = await self.channel_update(claim_id, tags=' pqr') + self.assertCountEqual(tx3['outputs'][0]['value']['tags'], ['abc', 'xyz', 'pqr']) + + tx4 = await self.channel_update(claim_id, tags=[' pqr', 'PQr '], clear_tags=True) + self.assertEqual(tx4['outputs'][0]['value']['tags'], ['pqr']) class StreamCommands(ClaimTestCase): @@ -1101,13 +1114,14 @@ class StreamCommands(ClaimTestCase): ) # publishing again clears channel - tx4 = await self.publish('foo', languages='uk-UA') + tx4 = await self.publish('foo', languages='uk-UA', tags=['Anime', 'anime ']) self.assertEqual(2, len(self.daemon.jsonrpc_file_list())) r = await self.resolve('lbry://foo') claim = r['lbry://foo'] self.assertEqual(claim['txid'], tx4['outputs'][0]['txid']) self.assertNotIn('signing_channel', claim) self.assertEqual(claim['value']['languages'], ['uk-UA']) + self.assertEqual(claim['value']['tags'], ['anime']) class SupportCommands(CommandTestCase): diff --git a/lbry/tests/unit/schema/test_models.py b/lbry/tests/unit/schema/test_models.py index b84eb9a77..f7f40c287 100644 --- a/lbry/tests/unit/schema/test_models.py +++ b/lbry/tests/unit/schema/test_models.py @@ -97,6 +97,27 @@ class TestLanguages(TestCase): stream.languages.append('en-Zzz-US') +class TestTags(TestCase): + + def test_normalize_tags(self): + claim = Claim() + + claim.channel.update(tags=['Anime', 'anime', ' aNiMe', 'maNGA ']) + self.assertCountEqual(claim.channel.tags, ['anime', 'manga']) + + claim.channel.update(tags=['Juri', 'juRi']) + self.assertCountEqual(claim.channel.tags, ['anime', 'manga', 'juri']) + + claim.channel.update(tags='Anime') + self.assertCountEqual(claim.channel.tags, ['anime', 'manga', 'juri']) + + claim.channel.update(clear_tags=True) + self.assertEqual(len(claim.channel.tags), 0) + + claim.channel.update(tags='Anime') + self.assertEqual(claim.channel.tags, ['anime']) + + class TestLocations(TestCase): def test_location_successful_parsing(self):