hive-nectar 0.0.7__py3-none-any.whl → 0.0.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hive-nectar might be problematic. Click here for more details.
- {hive_nectar-0.0.7.dist-info → hive_nectar-0.0.9.dist-info}/METADATA +1 -1
- {hive_nectar-0.0.7.dist-info → hive_nectar-0.0.9.dist-info}/RECORD +20 -20
- nectar/__init__.py +1 -0
- nectar/account.py +1 -1
- nectar/community.py +527 -180
- nectar/nodelist.py +83 -7
- nectar/storage.py +3 -4
- nectar/version.py +1 -1
- nectarapi/__init__.py +1 -0
- nectarapi/version.py +1 -1
- nectarbase/__init__.py +1 -0
- nectarbase/version.py +1 -1
- nectargraphenebase/__init__.py +1 -0
- nectargraphenebase/operations.py +2 -0
- nectargraphenebase/version.py +1 -1
- nectarstorage/__init__.py +21 -1
- nectarstorage/sqlite.py +1 -1
- {hive_nectar-0.0.7.dist-info → hive_nectar-0.0.9.dist-info}/WHEEL +0 -0
- {hive_nectar-0.0.7.dist-info → hive_nectar-0.0.9.dist-info}/entry_points.txt +0 -0
- {hive_nectar-0.0.7.dist-info → hive_nectar-0.0.9.dist-info}/licenses/LICENSE.txt +0 -0
nectar/community.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import date, datetime, time
|
|
5
|
+
from typing import Union
|
|
5
6
|
|
|
6
7
|
from prettytable import PrettyTable
|
|
7
8
|
|
|
@@ -18,27 +19,23 @@ log = logging.getLogger(__name__)
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class Community(BlockchainObject):
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
:param str account: Name of the account
|
|
24
|
-
:param Steem/Hive blockchain_instance: Hive or Steem
|
|
25
|
-
instance
|
|
26
|
-
:param bool lazy: Use lazy loading
|
|
27
|
-
:param bool full: Obtain all account data including orders, positions,
|
|
28
|
-
etc.
|
|
29
|
-
:param Hive hive_instance: Hive instance
|
|
30
|
-
:param Steem steem_instance: Steem instance
|
|
31
|
-
:returns: Account data
|
|
32
|
-
:rtype: dictionary
|
|
33
|
-
:raises nectar.exceptions.AccountDoesNotExistsException: if account
|
|
34
|
-
does not exist
|
|
35
|
-
|
|
36
|
-
Instances of this class are dictionaries that come with additional
|
|
37
|
-
methods (see below) that allow dealing with an community and its
|
|
38
|
-
corresponding functions.
|
|
39
|
-
|
|
40
|
-
.. code-block:: python
|
|
22
|
+
"""A class representing a Hive community with methods to interact with it.
|
|
41
23
|
|
|
24
|
+
This class provides an interface to access and manipulate community data on the Hive blockchain.
|
|
25
|
+
It extends BlockchainObject and provides additional community-specific functionality.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
community: Either a community name (str) or a dictionary containing community data
|
|
29
|
+
observer: Observer account for personalized results (default: "")
|
|
30
|
+
full: If True, fetch full community data (default: True)
|
|
31
|
+
lazy: If True, use lazy loading (default: False)
|
|
32
|
+
blockchain_instance: Hive or Steem instance for blockchain access
|
|
33
|
+
**kwargs: Additional arguments including 'hive_instance' or 'steem_instance'
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
type_id (int): Type identifier for blockchain objects (2 for communities)
|
|
37
|
+
|
|
38
|
+
Example:
|
|
42
39
|
>>> from nectar.community import Community
|
|
43
40
|
>>> from nectar import Hive
|
|
44
41
|
>>> from nectar.nodelist import NodeList
|
|
@@ -48,28 +45,32 @@ class Community(BlockchainObject):
|
|
|
48
45
|
>>> community = Community("hive-139531", blockchain_instance=stm)
|
|
49
46
|
>>> print(community)
|
|
50
47
|
<Community hive-139531>
|
|
51
|
-
>>> print(community.balances) # doctest: +SKIP
|
|
52
|
-
|
|
53
|
-
.. note:: This class comes with its own caching function to reduce the
|
|
54
|
-
load on the API server. Instances of this class can be
|
|
55
|
-
refreshed with ``Community.refresh()``. The cache can be
|
|
56
|
-
cleared with ``Community.clear_cache()``
|
|
57
48
|
|
|
49
|
+
Note:
|
|
50
|
+
This class includes caching to reduce API server load. Use refresh() to update
|
|
51
|
+
the data and clear_cache() to clear the cache.
|
|
58
52
|
"""
|
|
59
53
|
|
|
60
54
|
type_id = 2
|
|
61
55
|
|
|
62
56
|
def __init__(
|
|
63
|
-
self,
|
|
64
|
-
|
|
65
|
-
""
|
|
66
|
-
|
|
67
|
-
:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
57
|
+
self,
|
|
58
|
+
community: Union[str, dict],
|
|
59
|
+
observer: str = "",
|
|
60
|
+
full: bool = True,
|
|
61
|
+
lazy: bool = False,
|
|
62
|
+
blockchain_instance=None,
|
|
63
|
+
**kwargs,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Initialize a Community instance.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
community: Either a community name (str) or a dictionary containing community data
|
|
69
|
+
observer: Observer account for personalized results (default: "")
|
|
70
|
+
full: If True, fetch full community data (default: True)
|
|
71
|
+
lazy: If True, use lazy loading (default: False)
|
|
72
|
+
blockchain_instance: Hive or Steem instance for blockchain access
|
|
73
|
+
**kwargs: Additional arguments including 'hive_instance' or 'steem_instance'
|
|
73
74
|
"""
|
|
74
75
|
self.full = full
|
|
75
76
|
self.lazy = lazy
|
|
@@ -86,8 +87,16 @@ class Community(BlockchainObject):
|
|
|
86
87
|
community, lazy=lazy, full=full, id_item="name", blockchain_instance=blockchain_instance
|
|
87
88
|
)
|
|
88
89
|
|
|
89
|
-
def refresh(self):
|
|
90
|
-
"""Refresh
|
|
90
|
+
def refresh(self) -> None:
|
|
91
|
+
"""Refresh the community's data from the blockchain.
|
|
92
|
+
|
|
93
|
+
This method updates the community's data by fetching the latest information
|
|
94
|
+
from the blockchain. It raises an exception if the community doesn't exist.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
AccountDoesNotExistsException: If the community doesn't exist on the blockchain
|
|
98
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
99
|
+
"""
|
|
91
100
|
if not self.blockchain.is_connected():
|
|
92
101
|
return
|
|
93
102
|
self.blockchain.rpc.set_next_node_on_empty_reply(True)
|
|
@@ -109,208 +118,402 @@ class Community(BlockchainObject):
|
|
|
109
118
|
blockchain_instance=self.blockchain,
|
|
110
119
|
)
|
|
111
120
|
|
|
112
|
-
def _parse_json_data(self, community):
|
|
113
|
-
|
|
121
|
+
def _parse_json_data(self, community: dict) -> dict:
|
|
122
|
+
"""Parse and convert community JSON data into proper Python types.
|
|
123
|
+
|
|
124
|
+
This internal method converts string representations of numbers to integers
|
|
125
|
+
and parses date strings into datetime objects with timezone information.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
community: Dictionary containing raw community data from the API
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
dict: Processed community data with proper Python types
|
|
132
|
+
"""
|
|
133
|
+
# Convert string numbers to integers
|
|
134
|
+
int_fields = [
|
|
114
135
|
"sum_pending",
|
|
115
136
|
"subscribers",
|
|
116
137
|
"num_pending",
|
|
117
138
|
"num_authors",
|
|
118
139
|
]
|
|
119
|
-
for
|
|
120
|
-
if
|
|
121
|
-
community[
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
for field in int_fields:
|
|
141
|
+
if field in community and isinstance(community.get(field), str):
|
|
142
|
+
community[field] = int(community.get(field, 0))
|
|
143
|
+
|
|
144
|
+
# Parse date strings into datetime objects
|
|
145
|
+
date_fields = ["created_at"]
|
|
146
|
+
for field in date_fields:
|
|
147
|
+
if field in community and isinstance(community.get(field), str):
|
|
148
|
+
community[field] = addTzInfo(
|
|
149
|
+
datetime.strptime(
|
|
150
|
+
community.get(field, "1970-01-01 00:00:00"), "%Y-%m-%d %H:%M:%S"
|
|
151
|
+
)
|
|
127
152
|
)
|
|
153
|
+
|
|
128
154
|
return community
|
|
129
155
|
|
|
130
|
-
def json(self):
|
|
156
|
+
def json(self) -> dict:
|
|
157
|
+
"""Convert the community data to a JSON-serializable dictionary.
|
|
158
|
+
|
|
159
|
+
This method prepares the community data for JSON serialization by converting
|
|
160
|
+
non-JSON-serializable types (like datetime objects) to strings.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
dict: A dictionary containing the community data in a JSON-serializable format
|
|
164
|
+
"""
|
|
131
165
|
output = self.copy()
|
|
132
|
-
|
|
166
|
+
|
|
167
|
+
# Convert integer fields to strings for JSON serialization
|
|
168
|
+
int_fields = [
|
|
133
169
|
"sum_pending",
|
|
134
170
|
"subscribers",
|
|
135
171
|
"num_pending",
|
|
136
172
|
"num_authors",
|
|
137
173
|
]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
|
|
175
|
+
# Fields that should only be converted if non-zero
|
|
176
|
+
int_non_zero_fields = []
|
|
177
|
+
|
|
178
|
+
# Convert regular integer fields
|
|
179
|
+
for field in int_fields:
|
|
180
|
+
if field in output and isinstance(output[field], int):
|
|
181
|
+
output[field] = str(output[field])
|
|
182
|
+
|
|
183
|
+
# Convert non-zero integer fields
|
|
184
|
+
for field in int_non_zero_fields:
|
|
185
|
+
if field in output and isinstance(output[field], int) and output[field] != 0:
|
|
186
|
+
output[field] = str(output[field])
|
|
187
|
+
|
|
188
|
+
# Convert datetime fields to ISO format strings
|
|
189
|
+
date_fields = ["created_at"]
|
|
190
|
+
for field in date_fields:
|
|
191
|
+
if field in output:
|
|
192
|
+
date_val = output.get(field, datetime(1970, 1, 1, 0, 0))
|
|
193
|
+
if isinstance(date_val, (datetime, date, time)):
|
|
194
|
+
output[field] = formatTimeString(date_val).replace("T", " ")
|
|
154
195
|
else:
|
|
155
|
-
output[
|
|
196
|
+
output[field] = date_val
|
|
156
197
|
return json.loads(str(json.dumps(output)))
|
|
157
198
|
|
|
158
|
-
def get_community_roles(self):
|
|
159
|
-
"""Lists community roles
|
|
199
|
+
def get_community_roles(self, limit: int = 100, last: str = None) -> list:
|
|
200
|
+
"""Lists community roles
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
limit: Maximum number of roles to return (default: 100)
|
|
204
|
+
last: Account name of the last role from previous page for pagination
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
list: List of community roles
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
211
|
+
"""
|
|
160
212
|
community = self["name"]
|
|
161
213
|
if not self.blockchain.is_connected():
|
|
162
214
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
215
|
+
|
|
216
|
+
params = {"community": community, "limit": limit}
|
|
217
|
+
if last is not None:
|
|
218
|
+
params["last"] = last
|
|
219
|
+
|
|
163
220
|
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
164
|
-
return self.blockchain.rpc.list_community_roles(
|
|
221
|
+
return self.blockchain.rpc.list_community_roles(params, api="bridge")
|
|
222
|
+
|
|
223
|
+
def get_subscribers(self, limit: int = 100, last: str = None) -> list:
|
|
224
|
+
"""Returns subscribers
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
limit: Maximum number of subscribers to return (default: 100)
|
|
228
|
+
last: Account name of the last subscriber from previous page for pagination
|
|
165
229
|
|
|
166
|
-
|
|
167
|
-
|
|
230
|
+
Returns:
|
|
231
|
+
list: List of subscribers
|
|
232
|
+
|
|
233
|
+
Raises:
|
|
234
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
235
|
+
"""
|
|
168
236
|
community = self["name"]
|
|
169
237
|
if not self.blockchain.is_connected():
|
|
170
238
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
239
|
+
|
|
240
|
+
params = {"community": community, "limit": limit}
|
|
241
|
+
if last is not None:
|
|
242
|
+
params["last"] = last
|
|
243
|
+
|
|
171
244
|
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
172
|
-
return self.blockchain.rpc.list_subscribers(
|
|
245
|
+
return self.blockchain.rpc.list_subscribers(params, api="bridge")
|
|
246
|
+
|
|
247
|
+
def get_activities(self, limit: int = 100, last_id: str = None) -> list:
|
|
248
|
+
"""Returns community activity
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
limit: Maximum number of activities to return (default: 100)
|
|
252
|
+
last_id: ID of the last activity from previous page for pagination
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
list: List of community activities
|
|
173
256
|
|
|
174
|
-
|
|
175
|
-
|
|
257
|
+
Raises:
|
|
258
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
259
|
+
"""
|
|
176
260
|
community = self["name"]
|
|
177
261
|
if not self.blockchain.is_connected():
|
|
178
262
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
263
|
+
|
|
264
|
+
params = {"account": community, "limit": limit}
|
|
265
|
+
if last_id is not None:
|
|
266
|
+
params["last_id"] = last_id
|
|
267
|
+
|
|
179
268
|
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
180
|
-
return self.blockchain.rpc.account_notifications(
|
|
181
|
-
{"account": community, "limit": limit, "last_id": last_id}, api="bridge"
|
|
182
|
-
)
|
|
269
|
+
return self.blockchain.rpc.account_notifications(params, api="bridge")
|
|
183
270
|
|
|
184
271
|
def get_ranked_posts(
|
|
185
|
-
self,
|
|
186
|
-
|
|
187
|
-
|
|
272
|
+
self,
|
|
273
|
+
observer: str = None,
|
|
274
|
+
limit: int = 100,
|
|
275
|
+
start_author: str = None,
|
|
276
|
+
start_permlink: str = None,
|
|
277
|
+
sort: str = "created",
|
|
278
|
+
) -> list:
|
|
279
|
+
"""Returns community posts
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
observer: Account name of the observer (optional)
|
|
283
|
+
limit: Maximum number of posts to return (default: 100)
|
|
284
|
+
start_author: Author of the post to start from for pagination (optional)
|
|
285
|
+
start_permlink: Permlink of the post to start from for pagination (optional)
|
|
286
|
+
sort: Sort order (default: "created")
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
list: List of community posts
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
293
|
+
"""
|
|
188
294
|
community = self["name"]
|
|
189
295
|
if not self.blockchain.is_connected():
|
|
190
296
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
297
|
+
|
|
298
|
+
params = {"tag": community, "limit": limit, "sort": sort}
|
|
299
|
+
|
|
300
|
+
if observer is not None:
|
|
301
|
+
params["observer"] = observer
|
|
302
|
+
if start_author is not None:
|
|
303
|
+
params["start_author"] = start_author
|
|
304
|
+
if start_permlink is not None:
|
|
305
|
+
params["start_permlink"] = start_permlink
|
|
306
|
+
|
|
191
307
|
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
192
|
-
return self.blockchain.rpc.get_ranked_posts(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"limit": limit,
|
|
197
|
-
"start_author": start_author,
|
|
198
|
-
"start_permlink": start_permlink,
|
|
199
|
-
"sort": sort,
|
|
200
|
-
},
|
|
201
|
-
api="bridge",
|
|
202
|
-
)
|
|
308
|
+
return self.blockchain.rpc.get_ranked_posts(params, api="bridge")
|
|
309
|
+
|
|
310
|
+
def set_role(self, account: str, role: str, mod_account: str) -> dict:
|
|
311
|
+
"""Set role for a given account in the community.
|
|
203
312
|
|
|
204
|
-
|
|
205
|
-
|
|
313
|
+
Args:
|
|
314
|
+
account: Account name to set the role for
|
|
315
|
+
role: Role to assign (member, mod, admin, owner, or guest)
|
|
316
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
206
317
|
|
|
207
|
-
:
|
|
208
|
-
|
|
209
|
-
:param str mod_account: Account who broadcast this, (mods or higher)
|
|
318
|
+
Returns:
|
|
319
|
+
dict: Transaction result
|
|
210
320
|
|
|
321
|
+
Raises:
|
|
322
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
323
|
+
ValueError: If role is not one of the allowed values
|
|
211
324
|
"""
|
|
325
|
+
valid_roles = {"member", "mod", "admin", "owner", "guest"}
|
|
326
|
+
if role.lower() not in valid_roles:
|
|
327
|
+
raise ValueError(f"Invalid role. Must be one of: {', '.join(valid_roles)}")
|
|
328
|
+
|
|
212
329
|
community = self["name"]
|
|
213
330
|
if not self.blockchain.is_connected():
|
|
214
331
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
332
|
+
|
|
215
333
|
json_body = [
|
|
216
334
|
"setRole",
|
|
217
335
|
{
|
|
218
336
|
"community": community,
|
|
219
337
|
"account": account,
|
|
220
|
-
"role": role,
|
|
338
|
+
"role": role.lower(),
|
|
221
339
|
},
|
|
222
340
|
]
|
|
223
341
|
return self.blockchain.custom_json(
|
|
224
342
|
"community", json_body, required_posting_auths=[mod_account]
|
|
225
343
|
)
|
|
226
344
|
|
|
227
|
-
def set_user_title(self, account, title, mod_account):
|
|
228
|
-
"""Set title for a given account
|
|
345
|
+
def set_user_title(self, account: str, title: str, mod_account: str) -> dict:
|
|
346
|
+
"""Set the title for a given account in the community.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
account: Account name to set the title for
|
|
350
|
+
title: Title to assign to the account
|
|
351
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
229
352
|
|
|
230
|
-
:
|
|
231
|
-
|
|
232
|
-
:param str mod_account: Account who broadcast this, (mods or higher)
|
|
353
|
+
Returns:
|
|
354
|
+
dict: Transaction result
|
|
233
355
|
|
|
356
|
+
Raises:
|
|
357
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
358
|
+
ValueError: If account or title is empty
|
|
234
359
|
"""
|
|
360
|
+
if not account or not isinstance(account, str):
|
|
361
|
+
raise ValueError("Account must be a non-empty string")
|
|
362
|
+
|
|
363
|
+
if not title or not isinstance(title, str):
|
|
364
|
+
raise ValueError("Title must be a non-empty string")
|
|
365
|
+
|
|
235
366
|
community = self["name"]
|
|
236
367
|
if not self.blockchain.is_connected():
|
|
237
368
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
369
|
+
|
|
238
370
|
json_body = [
|
|
239
371
|
"setUserTitle",
|
|
240
372
|
{
|
|
241
373
|
"community": community,
|
|
242
374
|
"account": account,
|
|
243
|
-
"title": title,
|
|
375
|
+
"title": title.strip(),
|
|
244
376
|
},
|
|
245
377
|
]
|
|
246
378
|
return self.blockchain.custom_json(
|
|
247
379
|
"community", json_body, required_posting_auths=[mod_account]
|
|
248
380
|
)
|
|
249
381
|
|
|
250
|
-
def mute_post(self, account, permlink, notes, mod_account):
|
|
251
|
-
"""Mutes a post
|
|
382
|
+
def mute_post(self, account: str, permlink: str, notes: str, mod_account: str) -> dict:
|
|
383
|
+
"""Mutes a post in the community.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
account: Author of the post to mute
|
|
387
|
+
permlink: Permlink of the post to mute
|
|
388
|
+
notes: Reason for muting the post
|
|
389
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
252
390
|
|
|
253
|
-
:
|
|
254
|
-
|
|
255
|
-
:param str notes: permlink
|
|
256
|
-
:param str mod_account: Account who broadcast this, (mods or higher)
|
|
391
|
+
Returns:
|
|
392
|
+
dict: Transaction result
|
|
257
393
|
|
|
394
|
+
Raises:
|
|
395
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
396
|
+
ValueError: If any required parameter is invalid
|
|
258
397
|
"""
|
|
398
|
+
if not account or not isinstance(account, str):
|
|
399
|
+
raise ValueError("Account must be a non-empty string")
|
|
400
|
+
if not permlink or not isinstance(permlink, str):
|
|
401
|
+
raise ValueError("Permlink must be a non-empty string")
|
|
402
|
+
if not isinstance(notes, str):
|
|
403
|
+
raise ValueError("Notes must be a string")
|
|
404
|
+
if not mod_account or not isinstance(mod_account, str):
|
|
405
|
+
raise ValueError("Moderator account must be a non-empty string")
|
|
406
|
+
|
|
259
407
|
community = self["name"]
|
|
260
408
|
if not self.blockchain.is_connected():
|
|
261
409
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
410
|
+
|
|
262
411
|
json_body = [
|
|
263
412
|
"mutePost",
|
|
264
|
-
{
|
|
413
|
+
{
|
|
414
|
+
"community": community,
|
|
415
|
+
"account": account,
|
|
416
|
+
"permlink": permlink,
|
|
417
|
+
"notes": notes.strip(),
|
|
418
|
+
},
|
|
265
419
|
]
|
|
266
420
|
return self.blockchain.custom_json(
|
|
267
421
|
"community", json_body, required_posting_auths=[mod_account]
|
|
268
422
|
)
|
|
269
423
|
|
|
270
|
-
def unmute_post(self, account, permlink, notes, mod_account):
|
|
271
|
-
"""Unmute a post
|
|
424
|
+
def unmute_post(self, account: str, permlink: str, notes: str, mod_account: str) -> dict:
|
|
425
|
+
"""Unmute a previously muted post in the community.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
account: Author of the post to unmute
|
|
429
|
+
permlink: Permlink of the post to unmute
|
|
430
|
+
notes: Reason for unmuting the post
|
|
431
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
272
432
|
|
|
273
|
-
:
|
|
274
|
-
|
|
275
|
-
:param str notes: notes
|
|
276
|
-
:param str mod_account: Account who broadcast this, (mods or higher)
|
|
433
|
+
Returns:
|
|
434
|
+
dict: Transaction result
|
|
277
435
|
|
|
436
|
+
Raises:
|
|
437
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
438
|
+
ValueError: If any required parameter is invalid
|
|
278
439
|
"""
|
|
440
|
+
if not account or not isinstance(account, str):
|
|
441
|
+
raise ValueError("Account must be a non-empty string")
|
|
442
|
+
if not permlink or not isinstance(permlink, str):
|
|
443
|
+
raise ValueError("Permlink must be a non-empty string")
|
|
444
|
+
if not isinstance(notes, str):
|
|
445
|
+
raise ValueError("Notes must be a string")
|
|
446
|
+
if not mod_account or not isinstance(mod_account, str):
|
|
447
|
+
raise ValueError("Moderator account must be a non-empty string")
|
|
448
|
+
|
|
279
449
|
community = self["name"]
|
|
280
450
|
if not self.blockchain.is_connected():
|
|
281
451
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
452
|
+
|
|
282
453
|
json_body = [
|
|
283
454
|
"unmutePost",
|
|
284
|
-
{
|
|
455
|
+
{
|
|
456
|
+
"community": community,
|
|
457
|
+
"account": account,
|
|
458
|
+
"permlink": permlink,
|
|
459
|
+
"notes": notes.strip(),
|
|
460
|
+
},
|
|
285
461
|
]
|
|
286
462
|
return self.blockchain.custom_json(
|
|
287
463
|
"community", json_body, required_posting_auths=[mod_account]
|
|
288
464
|
)
|
|
289
465
|
|
|
290
|
-
def update_props(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
:
|
|
294
|
-
:
|
|
295
|
-
:
|
|
296
|
-
:
|
|
297
|
-
:
|
|
298
|
-
|
|
299
|
-
|
|
466
|
+
def update_props(
|
|
467
|
+
self,
|
|
468
|
+
title: str,
|
|
469
|
+
about: str,
|
|
470
|
+
is_nsfw: bool,
|
|
471
|
+
description: str,
|
|
472
|
+
flag_text: str,
|
|
473
|
+
admin_account: str,
|
|
474
|
+
) -> dict:
|
|
475
|
+
"""Update community properties.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
title: New title for the community (must be non-empty)
|
|
479
|
+
about: Brief description of the community
|
|
480
|
+
is_nsfw: Whether the community contains NSFW content
|
|
481
|
+
description: Detailed description of the community
|
|
482
|
+
flag_text: Text shown when flagging content in this community
|
|
483
|
+
admin_account: Account name of the admin performing this action
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
dict: Transaction result
|
|
487
|
+
|
|
488
|
+
Raises:
|
|
489
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
490
|
+
ValueError: If any required parameter is invalid
|
|
300
491
|
"""
|
|
492
|
+
if not title or not isinstance(title, str):
|
|
493
|
+
raise ValueError("Title must be a non-empty string")
|
|
494
|
+
if not isinstance(about, str):
|
|
495
|
+
about = ""
|
|
496
|
+
if not isinstance(description, str):
|
|
497
|
+
description = ""
|
|
498
|
+
if not isinstance(flag_text, str):
|
|
499
|
+
flag_text = ""
|
|
500
|
+
if not admin_account or not isinstance(admin_account, str):
|
|
501
|
+
raise ValueError("Admin account must be a non-empty string")
|
|
502
|
+
|
|
301
503
|
community = self["name"]
|
|
302
504
|
if not self.blockchain.is_connected():
|
|
303
505
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
506
|
+
|
|
304
507
|
json_body = [
|
|
305
508
|
"updateProps",
|
|
306
509
|
{
|
|
307
510
|
"community": community,
|
|
308
511
|
"props": {
|
|
309
|
-
"title": title,
|
|
310
|
-
"about": about,
|
|
311
|
-
"is_nsfw": is_nsfw,
|
|
312
|
-
"description": description,
|
|
313
|
-
"flag_text": flag_text,
|
|
512
|
+
"title": title.strip(),
|
|
513
|
+
"about": about.strip(),
|
|
514
|
+
"is_nsfw": bool(is_nsfw),
|
|
515
|
+
"description": description.strip(),
|
|
516
|
+
"flag_text": flag_text.strip(),
|
|
314
517
|
},
|
|
315
518
|
},
|
|
316
519
|
]
|
|
@@ -318,15 +521,29 @@ class Community(BlockchainObject):
|
|
|
318
521
|
"community", json_body, required_posting_auths=[admin_account]
|
|
319
522
|
)
|
|
320
523
|
|
|
321
|
-
def subscribe(self, account):
|
|
322
|
-
"""
|
|
524
|
+
def subscribe(self, account: str) -> dict:
|
|
525
|
+
"""Subscribe an account to this community.
|
|
526
|
+
|
|
527
|
+
The account that calls this method will be subscribed to the community.
|
|
528
|
+
The same account must be used to sign the transaction.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
account: Account name that wants to subscribe to the community
|
|
323
532
|
|
|
324
|
-
:
|
|
533
|
+
Returns:
|
|
534
|
+
dict: Transaction result
|
|
325
535
|
|
|
536
|
+
Raises:
|
|
537
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
538
|
+
ValueError: If account is invalid
|
|
326
539
|
"""
|
|
540
|
+
if not account or not isinstance(account, str):
|
|
541
|
+
raise ValueError("Account must be a non-empty string")
|
|
542
|
+
|
|
327
543
|
community = self["name"]
|
|
328
544
|
if not self.blockchain.is_connected():
|
|
329
545
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
546
|
+
|
|
330
547
|
json_body = [
|
|
331
548
|
"subscribe",
|
|
332
549
|
{
|
|
@@ -335,17 +552,35 @@ class Community(BlockchainObject):
|
|
|
335
552
|
]
|
|
336
553
|
return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
|
|
337
554
|
|
|
338
|
-
def pin_post(self, account, permlink, mod_account):
|
|
339
|
-
"""
|
|
555
|
+
def pin_post(self, account: str, permlink: str, mod_account: str) -> dict:
|
|
556
|
+
"""Pin a post to the top of the community feed.
|
|
557
|
+
|
|
558
|
+
This method allows community moderators to pin a specific post to the top of the
|
|
559
|
+
community's feed. The post will remain pinned until it is manually unpinned.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
account: Author of the post to pin
|
|
563
|
+
permlink: Permlink of the post to pin
|
|
564
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
340
565
|
|
|
341
|
-
:
|
|
342
|
-
|
|
343
|
-
:param str mod_account: Account who broadcast this, (mods or higher)
|
|
566
|
+
Returns:
|
|
567
|
+
dict: Transaction result
|
|
344
568
|
|
|
569
|
+
Raises:
|
|
570
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
571
|
+
ValueError: If any required parameter is invalid
|
|
345
572
|
"""
|
|
573
|
+
if not account or not isinstance(account, str):
|
|
574
|
+
raise ValueError("Account must be a non-empty string")
|
|
575
|
+
if not permlink or not isinstance(permlink, str):
|
|
576
|
+
raise ValueError("Permlink must be a non-empty string")
|
|
577
|
+
if not mod_account or not isinstance(mod_account, str):
|
|
578
|
+
raise ValueError("Moderator account must be a non-empty string")
|
|
579
|
+
|
|
346
580
|
community = self["name"]
|
|
347
581
|
if not self.blockchain.is_connected():
|
|
348
582
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
583
|
+
|
|
349
584
|
json_body = [
|
|
350
585
|
"pinPost",
|
|
351
586
|
{
|
|
@@ -358,15 +593,29 @@ class Community(BlockchainObject):
|
|
|
358
593
|
"community", json_body, required_posting_auths=[mod_account]
|
|
359
594
|
)
|
|
360
595
|
|
|
361
|
-
def unsubscribe(self, account):
|
|
362
|
-
"""
|
|
596
|
+
def unsubscribe(self, account: str) -> dict:
|
|
597
|
+
"""Unsubscribe an account from this community.
|
|
598
|
+
|
|
599
|
+
The account that calls this method will be unsubscribed from the community.
|
|
600
|
+
The same account must be used to sign the transaction.
|
|
363
601
|
|
|
364
|
-
:
|
|
602
|
+
Args:
|
|
603
|
+
account: Account name that wants to unsubscribe from the community
|
|
365
604
|
|
|
605
|
+
Returns:
|
|
606
|
+
dict: Transaction result
|
|
607
|
+
|
|
608
|
+
Raises:
|
|
609
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
610
|
+
ValueError: If account is invalid
|
|
366
611
|
"""
|
|
612
|
+
if not account or not isinstance(account, str):
|
|
613
|
+
raise ValueError("Account must be a non-empty string")
|
|
614
|
+
|
|
367
615
|
community = self["name"]
|
|
368
616
|
if not self.blockchain.is_connected():
|
|
369
617
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
618
|
+
|
|
370
619
|
json_body = [
|
|
371
620
|
"unsubscribe",
|
|
372
621
|
{
|
|
@@ -375,17 +624,35 @@ class Community(BlockchainObject):
|
|
|
375
624
|
]
|
|
376
625
|
return self.blockchain.custom_json("community", json_body, required_posting_auths=[account])
|
|
377
626
|
|
|
378
|
-
def unpin_post(self, account, permlink, mod_account):
|
|
379
|
-
"""
|
|
627
|
+
def unpin_post(self, account: str, permlink: str, mod_account: str) -> dict:
|
|
628
|
+
"""Remove a post from being pinned at the top of the community feed.
|
|
629
|
+
|
|
630
|
+
This method allows community moderators to unpin a previously pinned post.
|
|
631
|
+
After unpinning, the post will return to its normal position in the feed.
|
|
380
632
|
|
|
381
|
-
:
|
|
382
|
-
|
|
383
|
-
|
|
633
|
+
Args:
|
|
634
|
+
account: Author of the post to unpin
|
|
635
|
+
permlink: Permlink of the post to unpin
|
|
636
|
+
mod_account: Account name of the moderator performing this action (must be mod or higher)
|
|
384
637
|
|
|
638
|
+
Returns:
|
|
639
|
+
dict: Transaction result
|
|
640
|
+
|
|
641
|
+
Raises:
|
|
642
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
643
|
+
ValueError: If any required parameter is invalid
|
|
385
644
|
"""
|
|
645
|
+
if not account or not isinstance(account, str):
|
|
646
|
+
raise ValueError("Account must be a non-empty string")
|
|
647
|
+
if not permlink or not isinstance(permlink, str):
|
|
648
|
+
raise ValueError("Permlink must be a non-empty string")
|
|
649
|
+
if not mod_account or not isinstance(mod_account, str):
|
|
650
|
+
raise ValueError("Moderator account must be a non-empty string")
|
|
651
|
+
|
|
386
652
|
community = self["name"]
|
|
387
653
|
if not self.blockchain.is_connected():
|
|
388
654
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
655
|
+
|
|
389
656
|
json_body = [
|
|
390
657
|
"unpinPost",
|
|
391
658
|
{
|
|
@@ -398,20 +665,47 @@ class Community(BlockchainObject):
|
|
|
398
665
|
"community", json_body, required_posting_auths=[mod_account]
|
|
399
666
|
)
|
|
400
667
|
|
|
401
|
-
def flag_post(self, account, permlink, notes, reporter):
|
|
402
|
-
"""
|
|
668
|
+
def flag_post(self, account: str, permlink: str, notes: str, reporter: str) -> dict:
|
|
669
|
+
"""Report a post to the community moderators for review.
|
|
403
670
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
671
|
+
This method allows community members to flag posts that may violate
|
|
672
|
+
community guidelines. The post will be added to the community's
|
|
673
|
+
review queue for moderators to evaluate.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
account: Author of the post being reported
|
|
677
|
+
permlink: Permlink of the post being reported
|
|
678
|
+
notes: Explanation of why the post is being reported
|
|
679
|
+
reporter: Account name of the user reporting the post
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
dict: Transaction result
|
|
683
|
+
|
|
684
|
+
Raises:
|
|
685
|
+
OfflineHasNoRPCException: If not connected to the blockchain
|
|
686
|
+
ValueError: If any required parameter is invalid
|
|
408
687
|
"""
|
|
688
|
+
if not account or not isinstance(account, str):
|
|
689
|
+
raise ValueError("Account must be a non-empty string")
|
|
690
|
+
if not permlink or not isinstance(permlink, str):
|
|
691
|
+
raise ValueError("Permlink must be a non-empty string")
|
|
692
|
+
if not notes or not isinstance(notes, str):
|
|
693
|
+
raise ValueError("Notes must be a string")
|
|
694
|
+
if not reporter or not isinstance(reporter, str):
|
|
695
|
+
raise ValueError("Reporter account must be a non-empty string")
|
|
696
|
+
|
|
409
697
|
community = self["name"]
|
|
410
698
|
if not self.blockchain.is_connected():
|
|
411
699
|
raise OfflineHasNoRPCException("No RPC available in offline mode!")
|
|
700
|
+
|
|
412
701
|
json_body = [
|
|
413
702
|
"flagPost",
|
|
414
|
-
{
|
|
703
|
+
{
|
|
704
|
+
"community": community,
|
|
705
|
+
"account": account,
|
|
706
|
+
"permlink": permlink,
|
|
707
|
+
"notes": notes.strip(),
|
|
708
|
+
},
|
|
415
709
|
]
|
|
416
710
|
return self.blockchain.custom_json(
|
|
417
711
|
"community", json_body, required_posting_auths=[reporter]
|
|
@@ -419,7 +713,21 @@ class Community(BlockchainObject):
|
|
|
419
713
|
|
|
420
714
|
|
|
421
715
|
class CommunityObject(list):
|
|
422
|
-
|
|
716
|
+
"""A list-like container for Community objects with additional utility methods."""
|
|
717
|
+
|
|
718
|
+
def printAsTable(self) -> None:
|
|
719
|
+
"""Print a formatted table of communities with key metrics.
|
|
720
|
+
|
|
721
|
+
The table includes the following columns:
|
|
722
|
+
- Nr.: Sequential number
|
|
723
|
+
- Name: Community name
|
|
724
|
+
- Title: Community title
|
|
725
|
+
- lang: Language code
|
|
726
|
+
- subscribers: Number of subscribers
|
|
727
|
+
- sum_pending: Sum of pending payouts
|
|
728
|
+
- num_pending: Number of pending posts
|
|
729
|
+
- num_authors: Number of unique authors
|
|
730
|
+
"""
|
|
423
731
|
t = PrettyTable(
|
|
424
732
|
[
|
|
425
733
|
"Nr.",
|
|
@@ -452,26 +760,45 @@ class CommunityObject(list):
|
|
|
452
760
|
|
|
453
761
|
|
|
454
762
|
class Communities(CommunityObject):
|
|
455
|
-
"""
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
:
|
|
461
|
-
|
|
763
|
+
"""A list of communities with additional querying capabilities.
|
|
764
|
+
|
|
765
|
+
This class extends CommunityObject to provide methods for fetching and
|
|
766
|
+
searching communities from the blockchain.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
sort: Sort order for communities (default: "rank")
|
|
770
|
+
observer: Observer account for personalized results (optional)
|
|
771
|
+
last: Last community name for pagination (optional)
|
|
772
|
+
limit: Maximum number of communities to fetch (default: 100)
|
|
773
|
+
lazy: If True, use lazy loading (default: False)
|
|
774
|
+
full: If True, fetch full community data (default: True)
|
|
775
|
+
blockchain_instance: Hive or Steem instance to use for blockchain access
|
|
776
|
+
**kwargs: Additional arguments including 'steem_instance' or 'hive_instance'
|
|
462
777
|
"""
|
|
463
778
|
|
|
464
779
|
def __init__(
|
|
465
780
|
self,
|
|
466
|
-
sort="rank",
|
|
467
|
-
observer=None,
|
|
468
|
-
last=None,
|
|
469
|
-
limit=100,
|
|
470
|
-
lazy=False,
|
|
471
|
-
full=True,
|
|
781
|
+
sort: str = "rank",
|
|
782
|
+
observer: str = None,
|
|
783
|
+
last: str = None,
|
|
784
|
+
limit: int = 100,
|
|
785
|
+
lazy: bool = False,
|
|
786
|
+
full: bool = True,
|
|
472
787
|
blockchain_instance=None,
|
|
473
788
|
**kwargs,
|
|
474
|
-
):
|
|
789
|
+
) -> None:
|
|
790
|
+
"""Initialize the Communities list with the given parameters.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
sort: Sort order for communities (default: "rank")
|
|
794
|
+
observer: Observer account for personalized results (optional)
|
|
795
|
+
last: Last community name for pagination (optional)
|
|
796
|
+
limit: Maximum number of communities to fetch (default: 100)
|
|
797
|
+
lazy: If True, use lazy loading (default: False)
|
|
798
|
+
full: If True, fetch full community data (default: True)
|
|
799
|
+
blockchain_instance: Hive or Steem instance to use for blockchain access
|
|
800
|
+
**kwargs: Additional arguments including 'steem_instance' or 'hive_instance'
|
|
801
|
+
"""
|
|
475
802
|
if blockchain_instance is None:
|
|
476
803
|
if kwargs.get("steem_instance"):
|
|
477
804
|
blockchain_instance = kwargs["steem_instance"]
|
|
@@ -481,32 +808,52 @@ class Communities(CommunityObject):
|
|
|
481
808
|
|
|
482
809
|
if not self.blockchain.is_connected():
|
|
483
810
|
return
|
|
811
|
+
|
|
484
812
|
communities = []
|
|
485
813
|
community_cnt = 0
|
|
486
|
-
batch_limit = 100
|
|
487
|
-
if batch_limit > limit:
|
|
488
|
-
batch_limit = limit
|
|
814
|
+
batch_limit = min(100, limit) # Ensure we don't exceed the limit
|
|
489
815
|
|
|
490
816
|
while community_cnt < limit:
|
|
491
817
|
self.blockchain.rpc.set_next_node_on_empty_reply(False)
|
|
492
|
-
|
|
818
|
+
batch = self.blockchain.rpc.list_communities(
|
|
493
819
|
{"sort": sort, "observer": observer, "last": last, "limit": batch_limit},
|
|
494
820
|
api="bridge",
|
|
495
821
|
)
|
|
496
|
-
|
|
822
|
+
if not batch: # No more communities to fetch
|
|
823
|
+
break
|
|
824
|
+
|
|
825
|
+
communities.extend(batch)
|
|
826
|
+
community_cnt += len(batch)
|
|
497
827
|
last = communities[-1]["name"]
|
|
498
828
|
|
|
829
|
+
# Adjust batch size for the next iteration if needed
|
|
830
|
+
if community_cnt + batch_limit > limit:
|
|
831
|
+
batch_limit = limit - community_cnt
|
|
832
|
+
|
|
499
833
|
super(Communities, self).__init__(
|
|
500
834
|
[
|
|
501
835
|
Community(x, lazy=lazy, full=full, blockchain_instance=self.blockchain)
|
|
502
|
-
for x in communities
|
|
836
|
+
for x in communities[:limit] # Ensure we don't exceed the limit
|
|
503
837
|
]
|
|
504
838
|
)
|
|
505
839
|
|
|
506
|
-
def search_title(self, title):
|
|
507
|
-
"""
|
|
840
|
+
def search_title(self, title: str) -> "CommunityObject":
|
|
841
|
+
"""Search for communities with titles containing the given string.
|
|
842
|
+
|
|
843
|
+
The search is case-insensitive.
|
|
844
|
+
|
|
845
|
+
Args:
|
|
846
|
+
title: Text to search for in community titles
|
|
847
|
+
|
|
848
|
+
Returns:
|
|
849
|
+
CommunityObject: A new CommunityObject containing matching communities
|
|
850
|
+
"""
|
|
851
|
+
if not title or not isinstance(title, str):
|
|
852
|
+
raise ValueError("Title must be a non-empty string")
|
|
853
|
+
|
|
508
854
|
ret = CommunityObject()
|
|
855
|
+
title_lower = title.lower()
|
|
509
856
|
for community in self:
|
|
510
|
-
if
|
|
857
|
+
if title_lower in community["title"].lower():
|
|
511
858
|
ret.append(community)
|
|
512
859
|
return ret
|