diff --git a/plugins/__init__.py b/plugins/__init__.py index f26c41a72..2cc898e11 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -47,6 +47,12 @@ descriptions = [ 'requires_wallet_type': ['2of2', '2of3'], 'available_for': ['qt'], }, + { + 'name': 'email', + 'fullname': 'Email', + 'description': _("Send and receive payment request with an email account"), + 'available_for': ['qt'], + }, { 'name': 'exchange_rate', 'fullname': _("Exchange rates"), diff --git a/plugins/email.py b/plugins/email.py new file mode 100644 index 000000000..943fe485e --- /dev/null +++ b/plugins/email.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# +# Electrum - Lightweight Bitcoin Client +# Copyright (C) 2015 Thomas Voegtlin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import absolute_import + +import socket +import time +import threading +import base64 +import re +import platform +from decimal import Decimal +from Queue import Queue + +import smtplib +import imaplib +import email +from email.MIMEMultipart import MIMEMultipart +from email.MIMEBase import MIMEBase +from email import Encoders + +from PyQt4.QtGui import * +from PyQt4.QtCore import * +import PyQt4.QtCore as QtCore +import PyQt4.QtGui as QtGui + +from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED +from electrum.plugins import BasePlugin, hook +from electrum import util +from electrum.paymentrequest import PaymentRequest +from electrum.i18n import _ +from electrum_gui.qt.util import text_dialog, EnterButton + + + +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) + Encoders.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 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, a, b): + BasePlugin.__init__(self, a, b) + 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.win = None + + def on_receive(self, pr_str): + self.print_error('received payment request') + self.pr = PaymentRequest(pr_str) + if self.win: + self.win.emit(SIGNAL('email:new_invoice')) + + def new_invoice(self): + self.win.invoices.add(self.pr) + self.win.update_invoices_list() + + @hook + def init_qt(self, gui): + from electrum_gui.qt.util import ThreadedButton + self.win = gui.main_window + self.win.connect(self.win, SIGNAL('email:new_invoice'), self.new_invoice) + + self.send_button = EnterButton(_('Send'), self.send) + self.win.receive_buttons.insertWidget(1, self.send_button) + + def send(self): + addr = str(self.win.receive_address_e.text()) + message = unicode(self.win.receive_message_e.text()) + payment_request = self.win.make_payment_request(addr) + + if not self.win.save_payment_request(): + return + + recipient, ok = QtGui.QInputDialog.getText(self.win, 'Send request', 'Send request to:') + if not ok: + return + + recipient = str(recipient) + self.print_error('sending mail to', recipient) + try: + self.processor.send(recipient, message, payment_request) + except BaseException as e: + self.win.show_message(str(e)) + return + + self.win.show_message(_('Request sent.')) + + + def requires_settings(self): + return True + + def settings_widget(self, window): + return EnterButton(_('Settings'), self.settings_dialog) + + def settings_dialog(self, x): + from electrum_gui.qt.util import Buttons, CloseButton, OkButton + + d = QDialog(self.window) + d.setWindowTitle("Email settings") + d.setMinimumSize(500, 200) + + vbox = QVBoxLayout(d) + grid = QGridLayout() + vbox.addLayout(grid) + + grid.addWidget(QLabel('Server'), 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) + +