You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
8.9 KiB
247 lines
8.9 KiB
from electrum.util import print_error
|
|
|
|
import httplib, urllib
|
|
import socket
|
|
import threading
|
|
import hashlib
|
|
import json
|
|
from urlparse import urlparse, parse_qs
|
|
try:
|
|
import PyQt4
|
|
except Exception:
|
|
sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
|
|
|
|
from PyQt4.QtGui import *
|
|
from PyQt4.QtCore import *
|
|
import PyQt4.QtCore as QtCore
|
|
import PyQt4.QtGui as QtGui
|
|
import aes
|
|
import base64
|
|
|
|
import electrum
|
|
from electrum.plugins import BasePlugin, hook
|
|
from electrum.i18n import _
|
|
|
|
from electrum_gui.qt import HelpButton, EnterButton
|
|
|
|
class Plugin(BasePlugin):
|
|
|
|
target_host = 'labelectrum.herokuapp.com'
|
|
encode_password = None
|
|
|
|
def fullname(self):
|
|
return _('Label Sync')
|
|
|
|
def description(self):
|
|
return '%s\n\n%s%s%s' % (_("This plugin can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server. This code might increase the load of your wallet with a few microseconds as it will sync labels on each startup."), _("To get started visit"), " http://labelectrum.herokuapp.com/ ", _(" to sign up for an account."))
|
|
|
|
def version(self):
|
|
return "0.2.1"
|
|
|
|
def encode(self, message):
|
|
encrypted = electrum.bitcoin.aes_encrypt_with_iv(self.encode_password, self.iv, message.encode('utf8'))
|
|
encoded_message = base64.b64encode(encrypted)
|
|
return encoded_message
|
|
|
|
def decode(self, message):
|
|
decoded_message = electrum.bitcoin.aes_decrypt_with_iv(self.encode_password, self.iv, base64.b64decode(message)).decode('utf8')
|
|
return decoded_message
|
|
|
|
|
|
@hook
|
|
def init_qt(self, gui):
|
|
self.window = gui.main_window
|
|
if not self.auth_token(): # First run, throw plugin settings in your face
|
|
self.load_wallet(self.window.wallet)
|
|
if self.settings_dialog():
|
|
self.set_enabled(True)
|
|
return True
|
|
else:
|
|
self.set_enabled(False)
|
|
return False
|
|
|
|
@hook
|
|
def load_wallet(self, wallet):
|
|
self.wallet = wallet
|
|
mpk = self.wallet.get_master_public_key()
|
|
self.encode_password = hashlib.sha1(mpk).digest().encode('hex')[:32]
|
|
self.iv = hashlib.sha256(self.encode_password).digest()[:16]
|
|
self.wallet_id = hashlib.sha256(mpk).digest().encode('hex')
|
|
|
|
addresses = []
|
|
for account in self.wallet.accounts.values():
|
|
for address in account.get_addresses(0):
|
|
addresses.append(address)
|
|
|
|
self.addresses = addresses
|
|
|
|
if self.auth_token():
|
|
# If there is an auth token we can try to actually start syncing
|
|
threading.Thread(target=self.do_full_pull).start()
|
|
|
|
def auth_token(self):
|
|
return self.config.get("plugin_label_api_key")
|
|
|
|
def is_available(self):
|
|
return True
|
|
|
|
def requires_settings(self):
|
|
return True
|
|
|
|
@hook
|
|
def set_label(self, item,label, changed):
|
|
if self.encode_password is None:
|
|
return
|
|
if not changed:
|
|
return
|
|
try:
|
|
bundle = {"label": {"external_id": self.encode(item), "text": self.encode(label)}}
|
|
params = json.dumps(bundle)
|
|
connection = httplib.HTTPConnection(self.target_host)
|
|
connection.request("POST", ("/api/wallets/%s/labels.json?auth_token=%s" % (self.wallet_id, self.auth_token())), params, {'Content-Type': 'application/json'})
|
|
|
|
response = connection.getresponse()
|
|
if response.reason == httplib.responses[httplib.NOT_FOUND]:
|
|
return
|
|
response = json.loads(response.read())
|
|
except socket.gaierror as e:
|
|
print_error('Error connecting to service: %s ' % e)
|
|
return False
|
|
|
|
def settings_widget(self, window):
|
|
return EnterButton(_('Settings'), self.settings_dialog)
|
|
|
|
def settings_dialog(self):
|
|
def check_for_api_key(api_key):
|
|
if api_key and len(api_key) > 12:
|
|
self.config.set_key("plugin_label_api_key", str(self.auth_token_edit.text()))
|
|
self.upload.setEnabled(True)
|
|
self.download.setEnabled(True)
|
|
self.accept.setEnabled(True)
|
|
else:
|
|
self.upload.setEnabled(False)
|
|
self.download.setEnabled(False)
|
|
self.accept.setEnabled(False)
|
|
|
|
d = QDialog()
|
|
layout = QGridLayout(d)
|
|
layout.addWidget(QLabel("API Key: "),0,0)
|
|
|
|
self.auth_token_edit = QLineEdit(self.auth_token())
|
|
self.auth_token_edit.textChanged.connect(check_for_api_key)
|
|
|
|
layout.addWidget(QLabel("Label sync options: "),2,0)
|
|
layout.addWidget(self.auth_token_edit, 0,1,1,2)
|
|
|
|
decrypt_key_text = QLineEdit(self.encode_password)
|
|
decrypt_key_text.setReadOnly(True)
|
|
layout.addWidget(decrypt_key_text, 1,1)
|
|
layout.addWidget(QLabel("Decryption key: "),1,0)
|
|
layout.addWidget(HelpButton("This key can be used on the LabElectrum website to decrypt your data in case you want to review it online."),1,2)
|
|
|
|
self.upload = QPushButton("Force upload")
|
|
self.upload.clicked.connect(self.full_push)
|
|
layout.addWidget(self.upload, 2,1)
|
|
|
|
self.download = QPushButton("Force download")
|
|
self.download.clicked.connect(self.full_pull)
|
|
layout.addWidget(self.download, 2,2)
|
|
|
|
c = QPushButton(_("Cancel"))
|
|
c.clicked.connect(d.reject)
|
|
|
|
self.accept = QPushButton(_("Done"))
|
|
self.accept.clicked.connect(d.accept)
|
|
|
|
layout.addWidget(c,3,1)
|
|
layout.addWidget(self.accept,3,2)
|
|
|
|
check_for_api_key(self.auth_token())
|
|
|
|
self.window.labelsChanged.connect(self.done_processing)
|
|
|
|
if d.exec_():
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def done_processing(self):
|
|
QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised."))
|
|
|
|
def full_push(self):
|
|
threading.Thread(target=self.do_full_push).start()
|
|
|
|
def full_pull(self):
|
|
threading.Thread(target=self.do_full_pull, args=([True])).start()
|
|
|
|
def do_full_push(self):
|
|
try:
|
|
bundle = {"labels": {}}
|
|
for key, value in self.wallet.labels.iteritems():
|
|
try:
|
|
encoded_key = self.encode(key)
|
|
except:
|
|
print_error('cannot encode', repr(key))
|
|
continue
|
|
try:
|
|
encoded_value = self.encode(value)
|
|
except:
|
|
print_error('cannot encode', repr(value))
|
|
continue
|
|
bundle["labels"][encoded_key] = encoded_value
|
|
|
|
params = json.dumps(bundle)
|
|
connection = httplib.HTTPConnection(self.target_host)
|
|
connection.request("POST", ("/api/wallets/%s/labels/batch.json?auth_token=%s" % (self.wallet_id, self.auth_token())), params, {'Content-Type': 'application/json'})
|
|
|
|
response = connection.getresponse()
|
|
if response.reason == httplib.responses[httplib.NOT_FOUND]:
|
|
print_error('404 error' % e)
|
|
return
|
|
try:
|
|
response = json.loads(response.read())
|
|
except ValueError as e:
|
|
print_error('Error loading labelsync response: %s' % e)
|
|
return False
|
|
|
|
if "error" in response:
|
|
print_error('Error loading labelsync response.')
|
|
return False
|
|
|
|
except socket.gaierror as e:
|
|
print_error('Error connecting to service: %s ' % e)
|
|
return False
|
|
|
|
self.window.labelsChanged.emit()
|
|
|
|
def do_full_pull(self, force = False):
|
|
connection = httplib.HTTPConnection(self.target_host)
|
|
connection.request("GET", ("/api/wallets/%s/labels.json?auth_token=%s" % (self.wallet_id, self.auth_token())),"", {'Content-Type': 'application/json'})
|
|
response = connection.getresponse()
|
|
if response.status != 200:
|
|
print_error("Cannot retrieve labels:", response.status, response.reason)
|
|
return
|
|
response = json.loads(response.read())
|
|
if "error" in response:
|
|
raise BaseException(_("Could not sync labels: %s" % response["error"]))
|
|
|
|
for label in response:
|
|
try:
|
|
key = self.decode(label["external_id"])
|
|
except:
|
|
continue
|
|
try:
|
|
value = self.decode(label["text"])
|
|
except:
|
|
continue
|
|
try:
|
|
json.dumps(key)
|
|
json.dumps(value)
|
|
except:
|
|
print_error('error: no json', key)
|
|
continue
|
|
if force or not self.wallet.labels.get(key):
|
|
self.wallet.labels[key] = value
|
|
self.wallet.storage.put('labels', self.wallet.labels)
|
|
print_error("received %d labels"%len(response))
|
|
self.window.labelsChanged.emit()
|
|
|