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.
- hive_nectar-0.0.2.dist-info/METADATA +182 -0
- hive_nectar-0.0.2.dist-info/RECORD +86 -0
- hive_nectar-0.0.2.dist-info/WHEEL +4 -0
- hive_nectar-0.0.2.dist-info/entry_points.txt +2 -0
- hive_nectar-0.0.2.dist-info/licenses/LICENSE.txt +23 -0
- nectar/__init__.py +32 -0
- nectar/account.py +4371 -0
- nectar/amount.py +475 -0
- nectar/asciichart.py +270 -0
- nectar/asset.py +82 -0
- nectar/block.py +446 -0
- nectar/blockchain.py +1178 -0
- nectar/blockchaininstance.py +2284 -0
- nectar/blockchainobject.py +221 -0
- nectar/blurt.py +563 -0
- nectar/cli.py +6285 -0
- nectar/comment.py +1217 -0
- nectar/community.py +513 -0
- nectar/constants.py +111 -0
- nectar/conveyor.py +309 -0
- nectar/discussions.py +1709 -0
- nectar/exceptions.py +149 -0
- nectar/hive.py +546 -0
- nectar/hivesigner.py +420 -0
- nectar/imageuploader.py +72 -0
- nectar/instance.py +129 -0
- nectar/market.py +1013 -0
- nectar/memo.py +449 -0
- nectar/message.py +357 -0
- nectar/nodelist.py +444 -0
- nectar/price.py +557 -0
- nectar/profile.py +65 -0
- nectar/rc.py +308 -0
- nectar/snapshot.py +726 -0
- nectar/steem.py +582 -0
- nectar/storage.py +53 -0
- nectar/transactionbuilder.py +622 -0
- nectar/utils.py +545 -0
- nectar/version.py +2 -0
- nectar/vote.py +557 -0
- nectar/wallet.py +472 -0
- nectar/witness.py +617 -0
- nectarapi/__init__.py +11 -0
- nectarapi/exceptions.py +123 -0
- nectarapi/graphenerpc.py +589 -0
- nectarapi/node.py +178 -0
- nectarapi/noderpc.py +229 -0
- nectarapi/rpcutils.py +97 -0
- nectarapi/version.py +2 -0
- nectarbase/__init__.py +14 -0
- nectarbase/ledgertransactions.py +75 -0
- nectarbase/memo.py +243 -0
- nectarbase/objects.py +429 -0
- nectarbase/objecttypes.py +22 -0
- nectarbase/operationids.py +102 -0
- nectarbase/operations.py +1297 -0
- nectarbase/signedtransactions.py +48 -0
- nectarbase/transactions.py +11 -0
- nectarbase/version.py +2 -0
- nectargrapheneapi/__init__.py +6 -0
- nectargraphenebase/__init__.py +27 -0
- nectargraphenebase/account.py +846 -0
- nectargraphenebase/aes.py +52 -0
- nectargraphenebase/base58.py +192 -0
- nectargraphenebase/bip32.py +494 -0
- nectargraphenebase/bip38.py +134 -0
- nectargraphenebase/chains.py +149 -0
- nectargraphenebase/dictionary.py +3 -0
- nectargraphenebase/ecdsasig.py +326 -0
- nectargraphenebase/objects.py +123 -0
- nectargraphenebase/objecttypes.py +6 -0
- nectargraphenebase/operationids.py +3 -0
- nectargraphenebase/operations.py +23 -0
- nectargraphenebase/prefix.py +11 -0
- nectargraphenebase/py23.py +38 -0
- nectargraphenebase/signedtransactions.py +201 -0
- nectargraphenebase/types.py +419 -0
- nectargraphenebase/unsignedtransactions.py +283 -0
- nectargraphenebase/version.py +2 -0
- nectarstorage/__init__.py +38 -0
- nectarstorage/base.py +306 -0
- nectarstorage/exceptions.py +16 -0
- nectarstorage/interfaces.py +237 -0
- nectarstorage/masterpassword.py +239 -0
- nectarstorage/ram.py +30 -0
- nectarstorage/sqlite.py +334 -0
nectar/hivesigner.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
from urllib.parse import urlencode, urljoin, urlparse
|
|
6
|
+
except ImportError:
|
|
7
|
+
from urllib import urlencode
|
|
8
|
+
|
|
9
|
+
from urlparse import urljoin
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
from six import PY2
|
|
14
|
+
|
|
15
|
+
from nectar.amount import Amount
|
|
16
|
+
from nectar.exceptions import MissingKeyError, WalletExists
|
|
17
|
+
from nectar.instance import shared_blockchain_instance
|
|
18
|
+
from nectarstorage.exceptions import KeyAlreadyInStoreException
|
|
19
|
+
|
|
20
|
+
log = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HiveSigner(object):
|
|
24
|
+
"""HiveSigner
|
|
25
|
+
|
|
26
|
+
:param str scope: comma separated string with scopes
|
|
27
|
+
login,offline,vote,comment,delete_comment,comment_options,custom_json,claim_reward_balance
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
.. code-block:: python
|
|
31
|
+
|
|
32
|
+
# Run the login_app in examples and login with a account
|
|
33
|
+
from nectar import Steem
|
|
34
|
+
from nectar.HiveSigner import HiveSigner
|
|
35
|
+
from nectar.comment import Comment
|
|
36
|
+
hs = HiveSigner(client_id="nectar.app")
|
|
37
|
+
steem = Steem(HiveSigner=hs)
|
|
38
|
+
steem.wallet.unlock("supersecret-passphrase")
|
|
39
|
+
post = Comment("author/permlink", blockchain_instance=steem)
|
|
40
|
+
post.upvote(voter="test") # replace "test" with your account
|
|
41
|
+
|
|
42
|
+
Examples for creating HiveSigner urls for broadcasting in browser:
|
|
43
|
+
|
|
44
|
+
.. testoutput::
|
|
45
|
+
|
|
46
|
+
from nectar import Steem
|
|
47
|
+
from nectar.account import Account
|
|
48
|
+
from nectar.HiveSigner import HiveSigner
|
|
49
|
+
from pprint import pprint
|
|
50
|
+
steem = Steem(nobroadcast=True, unsigned=True)
|
|
51
|
+
hs = HiveSigner(blockchain_instance=steem)
|
|
52
|
+
acc = Account("test", blockchain_instance=steem)
|
|
53
|
+
pprint(hs.url_from_tx(acc.transfer("test1", 1, "HIVE", "test")))
|
|
54
|
+
|
|
55
|
+
.. testcode::
|
|
56
|
+
|
|
57
|
+
'https://hivesigner.com/sign/transfer?from=test&to=test1&amount=1.000+HIVE&memo=test'
|
|
58
|
+
|
|
59
|
+
.. testoutput::
|
|
60
|
+
|
|
61
|
+
from nectar import Steem
|
|
62
|
+
from nectar.transactionbuilder import TransactionBuilder
|
|
63
|
+
from nectarbase import operations
|
|
64
|
+
from nectar.HiveSigner import HiveSigner
|
|
65
|
+
from pprint import pprint
|
|
66
|
+
stm = Steem(nobroadcast=True, unsigned=True)
|
|
67
|
+
hs = HiveSigner(blockchain_instance=stm)
|
|
68
|
+
tx = TransactionBuilder(blockchain_instance=stm)
|
|
69
|
+
op = operations.Transfer(**{"from": 'test',
|
|
70
|
+
"to": 'test1',
|
|
71
|
+
"amount": '1.000 HIVE',
|
|
72
|
+
"memo": 'test'})
|
|
73
|
+
tx.appendOps(op)
|
|
74
|
+
pprint(hs.url_from_tx(tx.json()))
|
|
75
|
+
|
|
76
|
+
.. testcode::
|
|
77
|
+
|
|
78
|
+
'https://hivesigner.com/sign/transfer?from=test&to=test1&amount=1.000+HIVE&memo=test'
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, blockchain_instance=None, *args, **kwargs):
|
|
83
|
+
if blockchain_instance is None:
|
|
84
|
+
if kwargs.get("steem_instance"):
|
|
85
|
+
blockchain_instance = kwargs["steem_instance"]
|
|
86
|
+
elif kwargs.get("hive_instance"):
|
|
87
|
+
blockchain_instance = kwargs["hive_instance"]
|
|
88
|
+
self.blockchain = blockchain_instance or shared_blockchain_instance()
|
|
89
|
+
self.access_token = None
|
|
90
|
+
config = self.blockchain.config
|
|
91
|
+
self.get_refresh_token = kwargs.get("get_refresh_token", False)
|
|
92
|
+
self.hot_sign_redirect_uri = kwargs.get(
|
|
93
|
+
"hot_sign_redirect_uri", config["hot_sign_redirect_uri"]
|
|
94
|
+
)
|
|
95
|
+
if self.hot_sign_redirect_uri == "":
|
|
96
|
+
self.hot_sign_redirect_uri = None
|
|
97
|
+
self.client_id = kwargs.get("client_id", config["hs_client_id"])
|
|
98
|
+
self.scope = kwargs.get("scope", "login")
|
|
99
|
+
self.hs_oauth_base_url = kwargs.get("hs_oauth_base_url", config["hs_oauth_base_url"])
|
|
100
|
+
self.hs_api_url = kwargs.get("hs_api_url", config["hs_api_url"])
|
|
101
|
+
|
|
102
|
+
if "token" in kwargs and len(kwargs["token"]) > 0:
|
|
103
|
+
from nectarstorage import InRamPlainTokenStore
|
|
104
|
+
|
|
105
|
+
self.store = InRamPlainTokenStore()
|
|
106
|
+
token = kwargs["token"]
|
|
107
|
+
self.set_access_token(token)
|
|
108
|
+
name = self.me()["user"]
|
|
109
|
+
self.setToken({name: token})
|
|
110
|
+
else:
|
|
111
|
+
""" If no keys are provided manually we load the SQLite
|
|
112
|
+
keyStorage
|
|
113
|
+
"""
|
|
114
|
+
from nectarstorage import SqliteEncryptedTokenStore
|
|
115
|
+
|
|
116
|
+
self.store = kwargs.get(
|
|
117
|
+
"token_store",
|
|
118
|
+
SqliteEncryptedTokenStore(config=config, **kwargs),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def headers(self):
|
|
123
|
+
return {"Authorization": self.access_token}
|
|
124
|
+
|
|
125
|
+
def setToken(self, loadtoken):
|
|
126
|
+
"""This method is strictly only for in memory token that are
|
|
127
|
+
passed to Wallet/Steem with the ``token`` argument
|
|
128
|
+
"""
|
|
129
|
+
log.debug("Force setting of private token. Not using the wallet database!")
|
|
130
|
+
if not isinstance(loadtoken, (dict)):
|
|
131
|
+
raise ValueError("token must be a dict variable!")
|
|
132
|
+
for name in loadtoken:
|
|
133
|
+
self.store.add(loadtoken[name], name)
|
|
134
|
+
|
|
135
|
+
def is_encrypted(self):
|
|
136
|
+
"""Is the key store encrypted?"""
|
|
137
|
+
return self.store.is_encrypted()
|
|
138
|
+
|
|
139
|
+
def unlock(self, pwd):
|
|
140
|
+
"""Unlock the wallet database"""
|
|
141
|
+
unlock_ok = None
|
|
142
|
+
if self.store.is_encrypted():
|
|
143
|
+
unlock_ok = self.store.unlock(pwd)
|
|
144
|
+
return unlock_ok
|
|
145
|
+
|
|
146
|
+
def lock(self):
|
|
147
|
+
"""Lock the wallet database"""
|
|
148
|
+
lock_ok = False
|
|
149
|
+
if self.store.is_encrypted():
|
|
150
|
+
lock_ok = self.store.lock()
|
|
151
|
+
return lock_ok
|
|
152
|
+
|
|
153
|
+
def unlocked(self):
|
|
154
|
+
"""Is the wallet database unlocked?"""
|
|
155
|
+
unlocked = True
|
|
156
|
+
if self.store.is_encrypted():
|
|
157
|
+
unlocked = not self.store.locked()
|
|
158
|
+
return unlocked
|
|
159
|
+
|
|
160
|
+
def locked(self):
|
|
161
|
+
"""Is the wallet database locked?"""
|
|
162
|
+
if self.store.is_encrypted():
|
|
163
|
+
return self.store.locked()
|
|
164
|
+
else:
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
def changePassphrase(self, new_pwd):
|
|
168
|
+
"""Change the passphrase for the wallet database"""
|
|
169
|
+
self.store.change_password(new_pwd)
|
|
170
|
+
|
|
171
|
+
def created(self):
|
|
172
|
+
"""Do we have a wallet database already?"""
|
|
173
|
+
if len(self.store.getPublicKeys()):
|
|
174
|
+
# Already keys installed
|
|
175
|
+
return True
|
|
176
|
+
else:
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
def create(self, pwd):
|
|
180
|
+
"""Alias for :func:`newWallet`
|
|
181
|
+
|
|
182
|
+
:param str pwd: Passphrase for the created wallet
|
|
183
|
+
"""
|
|
184
|
+
self.newWallet(pwd)
|
|
185
|
+
|
|
186
|
+
def newWallet(self, pwd):
|
|
187
|
+
"""Create a new wallet database
|
|
188
|
+
|
|
189
|
+
:param str pwd: Passphrase for the created wallet
|
|
190
|
+
"""
|
|
191
|
+
if self.created():
|
|
192
|
+
raise WalletExists("You already have created a wallet!")
|
|
193
|
+
self.store.unlock(pwd)
|
|
194
|
+
|
|
195
|
+
def addToken(self, name, token):
|
|
196
|
+
if str(name) in self.store:
|
|
197
|
+
raise KeyAlreadyInStoreException("Token already in the store")
|
|
198
|
+
self.store.add(str(token), str(name))
|
|
199
|
+
|
|
200
|
+
def getTokenForAccountName(self, name):
|
|
201
|
+
"""Obtain the private token for a given public name
|
|
202
|
+
|
|
203
|
+
:param str name: Public name
|
|
204
|
+
"""
|
|
205
|
+
if str(name) not in self.store:
|
|
206
|
+
raise MissingKeyError
|
|
207
|
+
return self.store.getPrivateKeyForPublicKey(str(name))
|
|
208
|
+
|
|
209
|
+
def removeTokenFromPublicName(self, name):
|
|
210
|
+
"""Remove a token from the wallet database
|
|
211
|
+
|
|
212
|
+
:param str name: token to be removed
|
|
213
|
+
"""
|
|
214
|
+
self.store.delete(str(name))
|
|
215
|
+
|
|
216
|
+
def getPublicNames(self):
|
|
217
|
+
"""Return all installed public token"""
|
|
218
|
+
if self.store is None:
|
|
219
|
+
return
|
|
220
|
+
return self.store.getPublicNames()
|
|
221
|
+
|
|
222
|
+
def get_login_url(self, redirect_uri, **kwargs):
|
|
223
|
+
"""Returns a login url for receiving token from HiveSigner"""
|
|
224
|
+
client_id = kwargs.get("client_id", self.client_id)
|
|
225
|
+
scope = kwargs.get("scope", self.scope)
|
|
226
|
+
get_refresh_token = kwargs.get("get_refresh_token", self.get_refresh_token)
|
|
227
|
+
params = {
|
|
228
|
+
"client_id": client_id,
|
|
229
|
+
"redirect_uri": redirect_uri,
|
|
230
|
+
"scope": scope,
|
|
231
|
+
}
|
|
232
|
+
if get_refresh_token:
|
|
233
|
+
params.update(
|
|
234
|
+
{
|
|
235
|
+
"response_type": "code",
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
if PY2:
|
|
239
|
+
return urljoin(
|
|
240
|
+
self.hs_oauth_base_url, "authorize?" + urlencode(params).replace("%2C", ",")
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
return urljoin(self.hs_oauth_base_url, "authorize?" + urlencode(params, safe=","))
|
|
244
|
+
|
|
245
|
+
def get_access_token(self, code):
|
|
246
|
+
post_data = {
|
|
247
|
+
"grant_type": "authorization_code",
|
|
248
|
+
"code": code,
|
|
249
|
+
"client_id": self.client_id,
|
|
250
|
+
"client_secret": self.getTokenForAccountName(self.client_id),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
r = requests.post(urljoin(self.hs_api_url, "oauth2/token/"), data=post_data)
|
|
254
|
+
|
|
255
|
+
return r.json()
|
|
256
|
+
|
|
257
|
+
def me(self, username=None):
|
|
258
|
+
"""Calls the me function from HiveSigner
|
|
259
|
+
|
|
260
|
+
.. code-block:: python
|
|
261
|
+
|
|
262
|
+
from nectar.HiveSigner import HiveSigner
|
|
263
|
+
hs = HiveSigner()
|
|
264
|
+
hs.steem.wallet.unlock("supersecret-passphrase")
|
|
265
|
+
hs.me(username="test")
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
if username:
|
|
269
|
+
self.set_username(username)
|
|
270
|
+
url = urljoin(self.hs_api_url, "me/")
|
|
271
|
+
r = requests.post(url, headers=self.headers)
|
|
272
|
+
return r.json()
|
|
273
|
+
|
|
274
|
+
def set_access_token(self, access_token):
|
|
275
|
+
"""Is needed for :func:`broadcast` and :func:`me`"""
|
|
276
|
+
self.access_token = access_token
|
|
277
|
+
|
|
278
|
+
def set_username(self, username, permission="posting"):
|
|
279
|
+
"""Set a username for the next :func:`broadcast` or :func:`me` operation.
|
|
280
|
+
The necessary token is fetched from the wallet
|
|
281
|
+
"""
|
|
282
|
+
if permission != "posting":
|
|
283
|
+
self.access_token = None
|
|
284
|
+
return
|
|
285
|
+
self.access_token = self.getTokenForAccountName(username)
|
|
286
|
+
|
|
287
|
+
def broadcast(self, operations, username=None):
|
|
288
|
+
"""Broadcast an operation
|
|
289
|
+
|
|
290
|
+
Sample operations:
|
|
291
|
+
|
|
292
|
+
.. code-block:: js
|
|
293
|
+
|
|
294
|
+
[
|
|
295
|
+
[
|
|
296
|
+
'vote', {
|
|
297
|
+
'voter': 'gandalf',
|
|
298
|
+
'author': 'gtg',
|
|
299
|
+
'permlink': 'steem-pressure-4-need-for-speed',
|
|
300
|
+
'weight': 10000
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
"""
|
|
306
|
+
url = urljoin(self.hs_api_url, "broadcast/")
|
|
307
|
+
data = {
|
|
308
|
+
"operations": operations,
|
|
309
|
+
}
|
|
310
|
+
if username:
|
|
311
|
+
self.set_username(username)
|
|
312
|
+
headers = self.headers.copy()
|
|
313
|
+
headers.update(
|
|
314
|
+
{
|
|
315
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
316
|
+
"Accept": "application/json",
|
|
317
|
+
}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
r = requests.post(url, headers=headers, data=json.dumps(data))
|
|
321
|
+
try:
|
|
322
|
+
return r.json()
|
|
323
|
+
except ValueError:
|
|
324
|
+
return r.content
|
|
325
|
+
|
|
326
|
+
def refresh_access_token(self, code, scope):
|
|
327
|
+
post_data = {
|
|
328
|
+
"grant_type": "refresh_token",
|
|
329
|
+
"refresh_token": code,
|
|
330
|
+
"client_id": self.client_id,
|
|
331
|
+
"client_secret": self.getTokenForAccountName(self.client_id),
|
|
332
|
+
"scope": scope,
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
r = requests.post(
|
|
336
|
+
urljoin(self.hs_api_url, "oauth2/token/"),
|
|
337
|
+
data=post_data,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
return r.json()
|
|
341
|
+
|
|
342
|
+
def revoke_token(self, access_token):
|
|
343
|
+
post_data = {
|
|
344
|
+
"access_token": access_token,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
r = requests.post(urljoin(self.hs_api_url, "oauth2/token/revoke"), data=post_data)
|
|
348
|
+
|
|
349
|
+
return r.json()
|
|
350
|
+
|
|
351
|
+
def update_user_metadata(self, metadata):
|
|
352
|
+
put_data = {
|
|
353
|
+
"user_metadata": metadata,
|
|
354
|
+
}
|
|
355
|
+
r = requests.put(urljoin(self.hs_api_url, "me/"), data=put_data, headers=self.headers)
|
|
356
|
+
|
|
357
|
+
return r.json()
|
|
358
|
+
|
|
359
|
+
def url_from_tx(self, tx, redirect_uri=None):
|
|
360
|
+
"""Creates a link for broadcasting an operation
|
|
361
|
+
|
|
362
|
+
:param dict tx: includes the operation, which should be broadcast
|
|
363
|
+
:param str redirect_uri: Redirects to this uri, when set
|
|
364
|
+
"""
|
|
365
|
+
if not isinstance(tx, dict):
|
|
366
|
+
tx = tx.json()
|
|
367
|
+
if "operations" not in tx or not tx["operations"]:
|
|
368
|
+
return ""
|
|
369
|
+
urls = []
|
|
370
|
+
operations = tx["operations"]
|
|
371
|
+
for op in operations:
|
|
372
|
+
operation = op[0]
|
|
373
|
+
params = op[1]
|
|
374
|
+
for key in params:
|
|
375
|
+
value = params[key]
|
|
376
|
+
if isinstance(value, list) and len(value) == 3:
|
|
377
|
+
try:
|
|
378
|
+
amount = Amount(value, blockchain_instance=self.blockchain)
|
|
379
|
+
params[key] = str(amount)
|
|
380
|
+
except:
|
|
381
|
+
amount = None
|
|
382
|
+
elif isinstance(value, bool):
|
|
383
|
+
if value:
|
|
384
|
+
params[key] = 1
|
|
385
|
+
else:
|
|
386
|
+
params[key] = 0
|
|
387
|
+
urls.append(self.create_hot_sign_url(operation, params, redirect_uri=redirect_uri))
|
|
388
|
+
if len(urls) == 1:
|
|
389
|
+
return urls[0]
|
|
390
|
+
else:
|
|
391
|
+
return urls
|
|
392
|
+
|
|
393
|
+
def create_hot_sign_url(self, operation, params, redirect_uri=None):
|
|
394
|
+
"""Creates a link for broadcasting an operation
|
|
395
|
+
|
|
396
|
+
:param str operation: operation name (e.g.: vote)
|
|
397
|
+
:param dict params: operation dict params
|
|
398
|
+
:param str redirect_uri: Redirects to this uri, when set
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
if not isinstance(operation, str) or not isinstance(params, dict):
|
|
402
|
+
raise ValueError("Invalid Request.")
|
|
403
|
+
|
|
404
|
+
base_url = self.hs_api_url.replace("https://api.", "https://").replace("/api", "")
|
|
405
|
+
if redirect_uri == "":
|
|
406
|
+
redirect_uri = None
|
|
407
|
+
|
|
408
|
+
if redirect_uri is None and self.hot_sign_redirect_uri is not None:
|
|
409
|
+
redirect_uri = self.hot_sign_redirect_uri
|
|
410
|
+
if redirect_uri is not None:
|
|
411
|
+
params.update({"redirect_uri": redirect_uri})
|
|
412
|
+
|
|
413
|
+
for key in params:
|
|
414
|
+
if isinstance(params[key], list):
|
|
415
|
+
params[key] = json.dumps(params[key])
|
|
416
|
+
params = urlencode(params)
|
|
417
|
+
url = urljoin(base_url, "sign/%s" % operation)
|
|
418
|
+
url += "?" + params
|
|
419
|
+
|
|
420
|
+
return url
|
nectar/imageuploader.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import io
|
|
3
|
+
from binascii import hexlify
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from nectar.account import Account
|
|
8
|
+
from nectargraphenebase.ecdsasig import sign_message
|
|
9
|
+
from nectargraphenebase.py23 import py23_bytes, string_types
|
|
10
|
+
|
|
11
|
+
from .instance import shared_blockchain_instance
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ImageUploader(object):
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
base_url="https://steemitimages.com",
|
|
18
|
+
challenge="ImageSigningChallenge",
|
|
19
|
+
blockchain_instance=None,
|
|
20
|
+
**kwargs,
|
|
21
|
+
):
|
|
22
|
+
self.challenge = challenge
|
|
23
|
+
self.base_url = base_url
|
|
24
|
+
if blockchain_instance is None:
|
|
25
|
+
if kwargs.get("steem_instance"):
|
|
26
|
+
blockchain_instance = kwargs["steem_instance"]
|
|
27
|
+
elif kwargs.get("hive_instance"):
|
|
28
|
+
blockchain_instance = kwargs["hive_instance"]
|
|
29
|
+
self.steem = blockchain_instance or shared_blockchain_instance()
|
|
30
|
+
if self.steem.is_hive and base_url == "https://steemitimages.com":
|
|
31
|
+
self.base_url = "https://images.hive.blog"
|
|
32
|
+
|
|
33
|
+
def upload(self, image, account, image_name=None):
|
|
34
|
+
"""Uploads an image
|
|
35
|
+
|
|
36
|
+
:param image: path to the image or image in bytes representation which should be uploaded
|
|
37
|
+
:type image: str, bytes
|
|
38
|
+
:param str account: Account which is used to upload. A posting key must be provided.
|
|
39
|
+
:param str image_name: optional
|
|
40
|
+
|
|
41
|
+
.. code-block:: python
|
|
42
|
+
|
|
43
|
+
from nectar import Steem
|
|
44
|
+
from nectar.imageuploader import ImageUploader
|
|
45
|
+
stm = Steem(keys=["5xxx"]) # private posting key
|
|
46
|
+
iu = ImageUploader(blockchain_instance=stm)
|
|
47
|
+
iu.upload("path/to/image.png", "account_name") # "private posting key belongs to account_name
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
account = Account(account, blockchain_instance=self.steem)
|
|
51
|
+
if "posting" not in account:
|
|
52
|
+
account.refresh()
|
|
53
|
+
if "posting" not in account:
|
|
54
|
+
raise AssertionError("Could not access posting permission")
|
|
55
|
+
for authority in account["posting"]["key_auths"]:
|
|
56
|
+
posting_wif = self.steem.wallet.getPrivateKeyForPublicKey(authority[0])
|
|
57
|
+
|
|
58
|
+
if isinstance(image, string_types):
|
|
59
|
+
image_data = open(image, "rb").read()
|
|
60
|
+
elif isinstance(image, io.BytesIO):
|
|
61
|
+
image_data = image.read()
|
|
62
|
+
else:
|
|
63
|
+
image_data = image
|
|
64
|
+
|
|
65
|
+
message = py23_bytes(self.challenge, "ascii") + image_data
|
|
66
|
+
signature = sign_message(message, posting_wif)
|
|
67
|
+
signature_in_hex = hexlify(signature).decode("ascii")
|
|
68
|
+
|
|
69
|
+
files = {image_name or "image": image_data}
|
|
70
|
+
url = "%s/%s/%s" % (self.base_url, account["name"], signature_in_hex)
|
|
71
|
+
r = requests.post(url, files=files)
|
|
72
|
+
return r.json()
|
nectar/instance.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import nectar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SharedInstance(object):
|
|
6
|
+
"""Singelton for the Steem Instance"""
|
|
7
|
+
|
|
8
|
+
instance = None
|
|
9
|
+
config = {}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def shared_blockchain_instance():
|
|
13
|
+
"""This method will initialize ``SharedInstance.instance`` and return it.
|
|
14
|
+
The purpose of this method is to have offer single default
|
|
15
|
+
steem instance that can be reused by multiple classes.
|
|
16
|
+
|
|
17
|
+
.. code-block:: python
|
|
18
|
+
|
|
19
|
+
from nectar.account import Account
|
|
20
|
+
from nectar.instance import shared_steem_instance
|
|
21
|
+
|
|
22
|
+
account = Account("test")
|
|
23
|
+
# is equivalent with
|
|
24
|
+
account = Account("test", blockchain_instance=shared_steem_instance())
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
if not SharedInstance.instance:
|
|
28
|
+
clear_cache()
|
|
29
|
+
from nectar.storage import get_default_config_store
|
|
30
|
+
|
|
31
|
+
default_chain = get_default_config_store()["default_chain"]
|
|
32
|
+
if default_chain == "steem":
|
|
33
|
+
SharedInstance.instance = nectar.Steem(**SharedInstance.config)
|
|
34
|
+
else:
|
|
35
|
+
SharedInstance.instance = nectar.Hive(**SharedInstance.config)
|
|
36
|
+
return SharedInstance.instance
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def set_shared_blockchain_instance(blockchain_instance):
|
|
40
|
+
"""This method allows us to override default steem instance for all users of
|
|
41
|
+
``SharedInstance.instance``.
|
|
42
|
+
|
|
43
|
+
:param Steem blockchain_instance: Steem instance
|
|
44
|
+
"""
|
|
45
|
+
clear_cache()
|
|
46
|
+
SharedInstance.instance = blockchain_instance
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def shared_steem_instance():
|
|
50
|
+
"""This method will initialize ``SharedInstance.instance`` and return it.
|
|
51
|
+
The purpose of this method is to have offer single default
|
|
52
|
+
steem instance that can be reused by multiple classes.
|
|
53
|
+
|
|
54
|
+
.. code-block:: python
|
|
55
|
+
|
|
56
|
+
from nectar.account import Account
|
|
57
|
+
from nectar.instance import shared_steem_instance
|
|
58
|
+
|
|
59
|
+
account = Account("test")
|
|
60
|
+
# is equivalent with
|
|
61
|
+
account = Account("test", blockchain_instance=shared_steem_instance())
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
if not SharedInstance.instance:
|
|
65
|
+
clear_cache()
|
|
66
|
+
SharedInstance.instance = nectar.Steem(**SharedInstance.config)
|
|
67
|
+
return SharedInstance.instance
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def set_shared_steem_instance(steem_instance):
|
|
71
|
+
"""This method allows us to override default steem instance for all users of
|
|
72
|
+
``SharedInstance.instance``.
|
|
73
|
+
|
|
74
|
+
:param Steem steem_instance: Steem instance
|
|
75
|
+
"""
|
|
76
|
+
clear_cache()
|
|
77
|
+
SharedInstance.instance = steem_instance
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def shared_hive_instance():
|
|
81
|
+
"""This method will initialize ``SharedInstance.instance`` and return it.
|
|
82
|
+
The purpose of this method is to have offer single default
|
|
83
|
+
steem instance that can be reused by multiple classes.
|
|
84
|
+
|
|
85
|
+
.. code-block:: python
|
|
86
|
+
|
|
87
|
+
from nectar.account import Account
|
|
88
|
+
from nectar.instance import shared_hive_instance
|
|
89
|
+
|
|
90
|
+
account = Account("test")
|
|
91
|
+
# is equivalent with
|
|
92
|
+
account = Account("test", blockchain_instance=shared_hive_instance())
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
if not SharedInstance.instance:
|
|
96
|
+
clear_cache()
|
|
97
|
+
SharedInstance.instance = nectar.Hive(**SharedInstance.config)
|
|
98
|
+
return SharedInstance.instance
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def set_shared_hive_instance(hive_instance):
|
|
102
|
+
"""This method allows us to override default steem instance for all users of
|
|
103
|
+
``SharedInstance.instance``.
|
|
104
|
+
|
|
105
|
+
:param Hive hive_instance: Hive instance
|
|
106
|
+
"""
|
|
107
|
+
clear_cache()
|
|
108
|
+
SharedInstance.instance = hive_instance
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def clear_cache():
|
|
112
|
+
"""Clear Caches"""
|
|
113
|
+
from .blockchainobject import BlockchainObject
|
|
114
|
+
|
|
115
|
+
BlockchainObject.clear_cache()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def set_shared_config(config):
|
|
119
|
+
"""This allows to set a config that will be used when calling
|
|
120
|
+
``shared_steem_instance`` and allows to define the configuration
|
|
121
|
+
without requiring to actually create an instance
|
|
122
|
+
"""
|
|
123
|
+
if not isinstance(config, dict):
|
|
124
|
+
raise AssertionError()
|
|
125
|
+
SharedInstance.config.update(config)
|
|
126
|
+
# if one is already set, delete
|
|
127
|
+
if SharedInstance.instance:
|
|
128
|
+
clear_cache()
|
|
129
|
+
SharedInstance.instance = None
|