hive-nectar 0.0.2__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.

Files changed (86) hide show
  1. hive_nectar-0.0.2.dist-info/METADATA +182 -0
  2. hive_nectar-0.0.2.dist-info/RECORD +86 -0
  3. hive_nectar-0.0.2.dist-info/WHEEL +4 -0
  4. hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
  5. hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
  6. nectar/__init__.py +32 -0
  7. nectar/account.py +4371 -0
  8. nectar/amount.py +475 -0
  9. nectar/asciichart.py +270 -0
  10. nectar/asset.py +82 -0
  11. nectar/block.py +446 -0
  12. nectar/blockchain.py +1178 -0
  13. nectar/blockchaininstance.py +2284 -0
  14. nectar/blockchainobject.py +221 -0
  15. nectar/blurt.py +563 -0
  16. nectar/cli.py +6285 -0
  17. nectar/comment.py +1217 -0
  18. nectar/community.py +513 -0
  19. nectar/constants.py +111 -0
  20. nectar/conveyor.py +309 -0
  21. nectar/discussions.py +1709 -0
  22. nectar/exceptions.py +149 -0
  23. nectar/hive.py +546 -0
  24. nectar/hivesigner.py +420 -0
  25. nectar/imageuploader.py +72 -0
  26. nectar/instance.py +129 -0
  27. nectar/market.py +1013 -0
  28. nectar/memo.py +449 -0
  29. nectar/message.py +357 -0
  30. nectar/nodelist.py +444 -0
  31. nectar/price.py +557 -0
  32. nectar/profile.py +65 -0
  33. nectar/rc.py +308 -0
  34. nectar/snapshot.py +726 -0
  35. nectar/steem.py +582 -0
  36. nectar/storage.py +53 -0
  37. nectar/transactionbuilder.py +622 -0
  38. nectar/utils.py +545 -0
  39. nectar/version.py +2 -0
  40. nectar/vote.py +557 -0
  41. nectar/wallet.py +472 -0
  42. nectar/witness.py +617 -0
  43. nectarapi/__init__.py +11 -0
  44. nectarapi/exceptions.py +123 -0
  45. nectarapi/graphenerpc.py +589 -0
  46. nectarapi/node.py +178 -0
  47. nectarapi/noderpc.py +229 -0
  48. nectarapi/rpcutils.py +97 -0
  49. nectarapi/version.py +2 -0
  50. nectarbase/__init__.py +14 -0
  51. nectarbase/ledgertransactions.py +75 -0
  52. nectarbase/memo.py +243 -0
  53. nectarbase/objects.py +429 -0
  54. nectarbase/objecttypes.py +22 -0
  55. nectarbase/operationids.py +102 -0
  56. nectarbase/operations.py +1297 -0
  57. nectarbase/signedtransactions.py +48 -0
  58. nectarbase/transactions.py +11 -0
  59. nectarbase/version.py +2 -0
  60. nectargrapheneapi/__init__.py +6 -0
  61. nectargraphenebase/__init__.py +27 -0
  62. nectargraphenebase/account.py +846 -0
  63. nectargraphenebase/aes.py +52 -0
  64. nectargraphenebase/base58.py +192 -0
  65. nectargraphenebase/bip32.py +494 -0
  66. nectargraphenebase/bip38.py +134 -0
  67. nectargraphenebase/chains.py +149 -0
  68. nectargraphenebase/dictionary.py +3 -0
  69. nectargraphenebase/ecdsasig.py +326 -0
  70. nectargraphenebase/objects.py +123 -0
  71. nectargraphenebase/objecttypes.py +6 -0
  72. nectargraphenebase/operationids.py +3 -0
  73. nectargraphenebase/operations.py +23 -0
  74. nectargraphenebase/prefix.py +11 -0
  75. nectargraphenebase/py23.py +38 -0
  76. nectargraphenebase/signedtransactions.py +201 -0
  77. nectargraphenebase/types.py +419 -0
  78. nectargraphenebase/unsignedtransactions.py +283 -0
  79. nectargraphenebase/version.py +2 -0
  80. nectarstorage/__init__.py +38 -0
  81. nectarstorage/base.py +306 -0
  82. nectarstorage/exceptions.py +16 -0
  83. nectarstorage/interfaces.py +237 -0
  84. nectarstorage/masterpassword.py +239 -0
  85. nectarstorage/ram.py +30 -0
  86. nectarstorage/sqlite.py +334 -0
nectar/comment.py ADDED
@@ -0,0 +1,1217 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import logging
4
+ import math
5
+ from datetime import date, datetime, timezone
6
+
7
+ from nectar.constants import (
8
+ STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF6,
9
+ STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF20,
10
+ STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF21,
11
+ )
12
+ from nectarbase import operations
13
+ from nectargraphenebase.py23 import bytes_types, integer_types, string_types
14
+
15
+ from .account import Account
16
+ from .amount import Amount
17
+ from .blockchainobject import BlockchainObject
18
+ from .exceptions import ContentDoesNotExistsException, VotingInvalidOnArchivedPost
19
+ from .instance import shared_blockchain_instance
20
+ from .price import Price
21
+ from .utils import (
22
+ construct_authorperm,
23
+ formatTimeString,
24
+ formatToTimeStamp,
25
+ make_patch,
26
+ resolve_authorperm,
27
+ )
28
+
29
+ log = logging.getLogger(__name__)
30
+
31
+
32
+ class Comment(BlockchainObject):
33
+ """Read data about a Comment/Post in the chain
34
+
35
+ :param str authorperm: identifier to post/comment in the form of
36
+ ``@author/permlink``
37
+ :param str tags: defines which api is used. Can be bridge, tags, condenser or database (default = bridge)
38
+ :param Hive blockchain_instance: :class:`nectar.hive.Steem` instance to use when accessing a RPC
39
+
40
+
41
+ .. code-block:: python
42
+
43
+ >>> from nectar.comment import Comment
44
+ >>> from nectar.account import Account
45
+ >>> from nectar import Steem
46
+ >>> stm = Steem()
47
+ >>> acc = Account("gtg", blockchain_instance=stm)
48
+ >>> authorperm = acc.get_blog(limit=1)[0]["authorperm"]
49
+ >>> c = Comment(authorperm)
50
+ >>> postdate = c["created"]
51
+ >>> postdate_str = c.json()["created"]
52
+
53
+ """
54
+
55
+ type_id = 8
56
+
57
+ def __init__(
58
+ self,
59
+ authorperm,
60
+ api="bridge",
61
+ observer="",
62
+ full=True,
63
+ lazy=False,
64
+ blockchain_instance=None,
65
+ **kwargs,
66
+ ):
67
+ self.full = full
68
+ self.lazy = lazy
69
+ self.api = api
70
+ self.observer = observer
71
+ if blockchain_instance is None:
72
+ if kwargs.get("steem_instance"):
73
+ blockchain_instance = kwargs["steem_instance"]
74
+ elif kwargs.get("hive_instance"):
75
+ blockchain_instance = kwargs["hive_instance"]
76
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
77
+ if isinstance(authorperm, string_types) and authorperm != "":
78
+ [author, permlink] = resolve_authorperm(authorperm)
79
+ self["id"] = 0
80
+ self["author"] = author
81
+ self["permlink"] = permlink
82
+ self["authorperm"] = authorperm
83
+ elif isinstance(authorperm, dict) and "author" in authorperm and "permlink" in authorperm:
84
+ authorperm["authorperm"] = construct_authorperm(
85
+ authorperm["author"], authorperm["permlink"]
86
+ )
87
+ authorperm = self._parse_json_data(authorperm)
88
+ super(Comment, self).__init__(
89
+ authorperm,
90
+ id_item="authorperm",
91
+ lazy=lazy,
92
+ full=full,
93
+ blockchain_instance=blockchain_instance,
94
+ )
95
+
96
+ def _parse_json_data(self, comment):
97
+ parse_times = [
98
+ "active",
99
+ "cashout_time",
100
+ "created",
101
+ "last_payout",
102
+ "last_update",
103
+ "updated",
104
+ "max_cashout_time",
105
+ ]
106
+ for p in parse_times:
107
+ if p in comment and isinstance(comment.get(p), string_types):
108
+ comment[p] = formatTimeString(comment.get(p, "1970-01-01T00:00:00"))
109
+ # Parse Amounts
110
+ sbd_amounts = [
111
+ "total_payout_value",
112
+ "max_accepted_payout",
113
+ "pending_payout_value",
114
+ "curator_payout_value",
115
+ "total_pending_payout_value",
116
+ "promoted",
117
+ ]
118
+ for p in sbd_amounts:
119
+ if p in comment and isinstance(comment.get(p), (string_types, list, dict)):
120
+ value = comment.get(p, "0.000 %s" % (self.blockchain.backed_token_symbol))
121
+ if (
122
+ isinstance(value, str)
123
+ and value.split(" ")[1] != self.blockchain.backed_token_symbol
124
+ ):
125
+ value = value.split(" ")[0] + " " + self.blockchain.backed_token_symbol
126
+ comment[p] = Amount(value, blockchain_instance=self.blockchain)
127
+
128
+ # turn json_metadata into python dict
129
+ meta_str = comment.get("json_metadata", "{}")
130
+ if meta_str == "{}":
131
+ comment["json_metadata"] = meta_str
132
+ if isinstance(meta_str, (string_types, bytes_types, bytearray)):
133
+ try:
134
+ comment["json_metadata"] = json.loads(meta_str)
135
+ except Exception:
136
+ comment["json_metadata"] = {}
137
+
138
+ comment["tags"] = []
139
+ comment["community"] = ""
140
+ if isinstance(comment["json_metadata"], dict):
141
+ if "tags" in comment["json_metadata"]:
142
+ comment["tags"] = comment["json_metadata"]["tags"]
143
+ if "community" in comment["json_metadata"]:
144
+ comment["community"] = comment["json_metadata"]["community"]
145
+
146
+ parse_int = [
147
+ "author_reputation",
148
+ "net_rshares",
149
+ ]
150
+ for p in parse_int:
151
+ if p in comment and isinstance(comment.get(p), string_types):
152
+ comment[p] = int(comment.get(p, "0"))
153
+
154
+ if "active_votes" in comment:
155
+ new_active_votes = []
156
+ for vote in comment["active_votes"]:
157
+ if "time" in vote and isinstance(vote.get("time"), string_types):
158
+ vote["time"] = formatTimeString(vote.get("time", "1970-01-01T00:00:00"))
159
+ parse_int = [
160
+ "rshares",
161
+ "reputation",
162
+ ]
163
+ for p in parse_int:
164
+ if p in vote and isinstance(vote.get(p), string_types):
165
+ try:
166
+ vote[p] = int(vote.get(p, "0"))
167
+ except ValueError:
168
+ vote[p] = int(0)
169
+ new_active_votes.append(vote)
170
+ comment["active_votes"] = new_active_votes
171
+ return comment
172
+
173
+ def refresh(self):
174
+ if self.identifier == "":
175
+ return
176
+ if not self.blockchain.is_connected():
177
+ return
178
+ [author, permlink] = resolve_authorperm(self.identifier)
179
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
180
+ if self.blockchain.rpc.get_use_appbase():
181
+ from nectarapi.exceptions import InvalidParameters
182
+
183
+ try:
184
+ if self.api == "tags":
185
+ content = self.blockchain.rpc.get_discussion(
186
+ {"author": author, "permlink": permlink}, api="tags"
187
+ )
188
+ elif self.api == "database":
189
+ content = self.blockchain.rpc.list_comments(
190
+ {"start": [author, permlink], "limit": 1, "order": "by_permlink"},
191
+ api="database",
192
+ )
193
+ elif self.api == "bridge":
194
+ content = self.blockchain.rpc.get_post(
195
+ {"author": author, "permlink": permlink, "observer": self.observer},
196
+ api="bridge",
197
+ )
198
+ elif self.api == "condenser":
199
+ content = self.blockchain.rpc.get_content(author, permlink, api="condenser")
200
+ else:
201
+ raise ValueError("api must be: tags, database, bridge or condenser")
202
+ if content is not None and "comments" in content:
203
+ content = content["comments"]
204
+ if isinstance(content, list) and len(content) > 0:
205
+ content = content[0]
206
+ except InvalidParameters:
207
+ raise ContentDoesNotExistsException(self.identifier)
208
+ else:
209
+ from nectarapi.exceptions import InvalidParameters
210
+
211
+ try:
212
+ content = self.blockchain.rpc.get_content(author, permlink)
213
+ except InvalidParameters:
214
+ raise ContentDoesNotExistsException(self.identifier)
215
+ if not content or not content["author"] or not content["permlink"]:
216
+ raise ContentDoesNotExistsException(self.identifier)
217
+ content = self._parse_json_data(content)
218
+ content["authorperm"] = construct_authorperm(content["author"], content["permlink"])
219
+ super(Comment, self).__init__(
220
+ content,
221
+ id_item="authorperm",
222
+ lazy=self.lazy,
223
+ full=self.full,
224
+ blockchain_instance=self.blockchain,
225
+ )
226
+
227
+ def json(self):
228
+ output = self.copy()
229
+ if "authorperm" in output:
230
+ output.pop("authorperm")
231
+ if "json_metadata" in output:
232
+ output["json_metadata"] = json.dumps(output["json_metadata"], separators=[",", ":"])
233
+ if "tags" in output:
234
+ output.pop("tags")
235
+ if "community" in output:
236
+ output.pop("community")
237
+ parse_times = [
238
+ "active",
239
+ "cashout_time",
240
+ "created",
241
+ "last_payout",
242
+ "last_update",
243
+ "updated",
244
+ "max_cashout_time",
245
+ ]
246
+ for p in parse_times:
247
+ if p in output:
248
+ p_date = output.get(p, datetime(1970, 1, 1, 0, 0))
249
+ if isinstance(p_date, (datetime, date)):
250
+ output[p] = formatTimeString(p_date)
251
+ else:
252
+ output[p] = p_date
253
+ sbd_amounts = [
254
+ "total_payout_value",
255
+ "max_accepted_payout",
256
+ "pending_payout_value",
257
+ "curator_payout_value",
258
+ "total_pending_payout_value",
259
+ "promoted",
260
+ ]
261
+ for p in sbd_amounts:
262
+ if p in output and isinstance(output[p], Amount):
263
+ output[p] = output[p].json()
264
+ parse_int = [
265
+ "author_reputation",
266
+ "net_rshares",
267
+ ]
268
+ for p in parse_int:
269
+ if p in output and isinstance(output[p], integer_types):
270
+ output[p] = str(output[p])
271
+ if "active_votes" in output:
272
+ new_active_votes = []
273
+ for vote in output["active_votes"]:
274
+ if "time" in vote:
275
+ p_date = vote.get("time", datetime(1970, 1, 1, 0, 0))
276
+ if isinstance(p_date, (datetime, date)):
277
+ vote["time"] = formatTimeString(p_date)
278
+ else:
279
+ vote["time"] = p_date
280
+ parse_int = [
281
+ "rshares",
282
+ "reputation",
283
+ ]
284
+ for p in parse_int:
285
+ if p in vote and isinstance(vote[p], integer_types):
286
+ vote[p] = str(vote[p])
287
+ new_active_votes.append(vote)
288
+ output["active_votes"] = new_active_votes
289
+ return json.loads(str(json.dumps(output)))
290
+
291
+ @property
292
+ def id(self):
293
+ return self["id"]
294
+
295
+ @property
296
+ def author(self):
297
+ return self["author"]
298
+
299
+ @property
300
+ def permlink(self):
301
+ return self["permlink"]
302
+
303
+ @property
304
+ def authorperm(self):
305
+ return construct_authorperm(self["author"], self["permlink"])
306
+
307
+ @property
308
+ def category(self):
309
+ if "category" in self:
310
+ return self["category"]
311
+ else:
312
+ return ""
313
+
314
+ @property
315
+ def parent_author(self):
316
+ if "parent_author" in self:
317
+ return self["parent_author"]
318
+ else:
319
+ return ""
320
+
321
+ @property
322
+ def parent_permlink(self):
323
+ if "parent_permlink" in self:
324
+ return self["parent_permlink"]
325
+ else:
326
+ return ""
327
+
328
+ @property
329
+ def depth(self):
330
+ return self["depth"]
331
+
332
+ @property
333
+ def title(self):
334
+ if "title" in self:
335
+ return self["title"]
336
+ else:
337
+ return ""
338
+
339
+ @property
340
+ def body(self):
341
+ if "body" in self:
342
+ return self["body"]
343
+ else:
344
+ return ""
345
+
346
+ @property
347
+ def json_metadata(self):
348
+ if "json_metadata" in self:
349
+ return self["json_metadata"]
350
+ else:
351
+ return {}
352
+
353
+ def is_main_post(self):
354
+ """Returns True if main post, and False if this is a comment (reply)."""
355
+ if "depth" in self:
356
+ return self["depth"] == 0
357
+ else:
358
+ return self["parent_author"] == ""
359
+
360
+ def is_comment(self):
361
+ """Returns True if post is a comment"""
362
+ if "depth" in self:
363
+ return self["depth"] > 0
364
+ else:
365
+ return self["parent_author"] != ""
366
+
367
+ @property
368
+ def reward(self):
369
+ """Return the estimated total SBD reward."""
370
+ a_zero = Amount(0, self.blockchain.backed_token_symbol, blockchain_instance=self.blockchain)
371
+ author = Amount(self.get("total_payout_value", a_zero), blockchain_instance=self.blockchain)
372
+ curator = Amount(
373
+ self.get("curator_payout_value", a_zero), blockchain_instance=self.blockchain
374
+ )
375
+ pending = Amount(
376
+ self.get("pending_payout_value", a_zero), blockchain_instance=self.blockchain
377
+ )
378
+ return author + curator + pending
379
+
380
+ def is_pending(self):
381
+ """Returns if the payout is pending (the post/comment
382
+ is younger than 7 days)
383
+ """
384
+ a_zero = Amount(0, self.blockchain.backed_token_symbol, blockchain_instance=self.blockchain)
385
+ total = Amount(self.get("total_payout_value", a_zero), blockchain_instance=self.blockchain)
386
+ post_age_days = self.time_elapsed().total_seconds() / 60 / 60 / 24
387
+ return post_age_days < 7.0 and float(total) == 0
388
+
389
+ def time_elapsed(self):
390
+ """Returns a timedelta on how old the post is."""
391
+ return datetime.now(timezone.utc) - self["created"]
392
+
393
+ def curation_penalty_compensation_SBD(self):
394
+ """Returns The required post payout amount after 15 minutes
395
+ which will compentsate the curation penalty, if voting earlier than 15 minutes
396
+ """
397
+ self.refresh()
398
+ if self.blockchain.hardfork >= 21:
399
+ reverse_auction_window_seconds = STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF21
400
+ elif self.blockchain.hardfork >= 20:
401
+ reverse_auction_window_seconds = STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF20
402
+ else:
403
+ reverse_auction_window_seconds = STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF6
404
+ return (
405
+ self.reward
406
+ * reverse_auction_window_seconds
407
+ / ((self.time_elapsed()).total_seconds() / 60) ** 2
408
+ )
409
+
410
+ def estimate_curation_SBD(self, vote_value_SBD, estimated_value_SBD=None):
411
+ """Estimates curation reward
412
+
413
+ :param float vote_value_SBD: The vote value in SBD for which the curation
414
+ should be calculated
415
+ :param float estimated_value_SBD: When set, this value is used for calculate
416
+ the curation. When not set, the current post value is used.
417
+ """
418
+ self.refresh()
419
+ if estimated_value_SBD is None:
420
+ estimated_value_SBD = float(self.reward)
421
+ t = 1.0 - self.get_curation_penalty()
422
+ k = vote_value_SBD / (vote_value_SBD + float(self.reward))
423
+ K = (1 - math.sqrt(1 - k)) / 4 / k
424
+ return K * vote_value_SBD * t * math.sqrt(estimated_value_SBD)
425
+
426
+ def get_curation_penalty(self, vote_time=None):
427
+ """If post is less than 5 minutes old, it will incur a curation
428
+ reward penalty.
429
+
430
+ :param datetime vote_time: A vote time can be given and the curation
431
+ penalty is calculated regarding the given time (default is None)
432
+ When set to None, the current date is used.
433
+ :returns: Float number between 0 and 1 (0.0 -> no penalty, 1.0 -> 100 % curation penalty)
434
+ :rtype: float
435
+
436
+ """
437
+ if vote_time is None:
438
+ elapsed_seconds = self.time_elapsed().total_seconds()
439
+ elif isinstance(vote_time, str):
440
+ elapsed_seconds = (formatTimeString(vote_time) - self["created"]).total_seconds()
441
+ elif isinstance(vote_time, (datetime, date)):
442
+ elapsed_seconds = (vote_time - self["created"]).total_seconds()
443
+ else:
444
+ raise ValueError("vote_time must be a string or a datetime")
445
+ if self.blockchain.hardfork >= 21:
446
+ reward = elapsed_seconds / STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF21
447
+ elif self.blockchain.hardfork >= 20:
448
+ reward = elapsed_seconds / STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF20
449
+ else:
450
+ reward = elapsed_seconds / STEEM_REVERSE_AUCTION_WINDOW_SECONDS_HF6
451
+ if reward > 1:
452
+ reward = 1.0
453
+ return 1.0 - reward
454
+
455
+ def get_vote_with_curation(self, voter=None, raw_data=False, pending_payout_value=None):
456
+ """Returns vote for voter. Returns None, if the voter cannot be found in `active_votes`.
457
+
458
+ :param str voter: Voter for which the vote should be returned
459
+ :param bool raw_data: If True, the raw data are returned
460
+ :param pending_payout_SBD: When not None this value instead of the current
461
+ value is used for calculating the rewards
462
+ :type pending_payout_SBD: float, str
463
+
464
+ """
465
+ specific_vote = None
466
+ if voter is None:
467
+ voter = Account(self["author"], blockchain_instance=self.blockchain)
468
+ else:
469
+ voter = Account(voter, blockchain_instance=self.blockchain)
470
+ if "active_votes" in self:
471
+ for vote in self["active_votes"]:
472
+ if voter["name"] == vote["voter"]:
473
+ specific_vote = vote
474
+ else:
475
+ active_votes = self.get_votes()
476
+ for vote in active_votes:
477
+ if voter["name"] == vote["voter"]:
478
+ specific_vote = vote
479
+ if specific_vote is not None and (raw_data or not self.is_pending()):
480
+ return specific_vote
481
+ elif specific_vote is not None:
482
+ curation_reward = self.get_curation_rewards(
483
+ pending_payout_SBD=True, pending_payout_value=pending_payout_value
484
+ )
485
+ specific_vote["curation_reward"] = curation_reward["active_votes"][voter["name"]]
486
+ specific_vote["ROI"] = (
487
+ float(curation_reward["active_votes"][voter["name"]])
488
+ / float(voter.get_voting_value_SBD(voting_weight=specific_vote["percent"] / 100))
489
+ * 100
490
+ )
491
+ return specific_vote
492
+ else:
493
+ return None
494
+
495
+ def get_beneficiaries_pct(self):
496
+ """Returns the sum of all post beneficiaries in percentage"""
497
+ beneficiaries = self["beneficiaries"]
498
+ weight = 0
499
+ for b in beneficiaries:
500
+ weight += b["weight"]
501
+ return weight / 100.0
502
+
503
+ def get_rewards(self):
504
+ """Returns the total_payout, author_payout and the curator payout in SBD.
505
+ When the payout is still pending, the estimated payout is given out.
506
+
507
+ .. note:: Potential beneficiary rewards were already deducted from the
508
+ `author_payout` and the `total_payout`
509
+
510
+ Example:::
511
+
512
+ {
513
+ 'total_payout': 9.956 SBD,
514
+ 'author_payout': 7.166 SBD,
515
+ 'curator_payout': 2.790 SBD
516
+ }
517
+
518
+ """
519
+ if self.is_pending():
520
+ total_payout = Amount(self["pending_payout_value"], blockchain_instance=self.blockchain)
521
+ author_payout = self.get_author_rewards()["total_payout_SBD"]
522
+ curator_payout = total_payout - author_payout
523
+ else:
524
+ author_payout = Amount(self["total_payout_value"], blockchain_instance=self.blockchain)
525
+ curator_payout = Amount(
526
+ self["curator_payout_value"], blockchain_instance=self.blockchain
527
+ )
528
+ total_payout = author_payout + curator_payout
529
+ return {
530
+ "total_payout": total_payout,
531
+ "author_payout": author_payout,
532
+ "curator_payout": curator_payout,
533
+ }
534
+
535
+ def get_author_rewards(self):
536
+ """Returns the author rewards.
537
+
538
+
539
+
540
+ Example::
541
+
542
+ {
543
+ 'pending_rewards': True,
544
+ 'payout_SP': 0.912 STEEM,
545
+ 'payout_SBD': 3.583 SBD,
546
+ 'total_payout_SBD': 7.166 SBD
547
+ }
548
+
549
+ """
550
+ if not self.is_pending():
551
+ return {
552
+ "pending_rewards": False,
553
+ "payout_SP": Amount(
554
+ 0, self.blockchain.token_symbol, blockchain_instance=self.blockchain
555
+ ),
556
+ "payout_SBD": Amount(
557
+ 0, self.blockchain.backed_token_symbol, blockchain_instance=self.blockchain
558
+ ),
559
+ "total_payout_SBD": Amount(
560
+ self["total_payout_value"], blockchain_instance=self.blockchain
561
+ ),
562
+ }
563
+ author_reward_factor = 0.5
564
+ median_hist = self.blockchain.get_current_median_history()
565
+ if median_hist is not None:
566
+ median_price = Price(median_hist, blockchain_instance=self.blockchain)
567
+ beneficiaries_pct = self.get_beneficiaries_pct()
568
+ curation_tokens = self.reward * author_reward_factor
569
+ author_tokens = self.reward - curation_tokens
570
+ curation_rewards = self.get_curation_rewards()
571
+ if self.blockchain.hardfork >= 20 and median_hist is not None:
572
+ author_tokens += median_price * curation_rewards["unclaimed_rewards"]
573
+
574
+ benefactor_tokens = author_tokens * beneficiaries_pct / 100.0
575
+ author_tokens -= benefactor_tokens
576
+
577
+ if median_hist is not None and "percent_steem_dollars" in self:
578
+ sbd_steem = author_tokens * self["percent_steem_dollars"] / 20000.0
579
+ vesting_steem = median_price.as_base(self.blockchain.token_symbol) * (
580
+ author_tokens - sbd_steem
581
+ )
582
+ return {
583
+ "pending_rewards": True,
584
+ "payout_SP": vesting_steem,
585
+ "payout_SBD": sbd_steem,
586
+ "total_payout_SBD": author_tokens,
587
+ }
588
+ elif median_hist is not None and "percent_hbd" in self:
589
+ sbd_steem = author_tokens * self["percent_hbd"] / 20000.0
590
+ vesting_steem = median_price.as_base(self.blockchain.token_symbol) * (
591
+ author_tokens - sbd_steem
592
+ )
593
+ return {
594
+ "pending_rewards": True,
595
+ "payout_SP": vesting_steem,
596
+ "payout_SBD": sbd_steem,
597
+ "total_payout_SBD": author_tokens,
598
+ }
599
+ else:
600
+ return {
601
+ "pending_rewards": True,
602
+ "total_payout": author_tokens,
603
+ "payout_SBD": None,
604
+ "total_payout_SBD": None,
605
+ }
606
+
607
+ def get_curation_rewards(self, pending_payout_SBD=False, pending_payout_value=None):
608
+ """Returns the curation rewards. The split between creator/curator is currently 50%/50%.
609
+
610
+ :param bool pending_payout_SBD: If True, the rewards are returned in SBD and not in STEEM (default is False)
611
+ :param pending_payout_value: When not None this value instead of the current
612
+ value is used for calculating the rewards
613
+ :type pending_payout_value: float, str
614
+
615
+ `pending_rewards` is True when
616
+ the post is younger than 7 days. `unclaimed_rewards` is the
617
+ amount of curation_rewards that goes to the author (self-vote or votes within
618
+ the first 30 minutes). `active_votes` contains all voter with their curation reward.
619
+
620
+ Example::
621
+
622
+ {
623
+ 'pending_rewards': True, 'unclaimed_rewards': 0.245 STEEM,
624
+ 'active_votes': {
625
+ 'leprechaun': 0.006 STEEM, 'timcliff': 0.186 STEEM,
626
+ 'st3llar': 0.000 STEEM, 'crokkon': 0.015 STEEM, 'feedyourminnows': 0.003 STEEM,
627
+ 'isnochys': 0.003 STEEM, 'loshcat': 0.001 STEEM, 'greenorange': 0.000 STEEM,
628
+ 'qustodian': 0.123 STEEM, 'jpphotography': 0.002 STEEM, 'thinkingmind': 0.001 STEEM,
629
+ 'oups': 0.006 STEEM, 'mattockfs': 0.001 STEEM, 'thecrazygm': 0.003 STEEM, 'michaelizer': 0.004 STEEM,
630
+ 'flugschwein': 0.010 STEEM, 'ulisessabeque': 0.000 STEEM, 'hakancelik': 0.002 STEEM, 'sbi2': 0.008 STEEM,
631
+ 'zcool': 0.000 STEEM, 'steemhq': 0.002 STEEM, 'rowdiya': 0.000 STEEM, 'qurator-tier-1-2': 0.012 STEEM
632
+ }
633
+ }
634
+
635
+ """
636
+ median_hist = self.blockchain.get_current_median_history()
637
+ if median_hist is not None:
638
+ median_price = Price(median_hist, blockchain_instance=self.blockchain)
639
+ pending_rewards = False
640
+ active_votes_list = self.get_votes()
641
+ curator_reward_factor = 0.5
642
+
643
+ if "total_vote_weight" in self:
644
+ total_vote_weight = self["total_vote_weight"]
645
+ active_votes_json_list = []
646
+ for vote in active_votes_list:
647
+ if "weight" not in vote:
648
+ vote.refresh()
649
+ active_votes_json_list.append(vote.json())
650
+ else:
651
+ active_votes_json_list.append(vote.json())
652
+
653
+ total_vote_weight = 0
654
+ for vote in active_votes_json_list:
655
+ total_vote_weight += vote["weight"]
656
+
657
+ if not self.is_pending():
658
+ if pending_payout_SBD or median_hist is None:
659
+ max_rewards = Amount(
660
+ self["curator_payout_value"], blockchain_instance=self.blockchain
661
+ )
662
+ else:
663
+ max_rewards = median_price.as_base(self.blockchain.token_symbol) * Amount(
664
+ self["curator_payout_value"], blockchain_instance=self.blockchain
665
+ )
666
+ unclaimed_rewards = Amount(
667
+ 0, self.blockchain.token_symbol, blockchain_instance=self.blockchain
668
+ )
669
+ else:
670
+ if pending_payout_value is None and "pending_payout_value" in self:
671
+ pending_payout_value = Amount(
672
+ self["pending_payout_value"], blockchain_instance=self.blockchain
673
+ )
674
+ elif pending_payout_value is None:
675
+ pending_payout_value = 0
676
+ elif isinstance(pending_payout_value, (float, integer_types)):
677
+ pending_payout_value = Amount(
678
+ pending_payout_value,
679
+ self.blockchain.backed_token_symbol,
680
+ blockchain_instance=self.blockchain,
681
+ )
682
+ elif isinstance(pending_payout_value, str):
683
+ pending_payout_value = Amount(
684
+ pending_payout_value, blockchain_instance=self.blockchain
685
+ )
686
+ if pending_payout_SBD or median_hist is None:
687
+ max_rewards = pending_payout_value * curator_reward_factor
688
+ else:
689
+ max_rewards = median_price.as_base(self.blockchain.token_symbol) * (
690
+ pending_payout_value * curator_reward_factor
691
+ )
692
+ unclaimed_rewards = max_rewards.copy()
693
+ pending_rewards = True
694
+
695
+ active_votes = {}
696
+
697
+ for vote in active_votes_json_list:
698
+ if total_vote_weight > 0:
699
+ claim = max_rewards * int(vote["weight"]) / total_vote_weight
700
+ else:
701
+ claim = 0
702
+ if claim > 0 and pending_rewards:
703
+ unclaimed_rewards -= claim
704
+ if claim > 0:
705
+ active_votes[vote["voter"]] = claim
706
+ else:
707
+ active_votes[vote["voter"]] = 0
708
+
709
+ return {
710
+ "pending_rewards": pending_rewards,
711
+ "unclaimed_rewards": unclaimed_rewards,
712
+ "active_votes": active_votes,
713
+ }
714
+
715
+ def get_reblogged_by(self, identifier=None):
716
+ """Shows in which blogs this post appears"""
717
+ if not identifier:
718
+ post_author = self["author"]
719
+ post_permlink = self["permlink"]
720
+ else:
721
+ [post_author, post_permlink] = resolve_authorperm(identifier)
722
+ if not self.blockchain.is_connected():
723
+ return None
724
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
725
+ if self.blockchain.rpc.get_use_appbase():
726
+ return self.blockchain.rpc.get_reblogged_by(
727
+ {"author": post_author, "permlink": post_permlink}, api="condenser"
728
+ )["accounts"]
729
+ else:
730
+ return self.blockchain.rpc.get_reblogged_by(post_author, post_permlink, api="condenser")
731
+
732
+ def get_replies(self, raw_data=False, identifier=None):
733
+ """Returns content replies
734
+
735
+ :param bool raw_data: When set to False, the replies will be returned as Comment class objects
736
+ """
737
+ if not identifier:
738
+ post_author = self["author"]
739
+ post_permlink = self["permlink"]
740
+ else:
741
+ [post_author, post_permlink] = resolve_authorperm(identifier)
742
+ if not self.blockchain.is_connected():
743
+ return None
744
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
745
+
746
+ # Use bridge.get_discussion API
747
+ content_replies = self.blockchain.rpc.get_discussion(
748
+ {"author": post_author, "permlink": post_permlink, "observer": self.observer},
749
+ api="bridge",
750
+ )
751
+
752
+ if not content_replies:
753
+ return []
754
+
755
+ # The response format is a dict with keys in 'author/permlink' format
756
+ # We need to extract the replies by filtering out the original post
757
+ original_key = f"{post_author}/{post_permlink}"
758
+ replies = []
759
+
760
+ for key, content in content_replies.items():
761
+ # Skip the original post
762
+ if key == original_key:
763
+ continue
764
+ # Add the reply
765
+ replies.append(content)
766
+
767
+ if raw_data:
768
+ return replies
769
+ return [Comment(c, blockchain_instance=self.blockchain) for c in replies]
770
+
771
+ def get_all_replies(self, parent=None):
772
+ """Returns all content replies"""
773
+ if parent is None:
774
+ parent = self
775
+ if parent["children"] > 0:
776
+ children = parent.get_replies()
777
+ if children is None:
778
+ return []
779
+ for cc in children[:]:
780
+ children.extend(self.get_all_replies(parent=cc))
781
+ return children
782
+ return []
783
+
784
+ def get_parent(self, children=None):
785
+ """Returns the parent post with depth == 0"""
786
+ if children is None:
787
+ children = self
788
+ while children["depth"] > 0:
789
+ children = Comment(
790
+ construct_authorperm(children["parent_author"], children["parent_permlink"]),
791
+ blockchain_instance=self.blockchain,
792
+ )
793
+ return children
794
+
795
+ def get_votes(self, raw_data=False):
796
+ """Returns all votes as ActiveVotes object"""
797
+ if raw_data and "active_votes" in self:
798
+ return self["active_votes"]
799
+ from .vote import ActiveVotes
800
+
801
+ authorperm = construct_authorperm(self["author"], self["permlink"])
802
+ return ActiveVotes(authorperm, lazy=False, blockchain_instance=self.blockchain)
803
+
804
+ def upvote(self, weight=+100, voter=None):
805
+ """Upvote the post
806
+
807
+ :param float weight: (optional) Weight for posting (-100.0 -
808
+ +100.0) defaults to +100.0
809
+ :param str voter: (optional) Voting account
810
+
811
+ """
812
+ if weight < 0:
813
+ raise ValueError("Weight must be >= 0.")
814
+ last_payout = self.get("last_payout", None)
815
+ if last_payout is not None:
816
+ if formatToTimeStamp(last_payout) > 0:
817
+ raise VotingInvalidOnArchivedPost
818
+ return self.vote(weight, account=voter)
819
+
820
+ def downvote(self, weight=100, voter=None):
821
+ """Downvote the post
822
+
823
+ :param float weight: (optional) Weight for posting (-100.0 -
824
+ +100.0) defaults to -100.0
825
+ :param str voter: (optional) Voting account
826
+
827
+ """
828
+ if weight < 0:
829
+ raise ValueError("Weight must be >= 0.")
830
+ last_payout = self.get("last_payout", None)
831
+ if last_payout is not None:
832
+ if formatToTimeStamp(last_payout) > 0:
833
+ raise VotingInvalidOnArchivedPost
834
+ return self.vote(-weight, account=voter)
835
+
836
+ def vote(self, weight, account=None, identifier=None, **kwargs):
837
+ """Vote for a post
838
+
839
+ :param float weight: Voting weight. Range: -100.0 - +100.0.
840
+ :param str account: (optional) Account to use for voting. If
841
+ ``account`` is not defined, the ``default_account`` will be used
842
+ or a ValueError will be raised
843
+ :param str identifier: Identifier for the post to vote. Takes the
844
+ form ``@author/permlink``.
845
+
846
+ """
847
+ if not identifier:
848
+ identifier = construct_authorperm(self["author"], self["permlink"])
849
+
850
+ return self.blockchain.vote(weight, identifier, account=account)
851
+
852
+ def edit(self, body, meta=None, replace=False):
853
+ """Edit an existing post
854
+
855
+ :param str body: Body of the reply
856
+ :param json meta: JSON meta object that can be attached to the
857
+ post. (optional)
858
+ :param bool replace: Instead of calculating a *diff*, replace
859
+ the post entirely (defaults to ``False``)
860
+
861
+ """
862
+ if not meta:
863
+ meta = {}
864
+ original_post = self
865
+
866
+ if replace:
867
+ newbody = body
868
+ else:
869
+ newbody = make_patch(original_post["body"], body)
870
+ if not newbody:
871
+ log.info("No changes made! Skipping ...")
872
+ return
873
+
874
+ reply_identifier = construct_authorperm(
875
+ original_post["parent_author"], original_post["parent_permlink"]
876
+ )
877
+
878
+ new_meta = {}
879
+ if meta is not None:
880
+ if bool(original_post["json_metadata"]):
881
+ new_meta = original_post["json_metadata"]
882
+ for key in meta:
883
+ new_meta[key] = meta[key]
884
+ else:
885
+ new_meta = meta
886
+
887
+ return self.blockchain.post(
888
+ original_post["title"],
889
+ newbody,
890
+ reply_identifier=reply_identifier,
891
+ author=original_post["author"],
892
+ permlink=original_post["permlink"],
893
+ json_metadata=new_meta,
894
+ )
895
+
896
+ def reply(self, body, title="", author="", meta=None):
897
+ """Reply to an existing post
898
+
899
+ :param str body: Body of the reply
900
+ :param str title: Title of the reply post
901
+ :param str author: Author of reply (optional) if not provided
902
+ ``default_user`` will be used, if present, else
903
+ a ``ValueError`` will be raised.
904
+ :param json meta: JSON meta object that can be attached to the
905
+ post. (optional)
906
+
907
+ """
908
+ return self.blockchain.post(
909
+ title, body, json_metadata=meta, author=author, reply_identifier=self.identifier
910
+ )
911
+
912
+ def delete(self, account=None, identifier=None):
913
+ """Delete an existing post/comment
914
+
915
+ :param str account: (optional) Account to use for deletion. If
916
+ ``account`` is not defined, the ``default_account`` will be
917
+ taken or a ValueError will be raised.
918
+
919
+ :param str identifier: (optional) Identifier for the post to delete.
920
+ Takes the form ``@author/permlink``. By default the current post
921
+ will be used.
922
+
923
+ .. note:: A post/comment can only be deleted as long as it has no
924
+ replies and no positive rshares on it.
925
+
926
+ """
927
+ if not account:
928
+ if "default_account" in self.blockchain.config:
929
+ account = self.blockchain.config["default_account"]
930
+ if not account:
931
+ raise ValueError("You need to provide an account")
932
+ account = Account(account, blockchain_instance=self.blockchain)
933
+ if not identifier:
934
+ post_author = self["author"]
935
+ post_permlink = self["permlink"]
936
+ else:
937
+ [post_author, post_permlink] = resolve_authorperm(identifier)
938
+ op = operations.Delete_comment(**{"author": post_author, "permlink": post_permlink})
939
+ return self.blockchain.finalizeOp(op, account, "posting")
940
+
941
+ def resteem(self, identifier=None, account=None):
942
+ """Resteem a post
943
+
944
+ :param str identifier: post identifier (@<account>/<permlink>)
945
+ :param str account: (optional) the account to allow access
946
+ to (defaults to ``default_account``)
947
+
948
+ """
949
+ if not account:
950
+ account = self.blockchain.configStorage.get("default_account")
951
+ if not account:
952
+ raise ValueError("You need to provide an account")
953
+ account = Account(account, blockchain_instance=self.blockchain)
954
+ if identifier is None:
955
+ identifier = self.identifier
956
+ author, permlink = resolve_authorperm(identifier)
957
+ json_body = ["reblog", {"account": account["name"], "author": author, "permlink": permlink}]
958
+ return self.blockchain.custom_json(
959
+ id="follow", json_data=json_body, required_posting_auths=[account["name"]]
960
+ )
961
+
962
+
963
+ class RecentReplies(list):
964
+ """Obtain a list of recent replies
965
+
966
+ :param str author: author
967
+ :param bool skip_own: (optional) Skip replies of the author to him/herself.
968
+ Default: True
969
+ :param Steem blockchain_instance: Steem() instance to use when accesing a RPC
970
+ """
971
+
972
+ def __init__(
973
+ self,
974
+ author,
975
+ skip_own=True,
976
+ start_permlink="",
977
+ limit=100,
978
+ lazy=False,
979
+ full=True,
980
+ blockchain_instance=None,
981
+ **kwargs,
982
+ ):
983
+ if blockchain_instance is None:
984
+ if kwargs.get("steem_instance"):
985
+ blockchain_instance = kwargs["steem_instance"]
986
+ elif kwargs.get("hive_instance"):
987
+ blockchain_instance = kwargs["hive_instance"]
988
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
989
+ if not self.blockchain.is_connected():
990
+ return None
991
+ self.blockchain.rpc.set_next_node_on_empty_reply(True)
992
+ account = Account(author, blockchain_instance=self.blockchain)
993
+ replies = account.get_account_posts(sort="replies", raw_data=True)
994
+ comments = []
995
+ if replies is None:
996
+ replies = []
997
+ for post in replies:
998
+ if skip_own and post["author"] == author:
999
+ continue
1000
+ comments.append(
1001
+ Comment(post, lazy=lazy, full=full, blockchain_instance=self.blockchain)
1002
+ )
1003
+ super(RecentReplies, self).__init__(comments)
1004
+
1005
+
1006
+ class RecentByPath(list):
1007
+ """Obtain a list of posts recent by path, does the same as RankedPosts
1008
+
1009
+ :param str path: path
1010
+ :param str tag: tag
1011
+ :param str observer: observer
1012
+ :param Steem blockchain_instance: Steem() instance to use when accesing a RPC
1013
+ """
1014
+
1015
+ def __init__(
1016
+ self,
1017
+ path="trending",
1018
+ tag="",
1019
+ observer="",
1020
+ lazy=False,
1021
+ full=True,
1022
+ limit=20,
1023
+ blockchain_instance=None,
1024
+ **kwargs,
1025
+ ):
1026
+ if blockchain_instance is None:
1027
+ if kwargs.get("steem_instance"):
1028
+ blockchain_instance = kwargs["steem_instance"]
1029
+ elif kwargs.get("hive_instance"):
1030
+ blockchain_instance = kwargs["hive_instance"]
1031
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
1032
+
1033
+ # Create RankedPosts with proper parameters
1034
+ ranked_posts = RankedPosts(
1035
+ sort=path,
1036
+ tag=tag,
1037
+ observer=observer,
1038
+ limit=limit,
1039
+ lazy=lazy,
1040
+ full=full,
1041
+ blockchain_instance=self.blockchain,
1042
+ **kwargs,
1043
+ )
1044
+
1045
+ super(RecentByPath, self).__init__(ranked_posts)
1046
+
1047
+
1048
+ class RankedPosts(list):
1049
+ """Obtain a list of ranked posts
1050
+
1051
+ :param str sort: can be: trending, hot, created, promoted, payout, payout_comments, muted
1052
+ :param str tag: tag, when used my, the community posts of the observer are shown
1053
+ :param str observer: Observer name
1054
+ :param int limit: limits the number of returns comments
1055
+ :param str start_author: start author
1056
+ :param str start_permlink: start permlink
1057
+ :param Steem blockchain_instance: Steem() instance to use when accesing a RPC
1058
+ """
1059
+
1060
+ def __init__(
1061
+ self,
1062
+ sort,
1063
+ tag="",
1064
+ observer="",
1065
+ limit=21,
1066
+ start_author="",
1067
+ start_permlink="",
1068
+ lazy=False,
1069
+ full=True,
1070
+ raw_data=False,
1071
+ blockchain_instance=None,
1072
+ **kwargs,
1073
+ ):
1074
+ if blockchain_instance is None:
1075
+ if kwargs.get("steem_instance"):
1076
+ blockchain_instance = kwargs["steem_instance"]
1077
+ elif kwargs.get("hive_instance"):
1078
+ blockchain_instance = kwargs["hive_instance"]
1079
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
1080
+ if not self.blockchain.is_connected():
1081
+ return None
1082
+ comments = []
1083
+ api_limit = limit
1084
+ if api_limit > 100:
1085
+ api_limit = 100
1086
+ last_n = -1
1087
+ while len(comments) < limit and last_n != len(comments):
1088
+ last_n = len(comments)
1089
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1090
+ try:
1091
+ posts = self.blockchain.rpc.get_ranked_posts(
1092
+ {
1093
+ "sort": sort,
1094
+ "tag": tag,
1095
+ "observer": observer,
1096
+ "limit": api_limit,
1097
+ "start_author": start_author,
1098
+ "start_permlink": start_permlink,
1099
+ },
1100
+ api="bridge",
1101
+ )
1102
+ if posts is None:
1103
+ continue
1104
+ for post in posts:
1105
+ if (
1106
+ len(comments) > 0
1107
+ and comments[-1]["author"] == post["author"]
1108
+ and comments[-1]["permlink"] == post["permlink"]
1109
+ ):
1110
+ continue
1111
+ if len(comments) >= limit:
1112
+ continue
1113
+ if raw_data:
1114
+ comments.append(post)
1115
+ else:
1116
+ comments.append(
1117
+ Comment(post, lazy=lazy, full=full, blockchain_instance=self.blockchain)
1118
+ )
1119
+ if len(comments) > 0:
1120
+ start_author = comments[-1]["author"]
1121
+ start_permlink = comments[-1]["permlink"]
1122
+ if limit - len(comments) < 100:
1123
+ api_limit = limit - len(comments) + 1
1124
+ except Exception as e:
1125
+ # If we get an error but have some posts, return what we have
1126
+ if len(comments) > 0:
1127
+ logging.warning(f"Error in RankedPosts: {str(e)}. Returning partial results.")
1128
+ break
1129
+ # Otherwise, re-raise the exception
1130
+ raise
1131
+ super(RankedPosts, self).__init__(comments)
1132
+
1133
+
1134
+ class AccountPosts(list):
1135
+ """Obtain a list of account related posts
1136
+
1137
+ :param str sort: can be: comments, posts, blog, replies, feed
1138
+ :param str account: Account name
1139
+ :param str observer: Observer name
1140
+ :param int limit: limits the number of returns comments
1141
+ :param str start_author: start author
1142
+ :param str start_permlink: start permlink
1143
+ :param Hive blockchain_instance: Hive() instance to use when accesing a RPC
1144
+ """
1145
+
1146
+ def __init__(
1147
+ self,
1148
+ sort,
1149
+ account,
1150
+ observer="",
1151
+ limit=20,
1152
+ start_author="",
1153
+ start_permlink="",
1154
+ lazy=False,
1155
+ full=True,
1156
+ raw_data=False,
1157
+ blockchain_instance=None,
1158
+ **kwargs,
1159
+ ):
1160
+ if blockchain_instance is None:
1161
+ if kwargs.get("steem_instance"):
1162
+ blockchain_instance = kwargs["steem_instance"]
1163
+ elif kwargs.get("hive_instance"):
1164
+ blockchain_instance = kwargs["hive_instance"]
1165
+ self.blockchain = blockchain_instance or shared_blockchain_instance()
1166
+ if not self.blockchain.is_connected():
1167
+ return None
1168
+ comments = []
1169
+ api_limit = limit
1170
+ if api_limit > 100:
1171
+ api_limit = 100
1172
+ last_n = -1
1173
+ while len(comments) < limit and last_n != len(comments):
1174
+ last_n = len(comments)
1175
+ self.blockchain.rpc.set_next_node_on_empty_reply(False)
1176
+ try:
1177
+ posts = self.blockchain.rpc.get_account_posts(
1178
+ {
1179
+ "sort": sort,
1180
+ "account": account,
1181
+ "observer": observer,
1182
+ "limit": api_limit,
1183
+ "start_author": start_author,
1184
+ "start_permlink": start_permlink,
1185
+ },
1186
+ api="bridge",
1187
+ )
1188
+ if posts is None:
1189
+ continue
1190
+ for post in posts:
1191
+ if (
1192
+ len(comments) > 0
1193
+ and comments[-1]["author"] == post["author"]
1194
+ and comments[-1]["permlink"] == post["permlink"]
1195
+ ):
1196
+ continue
1197
+ if len(comments) >= limit:
1198
+ continue
1199
+ if raw_data:
1200
+ comments.append(post)
1201
+ else:
1202
+ comments.append(
1203
+ Comment(post, lazy=lazy, full=full, blockchain_instance=self.blockchain)
1204
+ )
1205
+ if len(comments) > 0:
1206
+ start_author = comments[-1]["author"]
1207
+ start_permlink = comments[-1]["permlink"]
1208
+ if limit - len(comments) < 100:
1209
+ api_limit = limit - len(comments) + 1
1210
+ except Exception as e:
1211
+ # If we get an error but have some posts, return what we have
1212
+ if len(comments) > 0:
1213
+ logging.warning(f"Error in AccountPosts: {str(e)}. Returning partial results.")
1214
+ break
1215
+ # Otherwise, re-raise the exception
1216
+ raise
1217
+ super(AccountPosts, self).__init__(comments)