From 425eded1a2b810f27dc5c3b176f4e3b1e6796789 Mon Sep 17 00:00:00 2001
From: peter-tank <30540412+peter-tank@users.noreply.github.com>
Date: Tue, 17 Jul 2018 20:01:02 +0800
Subject: [PATCH] Massive changes to src/bitmessagecli.py
* Mainly changes:
* Message attachments faults detecting, and allow to dump whole embeded message alone to files.
* Multi-line message acceptable in sending, (End with `Ctrl+D`)and allow resent on 'Connection Error.'
* Print out detailed API Error returned.
* Update Contacts list cmd, bcz contact list API returns unEncoded lable.
* Message pull routing refine, mainlly access inbox messages by IDs, to reduce bandwidth usages.
* API connections `SOCKS5` `HTTP` proxied.
* UIs
* Shorten the user cmds inputs.
* Try to remember user last choices.
* Refine user input checkings.(rest to default selection by input a blank string)
* A comprehensive command line parser, override configurations read from file `client.dat` in current working directory.
* Message review limited to 380 characters in default.
default settings `client.dat`
```
[global]
start_daemon = http://127.0.0.1:8888
start_daemon = http://127.0.0.1:8445
[api]
path = 127.0.0.1:8445
type = HTTP
[proxy]
path = 127.0.0.1:1080
type = none
timeout = 30
remotedns = True
```
Signed-off-by: peter-tank <30540412+peter-tank@users.noreply.github.com>
---
src/bitmessagecli.py | 3519 +++++++++++++++++++++++++-----------------
src/bmsettings.py | 373 +++++
2 files changed, 2471 insertions(+), 1421 deletions(-)
create mode 100644 src/bmsettings.py
diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py
index 02fed7e9..faa8c75a 100644
--- a/src/bitmessagecli.py
+++ b/src/bitmessagecli.py
@@ -7,646 +7,1153 @@
Created by Adam Melton (.dok) referenceing https://bitmessage.org/wiki/API_Reference for API documentation
Distributed under the MIT/X11 software license. See http://www.opensource.org/licenses/mit-license.php.
-This is an example of a daemon client for PyBitmessage 0.6.2, by .dok (Version 0.3.1) , modified
+This is an example of a daemon client for PyBitmessage 0.6.2, original by .dok (Version 0.3.1)
+Modified by .pt (Version 0.4.0), for PyBitmessage 0.6.3
TODO: fix the following (currently ignored) violations:
"""
-import xmlrpclib
-import datetime
+import argparse
+import ConfigParser
import imghdr
import ntpath
import json
-import socket
-import time
import sys
import os
-from bmconfigparser import BMConfigParser
+import base64
+import ssl
+import socket
+# python3 maybe
+try:
+ import httplib
+ import xmlrpclib
+ from urlparse import urlparse
+ from urllib import unquote
+except ImportError:
+ import http.client as httplib
+ import xmlrpc.client as xmlrpclib
+ from urllib.parse import urlparse, unquote
+
+import traceback
+
+import time
+import datetime
+import inspect
+import re
+import subprocess
+
+from collections import OrderedDict
-api = ''
-keysName = 'keys.dat'
-keysPath = 'keys.dat'
usrPrompt = 0 # 0 = First Start, 1 = prompt, 2 = no prompt if the program is starting up
-knownAddresses = dict()
+api = ''
+knownAddresses = dict({'addresses': []})
+cmdStr = 'getLabel'.lower()
+# menu by this order
+cmdTbl = OrderedDict()
+cmdTbl['Command'] = 'Description'
+cmdTbl['s1'] = '-'
+cmdTbl['help'] = 'This help'
+cmdTbl['daemon'] = 'Try to start PyBitmessage daemon locally'
+cmdTbl['apiTest'] = 'Daemon API connection tests'
+cmdTbl['status'] = 'Get the summary of running daemon'
+cmdTbl['addInfo'] = 'Request detailed info to a address'
+cmdTbl['bmSettings'] = 'PyBitmessage settings "keys.dat"'
+cmdTbl['exit'] = 'Use anytime to return to main menu'
+cmdTbl['quit'] = 'Quit this CLI'
+cmdTbl['shutdown'] = 'Shutdown the connectable daemon via. API'
+cmdTbl['s2'] = '-'
+cmdTbl['listAddresses'] = 'List user\'s addresse(s) (Senders)'
+cmdTbl['newAddress'] = 'Generate a new sender address'
+cmdTbl['getAddress'] = 'Get determinist address from passphrase'
+cmdTbl['s3'] = '-'
+cmdTbl['listAddressBK'] = 'List the "Address Book" entry (Contacts)'
+cmdTbl['addAddressBK'] = 'Add a address to the "Address Book"'
+cmdTbl['delAddressBK'] = 'Delete a address from the "Address Book"'
+cmdTbl['s4'] = '-'
+cmdTbl['listsubscrips'] = 'List subscriped addresses'
+cmdTbl['subscribe'] = 'Subscribes to an address'
+cmdTbl['unsubscribe'] = 'Unsubscribe from an address'
+cmdTbl['s5'] = '-'
+cmdTbl['create'] = 'Create a channel'
+cmdTbl['join'] = 'Join to a channel'
+cmdTbl['leave'] = 'Leave from a channel'
+cmdTbl['s6'] = '-'
+cmdTbl['getLabel'] = 'Retrieve addresse(s) label for message heads'
+cmdTbl['s7'] = '-'
+cmdTbl['inbox'] = 'List all inbox message heads'
+cmdTbl['outbox'] = 'List all outbox message heads heads'
+cmdTbl['news'] = 'List all "unread" inbox message heads'
+cmdTbl['send'] = 'Send out new message or broadcast'
+cmdTbl['s8'] = '-'
+cmdTbl['read'] = 'Read a message from in(out)box'
+cmdTbl['readAll'] = 'Mard "read" for all inbox message(s)'
+cmdTbl['unreadAll'] = 'Mark "unread" for all inbox message(s)'
+cmdTbl['s9'] = '-'
+cmdTbl['save'] = 'Save(Dump) a in(out)box message to disk'
+cmdTbl['delete'] = 'Delete a(ll) in(out)box messages from remote'
+cmdShorts = dict()
+
+retStrings = dict({
+ 'none': '',
+ 'usercancel': '\n User canceled.\n',
+ 'invalidinput': '\n Invalid input.\n',
+ 'invalidindex': '\n Invalid message index.\n',
+ 'invalidaddr': '\n Invalid address.\n',
+ 'indexoutofbound': '\n Reach end of index.\n',
+ 'bmsnotallow': '\n Daemon configure command not allowed.\n',
+ 'nomain': '\n Cannot locate "bitmessagemain.py", daemon start failed.\n',
+ })
+inputShorts = dict({
+ 'yes': ['y', 'yes'],
+ 'no': ['n', 'no'],
+ 'exit': ['e', 'ex', 'exit'],
+ 'save': ['save', 's', 'sv'],
+ 'deterministic': ['d', 'dt'],
+ 'random': ['r', 'rd', 'random'],
+ 'message': ['m', 'msg', 'message'],
+ 'broadcast': ['b', 'br', 'brd', 'broadcast'],
+ 'inbox': ['i', 'in', 'ib', 'inbox'],
+ 'outbox': ['o', 'ou', 'out', 'ob', 'outbox'],
+ 'dump': ['d', 'dp', 'dump'],
+ 'save': ['s', 'sa', 'save'],
+ 'reply': ['r', 'rp', 'reply'],
+ 'forward': ['f', 'fw', 'forward'],
+ 'delete': ['d', 'del', 'delete'],
+ 'all': ['a', 'all'],
+ })
+inputs = dict()
+
+
+def duplicated(out):
+
+ global cmdShorts
+
+ seen = dict()
+ dups = list()
+ dcmds = dict()
+ for x in out:
+ if x not in seen:
+ seen[x] = 1
+ else:
+ if seen[x] == 1:
+ dups.append(x)
+ seen[x] += 1
+ for x in dups:
+ for cmd in cmdShorts:
+ if x in cmdShorts[cmd]:
+ dcmds[cmd] = cmdShorts[cmd]
+ return dcmds
+
+
+def cmdGuess():
+
+ global cmdTbl, cmdShorts
+
+ fullWords = [
+ 'api', 'test', 'info', 'settings', 'quit', 'exit', 'set', 'list',
+ 'add', 'addresses', 'subscrips', 'label', 'all', 'delete', 'join',
+ 'scribe', 'build', 'in', 'out', 'box', 'new', 'create', 'end', 'shut',
+ 'read', 'down', 'get', 'del', 'address',
+ 'ubs', 'un', 'addressb', 'tatus', 'ave'
+ ]
+ halfWords = [
+ 'rate', 'lete', 'oin', 'lea', 'ead', 'eave', 'tatus', 'bke', 'un', 've',
+ 'fo', 'dress', 'boo', 'lete', 'reate', 'dae', 'mon', 'reate', 'sa',
+ 'inbox', 'outbox', 'send', 'in', 'add', 'news', 'all',
+ ]
+ fullWords.sort(key=lambda item: (-len(item), item))
+ halfWords.sort(key=lambda item: (-len(item), item))
+
+ out = list()
+ # shorten
+ wordscounter = 0
+ for guessWords in [fullWords, halfWords]:
+ wordscounter += 1
+ for cmd in cmdTbl:
+ lcmd = cmd.lower()
+ if not any(cmdShorts.get(cmd, [])): # keep full command name
+ cmdShorts[cmd] = [lcmd]
+ for words in guessWords:
+ lwords = words.lower()
+ lcmd = lcmd.replace(lwords, lwords[0], 1)
+ if lcmd == lwords:
+ break
+ counter = len(cmdShorts[cmd])
+ if lcmd not in cmdShorts[cmd]:
+ if counter > 1 and len(lcmd) < len(cmdShorts[cmd][1]):
+ cmdShorts[cmd].insert(1, lcmd)
+ else:
+ cmdShorts[cmd].append(lcmd)
+ out.append(lcmd)
+
+ dcmds = duplicated(out)
+ if any(dcmds):
+ print '\n cmdGuess() Fail!'
+ print ' duplicated =', dcmds
+ print ' Change your "guessWords" list(%d).\n' % wordscounter
+ return False
+
+ cmdShorts['Command'] = ''
+ cmdShorts['exit'] = ''
+ cmdShorts['help'] = list(['help', 'h', '?'])
+ return True
+
+
+def showCmdTbl():
+
+ global cmdTbl, cmdShorts
+
+ url = 'https://github.com/BitMessage/PyBitmessage'
+ print
+ print ''.join([5 * ' ', 73 * '-'])
+ print ''.join([5 * ' ', '|', url[:int(len(url)/2)].rjust(35), url[int(len(url)/2):].ljust(36), '|'])
+ print ''.join([5 * ' ', 73 * '-'])
+ for cmd in cmdTbl:
+ lcmd = ('' if len(cmd) > 18 else cmd + ' ') + str(cmdShorts[cmd][1:])
+ if len(lcmd) > 23:
+ lcmd = lcmd[:20] + '...'
+ des = cmdTbl[cmd]
+ if len(des) > 45:
+ des = des[:42] + '...'
+ if des == '-':
+ print '|'.join([5 * ' ', 24 * '-', 46 * '-', ''])
+ else:
+ print '| '.join([5 * ' ', lcmd.ljust(23), des.ljust(45), ''])
+ print ''.join([5 * ' ', 73 * '-'])
+
+
+class Config(object):
+ def __init__(self, argv):
+ self.version = "0.4.0"
+ self.argv = argv
+ self.action = None
+ self.config_file = "client.dat" # for initial default value
+ self.conn = 'HTTP://127.0.0.1:8445/' # default API uri
+ self.createParser()
+ self.createArguments()
+
+ def createParser(self):
+ # Create parser
+ self.parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ self.parser.register('type', 'bool', self.strToBool)
+ self.subparsers = self.parser.add_subparsers(title="Actions", dest="action")
+
+ def __str__(self):
+ return str(self.arguments).replace("Namespace", "Config") # Using argparse str output
+
+ def strToBool(self, v):
+ return v.lower() in ("yes", "true", "t", "1")
+
+ # Create command line arguments
+ def createArguments(self):
+ # this_file = os.path.abspath(__file__).replace("\\", "/").rstrip("cd")
+ # if this_file.endswith("/src/bitmessagecli.py"):
+
+ # main
+ action = self.subparsers.add_parser("main", help='Start this CLI (default)')
+ action = self.parser
+ self.parser.add_argument('--version', action='version', version='BitMessageCLI %s' % (self.version))
+ self.parser.add_argument('--start_daemon', help='Start BMs API daemon locally', default=None, metavar='BMs_host_uri')
+
+ # api settings
+ # action = self.subparsers.add_parser('api', help='Set API settings.')
+ action.add_argument('--api_username', help='BMs API basic auth user name.', default=None, metavar='username')
+ action.add_argument('--api_password', help='BMs API basic auth password.', default=None, metavar='password')
+ action.add_argument('--api_path', help='BMs API host address.', default=self.conn, metavar='ip:port')
+ action.add_argument('--api_type', help='BMs API hosts type.', default='HTTP', choices=["HTTP", "HTTPS"])
+
+ # proxy settings
+ # action = self.subparsers.add_parser('proxy', help='Use proxy for connections.')
+ action.add_argument('--proxy_username', help='Username to authenticate to the proxy server.', default=None, metavar='username')
+ action.add_argument('--proxy_password', help='Password to authenticate to the proxy server.', default=None, metavar='password')
+ action.add_argument('--proxy_path', help='Address of the proxy server.', default='127.0.0.1:1080', metavar='ip:port')
+ action.add_argument('--proxy_type', help='Proxy type.', default='none', choices=['none', 'SOCKS4', 'SOCKS5', 'HTTP'])
+ action.add_argument('--proxy_remotedns', help='Send DNS request to remote(socks proxied).', type='bool', choices=[True, False], default=True)
+ action.add_argument('--proxy_timeout', help='Network connection timeout.', default=30, type=int, metavar='seconds')
+
+ self.parser.add_argument('--config_file', help='Path of config file.', default=self.config_file, metavar='path')
+ self.parser.add_argument('--end', help='Stop multi value argument parsing(inner_use).', action='store_true')
+
+ return self.parser
+
+ # Find arguments specified for current action
+ def getActionArguments(self):
+ back = {}
+ arguments = self.parser._subparsers._group_actions[0].choices[self.action]._actions[0:] # First is --version
+ # for argument in arguments:
+ # if argument.dest != 'help':
+ # back[argument.dest] = getattr(self, argument.dest)
+ return back
+
+ # Try to find action from argv
+ def getAction(self, argv):
+ actions = [action.choices.keys() for action in self.parser._actions if action.dest == "action"][0] # Valid actions
+ found_action = False
+ for action in actions: # See if any in argv
+ if action in argv:
+ found_action = action
+ break
+ return found_action
+
+ # Move unknown parameters to end of argument list
+ def moveUnknownToEnd(self, argv, default_action):
+ valid_actions = sum([action.option_strings for action in self.parser._actions], [])
+ valid_parameters = []
+ unkown_parameters = []
+ unkown = False
+ for arg in argv:
+ if arg.startswith("--"):
+ if arg not in valid_actions:
+ unkown = True
+ else:
+ unkown = False
+ elif arg == default_action:
+ unkown = False
+
+ if unkown:
+ unkown_parameters.append(arg)
+ else:
+ valid_parameters.append(arg)
+ return valid_parameters + unkown_parameters
+
+ # Parse arguments from config file and command line
+ def parse(self, parse_config=True):
+ argv = self.argv[:] # Copy command line arguments
+ self.parseCommandline(argv) # Parse argv
+ self.setAttributes()
+ if parse_config:
+ argv = self.parseConfig(argv) # Add arguments from config file
+
+ self.parseCommandline(argv) # Parse argv
+ self.setAttributes()
+
+ # Parse command line arguments
+ def parseCommandline(self, argv):
+ # Find out if action is specificed on start
+ action = self.getAction(argv)
+ if not action:
+ argv.append("--end")
+ argv.append("main")
+
+ action = "main"
+ argv = self.moveUnknownToEnd(argv, action)
+ self.arguments = self.parser.parse_args(argv[1:])
+
+ # Parse config file
+ def parseConfig(self, argv):
+ # Find config file path from parameters
+ if "--config_file" in argv:
+ self.config_file = argv[argv.index("--config_file") + 1]
+ print '- Configuration loading . (%s)' % os.path.realpath(self.config_file)
+ if os.path.isfile(self.config_file):
+ config = ConfigParser.ConfigParser(allow_no_value=True)
+ config.read(self.config_file)
+ for section in config.sections():
+ for key, val in config.items(section):
+ if section != "global": # If not global prefix key with section
+ key = section + "_" + key
+
+ to_end = key == "start_daemon" # Prefer config value over argument
+ argv_extend = ["--%s" % key]
+ if val:
+ argv_extend.append(val)
+
+ if to_end:
+ argv = argv[:-1] + argv_extend + argv[-1:]
+ else:
+ argv = argv[:1] + argv_extend + argv[1:]
+ return argv
+
+ # Expose arguments as class attributes
+ def setAttributes(self):
+ # Set attributes from arguments
+ if self.arguments:
+ args = vars(self.arguments)
+ for key, val in args.items():
+ if type(val) is list:
+ val = val[:]
+ setattr(self, key, val)
+
+ def saveValue(self, key, value):
+ if not os.path.isfile(self.config_file):
+ content = ""
+ else:
+ content = open(self.config_file).read()
+ lines = content.splitlines()
+
+ global_line_i = None
+ key_line_i = None
+ i = 0
+ for line in lines:
+ if line.strip() == "[global]":
+ global_line_i = i
+ if line.startswith(key + " ="):
+ key_line_i = i
+ i += 1
+
+ if key_line_i and len(lines) > key_line_i + 1:
+ while True: # Delete previous multiline values
+ is_value_line = lines[key_line_i + 1].startswith(" ") or lines[key_line_i + 1].startswith("\t")
+ if not is_value_line:
+ break
+ del lines[key_line_i + 1]
+
+ if value is None: # Delete line
+ if key_line_i:
+ del lines[key_line_i]
+
+ else: # Add / update
+ if type(value) is list:
+ value_lines = [""] + [str(line).replace("\n", "").replace("\r", "") for line in value]
+ else:
+ value_lines = [str(value).replace("\n", "").replace("\r", "")]
+ new_line = "%s = %s" % (key, "\n ".join(value_lines))
+ if key_line_i: # Already in the config, change the line
+ lines[key_line_i] = new_line
+ elif global_line_i is None: # No global section yet, append to end of file
+ lines.append("[global]")
+ lines.append(new_line)
+ else: # Has global section, append the line after it
+ lines.insert(global_line_i + 1, new_line)
+
+ open(self.config_file, "w").write("\n".join(lines))
+
+
+class Actions(object):
+ def call(self, function_name, kwargs):
+ print '- Original by .dok (Version 0.3.1) https://github.com/Dokument/PyBitmessage-Daemon'
+ print '- Modified by .pt (Version 0.4.0) https://github.com/BitMessage/PyBitmessage'
+ print '- Version: %s, Python %s' % (config.version, sys.version)
+
+ func = getattr(self, function_name, None)
+ back = func(**kwargs)
+ if back:
+ print back
+
+ # Default action: Start CLI only
+ def main(self, *argv):
+ while True:
+ try:
+ CLI()
+ except InputException as err:
+ print retStrings.get(err.resKey, '\n Not defined error raised: %s.\n' % err.resKey)
+
+ def api(self, api_username, api_password, api_path, api_type):
+ print 'action api: api_username:', api_username
+
+ def proxy(self, proxy_username, proxy_password, proxy_path, proxy_type, proxy_timeout):
+ print 'action proxy: proxy_username:', proxy_username
+
+
+def start():
+ ''' Call actions '''
+
+ # action_kwargs = config.getActionArguments()
+ actions.call(config.action, {})
+
+
+# proxied start
+# original https://github.com/benhengx/xmlrpclibex
+# add basic auth support for top level host while none/HTTP proxied
+
+
+class ProxyError(Exception): pass
+class GeneralProxyError(ProxyError): pass
+class Socks5AuthError(ProxyError): pass
+class Socks5Error(ProxyError): pass
+class Socks4Error(ProxyError): pass
+class HTTPError(ProxyError): pass
+
+def init_socks(proxy, timeout):
+ '''init a socks proxy socket.'''
+ import urllib
+
+ map_to_type = {
+ 'SOCKS4': socks.PROXY_TYPE_SOCKS4,
+ 'SOCKS5': socks.PROXY_TYPE_SOCKS5,
+ 'HTTP': socks.PROXY_TYPE_HTTP
+ }
+ address_family = socket.AF_INET
+ ssock = socks.socksocket(address_family, socket.SOCK_STREAM)
+ ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ # ssock.setsockopt()
+ if isinstance(timeout, (int, float)):
+ ssock.settimeout(timeout)
+ proxytype = map_to_type[proxy['proxy_type']]
+ rdns = proxy['proxy_remotedns']
+ addr, port = proxy['proxy_path'].split(':', 1)
+ port = int(port)
+ username = proxy['proxy_username']
+ password = proxy['proxy_password']
+ isauth = username and password
+ if isauth is True:
+ ssock.setproxy(proxytype, addr, port, username, password, rdns)
+ socks.setdefaultproxy(proxytype, addr, port, username, password, rdns)
+ else:
+ ssock.setproxy(proxytype, addr, port, rdns)
+ socks.setdefaultproxy(proxytype, addr, port, rdns)
+
+ socket.socket = socks.socksocket
+ # ssock.connect(("www.google.com", 443))
+ # urllib.urlopen("https://www.google.com/")
+ return ssock
+
+
+class SocksProxiedHTTPConnection(httplib.HTTPConnection):
+ '''Proxy the http connection through a socks proxy.'''
+
+ def init_socks(self, proxy):
+ self.ssock = init_socks(proxy, self.timeout)
+
+ def connect(self):
+ self.ssock.connect((self.host, self.port))
+ self.sock = self.ssock
+
+
+class SocksProxiedHTTPSConnection(httplib.HTTPSConnection):
+ '''Proxy the https connection through a socks proxy.'''
+
+ def init_socks(self, proxy):
+ self.ssock = init_socks(proxy, self.timeout)
+
+ def connect(self):
+ self.ssock.connect((self.host, self.port))
+ self.sock = ssl.wrap_socket(self.ssock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_TLSv1)
+
+ def close(self):
+ httplib.HTTPSConnection.close(self)
+
+ if self.ssock:
+ self.ssock.close()
+ self.ssock = None
+
+
+class TransportWithTo(xmlrpclib.Transport):
+ '''Transport support timeout'''
+
+ cls_http_conn = httplib.HTTPConnection
+ cls_https_conn = httplib.HTTPSConnection
+
+ def __init__(self, use_datetime=0, is_https=False, timeout=None):
+ xmlrpclib.Transport.__init__(self, use_datetime)
+
+ self.is_https = is_https
+ if timeout is None:
+ timeout = socket._GLOBAL_DEFAULT_TIMEOUT
+ self.timeout = timeout
+
+ def make_connection(self, host):
+ self.realhost = host
+ if self._connection and host == self._connection[0]:
+ return self._connection[1]
+
+ # create a HTTP/HTTPS connection object from a host descriptor
+ # host may be a string, or a (host, x509-dict) tuple
+ # no basic auth head returns here
+ chost, self._extra_headers, x509 = self.get_host_info(host)
+
+ # store the host argument along with the connection object
+ if self.is_https: # xmlrpclib.SafeTransport + timeout
+ self._connection = host, self.cls_https_conn(chost, None, timeout=self.timeout, **(x509 or {}))
+ else: # xmlrpclib.Transport + timeout
+ self._connection = host, self.cls_http_conn(chost, timeout=self.timeout)
+ return self._connection[1]
+
+# def send_request(self, connection, handler, request_body):
+# connection.putrequest('POST', '%s://%s' % (self.realhost, handler))
+
+# def send_host(self, connection, host):
+# connection.putheader('Host', self.realhost)
+
+# def send_user_agent(self, connection):
+# connection.putheader("User-Agent", self.send_user_agent)
+
+
+class ProxiedTransportWithTo(TransportWithTo):
+ '''Transport supports timeout and http proxy'''
+
+ def __init__(self, proxy, use_datetime=0, timeout=None, api_cred=None):
+ TransportWithTo.__init__(self, use_datetime, False, timeout)
+ self.api_cred = api_cred
+ self.proxy_path = proxy['proxy_path']
+ if proxy['proxy_username'] and proxy['proxy_password']:
+ self.proxy_cred = base64.encodestring('%s:%s' % (unquote(proxy['proxy_username']), unquote(proxy['proxy_password']))).strip()
+ else:
+ self.proxy_cred = None
+
+ def request(self, host, handler, request_body, verbose=False):
+ realhandler = 'HTTP://%s%s' % (host, handler)
+ return TransportWithTo.request(self, host, realhandler, request_body, verbose)
+
+ def make_connection(self, host):
+ return TransportWithTo.make_connection(self, self.proxy_path)
+
+ def send_content(self, connection, request_body):
+ if self.proxy_cred:
+ connection.putheader('Proxy-Authorization', 'Basic ' + self.proxy_cred)
+ if self.api_cred:
+ connection.putheader('Authorization', 'Basic ' + self.api_cred)
+ return TransportWithTo.send_content(self, connection, request_body)
+
+
+class SocksProxiedTransportWithTo(TransportWithTo):
+ '''Transport supports timeout and socks SOCKS4/SOCKS5 and http connect tunnel'''
+
+ cls_http_conn = SocksProxiedHTTPConnection
+ cls_https_conn = SocksProxiedHTTPSConnection
+
+ def __init__(self, proxy, use_datetime=0, is_https=False, timeout=None):
+ TransportWithTo.__init__(self, use_datetime, is_https, timeout)
+ self.proxy = proxy
+
+ def make_connection(self, host):
+ conn = TransportWithTo.make_connection(self, host)
+ conn.init_socks(self.proxy)
+ return conn
+
+
+class Proxiedxmlrpclib(xmlrpclib.ServerProxy):
+ """New added keyword arguments
+ timeout: seconds waiting for the socket
+ proxy: a dict specify the proxy settings, it supports the following fields:
+ proxy_path: the address of the proxy server. default: 127.0.0.1:1080
+ proxy_username: username to authenticate to the server. default None
+ proxy_password: password to authenticate to the server, only relevant when
+ username is set. default None
+ proxy_type: string, 'SOCKS4', 'SOCKS5', 'HTTP' (HTTP connect tunnel), only
+ relevant when is_socks is True. default 'SOCKS5'
+ """
+
+ def __init__(self, uri, transport=None, encoding=None, verbose=0,
+ allow_none=0, use_datetime=0, timeout=None, proxy=None):
+
+ scheme, netloc, path, x, xx, xxx = urlparse(uri)
+ api_username = unquote(urlparse(uri).username)
+ api_password = unquote(urlparse(uri).password)
+ api_cred = None
+ self.uri = uri
+ if api_username and api_password:
+ api_cred = base64.encodestring('%s:%s' % (api_username, api_password)).strip()
+ netloc = netloc.split('@')[1]
+
+ if transport is None and (timeout or proxy):
+ is_https = scheme == 'https'
+
+ if proxy.get('proxy_type', 'none') == 'none':
+ transport = TransportWithTo(use_datetime, is_https, timeout)
+ else:
+ timeout = proxy.get('timeout', timeout) # overide default timeout from proxy dict
+ is_socks = 'SOCKS' in proxy['proxy_type']
+
+ if is_https and not is_socks: # set default HTTP type for https uri
+ # https must be tunnelled through http connect
+ is_socks = True
+ proxy['proxy_type'] = 'HTTP'
+
+ if not is_socks: # http proxy
+ self.uri = '%s://%s%s' % (scheme, netloc, path)
+ transport = ProxiedTransportWithTo(proxy, use_datetime, timeout, api_cred)
+ else: # http connect and socksx
+ transport = SocksProxiedTransportWithTo(proxy, use_datetime, is_https, timeout)
+
+ xmlrpclib.ServerProxy.__init__(self, self.uri, transport, encoding, verbose, allow_none, use_datetime)
+# proxied end
+
+
+class BMAPIWrapper(object):
+
+ def set_proxy(self, proxy=None):
+ self.proxy = proxy
+ self.__init__(self.conn, self.proxy)
+
+ def __init__(self, uri=None, proxy=None):
+ self.proxy = proxy
+ self.conn = uri
+
+ proxied = 'non-proxied'
+ if proxy:
+ proxied = proxy['proxy_type'] + ' | ' + proxy['proxy_path']
+
+ try:
+ self.xmlrpc = Proxiedxmlrpclib(uri, verbose=False, allow_none=True, use_datetime=True, timeout=30, proxy=self.proxy)
+ print '\n XML-RPC initialed on: "%s" (%s)' % (self.conn, proxied)
+
+ except Exception as err: # IOError, unsupported XML-RPC protocol/
+ self.xmlrpc = None
+ print '\n XML-RPC initial failed on: "%s" - {%s}\n' % (self.conn, err)
+ # traceback.print_exc()
+
+ def __getattr__(self, apiname):
+ attr = getattr(self.xmlrpc, apiname, None)
+
+ def wrapper(*args, **kwargs):
+ error = 0
+ result = ''
+ errormsg = ''
+ try:
+ if attr is None:
+ error = 1
+ errormsg = ' Not prepared for calling API methods. (%s)' % apiname
+ return {'error': error, 'result': result, 'errormsg': errormsg}
+
+ response = attr(*args, **kwargs)
+ if type(response) is str and ("API Error" in response or 'RPC ' in response): # API Error, Authorization Error, Proxy Error
+ error = 2
+ if "API Error" in response:
+ error = getAPIErrorCode(response)
+ if error in [20, 21]: # programing error, Invalid method/Unexpected API Failure
+ print '\n Maybe no such API method:', apiname
+ print ' Try helping:', self.xmlrpc.system.listMethods() if error == 20 else self.xmlrpc.system.methodHelp(apiname)
+ errormsg = '\n ' + response + '\n'
+ return {'error': error, 'result': result, 'errormsg': errormsg}
+
+ if apiname in [
+ 'add',
+ 'helloWorld',
+ 'statusBar',
+ ]:
+ result = response
+ else: # pre-checking for API returns
+ try:
+ if apiname in [
+ 'getAllInboxMessageIDs',
+ 'getAllInboxMessageIds',
+ ]:
+ result = json.loads(response)['inboxMessageIds']
+ elif apiname in [
+ 'getInboxMessageByID',
+ 'getInboxMessageById',
+ ]:
+ result = json.loads(response)['inboxMessage']
+ elif apiname in [
+ 'GetAllInboxMessages',
+ 'getInboxMessagesByReceiver',
+ 'getInboxMessagesByAddress',
+ ]:
+ result = json.loads(response)['inboxMessages']
+ elif apiname in [
+ 'getAllSentMessageIDs',
+ 'getAllSentMessageIds',
+ ]:
+ result = json.loads(response)['sentMessageIds']
+ elif apiname in [
+ 'getAllSentMessages',
+ 'getSentMessagesByAddress',
+ 'getSentMessagesBySender',
+ 'getSentMessageByAckData',
+ ]:
+ result = json.loads(response)['sentMessages']
+ elif apiname in [
+ 'getSentMessageByID',
+ 'getSentMessageById',
+ ]:
+ result = json.loads(response)['sentMessage']
+ elif apiname in [
+ 'listAddressBookEntries',
+ 'listAddressbook',
+ 'listAddresses',
+ 'listAddressbook',
+ 'createDeterministicAddresses',
+ ]:
+ result = json.loads(response)['addresses']
+ elif apiname in [
+ 'listSubscriptions',
+ ]:
+ result = json.loads(response)['subscriptions']
+ elif apiname in [
+ 'decodeAddress',
+ 'clientStatus',
+ 'getMessageDataByDestinationHash',
+ 'getMessageDataByDestinationTag',
+ ]:
+ result = json.loads(response)
+ elif apiname in [
+ 'addSubscription',
+ 'deleteSubscription',
+ 'createChan',
+ 'joinChan',
+ 'leaveChan',
+ 'sendMessage',
+ 'sendBroadcast',
+ 'getStatus',
+ 'trashMessage',
+ 'trashInboxMessage',
+ 'trashSentMessageByAckData',
+ 'trashSentMessage',
+ 'addAddressBK',
+ 'addAddressbook',
+ 'delAddressBK',
+ 'deleteAddressbook',
+ 'createRandomAddress',
+ 'getDeterministicAddress',
+ 'deleteAddress',
+ 'disseminatePreEncryptedMsg',
+ 'disseminatePubkey',
+ 'deleteAndVacuum',
+ 'shutdown',
+ ]:
+ result = response
+ else:
+ error = 99
+ errormsg = '\n BMAPIWrapper error: unexpected api. <%s>\n' % apiname
+ except ValueError as err: # json.loads error
+ error = 3
+ result = response
+ errormsg = '\n Server returns unexpected data, maybe a network problem there? (%s)\n' % err
+
+ except TypeError as err: # unsupported XML-RPC protocol
+ error = -1
+ errormsg = '\n XML-RPC not initialed correctly: %s.\n' % str(err)
+ # traceback.print_exc()
+ except (ProxyError, GeneralProxyError, Socks5AuthError, Socks5Error, Socks4Error, HTTPError, socket.error, xmlrpclib.ProtocolError, xmlrpclib.Fault) as err: # (xmlrpclib.Error, ConnectionError, socks.GeneralProxyError)
+ error = -2
+ errormsg = '\n Connection error: %s.\n' % str(err)
+ # traceback.print_exc()
+ except Exception: # /httplib.BadStatusLine: connection close immediatly
+ error = -99
+ errormsg = '\n Unexpected error: %s.\n' % sys.exc_info()[0]
+ # traceback.print_exc()
+
+ # print json.dumps({'error': error, 'result': result, 'errormsg': errormsg})
+ return {'error': error, 'result': result, 'errormsg': errormsg}
+
+ return wrapper
+
+
+class InputException(Exception):
+ def __init__(self, resKey):
+ Exception.__init__(self, resKey)
+ self.resKey = resKey
+
+ def __str__(self):
+ return self.resKey
+
+
+def inputAddress(prompt='What is the address?'):
+
+ global retStrings
+
+ src = retStrings['invalidaddr']
+ while True:
+ address = userInput(prompt + '\nTry again or')
+ if not validAddress(address):
+ print src
+ continue
+ else:
+ break
+
+ return address
+
+
+def inputIndex(prompt='Input a index: ', maximum=-1, alter=[]):
+
+ global retStrings
+
+ while True:
+ cinput = userInput(prompt + '\nTry again, (c) or').lower()
+ try:
+ if cinput == "c":
+ cinput = '-1'
+ raise InputException('usercancel')
+
+ elif cinput in alter:
+ break
+
+ elif int(cinput) < 0 or (maximum >= 0 and int(cinput) > maximum):
+ src = retStrings['invalidindex']
+ print src
+
+ else:
+ break
+
+ except (InputException, KeyboardInterrupt) as err:
+ raise
+
+ except ValueError:
+ src = retStrings['invalidinput']
+ print src
+
+ return cinput
def userInput(message):
"""Checks input for exit or quit. Also formats for input, etc"""
- global usrPrompt
+ global cmdStr, retStrings
- print '\n' + message
- uInput = raw_input('> ')
+ stack = list(inspect.stack())
+ where = ''.join([
+ str(stack[3][2]),
+ stack[3][3],
+ str(stack[2][2]),
+ stack[1][3],
+ str(stack[1][2]),
+ stack[3][3],
+ cmdStr
+ ])
+ print ('\n%s (exit) to cancel.\nPress Enter to input default [%s]: ' % (message, inputs.get(where, '')))
+ uInput = raw_input('>')
if uInput.lower() == 'exit': # Returns the user to the main menu
- usrPrompt = 1
- main()
+ raise InputException('usercancel')
- elif uInput.lower() == 'quit': # Quits the program
- print '\n Bye\n'
- sys.exit(0)
+ elif uInput == '': # Return last value.
+ return inputs.get(where, '')
else:
- return uInput
+ inputs[where] = uInput
-
-def restartBmNotify():
- """Prompt the user to restart Bitmessage"""
- print '\n *******************************************************************'
- print ' WARNING: If Bitmessage is running locally, you must restart it now.'
- print ' *******************************************************************\n'
-
-
-# Begin keys.dat interactions
-
-
-def lookupAppdataFolder():
- """gets the appropriate folders for the .dat files depending on the OS. Taken from bitmessagemain.py"""
-
- APPNAME = "PyBitmessage"
- if sys.platform == 'darwin':
- if "HOME" in os.environ:
- dataFolder = os.path.join(os.environ["HOME"], "Library/Application support/", APPNAME) + '/'
- else:
- print(
- ' Could not find home folder, please report '
- 'this message and your OS X version to the Daemon Github.')
- sys.exit(1)
-
- elif 'win32' in sys.platform or 'win64' in sys.platform:
- dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + '\\'
- else:
- dataFolder = os.path.expanduser(os.path.join("~", ".config/" + APPNAME + "/"))
- return dataFolder
-
-
-def configInit():
- """Initialised the configuration"""
-
- BMConfigParser().add_section('bitmessagesettings')
- # Sets the bitmessage port to stop the warning about the api not properly
- # being setup. This is in the event that the keys.dat is in a different
- # directory or is created locally to connect to a machine remotely.
- BMConfigParser().set('bitmessagesettings', 'port', '8444')
- BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat
-
- with open(keysName, 'wb') as configfile:
- BMConfigParser().write(configfile)
-
- print '\n ' + str(keysName) + ' Initalized in the same directory as daemon.py'
- print ' You will now need to configure the ' + str(keysName) + ' file.\n'
-
-
-def apiInit(apiEnabled):
- """Initialise the API"""
-
- global usrPrompt
- BMConfigParser().read(keysPath)
-
- if apiEnabled is False: # API information there but the api is disabled.
- uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower()
-
- if uInput == "y":
- BMConfigParser().set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat
- with open(keysPath, 'wb') as configfile:
- BMConfigParser().write(configfile)
-
- print 'Done'
- restartBmNotify()
- return True
-
- elif uInput == "n":
- print ' \n************************************************************'
- print ' Daemon will not work when the API is disabled. '
- print ' Please refer to the Bitmessage Wiki on how to setup the API.'
- print ' ************************************************************\n'
- usrPrompt = 1
- main()
-
- else:
- print '\n Invalid Entry\n'
- usrPrompt = 1
- main()
-
- elif apiEnabled: # API correctly setup
- # Everything is as it should be
- return True
-
- else: # API information was not present.
- print '\n ' + str(keysPath) + ' not properly configured!\n'
- uInput = userInput("Would you like to do this now, (Y)es or (N)o?").lower()
-
- if uInput == "y": # User said yes, initalize the api by writing these values to the keys.dat file
- print ' '
-
- apiUsr = userInput("API Username")
- apiPwd = userInput("API Password")
- apiPort = userInput("API Port")
- apiEnabled = userInput("API Enabled? (True) or (False)").lower()
- daemon = userInput("Daemon mode Enabled? (True) or (False)").lower()
-
- if (daemon != 'true' and daemon != 'false'):
- print '\n Invalid Entry for Daemon.\n'
- uInput = 1
- main()
-
- print ' -----------------------------------\n'
-
- # sets the bitmessage port to stop the warning about the api not properly
- # being setup. This is in the event that the keys.dat is in a different
- # directory or is created locally to connect to a machine remotely.
- BMConfigParser().set('bitmessagesettings', 'port', '8444')
- BMConfigParser().set('bitmessagesettings', 'apienabled', 'true')
- BMConfigParser().set('bitmessagesettings', 'apiport', apiPort)
- BMConfigParser().set('bitmessagesettings', 'apiinterface', '127.0.0.1')
- BMConfigParser().set('bitmessagesettings', 'apiusername', apiUsr)
- BMConfigParser().set('bitmessagesettings', 'apipassword', apiPwd)
- BMConfigParser().set('bitmessagesettings', 'daemon', daemon)
- with open(keysPath, 'wb') as configfile:
- BMConfigParser().write(configfile)
-
- print '\n Finished configuring the keys.dat file with API information.\n'
- restartBmNotify()
- return True
-
- elif uInput == "n":
- print '\n ***********************************************************'
- print ' Please refer to the Bitmessage Wiki on how to setup the API.'
- print ' ***********************************************************\n'
- usrPrompt = 1
- main()
- else:
- print ' \nInvalid entry\n'
- usrPrompt = 1
- main()
-
-
-def apiData():
- """TBC"""
-
- global keysName
- global keysPath
- global usrPrompt
-
- BMConfigParser().read(keysPath) # First try to load the config file (the keys.dat file) from the program directory
-
- try:
- BMConfigParser().get('bitmessagesettings', 'port')
- appDataFolder = ''
- except:
- # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory.
- appDataFolder = lookupAppdataFolder()
- keysPath = appDataFolder + keysPath
- BMConfigParser().read(keysPath)
-
- try:
- BMConfigParser().get('bitmessagesettings', 'port')
- except:
- # keys.dat was not there either, something is wrong.
- print '\n ******************************************************************'
- print ' There was a problem trying to access the Bitmessage keys.dat file'
- print ' or keys.dat is not set up correctly'
- print ' Make sure that daemon is in the same directory as Bitmessage. '
- print ' ******************************************************************\n'
-
- uInput = userInput("Would you like to create a keys.dat in the local directory, (Y)es or (N)o?").lower()
-
- if (uInput == "y" or uInput == "yes"):
- configInit()
- keysPath = keysName
- usrPrompt = 0
- main()
- elif (uInput == "n" or uInput == "no"):
- print '\n Trying Again.\n'
- usrPrompt = 0
- main()
- else:
- print '\n Invalid Input.\n'
-
- usrPrompt = 1
- main()
-
- try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after
- BMConfigParser().get('bitmessagesettings', 'apiport')
- BMConfigParser().get('bitmessagesettings', 'apiinterface')
- BMConfigParser().get('bitmessagesettings', 'apiusername')
- BMConfigParser().get('bitmessagesettings', 'apipassword')
-
- except:
- apiInit("") # Initalize the keys.dat file with API information
-
- # keys.dat file was found or appropriately configured, allow information retrieval
- # apiEnabled =
- # apiInit(BMConfigParser().safeGetBoolean('bitmessagesettings','apienabled'))
- # #if false it will prompt the user, if true it will return true
-
- BMConfigParser().read(keysPath) # read again since changes have been made
- apiPort = int(BMConfigParser().get('bitmessagesettings', 'apiport'))
- apiInterface = BMConfigParser().get('bitmessagesettings', 'apiinterface')
- apiUsername = BMConfigParser().get('bitmessagesettings', 'apiusername')
- apiPassword = BMConfigParser().get('bitmessagesettings', 'apipassword')
-
- print '\n API data successfully imported.\n'
-
- # Build the api credentials
- return "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface + ":" + str(apiPort) + "/"
-
-
-# End keys.dat interactions
+ return uInput
def apiTest():
"""Tests the API connection to bitmessage. Returns true if it is connected."""
- try:
- result = api.add(2, 3)
- except:
- return False
-
- return result == 5
-
-
-def bmSettings():
- """Allows the viewing and modification of keys.dat settings."""
-
- global keysPath
- global usrPrompt
-
- keysPath = 'keys.dat'
-
- BMConfigParser().read(keysPath) # Read the keys.dat
- try:
- port = BMConfigParser().get('bitmessagesettings', 'port')
- except:
- print '\n File not found.\n'
- usrPrompt = 0
- main()
-
- startonlogon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startonlogon')
- minimizetotray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'minimizetotray')
- showtraynotifications = BMConfigParser().safeGetBoolean('bitmessagesettings', 'showtraynotifications')
- startintray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startintray')
- defaultnoncetrialsperbyte = BMConfigParser().get('bitmessagesettings', 'defaultnoncetrialsperbyte')
- defaultpayloadlengthextrabytes = BMConfigParser().get('bitmessagesettings', 'defaultpayloadlengthextrabytes')
- daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon')
-
- socksproxytype = BMConfigParser().get('bitmessagesettings', 'socksproxytype')
- sockshostname = BMConfigParser().get('bitmessagesettings', 'sockshostname')
- socksport = BMConfigParser().get('bitmessagesettings', 'socksport')
- socksauthentication = BMConfigParser().safeGetBoolean('bitmessagesettings', 'socksauthentication')
- socksusername = BMConfigParser().get('bitmessagesettings', 'socksusername')
- sockspassword = BMConfigParser().get('bitmessagesettings', 'sockspassword')
-
- print '\n -----------------------------------'
- print ' | Current Bitmessage Settings |'
- print ' -----------------------------------'
- print ' port = ' + port
- print ' startonlogon = ' + str(startonlogon)
- print ' minimizetotray = ' + str(minimizetotray)
- print ' showtraynotifications = ' + str(showtraynotifications)
- print ' startintray = ' + str(startintray)
- print ' defaultnoncetrialsperbyte = ' + defaultnoncetrialsperbyte
- print ' defaultpayloadlengthextrabytes = ' + defaultpayloadlengthextrabytes
- print ' daemon = ' + str(daemon)
- print '\n ------------------------------------'
- print ' | Current Connection Settings |'
- print ' -----------------------------------'
- print ' socksproxytype = ' + socksproxytype
- print ' sockshostname = ' + sockshostname
- print ' socksport = ' + socksport
- print ' socksauthentication = ' + str(socksauthentication)
- print ' socksusername = ' + socksusername
- print ' sockspassword = ' + sockspassword
- print ' '
-
- uInput = userInput("Would you like to modify any of these settings, (Y)es or (N)o?").lower()
-
- if uInput == "y":
- while True: # loops if they mistype the setting name, they can exit the loop with 'exit'
- invalidInput = False
- uInput = userInput("What setting would you like to modify?").lower()
- print ' '
-
- if uInput == "port":
- print ' Current port number: ' + port
- uInput = userInput("Enter the new port number.")
- BMConfigParser().set('bitmessagesettings', 'port', str(uInput))
- elif uInput == "startonlogon":
- print ' Current status: ' + str(startonlogon)
- uInput = userInput("Enter the new status.")
- BMConfigParser().set('bitmessagesettings', 'startonlogon', str(uInput))
- elif uInput == "minimizetotray":
- print ' Current status: ' + str(minimizetotray)
- uInput = userInput("Enter the new status.")
- BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(uInput))
- elif uInput == "showtraynotifications":
- print ' Current status: ' + str(showtraynotifications)
- uInput = userInput("Enter the new status.")
- BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str(uInput))
- elif uInput == "startintray":
- print ' Current status: ' + str(startintray)
- uInput = userInput("Enter the new status.")
- BMConfigParser().set('bitmessagesettings', 'startintray', str(uInput))
- elif uInput == "defaultnoncetrialsperbyte":
- print ' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte
- uInput = userInput("Enter the new defaultnoncetrialsperbyte.")
- BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput))
- elif uInput == "defaultpayloadlengthextrabytes":
- print ' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes
- uInput = userInput("Enter the new defaultpayloadlengthextrabytes.")
- BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput))
- elif uInput == "daemon":
- print ' Current status: ' + str(daemon)
- uInput = userInput("Enter the new status.").lower()
- BMConfigParser().set('bitmessagesettings', 'daemon', str(uInput))
- elif uInput == "socksproxytype":
- print ' Current socks proxy type: ' + socksproxytype
- print "Possibilities: 'none', 'SOCKS4a', 'SOCKS5'."
- uInput = userInput("Enter the new socksproxytype.")
- BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(uInput))
- elif uInput == "sockshostname":
- print ' Current socks host name: ' + sockshostname
- uInput = userInput("Enter the new sockshostname.")
- BMConfigParser().set('bitmessagesettings', 'sockshostname', str(uInput))
- elif uInput == "socksport":
- print ' Current socks port number: ' + socksport
- uInput = userInput("Enter the new socksport.")
- BMConfigParser().set('bitmessagesettings', 'socksport', str(uInput))
- elif uInput == "socksauthentication":
- print ' Current status: ' + str(socksauthentication)
- uInput = userInput("Enter the new status.")
- BMConfigParser().set('bitmessagesettings', 'socksauthentication', str(uInput))
- elif uInput == "socksusername":
- print ' Current socks username: ' + socksusername
- uInput = userInput("Enter the new socksusername.")
- BMConfigParser().set('bitmessagesettings', 'socksusername', str(uInput))
- elif uInput == "sockspassword":
- print ' Current socks password: ' + sockspassword
- uInput = userInput("Enter the new password.")
- BMConfigParser().set('bitmessagesettings', 'sockspassword', str(uInput))
- else:
- print "\n Invalid input. Please try again.\n"
- invalidInput = True
-
- if invalidInput is not True: # don't prompt if they made a mistake.
- uInput = userInput("Would you like to change another setting, (Y)es or (N)o?").lower()
-
- if uInput != "y":
- print '\n Changes Made.\n'
- with open(keysPath, 'wb') as configfile:
- BMConfigParser().write(configfile)
- restartBmNotify()
- break
-
- elif uInput == "n":
- usrPrompt = 1
- main()
- else:
- print "Invalid input."
- usrPrompt = 1
- main()
+ response = api.add(2, 3)
+ return response['result'] == 5 if response['error'] == 0 else False
def validAddress(address):
"""Predicate to test address validity"""
- address_information = json.loads(api.decodeAddress(address))
- return 'success' in str(address_information['status']).lower()
+ print ' Validating...', address
+ response = api.decodeAddress(address)
+ if response['error'] != 0:
+ print response['errormsg']
+ return False
+
+ return 'success' in response['result']['status'].lower()
def getAddress(passphrase, vNumber, sNumber):
"""Get a deterministic address"""
- passphrase = passphrase.encode('base64') # passphrase must be encoded
- return api.getDeterministicAddress(passphrase, vNumber, sNumber)
+ passPhrase = passphrase.encode('base64') # passphrase must be encoded
+ print ' Getting address:', passphrase
+ response = api.getDeterministicAddress(passPhrase, vNumber, sNumber)
+ if response['error'] != 0:
+ return response['errormsg']
+
+ print ' Address:', response['result']
-def subscribe():
+def subscribe(address, label):
"""Subscribe to an address"""
- global usrPrompt
- while True:
- address = userInput("What address would you like to subscribe to?")
-
- if address == "c":
- usrPrompt = 1
- print ' '
- main()
- elif validAddress(address) is False:
- print '\n Invalid. "c" to cancel. Please try again.\n'
- else:
- break
-
- label = userInput("Enter a label for this address.")
label = label.encode('base64')
+ print ' Subscribing address:', label
+ response = api.addSubscription(address, label)
+ if response['error'] != 0:
+ return response['errormsg']
- api.addSubscription(address, label)
- print '\n You are now subscribed to: ' + address + '\n'
+ return '\n ' + response['result']
-def unsubscribe():
+def unsubscribe(address):
"""Unsusbcribe from an address"""
- global usrPrompt
- while True:
- address = userInput("What address would you like to unsubscribe from?")
+ print ' unSubscribing address:', address
+ response = api.deleteSubscription(address)
+ if response['error'] != 0:
+ return response['errormsg']
- if address == "c":
- usrPrompt = 1
- print ' '
- main()
- elif validAddress(address) is False:
- print '\n Invalid. "c" to cancel. Please try again.\n'
- else:
- break
-
- userInput("Are you sure, (Y)es or (N)o?").lower() # uInput =
-
- api.deleteSubscription(address)
- print '\n You are now unsubscribed from: ' + address + '\n'
+ return '\n ' + response['result']
def listSubscriptions():
"""List subscriptions"""
- global usrPrompt
- print '\nLabel, Address, Enabled\n'
- try:
- print api.listSubscriptions()
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
- print ' '
+ print ' Subscribed list retrieving...'
+ response = api.listSubscriptions()
+ if response['error'] != 0:
+ return response['errormsg']
+
+ jsonAddresses = response['result']
+ numAddresses = len(jsonAddresses)
+ print
+ print ' ------------------------------------------------------------------------'
+ print ' | # | Label | Address |Enabled|'
+ print ' |----|--------------------|------------------------------------|-------|'
+ for addNum in range(0, numAddresses): # processes all of the addresses and lists them out
+ label = (jsonAddresses[addNum]['label'].decode('base64')).encode(
+ 'utf-8') # may still misdiplay in some consoles
+ address = str(jsonAddresses[addNum]['address'])
+ enabled = str(jsonAddresses[addNum]['enabled'])
+
+ if len(label) > 19:
+ label = label[:16] + '...'
+
+ print '| '.join([' ', str(addNum).ljust(3), label.ljust(19), address.ljust(35), enabled.ljust(6), '', ])
+
+ print ''.join([' ', 72 * '-', '\n', ])
+
+ return ''
-def createChan():
+def createChan(password):
"""Create a channel"""
- global usrPrompt
- password = userInput("Enter channel name")
- password = password.encode('base64')
- try:
- print api.createChan(password)
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
+ base64 = password.encode('base64')
+ print ' Channel creating...', password
+ response = api.createChan(base64)
+ if response['error'] != 0:
+ return response['errormsg']
+
+ return '\n ' + response['result']
def joinChan():
"""Join a channel"""
- global usrPrompt
- while True:
- address = userInput("Enter channel address")
+ uInput = ''
+ address = inputAddress('Enter channel address')
+ while uInput == '':
+ uInput = userInput('Enter channel name[1~]')
+ password = uInput.encode('base64')
- if address == "c":
- usrPrompt = 1
- print ' '
- main()
- elif validAddress(address) is False:
- print '\n Invalid. "c" to cancel. Please try again.\n'
- else:
- break
+ print ' Channel joining...', uInput
+ response = api.joinChan(password, address)
+ if response['error'] != 0:
+ return response['errormsg']
- password = userInput("Enter channel name")
- password = password.encode('base64')
- try:
- print api.joinChan(password, address)
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
+ return '\n ' + response['result']
def leaveChan():
"""Leave a channel"""
- global usrPrompt
- while True:
- address = userInput("Enter channel address")
+ address = inputAddress("Enter channel address")
+ print ' Channel leaving...', 'address'
+ response = api.leaveChan(address)
+ if response['error'] != 0:
+ return response['errormsg']
- if address == "c":
- usrPrompt = 1
- print ' '
- main()
- elif validAddress(address) is False:
- print '\n Invalid. "c" to cancel. Please try again.\n'
- else:
- break
-
- try:
- print api.leaveChan(address)
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
+ return '\n ' + response['result']
def listAdd():
"""List all of the addresses and their info"""
- global usrPrompt
- try:
- jsonAddresses = json.loads(api.listAddresses())
- numAddresses = len(jsonAddresses['addresses']) # Number of addresses
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
- # print '\nAddress Number,Label,Address,Stream,Enabled\n'
- print '\n --------------------------------------------------------------------------'
- print ' | # | Label | Address |S#|Enabled|'
- print ' |---|-------------------|-------------------------------------|--|-------|'
+ print ' Retrieving...', 'Senders'
+ response = api.listAddresses()
+ if response['error'] != 0:
+ return response['errormsg']
+
+ jsonAddresses = response['result']
+ numAddresses = len(jsonAddresses) # Number of addresses
+ # print '\nAddress Index,Label,Address,Stream,Enabled\n'
+ print
+ print ' ------------------------------------------------------------------------------'
+ print ' | # | Label | Address |S# |Enabled|'
+ print ' |----|--------------------|--------------------------------------|---|-------|'
for addNum in range(0, numAddresses): # processes all of the addresses and lists them out
- label = (jsonAddresses['addresses'][addNum]['label']).encode(
- 'utf') # may still misdiplay in some consoles
- address = str(jsonAddresses['addresses'][addNum]['address'])
- stream = str(jsonAddresses['addresses'][addNum]['stream'])
- enabled = str(jsonAddresses['addresses'][addNum]['enabled'])
+ label = jsonAddresses[addNum]['label'].encode(
+ 'utf-8') # may still misdiplay in some consoles
+ address = str(jsonAddresses[addNum]['address'])
+ stream = str(jsonAddresses[addNum]['stream'])
+ enabled = str(jsonAddresses[addNum]['enabled'])
if len(label) > 19:
label = label[:16] + '...'
- print ''.join([
- ' |',
- str(addNum).ljust(3),
- '|',
- label.ljust(19),
- '|',
- address.ljust(37),
- '|',
- stream.ljust(1),
- '|',
- enabled.ljust(7),
- '|',
- ])
+ print '| '.join([' ', str(addNum).ljust(3), label.ljust(19), address.ljust(37), stream.ljust(2), enabled.ljust(6), '', ])
- print ''.join([
- ' ',
- 74 * '-',
- '\n',
- ])
+ print ''.join([' ', 78 * '-', '\n', ])
+
+ return ''
def genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe):
"""Generate address"""
- global usrPrompt
-
if deterministic is False: # Generates a new address with the user defined label. non-deterministic
addressLabel = lbl.encode('base64')
- try:
- generatedAddress = api.createRandomAddress(addressLabel)
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
+ print ' Address requesting...', lbl
+ response = api.createRandomAddress(addressLabel)
+ if response['error'] != 0:
+ return response['errormsg']
- return generatedAddress
+ else: # Generates a new deterministic address with the user inputs.
+ passPhrase = passphrase.encode('base64')
+ print ' Address deterministic...', passphrase
+ response = api.createDeterministicAddresses(passPhrase, numOfAdd, addVNum, streamNum, ripe)
+ if response['error'] != 0:
+ return response['errormsg']
- elif deterministic: # Generates a new deterministic address with the user inputs.
- passphrase = passphrase.encode('base64')
- try:
- generatedAddress = api.createDeterministicAddresses(passphrase, numOfAdd, addVNum, streamNum, ripe)
- except:
- print '\n Connection Error\n'
- usrPrompt = 0
- main()
- return generatedAddress
-
- return 'Entry Error'
+ return '\n Address:', response['result']
-def saveFile(fileName, fileData):
+def getBase64Len(x=''):
+ return int(len(x)*(3/4)) - 2 if x[-2:] == '==' else 1 if x[-1] == '=' else 0
+
+
+def dump2File(fileName, fileData, deCoded):
"""Allows attachments and messages/broadcats to be saved"""
+ global inputShorts
+
# This section finds all invalid characters and replaces them with ~
- fileName = fileName.replace(" ", "")
- fileName = fileName.replace("/", "~")
- # fileName = fileName.replace("\\", "~") How do I get this to work...?
- fileName = fileName.replace(":", "~")
- fileName = fileName.replace("*", "~")
- fileName = fileName.replace("?", "~")
- fileName = fileName.replace('"', "~")
- fileName = fileName.replace("<", "~")
- fileName = fileName.replace(">", "~")
- fileName = fileName.replace("|", "~")
+ for s in ' /\\:*?"<>|':
+ fileName = fileName.replace(s, '~')
directory = os.path.abspath('attachments')
if not os.path.exists(directory):
- os.makedirs(directory)
+ try:
+ os.makedirs(directory)
+
+ except OSError as err:
+ return '\n %s.\n' % str(err)
+ # return '\n Failed creating ' + directory + '\n'
+ except Exception:
+ return '\n Unexpected error: %s.\n' % sys.exc_info()[0]
filePath = os.path.join(directory, fileName)
- with open(filePath, 'wb+') as path_to_file:
- path_to_file.write(fileData.decode("base64"))
- print '\n Successfully saved ' + filePath + '\n'
+ if not deCoded:
+ x = filter(lambda z: not re.match(r'^\s*$', z), fileData)
+ trydecode = False
+ if len(x) % 4 == 0: # check by length before decode.
+ trydecode = True
+ else:
+ print '\n'.join([
+ ' -----------------------------------',
+ ' Contents seems not "BASE64" encoded. (base on length check)',
+ ' Start[%d] ~ Ends[%d].' % (x[:3], x[-3:]),
+ ' About: %d(bytes).' % getBase64Len(x),
+ ' FileName: "%s"' % fileName,
+ ])
+ uInput = userInput('Try to decode it anyway, (n)o or (Y)es?')
+ if uInput not in inputShorts['no']:
+ trydecode = True
+
+ if trydecode is True:
+ try:
+ y = x.decode('base64', 'strict')
+ if x == y.encode('base64').replace('\n', ''): # double check decoded string.
+ fileData = y
+ else:
+ print '\n Failed on "BASE64" re-encode checking.\n'
+
+ except ValueError:
+ return '\n Failed on "BASE64" decoding.\n'
+ else:
+ print '\n Not "BASE64" contents, dump to file directly.'
+
+ try:
+ with open(filePath, 'wb+') as path_to_file:
+ path_to_file.write(fileData)
+
+ except IOError as err:
+ return '\n %s.\n' % str(err)
+ # return '\n Failed on operating: "' + filePath + '"\n'
+ except Exception:
+ return '\n Unexpected error: %s.\n' % sys.exc_info()[0]
+
+ return ' Successfully saved to: "' + filePath + '"'
def attachment():
@@ -654,20 +1161,21 @@ def attachment():
theAttachmentS = ''
- while True:
+ global inputShorts
+ for counter in range(1, 3): # maximum 3 of attachments
isImage = False
theAttachment = ''
while True: # loops until valid path is entered
filePath = userInput(
- '\nPlease enter the path to the attachment or just the attachment name if in this folder.')
+ '\nPlease enter the path to the attachment or just the attachment name if in this folder[Max:180MB], %d/3 allowed.' % counter)
try:
with open(filePath):
break
except IOError:
- print '\n %s was not found on your filesystem or can not be opened.\n' % filePath
+ print '\n Failed open file on: ', filePath + '\n'
# print filesize, and encoding estimate with confirmation if file is over X size (1mb?)
invSize = os.path.getsize(filePath)
@@ -675,26 +1183,22 @@ def attachment():
round(invSize, 2) # Rounds to two decimal places
if invSize > 500.0: # If over 500KB
- print ''.join([
- '\n WARNING:The file that you are trying to attach is ',
- invSize,
- 'KB and will take considerable time to send.\n'
- ])
- uInput = userInput('Are you sure you still want to attach it, (Y)es or (N)o?').lower()
+ print '\n WARNING:The file that you are trying to attach is %d(KB) and will take considerable time to send.\n' % invSize
+ uInput = userInput('Are you sure you still want to attach it, (y)es or (N)o?').lower()
+
+ if uInput not in inputShorts['yes']:
+ return '\n Attachment discarded.'
- if uInput != "y":
- print '\n Attachment discarded.\n'
- return ''
elif invSize > 184320.0: # If larger than 180MB, discard.
- print '\n Attachment too big, maximum allowed size:180MB\n'
- main()
+ return '\n Attachment too big, maximum allowed size:180MB\n'
pathLen = len(str(ntpath.basename(filePath))) # Gets the length of the filepath excluding the filename
fileName = filePath[(len(str(filePath)) - pathLen):] # reads the filename
filetype = imghdr.what(filePath) # Tests if it is an image file
if filetype is not None:
- print '\n ---------------------------------------------------'
+ print
+ print ' ---------------------------------------------------'
print ' Attachment detected as an Image.'
print ' tags will automatically be included,'
print ' allowing the recipient to view the image'
@@ -704,7 +1208,7 @@ def attachment():
time.sleep(2)
# Alert the user that the encoding process may take some time.
- print '\n Encoding Attachment, Please Wait ...\n'
+ print ' Encoding Attachment, Please Wait ...'
with open(filePath, 'rb') as f: # Begin the actual encoding
data = f.read(188743680) # Reads files up to 180MB, the maximum size for Bitmessage.
@@ -713,81 +1217,67 @@ def attachment():
if isImage: # If it is an image, include image tags in the message
theAttachment = """
-
+
-Filename:%s
-Filesize:%sKB
-Encoding:base64
+Filename: %s
+Filesize: %sKB
+Encoding: base64