From 97711e5e7fe9bda99bacf789374ed3dcc13ea493 Mon Sep 17 00:00:00 2001 From: Holger Nahrstaedt Date: Tue, 29 May 2018 11:46:44 +0200 Subject: [PATCH] Several improvements and fixes Account * Example added for transfer Nodelist * Doc added Steem * steemconnect integration improved and use_sc2 added * Docu improved SteemConnect * compatible with py2.7 * several improvements * Examles added Transactionbuilder * Integration of steemconnect improved Wallet * fix bug SteemNodeRPC * fix old and removed parameter n_urls Doc * Fix bug in tutorial page --- beem/account.py | 12 ++++++ beem/nodelist.py | 15 +++++-- beem/steem.py | 18 ++++++++- beem/steemconnect.py | 80 +++++++++++++++++++++++++++++++------- beem/transactionbuilder.py | 18 +++++---- beem/wallet.py | 5 ++- beemapi/steemnoderpc.py | 8 ++-- docs/tutorials.rst | 2 +- 8 files changed, 125 insertions(+), 33 deletions(-) diff --git a/beem/account.py b/beem/account.py index b4ca42fa..b6373da0 100644 --- a/beem/account.py +++ b/beem/account.py @@ -1549,6 +1549,18 @@ def transfer(self, to, amount, asset, memo="", account=None, **kwargs): messaging :param str account: (optional) the source account for the transfer if not ``default_account`` + + + transfer example: + .. code-block:: python + + from beem.account import Account + from beem import Steem + active_wif = "5xxxx" + stm = Steem(keys=[active_wif]) + acc = Account("test", steem_instance=stm) + acc.transfer("test1", 1, "STEEM", "test") + """ if not account: diff --git a/beem/nodelist.py b/beem/nodelist.py index 22cb63d0..37ce3a24 100644 --- a/beem/nodelist.py +++ b/beem/nodelist.py @@ -10,6 +10,15 @@ class NodeList(list): + """ Returns a node list + + .. code-block:: python + + from beem.nodelist import NodeList + n = NodeList() + nodes_urls = n.get_nodes() + + """ def __init__(self): nodes = [ { @@ -59,7 +68,7 @@ def __init__(self): "version": "0.19.3", "type": "normal", "owner": "ausbitbank", - "score": 175 + "score": 125 }, { "url": "https://rpc.steemviz.com", @@ -171,14 +180,14 @@ def __init__(self): "version": "0.19.3", "type": "normal", "owner": "followbtcnews", - "score": 10 + "score": 120 }, { "url": "https://steemd.minnowsupportproject.org", "version": "0.19.3", "type": "normal", "owner": "followbtcnews", - "score": 10 + "score": 90 }, { "url": "https://rpc.curiesteem.com", diff --git a/beem/steem.py b/beem/steem.py index b0d86d2f..46f0d02f 100644 --- a/beem/steem.py +++ b/beem/steem.py @@ -63,6 +63,9 @@ class Steem(object): NumRetriesReached is raised. Disabled for -1. (default is -1) :param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5) :param int timeout: Timeout setting for https nodes (default is 60) + :param bool use_sc2: When True, a steemconnect object is created. Can be used for + broadcast posting op or creating hot_links (default is False) + :param SteemConnect steemconnect: A SteemConnect object can be set manually, set use_sc2 to True Three wallet operation modes are possible: @@ -138,6 +141,10 @@ def __init__(self, NumRetriesReached is raised. Disabled for -1. (default is -1) :param int num_retries_call: Repeat num_retries_call times a rpc call on node error (default is 5) :param int timeout: Timeout setting for https nodes (default is 60) + :param bool use_sc2: When True, a steemconnect object is created. Can be used for broadcast + posting op or creating hot_links (default is False) + :param SteemConnect steemconnect: A SteemConnect object can be set manually, set use_sc2 to True + """ self.rpc = None @@ -149,8 +156,7 @@ def __init__(self, self.expiration = int(kwargs.get("expiration", 30)) self.bundle = bool(kwargs.get("bundle", False)) self.steemconnect = kwargs.get("steemconnect", None) - if self.steemconnect is not None and not isinstance(self.steemconnect, SteemConnect): - raise ValueError("steemconnect musst be SteemConnect object") + self.use_sc2 = bool(kwargs.get("use_sc2", False)) self.blocking = kwargs.get("blocking", False) # Store config for access through other Classes @@ -174,6 +180,14 @@ def __init__(self, self.wallet = Wallet(steem_instance=self, **kwargs) + # set steemconnect + if self.steemconnect is not None and not isinstance(self.steemconnect, SteemConnect): + raise ValueError("steemconnect musst be SteemConnect object") + if self.steemconnect is None and self.use_sc2: + self.steemconnect = SteemConnect(steem_instance=self, **kwargs) + elif self.steemconnect is not None and not self.use_sc2: + self.use_sc2 = True + # ------------------------------------------------------------------------- # Basic Calls # ------------------------------------------------------------------------- diff --git a/beem/steemconnect.py b/beem/steemconnect.py index 8c0aef31..c520f2fa 100644 --- a/beem/steemconnect.py +++ b/beem/steemconnect.py @@ -5,7 +5,10 @@ from __future__ import unicode_literals from builtins import str import json -import urllib.parse +try: + from urllib.parse import urlparse, urlencode, urljoin +except ImportError: + from urlparse import urlparse, urlencode, urljoin import requests from .storage import configStorage as config from beem.instance import shared_steem_instance @@ -27,15 +30,43 @@ class SteemConnect(object): sc2 = SteemConnect(client_id="beem.app") steem = Steem(steemconnect=sc2) steem.wallet.unlock("supersecret-passphrase") - post = Comment("author/permlink", steem_instance=stm) + post = Comment("author/permlink", steem_instance=steem) post.upvote(voter="test") # replace "test" with your account + hot_sign example: + .. code-block:: python + + from beem import Steem + from beem.steemconnect import SteemConnect + from beem.comment import Comment + sc2 = SteemConnect(client_id="beem.app", hot_sign=True) + steem = Steem(steemconnect=sc2) + post = Comment("author/permlink", steem_instance=steem) + post.upvote(voter="test") # replace "test" with your account + + transfer example: + .. testoutput:: + + from beem import Steem + from beem.steemconnect import SteemConnect + from beem.account import Account + from pprint import pprint + steem = Steem(use_sc2=True, hot_sign=True) + acc = Account("test", steem_instance=steem) + pprint(acc.transfer("test1", 1, "STEEM", "test")) + + .. testcode:: + + 'https://v2.steemconnect.com/sign/transfer?from=test&to=test1&amount=1.000+STEEM&memo=test' + """ def __init__(self, steem_instance=None, *args, **kwargs): self.steem = steem_instance or shared_steem_instance() self.access_token = None self.get_refresh_token = kwargs.get("get_refresh_token", False) + self.hot_sign = kwargs.get("hot_sign", False) + self.hot_sign_redirect_uri = kwargs.get("hot_sign_redirect_uri", None) self.client_id = kwargs.get("client_id", config["sc2_client_id"]) self.scope = kwargs.get("scope", config["sc2_scope"]) self.oauth_base_url = kwargs.get("oauth_base_url", config["oauth_base_url"]) @@ -56,9 +87,9 @@ def get_login_url(self, redirect_uri): "response_type": "code", }) - return urllib.parse.urljoin( + return urljoin( self.oauth_base_url, - "authorize?" + urllib.parse.urlencode(params, safe=",")) + "authorize?" + urlencode(params, safe=",")) def get_access_token(self, code): post_data = { @@ -69,7 +100,7 @@ def get_access_token(self, code): } r = requests.post( - urllib.parse.urljoin(self.sc2_api_url, "oauth2/token/"), + urljoin(self.sc2_api_url, "oauth2/token/"), data=post_data ) @@ -78,7 +109,7 @@ def get_access_token(self, code): def me(self, username=None): if username: self.set_username(username) - url = urllib.parse.urljoin(self.sc2_api_url, "me/") + url = urljoin(self.sc2_api_url, "me/") r = requests.post(url, headers=self.headers) return r.json() @@ -87,12 +118,27 @@ def set_access_token(self, access_token): """ self.access_token = access_token - def set_username(self, username): + def set_username(self, username, permission): """ Set a username for the next broadcast() or me operation() The necessary token is fetched from the wallet """ + if self.hot_sign or permission != "posting": + self.access_token = None + return self.access_token = self.steem.wallet.getTokenForAccountName(username) + def boadcast_or_hot_sign(self, operations, username=None): + if self.hot_sign or self.access_token is None: + urls = [] + for op in operations: + urls.append(self.create_hot_sign_url(op[0], op[1])) + if len(urls) == 1: + return urls[0] + else: + return urls + else: + return self.broadcast(operations, username=None) + def broadcast(self, operations, username=None): """ Broadcast a operations @@ -112,7 +158,7 @@ def broadcast(self, operations, username=None): ] """ - url = urllib.parse.urljoin(self.sc2_api_url, "broadcast/") + url = urljoin(self.sc2_api_url, "broadcast/") data = { "operations": operations, } @@ -140,7 +186,7 @@ def refresh_access_token(self, code, scope): } r = requests.post( - urllib.parse.urljoin(self.sc2_api_url, "oauth2/token/"), + urljoin(self.sc2_api_url, "oauth2/token/"), data=post_data, ) @@ -152,7 +198,7 @@ def revoke_token(self, access_token): } r = requests.post( - urllib.parse.urljoin(self.sc2_api_url, "oauth2/token/revoke"), + urljoin(self.sc2_api_url, "oauth2/token/revoke"), data=post_data ) @@ -163,12 +209,12 @@ def update_user_metadata(self, metadata): "user_metadata": metadata, } r = requests.put( - urllib.parse.urljoin(self.sc2_api_url, "me/"), + urljoin(self.sc2_api_url, "me/"), data=put_data, headers=self.headers) return r.json() - def hot_sign(self, operation, params, redirect_uri=None): + def create_hot_sign_url(self, operation, params, redirect_uri=None): """ Creates a link for broadcasting a operation :param str operation: operation name (e.g.: vote) @@ -180,11 +226,17 @@ def hot_sign(self, operation, params, redirect_uri=None): base_url = self.sc2_api_url.replace("/api", "") + if not redirect_uri and self.hot_sign_redirect_uri: + redirect_uri = self.hot_sign_redirect_uri if redirect_uri: params.update({"redirect_uri": redirect_uri}) - params = urllib.parse.urlencode(params) - url = urllib.parse.urljoin(base_url, "sign/%s" % operation) + params = urlencode(params) + url = urljoin(base_url, "sign/%s" % operation) url += "?" + params return url + + +# https://steemconnect.com/authorize/@steemauto/?redirect_uri=https://steemauto.com/dash.php +# https://steemconnect.com/oauth2/authorize?client_id=steem.app&redirect_uri=https://steemauto.com/callback.php&scope=login diff --git a/beem/transactionbuilder.py b/beem/transactionbuilder.py index 0104613e..adc336ae 100644 --- a/beem/transactionbuilder.py +++ b/beem/transactionbuilder.py @@ -149,11 +149,15 @@ def appendSigner(self, account, permission): required_treshold = account[permission]["weight_threshold"] - if self.steem.wallet.locked(): - raise WalletLocked() - if self.steem.steemconnect is not None: - self.steem.steemconnect.set_username(account["name"]) + if self.steem.use_sc2: + if not self.steem.steemconnect.hot_sign and permission == "posting": + if self.steem.wallet.locked(): + raise WalletLocked() + self.steem.steemconnect.set_username(account["name"], permission) return + else: + if self.steem.wallet.locked(): + raise WalletLocked() def fetchkeys(account, perm, level=0): if level > 2: @@ -266,7 +270,7 @@ def sign(self, reconstruct_tx=True): self.constructTx() if "operations" not in self or not self["operations"]: return - if self.steem.steemconnect is not None: + if self.steem.use_sc2: return # We need to set the default prefix, otherwise pubkeys are # presented wrongly! @@ -368,8 +372,8 @@ def broadcast(self, max_block_age=-1): return ret # Broadcast try: - if self.steem.steemconnect is not None: - ret = self.steem.steemconnect.broadcast(self["operations"]) + if self.steem.use_sc2: + ret = self.steem.steemconnect.boadcast_or_hot_sign(self["operations"]) elif self.steem.blocking: ret = self.steem.rpc.broadcast_transaction_synchronous( args, api="network_broadcast") diff --git a/beem/wallet.py b/beem/wallet.py index f4ac5f8c..011fc176 100644 --- a/beem/wallet.py +++ b/beem/wallet.py @@ -117,7 +117,7 @@ def __init__(self, steem_instance=None, *args, **kwargs): # Compatibility after name change from wif->keys if "wif" in kwargs and "keys" not in kwargs: kwargs["keys"] = kwargs["wif"] - + master_password_set = False if "keys" in kwargs: self.setKeys(kwargs["keys"]) else: @@ -127,6 +127,7 @@ def __init__(self, steem_instance=None, *args, **kwargs): from .storage import (keyStorage, MasterPassword) self.MasterPassword = MasterPassword + master_password_set = True self.keyStorage = keyStorage if "token" in kwargs: @@ -136,7 +137,7 @@ def __init__(self, steem_instance=None, *args, **kwargs): keyStorage """ from .storage import tokenStorage - if MasterPassword is None: + if not master_password_set: from .storage import MasterPassword self.MasterPassword = MasterPassword self.tokenStorage = tokenStorage diff --git a/beemapi/steemnoderpc.py b/beemapi/steemnoderpc.py index 575cdae5..2cca3e1a 100644 --- a/beemapi/steemnoderpc.py +++ b/beemapi/steemnoderpc.py @@ -61,7 +61,7 @@ def rpcexec(self, payload): try: # Forward call to GrapheneWebsocketRPC and catch+evaluate errors reply = super(SteemNodeRPC, self).rpcexec(payload) - if self.next_node_on_empty_reply and not bool(reply) and self.n_urls > 1: + if self.next_node_on_empty_reply and not bool(reply) and self.nodes.working_nodes_count > 1: self._retry_on_next_node("Empty Reply") doRetry = True self.next_node_on_empty_reply = True @@ -74,7 +74,7 @@ def rpcexec(self, payload): self.nodes.sleep_and_check_retries(str(msg), call_retry=True) doRetry = True except exceptions.CallRetriesReached: - if self.n_urls > 1: + if self.nodes.working_nodes_count > 1: self._retry_on_next_node(msg) doRetry = True else: @@ -84,7 +84,7 @@ def rpcexec(self, payload): doRetry = self._check_error_message(e, self.error_cnt_call) except exceptions.CallRetriesReached: msg = exceptions.decodeRPCErrorMsg(e).strip() - if self.n_urls > 1: + if self.nodes.working_nodes_count > 1: self._retry_on_next_node(msg) doRetry = True else: @@ -126,7 +126,7 @@ def _check_error_message(self, e, cnt): self.nodes.sleep_and_check_retries(str(msg), call_retry=True) doRetry = True elif re.search("!check_max_block_age", str(e)): - if self.n_urls == 1: + if self.nodes.working_nodes_count == 1: raise exceptions.UnhandledRPCError(msg) self.nodes.increase_error_cnt() self.nodes.sleep_and_check_retries(str(msg), sleep=False) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index 499b1114..e7f5b962 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -45,7 +45,7 @@ one comment operation from each sender. # Upvote post with 25% c.upvote(25, voter=account) - pprint(testnet.broadcast()) + pprint(stm.broadcast()) Use nobroadcast for testing