#!/usr/bin/env python # # Electrum - Lightweight Bitcoin Client # Copyright (C) 2015 Thomas Voegtlin # # 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: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # 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 time import threading import base64 from functools import partial import smtplib import imaplib import email from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase from email.encoders import encode_base64 from PyQt5.QtGui import * from PyQt5.QtCore import * import PyQt5.QtGui as QtGui from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit) from electrum.plugins import BasePlugin, hook from electrum.paymentrequest import PaymentRequest from electrum.i18n import _ from electrum_gui.qt.util import EnterButton, Buttons, CloseButton from electrum_gui.qt.util import OkButton, WindowModalDialog class Processor(threading.Thread): 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 def poll(self): try: self.M.select() except: return typ, data = self.M.search(None, 'ALL') for num in data[0].split(): typ, msg_data = self.M.fetch(num, '(RFC822)') msg = email.message_from_string(msg_data[0][1]) p = msg.get_payload() if not msg.is_multipart(): p = [p] continue for item in p: if item.get_content_type() == "application/bitcoin-paymentrequest": pr_str = item.get_payload() pr_str = base64.b64decode(pr_str) self.on_receive(pr_str) def run(self): self.M = imaplib.IMAP4_SSL(self.imap_server) self.M.login(self.username, self.password) while True: self.poll() time.sleep(self.polling_interval) self.M.close() self.M.logout() def send(self, recipient, message, payment_request): msg = MIMEMultipart() msg['Subject'] = message msg['To'] = recipient msg['From'] = self.username part = MIMEBase('application', "bitcoin-paymentrequest") part.set_payload(payment_request) encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="payreq.btc"') msg.attach(part) 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() class QEmailSignalObject(QObject): email_new_invoice_signal = pyqtSignal() class Plugin(BasePlugin): 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) 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() self.obj = QEmailSignalObject() self.obj.email_new_invoice_signal.connect(self.new_invoice) def on_receive(self, pr_str): self.print_error('received payment request') self.pr = PaymentRequest(pr_str) self.obj.email_new_invoice_signal.emit() def new_invoice(self): self.parent.invoices.add(self.pr) #window.update_invoices_list() @hook def receive_list_menu(self, menu, addr): window = menu.parentWidget() menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr)) def send(self, window, addr): from electrum import paymentrequest r = window.wallet.receive_requests.get(addr) message = r.get('memo', '') if r.get('signature'): pr = paymentrequest.serialize_request(r) else: pr = paymentrequest.make_request(self.config, r) if not pr: return recipient, ok = QtGui.QInputDialog.getText(window, 'Send request', 'Email invoice to:') if not ok: return recipient = str(recipient) payload = pr.SerializeToString() self.print_error('sending mail to', recipient) try: self.processor.send(recipient, message, payload) except BaseException as e: window.show_message(str(e)) return window.show_message(_('Request sent.')) def requires_settings(self): return True def settings_widget(self, window): return EnterButton(_('Settings'), partial(self.settings_dialog, window)) def settings_dialog(self, window): d = WindowModalDialog(window, _("Email settings")) d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_('Server hosting your email acount'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel('Server (IMAP)'), 0, 0) 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_(): return server = str(server_e.text()) self.config.set_key('email_server', server) username = str(username_e.text()) self.config.set_key('email_username', username) password = str(password_e.text()) self.config.set_key('email_password', password)