remove email plugin
This commit is contained in:
@@ -1,5 +0,0 @@
|
|||||||
from electrum.i18n import _
|
|
||||||
|
|
||||||
fullname = _('Email')
|
|
||||||
description = _("Send and receive payment request with an email account")
|
|
||||||
available_for = ['qt']
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
#!/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 random
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import base64
|
|
||||||
from functools import partial
|
|
||||||
import traceback
|
|
||||||
import sys
|
|
||||||
from typing import Set
|
|
||||||
import ssl
|
|
||||||
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.QtCore import QObject, pyqtSignal, QThread
|
|
||||||
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
|
|
||||||
QInputDialog)
|
|
||||||
import certifi
|
|
||||||
|
|
||||||
from electrum.gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
|
|
||||||
WindowModalDialog)
|
|
||||||
from electrum.gui.qt.main_window import ElectrumWindow
|
|
||||||
|
|
||||||
from electrum.plugin import BasePlugin, hook
|
|
||||||
from electrum.paymentrequest import PaymentRequest
|
|
||||||
from electrum.i18n import _
|
|
||||||
from electrum.logging import Logger
|
|
||||||
from electrum.wallet import Abstract_Wallet
|
|
||||||
from electrum.invoices import Invoice
|
|
||||||
|
|
||||||
|
|
||||||
ca_path = certifi.where()
|
|
||||||
ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_path)
|
|
||||||
|
|
||||||
|
|
||||||
class Processor(threading.Thread, Logger):
|
|
||||||
polling_interval = 5*60
|
|
||||||
|
|
||||||
def __init__(self, imap_server, username, password, callback):
|
|
||||||
threading.Thread.__init__(self)
|
|
||||||
Logger.__init__(self)
|
|
||||||
self.daemon = True
|
|
||||||
self.username = username
|
|
||||||
self.password = password
|
|
||||||
self.imap_server = imap_server
|
|
||||||
self.on_receive = callback
|
|
||||||
self.M = None
|
|
||||||
self.reset_connect_wait()
|
|
||||||
|
|
||||||
def reset_connect_wait(self):
|
|
||||||
self.connect_wait = 100 # ms, between failed connection attempts
|
|
||||||
|
|
||||||
def poll(self):
|
|
||||||
try:
|
|
||||||
self.M.select()
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
typ, data = self.M.search(None, 'ALL')
|
|
||||||
for num in str(data[0], 'utf8').split():
|
|
||||||
typ, msg_data = self.M.fetch(num, '(RFC822)')
|
|
||||||
msg = email.message_from_bytes(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):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.M = imaplib.IMAP4_SSL(self.imap_server, ssl_context=ssl_context)
|
|
||||||
self.M.login(self.username, self.password)
|
|
||||||
except BaseException as e:
|
|
||||||
self.logger.info(f'connecting failed: {repr(e)}')
|
|
||||||
self.connect_wait *= 2
|
|
||||||
else:
|
|
||||||
self.reset_connect_wait()
|
|
||||||
# Reconnect when host changes
|
|
||||||
while self.M and self.M.host == self.imap_server:
|
|
||||||
try:
|
|
||||||
self.poll()
|
|
||||||
except BaseException as e:
|
|
||||||
self.logger.info(f'polling failed: {repr(e)}')
|
|
||||||
break
|
|
||||||
time.sleep(self.polling_interval)
|
|
||||||
time.sleep(random.randint(0, self.connect_wait))
|
|
||||||
|
|
||||||
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)
|
|
||||||
try:
|
|
||||||
s = smtplib.SMTP_SSL(self.imap_server, timeout=2, context=ssl_context)
|
|
||||||
s.login(self.username, self.password)
|
|
||||||
s.sendmail(self.username, [recipient], msg.as_string())
|
|
||||||
s.quit()
|
|
||||||
except BaseException as e:
|
|
||||||
self.logger.info(e)
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
self.wallets = set() # type: Set[Abstract_Wallet]
|
|
||||||
|
|
||||||
def on_receive(self, pr_str):
|
|
||||||
self.logger.info('received payment request')
|
|
||||||
self.pr = PaymentRequest(pr_str)
|
|
||||||
self.obj.email_new_invoice_signal.emit()
|
|
||||||
|
|
||||||
@hook
|
|
||||||
def load_wallet(self, wallet, main_window):
|
|
||||||
self.wallets |= {wallet}
|
|
||||||
|
|
||||||
@hook
|
|
||||||
def close_wallet(self, wallet):
|
|
||||||
self.wallets -= {wallet}
|
|
||||||
|
|
||||||
def new_invoice(self):
|
|
||||||
invoice = Invoice.from_bip70_payreq(self.pr)
|
|
||||||
for wallet in self.wallets:
|
|
||||||
wallet.save_invoice(invoice)
|
|
||||||
#main_window.invoice_list.update()
|
|
||||||
|
|
||||||
@hook
|
|
||||||
def receive_list_menu(self, window: ElectrumWindow, menu, addr):
|
|
||||||
menu.addAction(_("Send via e-mail"), lambda: self.send(window, addr))
|
|
||||||
|
|
||||||
def send(self, window: ElectrumWindow, addr):
|
|
||||||
from electrum import paymentrequest
|
|
||||||
req = window.wallet.get_request(addr)
|
|
||||||
# FIXME only on-chain requests are supported
|
|
||||||
message = req.message
|
|
||||||
if req.bip70:
|
|
||||||
payload = bytes.fromhex(req.bip70)
|
|
||||||
else:
|
|
||||||
pr = paymentrequest.make_request(self.config, req)
|
|
||||||
payload = pr.SerializeToString()
|
|
||||||
if not payload:
|
|
||||||
return
|
|
||||||
recipient, ok = QInputDialog.getText(window, 'Send request', 'Email invoice to:')
|
|
||||||
if not ok:
|
|
||||||
return
|
|
||||||
recipient = str(recipient)
|
|
||||||
self.logger.info(f'sending mail to {recipient}')
|
|
||||||
try:
|
|
||||||
# FIXME this runs in the GUI thread and blocks it...
|
|
||||||
self.processor.send(recipient, message, payload)
|
|
||||||
except BaseException as e:
|
|
||||||
self.logger.exception('')
|
|
||||||
window.show_message(repr(e))
|
|
||||||
else:
|
|
||||||
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 account')))
|
|
||||||
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)
|
|
||||||
self.imap_server = server
|
|
||||||
|
|
||||||
username = str(username_e.text())
|
|
||||||
self.config.set_key('email_username', username)
|
|
||||||
self.username = username
|
|
||||||
|
|
||||||
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, ssl_context=ssl_context)
|
|
||||||
conn.login(self.username, self.password)
|
|
||||||
except BaseException as e:
|
|
||||||
self.connection_error_signal.emit(repr(e))
|
|
||||||
Reference in New Issue
Block a user