├── README.md ├── README_cn.md ├── requirements.txt └── rss3_sdk ├── __init__.py ├── config.py ├── converter.py ├── exceptions.py ├── rss3_account.py ├── rss3_handle.py ├── type ├── __init__.py ├── inn_type.py └── rss3_type.py └── until.py /README.md: -------------------------------------------------------------------------------- 1 | # RSS3 Python SDK 2 | 3 | python3 version: v3.9.X 4 | SDK version: 0.1.0-alpha 5 | 6 | ## Current issues to be resolved 7 | 8 | * Due to time development, the test case has not yet been written completely 9 | * Due to the development time, I have temporarily kept up with the development process of the first version. Of course, it will be optimized more comprehensively in the later period. 10 | 11 | ## Fix the problem 12 | 13 | * Sign signature mechanism 14 | 15 | ## API 16 | 17 | ### Account 18 | 19 | ```python 20 | from rss3_sdk import rss3_account 21 | ``` 22 | 23 | #### Generate a new account 24 | 25 | ```python 26 | curr_account = rss3_account.RSS3Account() 27 | ``` 28 | 29 | #### Initialize with the original private key 30 | 31 | ```python 32 | curr_account = rss3_account.RSS3Account('0x47e18d6c386898b424025cd9db446f779ef24ad33a26c499c87bb3d9372540ba') 33 | ``` 34 | 35 | ### RSS3Handle 36 | 37 | ```python 38 | from rss3_sdk import rss3_handle 39 | ``` 40 | 41 | #### Initialization 42 | 43 | ```python 44 | handle = rss3_handle.RSS3Handle( 45 | endpoint = 'rss3-hub-playground-6raed.ondigitalocean.app', 46 | rss3_account = curr_account, 47 | fill_update_callback = fill_update) 48 | ``` 49 | 50 | #### Get and modify item 51 | 52 | ```python 53 | # The type of'inn_item' is IInnItem, you can also generate a new one yourself 54 | 55 | inn_item = handle.item_get('0x6338ee94fB85e157D117d681E808a34a9aC21f31-item-1') 56 | inn_item.title = "Change this one" 57 | handle.item_patch(inn_item) 58 | ``` 59 | 60 | #### User information modification 61 | 62 | ```python 63 | # The type of'inn_profile' is IInnProfile, you can also generate a new one yourself 64 | 65 | inn_profile = handle.profile_get() 66 | inn_profile.name = "Child" 67 | handle.profile_patch(inn_profile) 68 | ``` 69 | 70 | ## Next step plan 71 | 72 | * Develop with the latest official js version of the SDK (there will be slightly different), split the module into more detail, split the file, item, and items, and publish it to pip 73 | 74 | 75 | ## Final appeal 76 | Since I have too little time and stepped on more pits, I hope that colleagues who have the time can give more valuable opinions and cooperate in development. 77 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # RSS3 Python SDK 2 | 3 | python3 version:v3.9.X 4 | SDK版本:0.1.0-alpha版 5 | 6 | ## 当前暂未解决问题 7 | 8 | * 由于时间开发的关系,测试用例暂未编写完整 9 | * 由于开发时间的关系,暂时跟上了第一版的开发进程,当然,后期会优化的更加全面 10 | 11 | ## 修复问题 12 | 13 | * sign 签名机制的问题 14 | 15 | ## API 16 | 17 | ### Account 18 | 19 | ```python 20 | from rss3_sdk import rss3_account 21 | ``` 22 | 23 | #### 生成一个新的账户 24 | 25 | ```python 26 | curr_account = rss3_account.RSS3Account() 27 | ``` 28 | 29 | #### 用原来的私钥进行初始化 30 | 31 | ```python 32 | curr_account = rss3_account.RSS3Account('0x47e18d6c386898b424025cd9db446f779ef24ad33a26c499c87bb3d9372540ba') 33 | ``` 34 | 35 | ### RSS3Handle 36 | 37 | ```python 38 | from rss3_sdk import rss3_handle 39 | ``` 40 | 41 | #### 初始化 42 | 43 | ```python 44 | handle = rss3_handle.RSS3Handle( 45 | endpoint = 'hub.rss3.io', 46 | rss3_account = curr_account) 47 | ``` 48 | 49 | #### 获取并修改item 50 | 51 | ```python 52 | # The type of'inn_item' is IInnItem, you can also generate a new one yourself 53 | 54 | inn_item = handle.item_get('0x6338ee94fB85e157D117d681E808a34a9aC21f31-item-1') 55 | inn_item.title = "Change this one" 56 | handle.item_patch(inn_item) 57 | ``` 58 | 59 | #### 用户信息修改 60 | 61 | ```python 62 | # The type of'inn_profile' is IInnProfile, you can also generate a new one yourself 63 | 64 | inn_profile = handle.profile_get() 65 | inn_profile.name = "Child" 66 | handle.profile_patch(inn_profile) 67 | ``` 68 | 69 | ## 下一步计划 70 | 71 | * 以最跟进新版本官方的js版本的SDK进行开发(会略有区别),将模块拆分得更细,将file、item、items拆分开后,发布到pip上 72 | 73 | ## 最后呼吁 74 | * 由于本人时间过少,踩坑多,所以希望有时间的同僚多提宝贵意见以及共同合作开发 75 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==21.2.0 2 | base58==2.1.0 3 | bitarray==1.2.2 4 | certifi==2021.5.30 5 | chardet==4.0.0 6 | cytoolz==0.11.0 7 | eth-abi==2.1.1 8 | eth-account==0.5.4 9 | eth-hash==0.3.1 10 | eth-keyfile==0.5.1 11 | eth-keys==0.3.3 12 | eth-rlp==0.2.1 13 | eth-typing==2.2.2 14 | eth-utils==1.10.0 15 | hexbytes==0.2.1 16 | idna==2.10 17 | ipfshttpclient==0.7.0a1 18 | jsonschema==3.2.0 19 | lru-dict==1.1.7 20 | marshmallow==3.12.1 21 | multiaddr==0.0.9 22 | netaddr==0.8.0 23 | parsimonious==0.8.1 24 | protobuf==3.17.3 25 | pycryptodome==3.10.1 26 | pyrsistent==0.18.0 27 | pysha3==1.0.2 28 | pytz==2021.1 29 | requests==2.25.1 30 | rlp==2.0.1 31 | sha3==0.2.1 32 | six==1.16.0 33 | toolz==0.11.1 34 | tzlocal==2.1 35 | urllib3==1.26.0 36 | varint==1.0.2 37 | web3==5.16.0 38 | websockets==8.1 39 | -------------------------------------------------------------------------------- /rss3_sdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazzyRabbit/RSS3-Python-SDK/0e5137319c77f72f8454a0925751def62afa748d/rss3_sdk/__init__.py -------------------------------------------------------------------------------- /rss3_sdk/config.py: -------------------------------------------------------------------------------- 1 | conf = { 2 | "itemPageSize": 100, 3 | "maxValueLength": 280, 4 | "version": 'rss3.io/version/v0.1.0', 5 | # "proxy":"http://127.0.0.1:4780", # 在需要翻墙的时候可以用来及时的访问 6 | } -------------------------------------------------------------------------------- /rss3_sdk/converter.py: -------------------------------------------------------------------------------- 1 | from rss3_sdk.type import rss3_type 2 | from rss3_sdk.type import inn_type 3 | from marshmallow import Schema, fields, post_load, EXCLUDE 4 | 5 | # rss3 json converter 6 | ######################################### 7 | class IRSS3ContentSchema(Schema) : 8 | address = fields.List(fields.String, data_key = 'address', required = True) 9 | mime_type = fields.String(data_key = 'mime_type', required = True) 10 | name = fields.String(data_key = 'name') 11 | tags = fields.List(fields.String, data_key = 'tags') 12 | size_in_bytes = fields.String(data_key = 'size_in_bytes') 13 | duration_in_seconds = fields.String(data_key = 'duration_in_seconds') 14 | 15 | @post_load 16 | def make_content(self, data, **kwargs): 17 | return rss3_type.IRSS3Content(**data) 18 | 19 | class IRSS3ContextSchema(Schema) : 20 | type = fields.String(data_key = 'type') 21 | list = fields.List(fields.String, data_key = 'list', required = True) 22 | list_next = fields.String(data_key = 'list_next') 23 | 24 | @post_load 25 | def make_context(self, data, **kwargs): 26 | return rss3_type.IRSS3Context(**data) 27 | 28 | class IRSS3ItemSchema(Schema): 29 | id = fields.String(data_key = 'id', required = True) 30 | authors = fields.List(fields.String, data_key = 'authors') 31 | title = fields.String(data_key = 'title') 32 | summary = fields.String(data_key = 'summary') 33 | tags = fields.List(fields.String, data_key = 'tags') 34 | date_published = fields.String(data_key = 'date_published') 35 | date_modified = fields.String(data_key = 'date_modified') 36 | 37 | type = fields.String(data_key = 'type') 38 | upstream = fields.String(data_key = 'upstream') 39 | 40 | contents = fields.List(fields.Nested(IRSS3ContentSchema), data_key = 'contents') 41 | a_contexts = fields.List(fields.Nested(IRSS3ContextSchema), data_key = '@contexts') 42 | 43 | signature = fields.String(data_key = 'signature', required = True) 44 | 45 | @post_load 46 | def make_item(self, data, **kwargs): 47 | return rss3_type.IRSS3Item(**data) 48 | 49 | class IRSS3ProfileSchema(Schema) : 50 | name = fields.String(data_key = 'name') 51 | avatar = fields.List(fields.String, data_key = 'avatar', required = False) 52 | bio = fields.String(data_key = 'bio', required = False) 53 | tags = fields.List(fields.String, data_key = 'tags', required = False) 54 | signature = fields.String(data_key = 'signature', required = True) 55 | 56 | @post_load 57 | def make_profile(self, data, **kwargs): 58 | return rss3_type.IRSS3Profile(**data) 59 | 60 | class IRSS3LinkSchema(Schema) : 61 | type = fields.String(data_key = 'type', required = True) 62 | tags = fields.List(fields.String, data_key = 'tags') 63 | list = fields.List(fields.String, data_key = 'list', required = True) 64 | list_next = fields.String(data_key = 'list_next') 65 | signature = fields.String(data_key = 'signature', required = True) 66 | 67 | @post_load 68 | def make_link(self, data, **kwargs): 69 | return rss3_type.IRSS3Link(**data) 70 | 71 | class IRSS3BacklinkSchema(Schema) : 72 | type = fields.String(data_key = 'type', required = True) 73 | list = fields.List(fields.String, data_key = 'list', required = True) 74 | list_next = fields.String(data_key = 'list_next') 75 | 76 | @post_load 77 | def make_backlink(self, data, **kwargs): 78 | return rss3_type.IRSS3Backlink(**data) 79 | 80 | class IRSS3AssetSchema(Schema) : 81 | type = fields.String(data_key = 'type', required = True) 82 | tags = fields.List(fields.String, data_key = 'tags') 83 | content = fields.String(data_key = 'content', required = True) 84 | 85 | @post_load 86 | def make_asset(self, data, **kwargs): 87 | return rss3_type.IRSS3Asset(**data) 88 | 89 | class IRSS3IndexSchema(Schema): 90 | id = fields.String(data_key = 'id', required = True) 91 | a_version = fields.String(data_key = '@version', required = True) 92 | date_created = fields.String(data_key = 'date_created', required = True) 93 | date_updated = fields.String(data_key = 'date_updated', required = True) 94 | signature = fields.String(data_key = 'signature', required = True) 95 | 96 | profile = fields.Nested(IRSS3ProfileSchema, data_key = 'profile') 97 | 98 | items = fields.List(fields.Nested(IRSS3ItemSchema), data_key = 'items') 99 | items_next = fields.String(data_key = 'items_next') 100 | 101 | links = fields.List(fields.Nested(IRSS3LinkSchema, data_key = 'links')) 102 | a_backlinks = fields.List(fields.Nested(IRSS3BacklinkSchema), data_key = '@backlinks') 103 | assets = fields.List(fields.Nested(IRSS3AssetSchema, data_key = 'assets')) 104 | 105 | @post_load 106 | def make_index(self, data, **kwargs): 107 | return rss3_type.IRSS3Index(**data) 108 | 109 | class IRSS3ItemsSchema(Schema) : 110 | id = fields.String(data_key = 'id', required = True) 111 | a_version = fields.String(data_key = '@version', required = True) 112 | date_created = fields.String(data_key = 'date_published', required = True) 113 | date_updated = fields.String(data_key = 'date_updated', required = True) 114 | signature = fields.String(data_key = 'signature') 115 | 116 | items = fields.Nested(IRSS3ItemSchema, data_key = 'item', required = True) 117 | items_next = fields.String(data_key = 'items_next', required = True) 118 | 119 | @post_load 120 | def make_items(self, data, **kwargs): 121 | return rss3_type.IRSS3Items(**data) 122 | 123 | class IRSS3ListSchema(Schema) : 124 | id = fields.String(data_key = 'id', required = True) 125 | 126 | list = fields.List(fields.String, data_key = 'list', required = True) 127 | next = fields.String(data_key = 'next', required = True) 128 | 129 | @post_load 130 | def make_list(self, data, **kwargs): 131 | return rss3_type.IRSS3List(**data) 132 | 133 | # inn json converter 134 | ######################################### 135 | class IInnProfileSchema(Schema) : 136 | class Meta: 137 | unknown = EXCLUDE 138 | 139 | name = fields.String(data_key='name', required = False) 140 | avatar = fields.List(fields.String, data_key='avatar', required = False) 141 | bio = fields.String(data_key='bio', required = False) 142 | tags = fields.List(fields.String, data_key='tags', required = False) 143 | 144 | @post_load 145 | def make_profile(self, data, **kwargs): 146 | return inn_type.IInnProfile(**data) 147 | 148 | class IInnItemSchema(Schema) : 149 | class Meta: 150 | unknown = EXCLUDE 151 | 152 | id = fields.String(data_key = 'id', required = True) 153 | authors = fields.List(fields.String, data_key = 'authors') 154 | title = fields.String(data_key = 'title') 155 | summary = fields.String(data_key = 'summary') 156 | tags = fields.List(fields.String, data_key = 'tags') 157 | 158 | type = fields.String(data_key = 'type') 159 | upstream = fields.String(data_key = 'upstream') 160 | 161 | contents = fields.List(fields.Nested(IRSS3ContentSchema), data_key = 'contents') 162 | 163 | @post_load 164 | def make_item(self, data, **kwargs): 165 | print(data) 166 | return inn_type.IInnItem(**data) 167 | 168 | -------------------------------------------------------------------------------- /rss3_sdk/exceptions.py: -------------------------------------------------------------------------------- 1 | class HttpError(Exception) : 2 | pass -------------------------------------------------------------------------------- /rss3_sdk/rss3_account.py: -------------------------------------------------------------------------------- 1 | import hexbytes 2 | from eth_keys import keys 3 | from eth_account import account 4 | 5 | # If the hexadecimal number is wrong, an exception will be thrown 6 | class RSS3Account : 7 | def __init__(self, private_key = None): 8 | self.private_key = private_key 9 | self.address = None 10 | # New account means that you can’t get it online 11 | self.new_account_tag = None 12 | 13 | if self.private_key != None : 14 | pk = keys.PrivateKey(hexbytes.HexBytes(self.private_key)) 15 | self.address = pk.public_key.to_checksum_address() 16 | self.new_account_tag = False 17 | else : 18 | new_account_key = account.Account().create() 19 | self.address = new_account_key.address 20 | self.private_key = hexbytes.HexBytes(new_account_key.key).hex() 21 | self.new_account_tag = True 22 | -------------------------------------------------------------------------------- /rss3_sdk/rss3_handle.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import math 3 | import json 4 | import urllib3 5 | from . import until 6 | from . import config 7 | from . import converter 8 | from . import exceptions 9 | from .type import rss3_type 10 | from .type import inn_type 11 | 12 | class RSS3Handle : 13 | def __init__(self, endpoint, rss3_account, fill_update_callback = None) : 14 | self._endpoint = endpoint 15 | self._rss3_account = rss3_account 16 | self._fill_update_callback = fill_update_callback 17 | 18 | if 'proxy' in config.conf : 19 | self._http = urllib3.ProxyManager(config.conf['proxy']) 20 | else : 21 | self._http = urllib3.PoolManager() 22 | 23 | self._file_stroge_dict = {} 24 | self._file_update_tag = set() 25 | 26 | if self._rss3_account == None or self._endpoint == None : 27 | raise ValueError("Rss3_account or endpoint is invalid parameter") 28 | 29 | 30 | if self._rss3_account.new_account_tag == False : 31 | self.get_file(self._rss3_account.address) 32 | else : 33 | now_date = until.get_datetime_isostring() 34 | personl_file = rss3_type.IRSS3Index(id = self._rss3_account.address, 35 | date_created = now_date, 36 | date_updated = now_date) 37 | self._file_stroge_dict[self._rss3_account.address] = personl_file 38 | self._file_update_tag.add(self._rss3_account.address) 39 | 40 | # profile used 41 | def profile_get(self): 42 | personl_file = self._file_stroge_dict[self._rss3_account.address] 43 | if personl_file == None: 44 | raise ValueError("can not find %s in stroge" % self._rss3_account.address) 45 | 46 | curr_profile = personl_file.profile 47 | if curr_profile == None : 48 | return inn_type.IInnProfile() 49 | else : 50 | profile_dict = converter.IRSS3ProfileSchema().dump(curr_profile) 51 | profile_dict = until.remove_empty_properties(profile_dict) 52 | inn_profile = converter.IInnProfileSchema().load(profile_dict) 53 | 54 | return inn_profile 55 | 56 | def profile_patch(self, inn_profile) : 57 | if isinstance(inn_profile, inn_type.IInnProfile) == False and inn_profile != None : 58 | raise ValueError("Inn_profile is invalid parameter") 59 | 60 | personl_file = self._file_stroge_dict[self._rss3_account.address] 61 | if personl_file == None : 62 | raise ValueError("can not find %s in stroge" % self._rss3_account.address) 63 | 64 | if personl_file.profile == None : 65 | personl_file.profile = rss3_type.IRSS3Profile() 66 | 67 | inn_profile_dict = converter.IInnProfileSchema().dump(inn_profile) 68 | inn_profile_dict = until.remove_empty_properties(inn_profile_dict) 69 | signature = until.sign(inn_profile_dict, self._rss3_account.private_key) 70 | inn_profile_dict['signature'] = signature 71 | personl_file.profile = converter.IRSS3ProfileSchema().load(inn_profile_dict) 72 | self._update(personl_file) 73 | 74 | # item used 75 | def _get_position(self, item_id) : 76 | prase_ele = until.prase_id(item_id) 77 | if prase_ele['address'] != self._rss3_account.address: 78 | raise ValueError("File_id is invalid parameter, address %s is error." % prase_ele['address']) 79 | 80 | personl_file = self._file_stroge_dict[self._rss3_account.address] 81 | if personl_file == None and isinstance(personl_file, rss3_type.IRSS3Index) and type(personl_file) != rss3_type.IRSS3Items: 82 | raise TypeError("Address [%s] find irss3 index is error" % self._rss3_account.address) 83 | 84 | items_file = self.get_file(self._rss3_account.address) 85 | item_filter_id_list = [item.id for item in personl_file.items] 86 | index = item_filter_id_list.index(item_id) 87 | 88 | if item_id not in item_filter_id_list: 89 | items_file_id = self._rss3_account.address + '-items-' + str( 90 | math.ceil(prase_ele['index'] / config.conf['itemPageSize'])) 91 | items_file = self.get_file(items_file_id) 92 | if items_file != None: 93 | item_filter_id_list = [item.id for item in personl_file.items] 94 | index = item_filter_id_list.index(item_id) 95 | else: 96 | return None 97 | 98 | return { 99 | 'file' : items_file, 100 | 'index' : index 101 | } 102 | 103 | def item_get(self, item_id): 104 | if item_id == None: 105 | raise ValueError("File_id is invalid parameter") 106 | 107 | item = None 108 | position = self._get_position(item_id) 109 | if position != None : 110 | item = position['file'].items[position['index']] 111 | if item != None : 112 | item_dict = converter.IRSS3ItemSchema().dump(item) 113 | item_dict = until.remove_empty_properties(item_dict) 114 | item = converter.IInnItemSchema().load(item_dict) 115 | 116 | return item 117 | 118 | def item_post(self, inn_item) : 119 | if isinstance(inn_item, inn_type.IInnItem) == False : 120 | raise ValueError("Inn_item is invalid parameter") 121 | 122 | now_date = until.get_datetime_isostring() 123 | personl_file = self._file_stroge_dict[self._rss3_account.address] 124 | if personl_file == None : 125 | raise 126 | 127 | inn_item_dict = converter.IInnItemSchema().dump(inn_item) 128 | inn_item_dict['date_published'] = now_date 129 | inn_item_dict['date_modified'] = now_date 130 | 131 | id_suffix = 0 132 | if len(personl_file.items) != 0 : 133 | prase_ele = until.prase_id(personl_file.items[0].id) 134 | old_index = prase_ele['index'] 135 | try : 136 | id_suffix = old_index + 1 137 | except Exception as e : 138 | raise ValueError("Inn_item conversion failed : %s " % e) 139 | 140 | new_item_id = self._rss3_account.address + '-item-' + str(id_suffix) 141 | inn_item_dict['id'] = new_item_id 142 | 143 | inn_item_dict = until.remove_empty_properties(inn_item_dict) 144 | signature = until.sign(inn_item_dict, self._rss3_account.private_key) 145 | inn_item_dict['signature'] = signature 146 | 147 | new_item = converter.IRSS3ItemSchema().load(inn_item_dict) 148 | 149 | if len(personl_file.items) + 1 <= config.conf["itemPageSize"] : 150 | personl_file.items.insert(0, new_item) 151 | else : 152 | new_items_list = list() 153 | new_items_list.append(new_item) 154 | old_items_id_suffix = 0 if personl_file.items_next == None else until.prase_id(personl_file.items_next)['index'] + 1 155 | 156 | new_items_id = self._rss3_account.address + '-items-' + str(old_items_id_suffix) 157 | new_items = rss3_type.IRSS3Items(id = new_items_id, 158 | a_version = 'rss3.io/version/v0.1.0', 159 | date_created = now_date, 160 | signature = '', 161 | items = new_items_list, 162 | items_next = personl_file.items_next) 163 | 164 | personl_file.items = new_items_list 165 | personl_file.items_next = new_items_id 166 | self._update(new_items) 167 | 168 | self._update(personl_file) 169 | personl_file.date_updated = now_date 170 | self._file_update_tag.add(self._rss3_account.address) 171 | 172 | return new_item 173 | 174 | def item_patch(self, inn_item) : 175 | if inn_item == None or \ 176 | isinstance(inn_item, inn_type.IInnItem) == False or \ 177 | len(inn_item.id) == 0 : 178 | raise ValueError("Inn_item and items_id is invalid parameter") 179 | 180 | personl_file = self._file_stroge_dict[self._rss3_account.address] 181 | if personl_file == None and isinstance(personl_file, rss3_type.IRSS3Index) == False and isinstance(personl_file, rss3_type.IRSS3Items) : 182 | return TypeError("Items_id %s find irss3 index is error" % self._rss3_account.address) 183 | 184 | now_date = until.get_datetime_isostring() 185 | position = self._get_position(inn_item.id) 186 | if position != None : 187 | inn_item_dict = converter.IInnItemSchema().dump(inn_item) 188 | inn_item_dict['date_modified'] = now_date 189 | inn_item_dict = until.remove_empty_properties(inn_item_dict) 190 | signature = until.sign(inn_item_dict, self._rss3_account.private_key) 191 | inn_item_dict['signature'] = signature 192 | rss3_item = converter.IRSS3ItemSchema().load(inn_item_dict) 193 | position['file'].items[position['index']] = rss3_item 194 | self._update(personl_file) 195 | 196 | return position['file'].items[position['index']] 197 | 198 | def get_file(self, file_id) : 199 | if file_id == None: 200 | raise ValueError("File_id is invalid parameter") 201 | 202 | if self._file_stroge_dict.get(file_id) != None : 203 | return self._file_stroge_dict[file_id] 204 | 205 | file_get_url = "https://" + self._endpoint + '/' + file_id 206 | try: 207 | response = self._http.request(method = 'GET', url = file_get_url) 208 | if response.status == 200 : 209 | resp_dict = json.loads(response.data.decode()) 210 | file_id = resp_dict['id'] 211 | if file_id == None : 212 | raise ValueError("Can't find file_id : %s" % file_id) 213 | 214 | # Verify the pulled file 215 | check = until.check(resp_dict, 216 | file_id.split('-')[0]) 217 | if check == False : 218 | raise ValueError("The file_id %s signature does not match " % file_id) 219 | 220 | rss3_obj = until.get_rss3_obj(file_id, resp_dict) 221 | self._file_stroge_dict[file_id] = rss3_obj 222 | 223 | return rss3_obj 224 | 225 | else : 226 | if response.data != None : 227 | resp_dict = json.loads(response.data.decode()) 228 | if resp_dict['code'] == 5001 : 229 | now_date = until.get_datetime_isostring() 230 | new_person_file = until.get_rss3_obj(file_id) 231 | new_person_file.date_created = now_date 232 | new_person_file.date_updated = now_date 233 | new_person_file.signature = '' 234 | self._file_stroge_dict[self._rss3_account.address] = new_person_file 235 | self._file_update_tag.add(self._rss3_account.address) 236 | else : 237 | raise exceptions.HttpError("Execute wrong network code : %d" % response.status) 238 | except urllib3.exceptions.HTTPError as e: 239 | raise exceptions.HttpError("Connect Error : %s" % e) 240 | 241 | def update_file(self) : 242 | file_get_url = "https://" + self._endpoint 243 | contents = list() 244 | 245 | for file_name in self._file_update_tag : 246 | file = self._file_stroge_dict.get(file_name) 247 | if file != None : 248 | try : 249 | file_dict = until.get_rss3_json_dict(file, 2) 250 | except TypeError as e : 251 | continue 252 | file_dict = until.remove_empty_properties(file_dict) 253 | file.signature = until.sign(file_dict, self._rss3_account.private_key) 254 | file_dict['signature'] = file.signature 255 | contents.append(file_dict) 256 | 257 | contents_dict = { 258 | "contents" : contents 259 | } 260 | content_json_str = json.dumps(contents_dict, ensure_ascii = False).encode("utf-8") 261 | 262 | try: 263 | response = self._http.request(method = 'PUT', 264 | url = file_get_url, 265 | body = content_json_str, 266 | headers={"Content-Type": "application/json"}) 267 | if response.status == 200: 268 | self._file_update_tag.clear() 269 | elif response.data != None : 270 | resp_dict = json.loads(response.data.decode()) 271 | if resp_dict != None : 272 | raise exceptions.HttpError("Rss3 error code %r, Rss3 error result %r" % (resp_dict['code'], resp_dict['message'])) 273 | else : 274 | raise exceptions.HttpError("Execute wrong network code: %d" % response.status) 275 | else : 276 | raise exceptions.HttpError("Execute wrong network code: %d" % response.status) 277 | except urllib3.exceptions.HTTPError as e: 278 | raise exceptions.HttpError("Connect Error : %s" % e) 279 | 280 | def _update(self, irss3_base) : 281 | if isinstance(irss3_base, rss3_type.IRSS3Base) == False : 282 | raise ValueError("irss3_base is invalid parameter") 283 | 284 | irss3_base.date_updated = until.get_datetime_isostring() 285 | self._file_stroge_dict[irss3_base.id] = irss3_base 286 | self._file_update_tag.add(irss3_base.id) 287 | 288 | 289 | 290 | 291 | 292 | -------------------------------------------------------------------------------- /rss3_sdk/type/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazzyRabbit/RSS3-Python-SDK/0e5137319c77f72f8454a0925751def62afa748d/rss3_sdk/type/__init__.py -------------------------------------------------------------------------------- /rss3_sdk/type/inn_type.py: -------------------------------------------------------------------------------- 1 | class IInnProfile() : 2 | def __init__(self, name=None, avatar=None, bio=None, tags=[], signature=None): 3 | self.name = name 4 | self.avatar = avatar 5 | self.bio = bio 6 | self.tags = tags 7 | 8 | class IInnItem() : 9 | def __init__(self, id = None, authors = [], title = None, summary = None, tags = [], type = None, upstream = None, contents = []) : 10 | self.id = id 11 | self.authors = authors 12 | self.title = title 13 | self.summary = summary 14 | self.tags = tags 15 | 16 | self.type = type 17 | self.upstream = upstream 18 | 19 | self.contents = contents -------------------------------------------------------------------------------- /rss3_sdk/type/rss3_type.py: -------------------------------------------------------------------------------- 1 | ######################################### 2 | class IRSS3Content(): 3 | def __init__(self, address = [], mime_type = None, name = None, tags = [], size_in_bytes = None, duration_in_seconds = None) : 4 | self.address = address 5 | self.mime_type = mime_type 6 | self.name = name 7 | self.tags = tags 8 | self.size_in_bytes = size_in_bytes 9 | self.duration_in_seconds = duration_in_seconds 10 | 11 | class IRSS3Context() : 12 | def __init__(self, type = None, list = [], list_next = None) : 13 | self.type = type 14 | self.list = list 15 | self.list_next = list_next 16 | 17 | class IRSS3Item(): 18 | def __init__(self, id = None, authors = [], title = None, summary = None, tags = [], date_published = None, date_modified = None, type = None, upstream = None, contents = [], a_contexts = [], signature = None) : 19 | self.id = id 20 | self.authors = authors 21 | self.title = title 22 | self.summary = summary 23 | self.tags = tags 24 | self.date_published = date_published 25 | self.date_modified = date_modified 26 | 27 | self.type = type 28 | self.upstream = upstream 29 | 30 | self.contents = contents 31 | self.a_contexts = a_contexts 32 | 33 | self.signature = signature 34 | 35 | ######################################### 36 | 37 | class IRSS3Profile() : 38 | def __init__(self, name = None, avatar = None, bio = None, tags = [], signature = None) : 39 | self.name = name 40 | self.avatar = avatar 41 | self.bio = bio 42 | self.tags = tags 43 | self.signature = signature 44 | 45 | ######################################### 46 | 47 | class IRSS3Base() : 48 | def __init__(self, id = None, a_version = 'rss3.io/version/v0.1.0', date_created = 0, date_updated = 0, signature = None) : 49 | self.id = id 50 | self.a_version = a_version 51 | self.date_created = date_created 52 | self.date_updated = date_updated 53 | self.signature = signature 54 | 55 | ######################################### 56 | class IRSS3Link() : 57 | def __init__(self, type = None, tags = [], list = [], list_next = None, signature = None) : 58 | self.type = type 59 | self.tags = tags 60 | self.list = list 61 | self.list_next = list_next 62 | self.signature = signature 63 | 64 | class IRSS3Backlink() : 65 | def __init__(self, type = None, list = [], next = None) : 66 | self.type = type 67 | self.list = list 68 | self.next = next 69 | 70 | class IRSS3Asset() : 71 | def __init__(self, type = None, tags = [], content = None) : 72 | self.type = type 73 | self.tags = tags 74 | self.content = content 75 | 76 | class IRSS3Index(IRSS3Base): 77 | def __init__(self, id = None, a_version = 'rss3.io/version/v0.1.0', date_created = 0, date_updated = 0, signature = None, \ 78 | profile = None, items = [], items_next = None, links = [], a_backlinks = [], assets = []) : 79 | super().__init__(id, a_version, date_created, date_updated, signature) 80 | 81 | self.profile = profile 82 | 83 | self.items = items 84 | self.items_next = items_next 85 | 86 | self.links = links # IRSS3Link 87 | self.a_backlinks = a_backlinks # IRSS3Backlink 88 | self.assets = assets # IRSS3Asset 89 | 90 | ######################################### 91 | class IRSS3Items(IRSS3Base) : 92 | def __init__(self, id = None, a_version = 'rss3.io/version/v0.1.0', date_created = 0, date_updated = 0, signature = None, \ 93 | items = [], items_next = None) : 94 | super().__init__(id, a_version, date_created, date_updated, signature) 95 | self.items = items 96 | self.items_next = items_next 97 | 98 | ######################################### 99 | 100 | class IRSS3List : 101 | def __init__(self, id = None, list = [], next = None) : 102 | self.id = id 103 | 104 | self.list = list 105 | self.next = next -------------------------------------------------------------------------------- /rss3_sdk/until.py: -------------------------------------------------------------------------------- 1 | import json 2 | import copy 3 | import tzlocal 4 | from .type import rss3_type 5 | from datetime import datetime 6 | from . import converter 7 | from web3.auto import w3 8 | from eth_account.messages import encode_defunct 9 | 10 | def get_datetime_isostring() : 11 | dt = datetime.now(tzlocal.get_localzone()) 12 | try: 13 | utc = dt - dt.utcoffset() 14 | except TypeError as e: 15 | raise ("Get current UTC Time False, current time %r " % dt) 16 | isostring = datetime.strftime(utc, '%Y-%m-%dT%H:%M:%S.{0}Z') 17 | return isostring.format(int(round(utc.microsecond/1000.0))) 18 | 19 | ######################################### 20 | 21 | def value_is_not_empty(value) : 22 | return value not in ['', None, {}, []] 23 | 24 | def remove_not_sign_properties(data) : 25 | if data == None : 26 | return None 27 | 28 | temp_data = dict() 29 | for key, value in data.items(): 30 | if (key.find('@') == -1) and key != 'signature' : 31 | temp_data[key] = value 32 | return temp_data 33 | 34 | def remove_empty_properties(data) : 35 | if isinstance(data, dict): 36 | temp_data = dict() 37 | for key, value in data.items(): 38 | if value_is_not_empty(value): 39 | new_value = remove_empty_properties(value) 40 | if value_is_not_empty(new_value) : 41 | temp_data[key] = new_value 42 | return None if not temp_data else temp_data 43 | 44 | elif isinstance(data, list): 45 | temp_data = list() 46 | for value in data: 47 | if value_is_not_empty(value): 48 | new_value = remove_empty_properties(value) 49 | if value_is_not_empty(new_value) : 50 | temp_data.append(new_value) 51 | return None if not temp_data else temp_data 52 | 53 | elif value_is_not_empty(data): 54 | return data 55 | 56 | def sorted_irss_dict(data) : 57 | if isinstance(data, dict) : 58 | temp_data = dict() 59 | for key, value in data.items() : 60 | new_value = sorted_irss_dict(value) 61 | temp_data[key] = new_value 62 | return sorted(temp_data.items(), key=lambda d: d[0]) 63 | 64 | elif isinstance(data, list) : 65 | temp_data = list() 66 | count = 0 67 | for value in data: 68 | new_value = sorted_irss_dict(value) 69 | temp_data.append([str(count), new_value]) 70 | count = count + 1 71 | 72 | return None if not temp_data else temp_data 73 | 74 | elif value_is_not_empty(data) : 75 | return data 76 | 77 | def irss3_data_dump_handle(irss3_data) : 78 | not_sign_irss3_data = copy.deepcopy(irss3_data) 79 | not_sign_irss3_data = remove_not_sign_properties(not_sign_irss3_data) 80 | not_sign_irss3_data = sorted_irss_dict(not_sign_irss3_data) 81 | 82 | return json.dumps(not_sign_irss3_data, separators=(',',':'), ensure_ascii = False) 83 | 84 | ######################################### 85 | 86 | def sign(irss3_data, private_key) : 87 | if irss3_data == None or private_key == None : 88 | return None 89 | 90 | irss3_json_msg = irss3_data_dump_handle(irss3_data) 91 | message = encode_defunct(text = irss3_json_msg) 92 | 93 | return w3.eth.account.sign_message(message, private_key).signature.hex() 94 | 95 | def check(irss3_data, personal_address) : 96 | if irss3_data == None or isinstance(irss3_data, dict) == False or irss3_data['signature'] == None or personal_address == None : 97 | return False 98 | 99 | irss3_json_msg = irss3_data_dump_handle(irss3_data) 100 | message = encode_defunct(text = irss3_json_msg) 101 | return w3.eth.account.recover_message(message, signature=irss3_data['signature']) == personal_address 102 | 103 | 104 | def get_rss3_obj(file_id, rss3_dict = None) : 105 | if file_id == None and (rss3_dict != None or isinstance(rss3_dict, dict)): 106 | raise TypeError("File_id and rss3_dict is is invalid parameter") 107 | if file_id.find('-items-') != -1 : 108 | if rss3_dict == None : 109 | return rss3_type.IRSS3Items(id = file_id) 110 | else : 111 | return converter.IRSS3ItemsSchema().load(rss3_dict) 112 | elif file_id.find('-list-') != -1 : 113 | if rss3_dict == None : 114 | return rss3_type.IRSS3List(id = file_id) 115 | else : 116 | return converter.IRSS3ListSchema().load(rss3_dict) 117 | else : 118 | if rss3_dict == None : 119 | return rss3_type.IRSS3Index(id = file_id) 120 | else : 121 | return converter.IRSS3IndexSchema().load(rss3_dict) 122 | 123 | # return_type == 1 json 124 | # return_type == 2 dict 125 | def get_rss3_json_dict(rss3_obj, return_type = 1) : 126 | if rss3_obj == None and return_type == None and \ 127 | (return_type != 1 or return_type != 2): 128 | raise TypeError("Rss3_obj and return_type is is invalid parameter") 129 | 130 | return_result = None 131 | if isinstance(rss3_obj, rss3_type.IRSS3Items) : 132 | irss3_items_schema = converter.IRSS3ItemsSchema() 133 | return_result = irss3_items_schema.dumps(rss3_obj) if return_type == 1 else irss3_items_schema.dump(rss3_obj) 134 | elif isinstance(rss3_obj, rss3_type.IRSS3List) : 135 | irss3_list_schema = converter.IRSS3ListSchema() 136 | return_result = irss3_list_schema.dumps(rss3_obj) if return_type == 1 else irss3_list_schema.dump(rss3_obj) 137 | elif isinstance(rss3_obj, rss3_type.IRSS3Index) : 138 | irss3_index_schema = converter.IRSS3IndexSchema() 139 | return_result = irss3_index_schema.dumps(rss3_obj) if return_type == 1 else irss3_index_schema.dump(rss3_obj) 140 | else : 141 | raise TypeError("Rss3_obj type is %s, cannot be converted to the specified type" % type(rss3_obj)) 142 | 143 | return return_result 144 | 145 | ######################################### 146 | 147 | def prase_id(id) : 148 | split_list = id.split('-') 149 | address = split_list[0] 150 | type = split_list[1] 151 | index = None 152 | if len(split_list) >= 3 : 153 | index = int(split_list[2]) 154 | return {'address':address, 155 | 'type':type, 156 | 'index':index} 157 | 158 | 159 | 160 | 161 | --------------------------------------------------------------------------------