From db95313f7355c17a278853f4ff6ee1b0008716e7 Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holgernahrstaedt@gmx.de>
Date: Fri, 21 Aug 2020 23:43:33 +0200
Subject: [PATCH] Prepare next release 0.24.6

* Improved community selection in beempy createpost
* Improved Transactionbuilder object representation
* _fetchkeys function moved outside appendSigner
* Fix get urls in parse body
* Two more nodes have been added to nodelist
---
 CHANGELOG.rst               |   8 +++
 beem/blockchaininstance.py  |   5 +-
 beem/cli.py                 |   7 +++
 beem/nodelist.py            |  16 +++++
 beem/transactionbuilder.py  | 117 +++++++++++++++++++++++-------------
 beem/version.py             |   2 +-
 beemapi/version.py          |   2 +-
 beembase/operationids.py    |   2 -
 beembase/version.py         |   2 +-
 beemgraphenebase/version.py |   2 +-
 setup.py                    |   2 +-
 11 files changed, 115 insertions(+), 50 deletions(-)

diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 50c7a3ae..ae85edab 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,5 +1,13 @@
 Changelog
 =========
+0.24.6
+------
+* Improved community selection in beempy createpost
+* Improved Transactionbuilder object representation
+* _fetchkeys function moved outside appendSigner
+* Fix get urls in parse body
+* Two more nodes have been added to nodelist
+
 0.24.5
 ------
 * replace percent_hive_dollars by percent_hbd (to make beem HF24 ready)
diff --git a/beem/blockchaininstance.py b/beem/blockchaininstance.py
index 00a0c589..ab58f3be 100644
--- a/beem/blockchaininstance.py
+++ b/beem/blockchaininstance.py
@@ -1930,7 +1930,8 @@ class BlockChainInstance(object):
 
         if parse_body:
             def get_urls(mdstring):
-                return list(set(re.findall(r'http[s]*://[^\s"><\)\(]+', mdstring)))
+                urls = (re.findall(r'http[s]*://[^\s"><\)\(]+', mdstring))
+                return list(dict.fromkeys(urls))
 
             def get_users(mdstring):
                 users = []
@@ -1951,6 +1952,8 @@ class BlockChainInstance(object):
                 img_exts = ['.jpg', '.png', '.gif', '.svg', '.jpeg']
                 if os.path.splitext(url)[1].lower() in img_exts:
                     image.append(url)
+                elif url[:25] == "https://images.hive.blog/":
+                    image.append(url)
                 else:
                     links.append(url)
             users = get_users(body)
diff --git a/beem/cli.py b/beem/cli.py
index 84c9b2c0..496099ec 100644
--- a/beem/cli.py
+++ b/beem/cli.py
@@ -2540,6 +2540,9 @@ def createpost(markdown_file, account, title, tags, community, beneficiaries, pe
             except:
                 c = Communities(limit=1000)
                 comm_cand = c.search_title(community)
+                if len(comm_cand) == 0:
+                    print("No community could be found!")
+                    continue
                 print(comm_cand.printAsTable())
                 index = input("Enter community Nr:")
                 if int(index) - 1 >= len(comm_cand):
@@ -4739,6 +4742,7 @@ def info(objects):
             account = Account(obj, blockchain_instance=stm)
             t = PrettyTable(["Key", "Value"])
             t.align = "l"
+            t._max_width = {"Value" : 80}
             account_json = account.json()
             for key in sorted(account_json):
                 value = account_json[key]
@@ -4777,6 +4781,7 @@ def info(objects):
                 key_type = stm.wallet.getKeyType(account, obj)
                 t = PrettyTable(["Account", "Key_type"])
                 t.align = "l"
+                t._max_width = {"Value" : 80}
                 t.add_row([account["name"], key_type])
                 print(t)
             else:
@@ -4788,6 +4793,7 @@ def info(objects):
             if post_json:
                 t = PrettyTable(["Key", "Value"])
                 t.align = "l"
+                t._max_width = {"Value" : 80}
                 for key in sorted(post_json):
                     if key in ["body", "active_votes"]:
                         value = "not shown"
@@ -4807,6 +4813,7 @@ def info(objects):
             trx = b.get_transaction(obj)
             t = PrettyTable(["Key", "Value"])
             t.align = "l"
+            t._max_width = {"Value" : 80}
             for key in trx:
                 value = trx[key]
                 if key in ["operations", "signatures"]:
diff --git a/beem/nodelist.py b/beem/nodelist.py
index 45965b77..e6426bbe 100644
--- a/beem/nodelist.py
+++ b/beem/nodelist.py
@@ -178,6 +178,22 @@ class NodeList(list):
                 "owner": "roelandp",
                 "hive": True,
                 "score": 50                
+            },
+            {
+                "url": "https://api.c0ff33a.uk",
+                "version": "0.23.0",
+                "type": "appbase",
+                "owner": "c0ff33a",
+                "hive": True,
+                "score": 40                
+            },
+            {
+                "url": "https://api.deathwing.me",
+                "version": "0.23.0",
+                "type": "appbase",
+                "owner": "deathwing",
+                "hive": True,
+                "score": 40                
             }
         ]
         super(NodeList, self).__init__(nodes)
diff --git a/beem/transactionbuilder.py b/beem/transactionbuilder.py
index 9bb676fe..35d34e33 100644
--- a/beem/transactionbuilder.py
+++ b/beem/transactionbuilder.py
@@ -36,17 +36,17 @@ class TransactionBuilder(dict):
         :param dict tx: transaction (Optional). If not set, the new transaction is created.
         :param int expiration: Delay in seconds until transactions are supposed
             to expire *(optional)* (default is 30)
-        :param Steem steem_instance: If not set, shared_blockchain_instance() is used
+        :param Hive/Steem blockchain_instance: If not set, shared_blockchain_instance() is used
 
         .. testcode::
 
            from beem.transactionbuilder import TransactionBuilder
            from beembase.operations import Transfer
-           from beem import Steem
+           from beem import Hive
            wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"
-           stm = Steem(nobroadcast=True, keys={'active': wif})
-           tx = TransactionBuilder(steem_instance=stm)
-           transfer = {"from": "test", "to": "test1", "amount": "1 STEEM", "memo": ""}
+           hive = Hive(nobroadcast=True, keys={'active': wif})
+           tx = TransactionBuilder(blockchain_instance=hive)
+           transfer = {"from": "test", "to": "test1", "amount": "1 HIVE", "memo": ""}
            tx.appendOps(Transfer(transfer))
            tx.appendSigner("test", "active") # or tx.appendWif(wif)
            signed_tx = tx.sign()
@@ -113,8 +113,13 @@ class TransactionBuilder(dict):
     def _unset_require_reconstruction(self):
         self._require_reconstruction = False
 
+    def _get_auth_field(self, permission):
+        return permission
+
     def __repr__(self):
-        return str(self)
+        return "<Transaction num_ops={}, ops={}>".format(
+            len(self.ops), [op.__class__.__name__ for op in self.ops]
+        )
 
     def __str__(self):
         return str(self.json())
@@ -150,26 +155,77 @@ class TransactionBuilder(dict):
             self.ops.append(ops)
         self._set_require_reconstruction()
 
+    def _fetchkeys(self, account, perm, level=0, required_treshold=1):
+
+        # Do not travel recursion more than 2 levels
+        if level > 2:
+            return []
+
+        r = []
+        # Let's go through all *keys* of the account
+        for authority in account[perm]["key_auths"]:
+            try:
+                # Try obtain the private key from wallet
+                wif = self.blockchain.wallet.getPrivateKeyForPublicKey(authority[0])
+            except ValueError:
+                pass
+            except MissingKeyError:
+                pass
+
+            if wif:
+                r.append([wif, authority[1]])
+                # If we found a key for account, we add it
+                # to signing_accounts to be sure we do not resign
+                # another operation with the same account/wif
+                self.signing_accounts.append(account)
+
+            # Test if we reached threshold already
+            if sum([x[1] for x in r]) >= required_treshold:
+                break
+
+        # Let's see if we still need to go through accounts
+        if sum([x[1] for x in r]) < required_treshold:
+            # go one level deeper
+            for authority in account[perm]["account_auths"]:
+                # Let's see if we can find keys for an account in
+                # account_auths
+                # This is recursive with a limit at level 2 (see above)
+                auth_account = Account(authority[0], blockchain_instance=self.blockchain)
+                required_treshold = auth_account[perm]["weight_threshold"]
+                keys = self._fetchkeys(auth_account, perm, level + 1, required_treshold)
+
+                for key in keys:
+                    r.append(key)
+
+                    # Test if we reached threshold already and break
+                    if sum([x[1] for x in r]) >= required_treshold:
+                        break
+
+        return r
+
     def appendSigner(self, account, permission):
         """ Try to obtain the wif key from the wallet by telling which account
             and permission is supposed to sign the transaction
             It is possible to add more than one signer.
+
+            :param str account: account to sign transaction with
+            :param str permission: type of permission, e.g. "active", "owner" etc
         """
         if not self.blockchain.is_connected():
             return
         if permission not in ["active", "owner", "posting"]:
             raise AssertionError("Invalid permission")
         account = Account(account, blockchain_instance=self.blockchain)
-        if permission not in account:
+        auth_field = self._get_auth_field(permission)
+        if auth_field not in account:
             account = Account(account, blockchain_instance=self.blockchain, lazy=False, full=True)
             account.clear_cache()
             account.refresh()
-        if permission not in account:
+        if auth_field not in account:
             account = Account(account, blockchain_instance=self.blockchain)
-        if permission not in account:
+        if auth_field not in account:
             raise AssertionError("Could not access permission")
-
-        required_treshold = account[permission]["weight_threshold"]
+        
         if self._use_ledger:
             if not self._is_constructed() or self._is_require_reconstruction():
                 self.constructTx()
@@ -177,7 +233,7 @@ class TransactionBuilder(dict):
             key_found = False
             if self.path is not None:
                 current_pubkey = self.ledgertx.get_pubkey(self.path)
-                for authority in account[permission]["key_auths"]:
+                for authority in account[auth_field]["key_auths"]:
                     if str(current_pubkey) == authority[0]:
                         key_found = True
                 if permission == "posting" and not key_found:
@@ -197,30 +253,7 @@ class TransactionBuilder(dict):
         if self.blockchain.use_sc2 and self.blockchain.steemconnect is not None:
             self.blockchain.steemconnect.set_username(account["name"], permission)
             return
-
-        def fetchkeys(account, perm, level=0):
-            if level > 2:
-                return []
-            r = []
-            for authority in account[perm]["key_auths"]:
-                try:
-                    wif = self.blockchain.wallet.getPrivateKeyForPublicKey(
-                        authority[0])
-                    if wif:
-                        r.append([wif, authority[1]])
-                except ValueError:
-                    pass
-                except MissingKeyError:
-                    pass
-
-            if sum([x[1] for x in r]) < required_treshold:
-                # go one level deeper
-                for authority in account[perm]["account_auths"]:
-                    auth_account = Account(
-                        authority[0], blockchain_instance=self.blockchain)
-                    r.extend(fetchkeys(auth_account, perm, level + 1))
-
-            return r
+        
 
         if account["name"] not in self.signing_accounts:
             # is the account an instance of public key?
@@ -231,20 +264,20 @@ class TransactionBuilder(dict):
                     )
                 )
             else:
-                if permission not in account:
+                if auth_field not in account:
                     raise AssertionError("Could not access permission")
-                required_treshold = account[permission]["weight_threshold"]
-                keys = fetchkeys(account, permission)
+                required_treshold = account[auth_field]["weight_threshold"]
+                keys = self._fetchkeys(account, permission, required_treshold=required_treshold)
                 # If keys are empty, try again with active key
                 if not keys and permission == "posting":
-                    _keys = fetchkeys(account, "active")
+                    _keys = self._fetchkeys(account, "active", required_treshold=required_treshold)
                     keys.extend(_keys)
                 # If keys are empty, try again with owner key
                 if not keys and permission != "owner":
-                    _keys = fetchkeys(account, "owner")
+                    _keys = self._fetchkeys(account, "owner", required_treshold=required_treshold)
                     keys.extend(_keys)
                 for x in keys:
-                    self.wifs.add(x[0])
+                    self.appendWif(x[0])
 
             self.signing_accounts.append(account["name"])
 
diff --git a/beem/version.py b/beem/version.py
index f8f66182..162cfc92 100644
--- a/beem/version.py
+++ b/beem/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.24.5'
+version = '0.24.6'
diff --git a/beemapi/version.py b/beemapi/version.py
index f8f66182..162cfc92 100644
--- a/beemapi/version.py
+++ b/beemapi/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.24.5'
+version = '0.24.6'
diff --git a/beembase/operationids.py b/beembase/operationids.py
index 70be3c32..6a1fbf7d 100644
--- a/beembase/operationids.py
+++ b/beembase/operationids.py
@@ -66,8 +66,6 @@ ops = [
     'comment_payout_update',
     'return_vesting_delegation',
     'comment_benefactor_reward',
-    'return_vesting_delegation',
-    'comment_benefactor_reward',
     'producer_reward',
     'clear_null_account_balance',
     'proposal_pay',
diff --git a/beembase/version.py b/beembase/version.py
index f8f66182..162cfc92 100644
--- a/beembase/version.py
+++ b/beembase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.24.5'
+version = '0.24.6'
diff --git a/beemgraphenebase/version.py b/beemgraphenebase/version.py
index f8f66182..162cfc92 100644
--- a/beemgraphenebase/version.py
+++ b/beemgraphenebase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.24.5'
+version = '0.24.6'
diff --git a/setup.py b/setup.py
index 4c22d642..1d9a6015 100755
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ except LookupError:
     ascii = codecs.lookup('ascii')
     codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs'))
 
-VERSION = '0.24.5'
+VERSION = '0.24.6'
 
 tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized']
 
-- 
GitLab