electrum-bitcoinprivate/plugins/email_requests/qt.py

263 lines
9.1 KiB
Python
Raw Normal View History

2014-11-11 02:08:25 -08:00
#!/usr/bin/env python
#
2019-12-24 03:21:35 -08:00
# Electrum - Lightweight bitcoinprivate Client
2014-11-11 02:08:25 -08:00
# Copyright (C) 2015 Thomas Voegtlin
#
2016-02-23 02:36:42 -08:00
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
2014-11-11 02:08:25 -08:00
#
2016-02-23 02:36:42 -08:00
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
2014-11-11 02:08:25 -08:00
#
2016-02-23 02:36:42 -08:00
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import random
2014-11-11 02:08:25 -08:00
import time
import threading
import base64
from functools import partial
import traceback
import sys
2014-11-11 02:08:25 -08:00
import smtplib
import imaplib
import email
2017-02-05 02:38:44 -08:00
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.encoders import encode_base64
2014-11-11 02:08:25 -08:00
2017-09-22 20:54:38 -07:00
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
QInputDialog)
2014-11-11 02:08:25 -08:00
2019-12-24 03:21:35 -08:00
from electrum_bitcoinprivate.plugins import BasePlugin, hook
from electrum_bitcoinprivate.paymentrequest import PaymentRequest
from electrum_bitcoinprivate.i18n import _
from electrum_bitcoinprivate.util import PrintError
from electrum_bitcoinprivate_gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
WindowModalDialog, get_parent_main_window)
2014-11-11 02:08:25 -08:00
class Processor(threading.Thread, PrintError):
2014-11-11 02:08:25 -08:00
polling_interval = 5*60
def __init__(self, imap_server, username, password, callback):
threading.Thread.__init__(self)
self.daemon = True
self.username = username
self.password = password
self.imap_server = imap_server
self.on_receive = callback
self.M = None
self.connect_wait = 100 # ms, between failed connection attempts
2014-11-11 02:08:25 -08:00
def poll(self):
try:
self.M.select()
except:
return
typ, data = self.M.search(None, 'ALL')
for num in str(data[0], 'utf8').split():
2014-11-11 02:08:25 -08:00
typ, msg_data = self.M.fetch(num, '(RFC822)')
msg = email.message_from_string(str(msg_data[0][1], 'utf8'))
2014-11-11 02:08:25 -08:00
p = msg.get_payload()
if not msg.is_multipart():
p = [p]
continue
for item in p:
2019-12-24 03:21:35 -08:00
if item.get_content_type() == "application/bitcoinprivate-paymentrequest":
2014-11-11 02:08:25 -08:00
pr_str = item.get_payload()
pr_str = base64.b64decode(pr_str)
self.on_receive(pr_str)
def run(self):
while True:
try:
self.M = imaplib.IMAP4_SSL(self.imap_server)
self.M.login(self.username, self.password)
except BaseException as e:
self.print_error(e)
self.connect_wait *= 2
# Reconnect when host changes
while self.M and self.M.host == self.imap_server:
self.poll()
time.sleep(self.polling_interval)
time.sleep(random.randint(0, self.connect_wait))
2014-11-11 02:08:25 -08:00
def send(self, recipient, message, payment_request):
msg = MIMEMultipart()
msg['Subject'] = message
msg['To'] = recipient
msg['From'] = self.username
2019-12-24 03:21:35 -08:00
part = MIMEBase('application', "bitcoinprivate-paymentrequest")
2014-11-11 02:08:25 -08:00
part.set_payload(payment_request)
2017-02-05 02:38:44 -08:00
encode_base64(part)
2019-12-24 03:21:35 -08:00
part.add_header('Content-Disposition', 'attachment; filename="payreq.btcp"')
2014-11-11 02:08:25 -08:00
msg.attach(part)
try:
s = smtplib.SMTP_SSL(self.imap_server, timeout=2)
s.login(self.username, self.password)
s.sendmail(self.username, [recipient], msg.as_string())
s.quit()
except BaseException as e:
self.print_error(e)
2014-11-11 02:08:25 -08:00
2017-09-22 20:54:38 -07:00
class QEmailSignalObject(QObject):
email_new_invoice_signal = pyqtSignal()
class Plugin(BasePlugin):
2014-11-11 02:08:25 -08:00
def fullname(self):
return 'Email'
def description(self):
return _("Send and receive payment requests via email")
def is_available(self):
return True
def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name)
2014-11-11 02:08:25 -08:00
self.imap_server = self.config.get('email_server', '')
self.username = self.config.get('email_username', '')
self.password = self.config.get('email_password', '')
if self.imap_server and self.username and self.password:
self.processor = Processor(self.imap_server, self.username, self.password, self.on_receive)
self.processor.start()
2017-09-22 20:54:38 -07:00
self.obj = QEmailSignalObject()
2017-09-23 15:55:26 -07:00
self.obj.email_new_invoice_signal.connect(self.new_invoice)
self.wallets = set()
2014-11-11 02:08:25 -08:00
def on_receive(self, pr_str):
self.print_error('received payment request')
self.pr = PaymentRequest(pr_str)
2017-09-22 20:54:38 -07:00
self.obj.email_new_invoice_signal.emit()
2014-11-11 02:08:25 -08:00
@hook
def load_wallet(self, wallet, main_window):
self.wallets |= {wallet}
@hook
def close_wallet(self, wallet):
self.wallets -= {wallet}
2014-11-11 02:08:25 -08:00
def new_invoice(self):
for wallet in self.wallets:
wallet.invoices.add(self.pr)
#main_window.invoice_list.update()
2014-11-11 02:08:25 -08:00
2015-07-11 04:57:15 -07:00
@hook
def receive_list_menu(self, menu, addr):
window = get_parent_main_window(menu)
menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr))
2015-07-11 04:57:15 -07:00
def send(self, window, addr):
2019-12-24 03:21:35 -08:00
from electrum_bitcoinprivate import paymentrequest
r = window.wallet.receive_requests.get(addr)
2015-07-11 04:57:15 -07:00
message = r.get('memo', '')
if r.get('signature'):
pr = paymentrequest.serialize_request(r)
else:
2015-07-31 23:48:04 -07:00
pr = paymentrequest.make_request(self.config, r)
2015-07-11 04:57:15 -07:00
if not pr:
2014-11-11 02:08:25 -08:00
return
recipient, ok = QInputDialog.getText(window, 'Send request', 'Email invoice to:')
2014-11-11 02:08:25 -08:00
if not ok:
return
recipient = str(recipient)
2015-07-11 04:57:15 -07:00
payload = pr.SerializeToString()
2014-11-11 02:08:25 -08:00
self.print_error('sending mail to', recipient)
try:
# FIXME this runs in the GUI thread and blocks it...
self.processor.send(recipient, message, payload)
2014-11-11 02:08:25 -08:00
except BaseException as e:
traceback.print_exc(file=sys.stderr)
window.show_message(str(e))
else:
window.show_message(_('Request sent.'))
2014-11-11 02:08:25 -08:00
def requires_settings(self):
return True
def settings_widget(self, window):
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
2014-11-11 02:08:25 -08:00
def settings_dialog(self, window):
d = WindowModalDialog(window, _("Email settings"))
2014-11-11 02:08:25 -08:00
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(_('Server hosting your email account')))
2014-11-11 02:08:25 -08:00
grid = QGridLayout()
vbox.addLayout(grid)
2015-07-11 03:13:56 -07:00
grid.addWidget(QLabel('Server (IMAP)'), 0, 0)
2014-11-11 02:08:25 -08:00
server_e = QLineEdit()
server_e.setText(self.imap_server)
grid.addWidget(server_e, 0, 1)
grid.addWidget(QLabel('Username'), 1, 0)
username_e = QLineEdit()
username_e.setText(self.username)
grid.addWidget(username_e, 1, 1)
grid.addWidget(QLabel('Password'), 2, 0)
password_e = QLineEdit()
password_e.setText(self.password)
grid.addWidget(password_e, 2, 1)
vbox.addStretch()
vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
if not d.exec_():
2014-11-11 02:08:25 -08:00
return
server = str(server_e.text())
self.config.set_key('email_server', server)
self.imap_server = server
2014-11-11 02:08:25 -08:00
username = str(username_e.text())
self.config.set_key('email_username', username)
self.username = username
2014-11-11 02:08:25 -08:00
password = str(password_e.text())
self.config.set_key('email_password', password)
self.password = password
check_connection = CheckConnectionThread(server, username, password)
check_connection.connection_error_signal.connect(lambda e: window.show_message(
_("Unable to connect to mail server:\n {}").format(e) + "\n" +
_("Please check your connection and credentials.")
))
check_connection.start()
class CheckConnectionThread(QThread):
connection_error_signal = pyqtSignal(str)
def __init__(self, server, username, password):
super().__init__()
self.server = server
self.username = username
self.password = password
def run(self):
try:
conn = imaplib.IMAP4_SSL(self.server)
conn.login(self.username, self.password)
except BaseException as e:
self.connection_error_signal.emit(str(e))