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
- %s + %s
""" % (fileName, invSize, fileName, filetype, data) else: # Else it is not an image so do not include the embedded image code. theAttachment = """ - + Filename:%s Filesize:%sKB Encoding:base64 -""" % (fileName, invSize, fileName, fileName, data) +""" % (fileName, invSize, fileName, fileName, data) - uInput = userInput('Would you like to add another attachment, (Y)es or (N)o?').lower() + uInput = userInput('Would you like to add another attachment, (y)es or (N)o?').lower() - if uInput == 'y' or uInput == 'yes': # Allows multiple attachments to be added to one message + if uInput in inputShorts['yes']: # Allows multiple attachments to be added to one message theAttachmentS = str(theAttachmentS) + str(theAttachment) + '\n\n' - elif uInput == 'n' or uInput == 'no': + else: break theAttachmentS = theAttachmentS + theAttachment return theAttachmentS -def sendMsg(toAddress, fromAddress, subject, message): +def sendMsg(toAddress, fromAddress, subject=None, message=None): """ With no arguments sent, sendMsg fills in the blanks. subject and message must be encoded before they are passed. """ - global usrPrompt - if validAddress(toAddress) is False: - while True: - toAddress = userInput("What is the To Address?") + global retStrings, inputShorts - if toAddress == "c": - usrPrompt = 1 - print ' ' - main() - elif validAddress(toAddress) is False: - print '\n Invalid Address. "c" to cancel. Please try again.\n' - else: - break + if validAddress(toAddress) is False: + toAddress = inputAddress("What is the To Address indeed?") if validAddress(fromAddress) is False: - try: - jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print ' Sender retrieving...', fromAddress + response = api.listAddresses() + if response['error'] != 0: + return response['errormsg'] + + jsonAddresses = response['result'] + numAddresses = len(jsonAddresses) # Number of addresses if numAddresses > 1: # Ask what address to send from if multiple addresses found = False while True: - print ' ' - fromAddress = userInput("Enter an Address or Address Label to send from.") - - if fromAddress == "exit": - usrPrompt = 1 - main() + fromAddress = userInput('Enter an Address or Address Label to send from.') for addNum in range(0, numAddresses): # processes all of the addresses - label = jsonAddresses['addresses'][addNum]['label'] - address = jsonAddresses['addresses'][addNum]['address'] + label = jsonAddresses[addNum]['label'] + address = jsonAddresses[addNum]['address'] if fromAddress == label: # address entered was a label and is found fromAddress = address found = True @@ -799,7 +1289,7 @@ def sendMsg(toAddress, fromAddress, subject, message): else: for addNum in range(0, numAddresses): # processes all of the addresses - address = jsonAddresses['addresses'][addNum]['address'] + address = jsonAddresses[addNum]['address'] if fromAddress == address: # address entered was a found in our addressbook. found = True break @@ -812,55 +1302,70 @@ def sendMsg(toAddress, fromAddress, subject, message): else: # Only one address in address book print '\n Using the only address in the addressbook to send from.\n' - fromAddress = jsonAddresses['addresses'][0]['address'] + fromAddress = jsonAddresses[0]['address'] - if subject == '': - subject = userInput("Enter your Subject.") + if subject is None: + subject = userInput('Enter your Subject.') subject = subject.encode('base64') - if message == '': - message = userInput("Enter your Message.") - uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() - if uInput == "y": + if message is None: + while True: + try: + message += '\n' + raw_input('Continue enter your message line by line, end with .\n>') + except EOFError: + break + + uInput = userInput('Would you like to add an attachment, (y)es or (N)o?').lower() + if uInput in inputShorts['yes']: message = message + '\n\n' + attachment() message = message.encode('base64') - try: + while True: + print ' Message sending...', subject.decode('base64') ackData = api.sendMessage(toAddress, fromAddress, subject, message) - print '\n Message Status:', api.getStatus(ackData), '\n' - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + if ackData['error'] == 1: + return ackData['errormsg'] + elif ackData['error'] != 0: + print ackData['errormsg'] + uInput = userInput('Would you like to try again, (n)o or (Y)es?').lower() + if uInput in inputShorts['no']: + break + + if ackData['error'] == 0: + print ' Fetching send status...' + status = api.getStatus(ackData['result']) + if status['error'] == 1: + return status['errormsg'] + elif status['error'] != 0: + print status['errormsg'] + else: + return ' Message Status:' + status['result'] + + return '' -def sendBrd(fromAddress, subject, message): +def sendBrd(fromAddress=None, subject=None, message=None): """Send a broadcast""" - global usrPrompt - if fromAddress == '': + global inputShorts + if fromAddress is None: + print ' Retrieving...', 'Senders' + response = api.listAddresses() + if response['error'] != 0: + return response['errormsg'] - try: - jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + jsonAddresses = response['result'] + numAddresses = len(jsonAddresses) # Number of addresses if numAddresses > 1: # Ask what address to send from if multiple addresses found = False while True: - fromAddress = userInput("\nEnter an Address or Address Label to send from.") - - if fromAddress == "exit": - usrPrompt = 1 - main() + fromAddress = userInput('Enter an Address or Address Label to send from.') for addNum in range(0, numAddresses): # processes all of the addresses - label = jsonAddresses['addresses'][addNum]['label'] - address = jsonAddresses['addresses'][addNum]['address'] + label = jsonAddresses[addNum]['label'] + address = jsonAddresses[addNum]['address'] if fromAddress == label: # address entered was a label and is found fromAddress = address found = True @@ -872,7 +1377,7 @@ def sendBrd(fromAddress, subject, message): else: for addNum in range(0, numAddresses): # processes all of the addresses - address = jsonAddresses['addresses'][addNum]['address'] + address = jsonAddresses[addNum]['address'] if fromAddress == address: # address entered was a found in our addressbook. found = True break @@ -884,432 +1389,744 @@ def sendBrd(fromAddress, subject, message): break # Address was found else: # Only one address in address book - print '\n Using the only address in the addressbook to send from.\n' - fromAddress = jsonAddresses['addresses'][0]['address'] + print ' Using the only address in the addressbook to send from.\n' + fromAddress = jsonAddresses[0]['address'] - if subject == '': - subject = userInput("Enter your Subject.") - subject = subject.encode('base64') - if message == '': - message = userInput("Enter your Message.") + if subject is None: + subject = userInput('Enter your Subject.') + subject = subject.encode('base64') + if message is None: + while True: + try: + message += '\n' + raw_input('Continue enter your message line by line, end with .\n>') + except EOFError: + break - uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() - if uInput == "y": + uInput = userInput('Would you like to add an attachment, (y)es or (N)o?').lower() + if uInput in inputShorts['yes']: message = message + '\n\n' + attachment() message = message.encode('base64') - try: + while True: + print ' Broadcast message sending...' ackData = api.sendBroadcast(fromAddress, subject, message) - print '\n Message Status:', api.getStatus(ackData), '\n' - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + if ackData['error'] == 1: + return ackData['errormsg'] + elif status['error'] != 0: + print '\n %s.\n' % str(err) + uInput = userInput('Would you like to try again, no or (Y)es?').lower() + if uInput in inputShorts['no']: + break + + if ackData['error'] == 0: + print ' Fetching send status...' + status = api.getStatus(ackData['result']) + if status['error'] == 1: + return status['errormsg'] + elif status['error'] != 0: + print status['errormsg'] + else: + return ' Message Status:' + status['result'] + + return '' -def inbox(unreadOnly=False): - """Lists the messages by: Message Number, To Address Label, From Address Label, Subject, Received Time)""" +def inbox(unreadOnly=False, pageNum=20): + """Lists the messages by: message index, To Address Label, From Address Label, Subject, Received Time)""" - global usrPrompt - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - numMessages = len(inboxMessages['inboxMessages']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print ' Inbox index fetching...' + response = api.getAllInboxMessageIDs() + if response['error'] != 0: + return response['errormsg'] + messageIds = response['result'] + numMessages = len(messageIds) messagesPrinted = 0 messagesUnread = 0 - for msgNum in range(0, numMessages): # processes all of the messages in the inbox - message = inboxMessages['inboxMessages'][msgNum] - # if we are displaying all messages or if this message is unread then display it - if not unreadOnly or not message['read']: - print ' -----------------------------------\n' - print ' Message Number:', msgNum # Message Number - print ' To:', getLabelForAddress(message['toAddress']) # Get the to address - print ' From:', getLabelForAddress(message['fromAddress']) # Get the from address - print ' Subject:', message['subject'].decode('base64') # Get the subject - print ''.join([ - ' Received:', - datetime.datetime.fromtimestamp( - float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ]) - messagesPrinted += 1 - if not message['read']: - messagesUnread += 1 + for msgNum in range(numMessages - 1, -1, -1): # processes all of the messages in the inbox + messageID = messageIds[msgNum]['msgid'] + print ' -----------------------------------' + print ' Inbox message retrieving...', messageID + response = api.getInboxMessageByID(messageID) + if response['error'] == 1: + return response['errormsg'] + elif response['error'] != 0: + print '\n Retrieve failed on:', msgNum, messageID + print response['errormsg'] + else: + message = response['result'][0] + # if we are displaying all messages or if this message is unread then display it + if not unreadOnly or not message['read']: + print ' -----------------------------------' + print ' Inbox index: %d/%d' % (msgNum, numMessages - 1) # message index + print ' Message ID:', message['msgid'] + print ' Read:', message['read'] + print ' To:', getLabelForAddress(message['toAddress']) # Get the to address + print ' From:', getLabelForAddress(message['fromAddress']) # Get the from address + print ' Subject:', message['subject'].decode('base64') # Get the subject + print ' Received:', datetime.datetime.fromtimestamp(float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Base64Len:', str(len(message['message'])) + messagesPrinted += 1 + if not message['read']: + messagesUnread += 1 - if messagesPrinted % 20 == 0 and messagesPrinted != 0: - userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput = + if messagesPrinted % pageNum == 0 and messagesPrinted != 0: + try: + uInput = userInput('Paused on %d/%d, next [%d],' % (msgNum, numMessages - 1, msgNum - pageNum if msgNum > pageNum else 0)) - print '\n -----------------------------------' + except InputException as err: + raise InputException(str(err)) + + print ' -----------------------------------' print ' There are %d unread messages of %d messages in the inbox.' % (messagesUnread, numMessages) - print ' -----------------------------------\n' + print ' -----------------------------------' + + return '' -def outbox(): +def outbox(pageNum=20): """TBC""" - global usrPrompt - try: - outboxMessages = json.loads(api.getAllSentMessages()) - numMessages = len(outboxMessages['sentMessages']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print ' All outbox messages downloading...' + response = api.getAllSentMessages() + if response['error'] != 0: + return response['errormsg'] - for msgNum in range(0, numMessages): # processes all of the messages in the outbox - print '\n -----------------------------------\n' - print ' Message Number:', msgNum # Message Number - # print ' Message ID:', outboxMessages['sentMessages'][msgNum]['msgid'] - print ' To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress']) # Get the to address + outboxMessages = response['result'] + numMessages = len(outboxMessages) + for msgNum in range(numMessages - 1, -1, -1): # processes all of the messages in the outbox + message = outboxMessages[msgNum] + print ' -----------------------------------' + print ' Outbox index: %d/%d' % (msgNum, numMessages - 1) # message index + print ' Message ID:', message['msgid'] + print ' To:', getLabelForAddress(message['toAddress']) # Get the to address # Get the from address - print ' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress']) - print ' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') # Get the subject - print ' Status:', outboxMessages['sentMessages'][msgNum]['status'] # Get the subject + print ' From:', getLabelForAddress(message['fromAddress']) + print ' Subject:', message['subject'].decode('base64') # Get the subject + print ' Status:', message['status'] # Get the status + print ' Ack:', message['ackData'] # Get the ackData + print ' Last Action Time:', datetime.datetime.fromtimestamp(float(message['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Base64Len:', str(len(message['message'])) - print ''.join([ - ' Last Action Time:', - datetime.datetime.fromtimestamp( - float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ]) + if msgNum % pageNum == 0 and msgNum != 0: + uInput = userInput('Paused on %d/%d, next [%d],' % (msgNum, numMessages - 1, msgNum - pageNum if msgNum > pageNum else 0)) - if msgNum % 20 == 0 and msgNum != 0: - userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput = - - print '\n -----------------------------------' + print ' -----------------------------------' print ' There are ', numMessages, ' messages in the outbox.' print ' -----------------------------------\n' + return '' -def readSentMsg(msgNum): + +def attDetect(content=None, textmsg=None, attPrefix=None, askSave=True): + + global inputShorts + + attPos = msgPos = 0 + # Hard way search attachments + for counter in range(1, 3): # Allows maximum 3 of attachments to be downloaded/saved + try: + attPos = content.index(';base64,', attPos) + 9 # Finds the attachment position + attEndPos = content.index('/>', attPos) - 1 # Finds the end of the attachment + + attPre = attPos - 9 # back for ;base64, | = (msgPos + 12): + if '<' == content[attPre - 12]: + pBack = 12 + elif attPre - msgPos + 5 >= 0: + if '<' == content[attPre - 5]: + pBack = 5 + attPre = attPre - pBack + + try: + fnPos = content.index('alt="', msgPos, attPos) + 5 # Finds position of the filename + fnEndPos = content.index('" src=', msgPos, attPos) # Finds the end position + # fnLen = fnEndPos - fnPos #Finds the length of the filename + fn = content[fnPos:fnEndPos] + + attPre = fnPos - 5 # back for alt=" |  | <attachment alt== (msgPos + 12): + if '<' == content[attPre - 12]: + pBack = 12 + elif attPre - msgPos + 5 >= 0: + if '<' == content[attPre - 5]: + pBack = 5 + attPre = attPre - pBack + + except ValueError: + fn = 'notdetected' + fn = '%d_attachment_%d_%s' % (attPrefix, attPos, fn) + + this_attachment = content[attPos:attEndPos] + x = filter(lambda z: not re.match(r'^\s*$', z), this_attachment) + # x = x.replace('\n', '').strip() + trydecode = False + if len(x) % 4 == 0: # check by length before decode. + trydecode = True + else: + print '\n'.join([ + ' -----------------------------------', + ' Embeded mesaage seems not "BASE64" encoded. (base on length check)', + ' Offset: %d, about: %d(bytes).' % (attPos, (int(len(x)*3/4)) - 2 if x[-2:] == '==' else 1 if x[-1] == '=' else 0), + ' Start[%d] ~ Ends[%d].' % (x[:3], x[-3:]), + ' FileName: "%s"' % fn, + ]) + uInput = userInput('Try to decode 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. + print ' This embeded message decoded successfully:', fn + if askSave is True: + uInput = userInput('Download the "decoded" attachment, (y)es or (No)?\nName(%d): %s,' % (counter, fn)).lower() + if uInput in inputShorts['yes']: + src = dump2File(fn, y, True) + else: + src = dump2File(fn, y, True) + + print src + attmsg = '\n'.join([ + ' -----------------------------------', + ' Attachment: "%s"' % fn, + ' Size: %d(bytes)' % getBase64Len(x), + ' -----------------------------------', + ]) + # remove base64 and '' stuff + textmsg = textmsg + content[msgPos:attPre] + attmsg + attEndPos += 3 + msgPos = attEndPos + else: + print '\n Failed on decode this embeded "BASE64" like message on re-encode check.\n' + + except ValueError: + pass + print '\n Failed on decode this emdeded "BASE64" encoded like message.\n' + except Exception: + print '\n Unexpected error: %s.\n' % sys.exc_info()[0] + + else: + print '\n Skiped a embeded "BASE64" encoded like message.' + + if attEndPos != msgPos: + textmsg = textmsg + content[msgPos:attEndPos] + msgPos = attEndPos + + except ValueError: + textmsg = textmsg + content[msgPos:] + break + except Exception: + print '\n Unexpected error: %s.\n' % sys.exc_info()[0] + + return textmsg + + +def readSentMsg(cmd='read', msgNum=-1, messageID=None, trunck=380, withAtta=False): """Opens a sent message for reading""" - global usrPrompt - try: - outboxMessages = json.loads(api.getAllSentMessages()) - numMessages = len(outboxMessages['sentMessages']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print ' All outbox messages downloading...', str(msgNum) + if messageID is None: + response = api.getAllSentMessages() + if response['error'] != 0: + return response['errormsg'] - print ' ' + message = response['result'][msgNum] + else: + response = api.getSentMessageByID(messageID) + if response['error'] != 0: + return response['errormsg'] - if msgNum >= numMessages: - print '\n Invalid Message Number.\n' - main() + message = response['result'][0] - # Begin attachment detection - message = outboxMessages['sentMessages'][msgNum]['message'].decode('base64') + subject = message['subject'].decode('base64') + content = message['message'].decode('base64') + full = len(content) + textmsg = '' + textmsg = content if withAtta else attDetect(content, textmsg, 'outbox_' + subject, cmd != 'save') - while True: # Allows multiple messages to be downloaded/saved - if ';base64,' in message: # Found this text in the message, there is probably an attachment. - attPos = message.index(";base64,") # Finds the attachment position - attEndPos = message.index("' />") # Finds the end of the attachment - # attLen = attEndPos - attPos #Finds the length of the message - - if 'alt = "' in message: # We can get the filename too - fnPos = message.index('alt = "') # Finds position of the filename - fnEndPos = message.index('" src=') # Finds the end position - # fnLen = fnEndPos - fnPos #Finds the length of the filename - - fileName = message[fnPos + 7:fnEndPos] - else: - fnPos = attPos - fileName = 'Attachment' - - uInput = userInput( - '\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() - if uInput == "y" or uInput == 'yes': - - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - message = message[:fnPos] + '~~' + message[(attEndPos + 4):] - - else: - break - - # End attachment Detection - - print '\n To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress']) # Get the to address + print ' ', 74 * '-' + print ' Message index:', str(msgNum) # message outdex + print ' Message ID:', message['msgid'] + print ' To:', getLabelForAddress(message['toAddress']) # Get the to address # Get the from address - print ' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress']) - print ' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') # Get the subject - print ' Status:', outboxMessages['sentMessages'][msgNum]['status'] # Get the subject - print ''.join([ - ' Last Action Time:', - datetime.datetime.fromtimestamp( - float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ]) + print ' From:', getLabelForAddress(message['fromAddress']) + print ' Subject:', subject # Get the subject + print ' Status:', message['status'] # Get the status + print ' Ack:', message['ackData'] # Get the ackData + + print ' Last Action Time:', datetime.datetime.fromtimestamp(float(message['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Length: %d/%d' % (trunck if trunck <= full else full, full) print ' Message:\n' - print message # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') - print ' ' + print textmsg if trunck < 0 or len(textmsg) <= trunck else textmsg[:trunck] + '\n\n ~< MESSAGE TOO LONG TRUNCKED TO SHOW >~' + print ' ', 74 * '-' + + if cmd == 'save': + ret = dump2File('outbox_' + subject, textmsg, withAtta) + print ret + + return '' -def readMsg(msgNum): +def readMsg(cmd='read', msgNum=-1, messageID=None, trunck=380, withAtta=False): """Open a message for reading""" - global usrPrompt - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - numMessages = len(inboxMessages['inboxMessages']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() - if msgNum >= numMessages: - print '\n Invalid Message Number.\n' - main() + print ' Inbox message reading...', str(msgNum) + response = api.getInboxMessageByID(messageID, True) + if response['error'] != 0: + return response['errormsg'] - # Begin attachment detection - message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') + message = response['result'][0] + subject = message['subject'].decode('base64') + content = message['message'].decode('base64') # Message that you are replying too. + full = len(content) + textmsg = '' + textmsg = content if withAtta else attDetect(content, textmsg, 'inbox_' + subject, cmd != 'save') - while True: # Allows multiple messages to be downloaded/saved - if ';base64,' in message: # Found this text in the message, there is probably an attachment. - attPos = message.index(";base64,") # Finds the attachment position - attEndPos = message.index("' />") # Finds the end of the attachment - # attLen = attEndPos - attPos #Finds the length of the message - - if 'alt = "' in message: # We can get the filename too - fnPos = message.index('alt = "') # Finds position of the filename - fnEndPos = message.index('" src=') # Finds the end position - # fnLen = fnEndPos - fnPos #Finds the length of the filename - - fileName = message[fnPos + 7:fnEndPos] - else: - fnPos = attPos - fileName = 'Attachment' - - uInput = userInput( - '\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() - if uInput == "y" or uInput == 'yes': - - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - message = message[:fnPos] + '~~' + message[attEndPos + 4:] - - else: - break - - # End attachment Detection - print '\n To:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['toAddress']) # Get the to address + print ' ', 74 * '-' + print ' Inbox index :', msgNum # message index + print ' Message ID:', message['msgid'] + print ' Read:', message['read'] + print ' To:', getLabelForAddress(message['toAddress']) # Get the to address # Get the from address - print ' From:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['fromAddress']) - print ' Subject:', inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64') # Get the subject - print ''.join([ - ' Received:', datetime.datetime.fromtimestamp( - float(inboxMessages['inboxMessages'][msgNum]['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ]) + print ' From:', getLabelForAddress(message['fromAddress']) + print ' Subject:', subject # Get the subject + print ' Received:', datetime.datetime.fromtimestamp(float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Length: %d/%d' % (trunck if trunck <= full else full, full) print ' Message:\n' - print message # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') - print ' ' - return inboxMessages['inboxMessages'][msgNum]['msgid'] + print textmsg if trunck < 0 or len(textmsg) <= trunck else textmsg[:trunck] + '\n\n ~< MESSAGE TOO LONG TRUNCKED TO SHOW >~' + print ' ', 74 * '-' + + if cmd == 'save': + ret = dump2File('inbox_' + subject + str(full), textmsg, withAtta) + print ret + + return '' -def replyMsg(msgNum, forwardORreply): +def replyMsg(msgNum=-1, messageID=None, forwardORreply=None): """Allows you to reply to the message you are currently on. Saves typing in the addresses and subject.""" - global usrPrompt + global inputShorts, retStrings + forwardORreply = forwardORreply.lower() # makes it lowercase - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print ' Inbox message %s... %d' % (forwardORreply, msgNum) + response = api.getInboxMessageByID(messageID, True) + if response['error'] != 0: + return response['errormsg'] - fromAdd = inboxMessages['inboxMessages'][msgNum]['toAddress'] # Address it was sent To, now the From address - message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') # Message that you are replying too. + message = response['result'][0] + subject = message['subject'].decode('base64') + content = message['message'].decode('base64') # Message that you are replying too. + full = len(content) + textmsg = '' + textmsg = attDetect(content, textmsg, subject, True) - subject = inboxMessages['inboxMessages'][msgNum]['subject'] - subject = subject.decode('base64') + if forwardORreply == 'forward': + attachMessage = '\n'.join([ + '> To: ', fwdFrom, + '> From: ', fromAdd, + '> Subject: ', subject, + '> Received: ', recvTime, + '> Message:', + ]) + else: + attachMessage = '' + for line in textmsg.splitlines(): + attachMessage = attachMessage + '> ' + line + '\n' + + fromAdd = message['toAddress'] # Address it was sent To, now the From address + fwdFrom = message['fromAddress'] # Address it was sent To, will attached to fwd + recvTime = datetime.datetime.fromtimestamp(float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S') if forwardORreply == 'reply': - toAdd = inboxMessages['inboxMessages'][msgNum]['fromAddress'] # Address it was From, now the To address - subject = "Re: " + subject + toAdd = message['fromAddress'] # Address it was From, now the To address + subject = "Re: " + re.sub('^Re: *', '', subject) elif forwardORreply == 'forward': - subject = "Fwd: " + subject + subject = "Fwd: " + re.sub('^Fwd: *', '', subject) + toAdd = inputAddress("What is the To Address?") - while True: - toAdd = userInput("What is the To Address?") - - if toAdd == "c": - usrPrompt = 1 - print ' ' - main() - elif validAddress(toAdd) is False: - print '\n Invalid Address. "c" to cancel. Please try again.\n' - else: - break else: - print '\n Invalid Selection. Reply or Forward only' - usrPrompt = 0 - main() + return '\n Invalid Selection. Reply or Forward only' subject = subject.encode('base64') - newMessage = userInput("Enter your Message.") + message = '' + while True: + try: + print '\n'.join([ + ' Drafting:', + message, + ' -----------------------------------', + ]) + message += '\n' + raw_input('Continue enter your message line by line, end with .\n>') + except EOFError: + break - uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() - if uInput == "y": - newMessage = newMessage + '\n\n' + attachment() + src = retStrings['usercancel'] + uInput = userInput('Would you like to add an attachment, (y)es or (N)o?').lower() + if uInput in inputShorts['yes']: + message = message + '\n\n' + attachment() - newMessage = newMessage + '\n\n------------------------------------------------------\n' - newMessage = newMessage + message - newMessage = newMessage.encode('base64') + message = message + '\n\n------------------------------------------------------\n' + message = message + attachMessage + message = message.encode('base64') - sendMsg(toAdd, fromAdd, subject, newMessage) + print message + uInput = userInput('Realy want to send upper message, (n)o or (Y)es?').lower() + if uInput not in inputShorts['no']: + src = sendMsg(toAdd, fromAdd, subject, message) - main() + return src -def delMsg(msgNum): +def delMsg(msgNum=-1, messageID=None): """Deletes a specified message from the inbox""" - global usrPrompt - try: - inboxMessages = json.loads(api.getAllInboxMessages()) - # gets the message ID via the message index number - msgId = inboxMessages['inboxMessages'][int(msgNum)]['msgid'] + print ' Inbox message deleting...', messageID + response = api.trashMessage(messageID) + if response['error'] != 0: + return response['errormsg'] - msgAck = api.trashMessage(msgId) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() - - return msgAck + return '\n ' + response['result'] -def delSentMsg(msgNum): +def delSentMsg(msgNum=-1, messageID=None): """Deletes a specified message from the outbox""" - global usrPrompt - try: - outboxMessages = json.loads(api.getAllSentMessages()) - # gets the message ID via the message index number - msgId = outboxMessages['sentMessages'][int(msgNum)]['msgid'] - msgAck = api.trashSentMessage(msgId) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + if messageID is None: + print ' All outbox messages downloading...', str(msgNum) + response = api.getAllSentMessages() + if response['error'] != 0: + return response['errormsg'] - return msgAck + outboxMessages = response['result'] + # gets the message ackData via the message index number + ackData = outboxMessages[msgNum]['ackData'] + print ' Outbox message deleting...', ackData + response = api.trashSentMessageByAckData(ackData) + if response['error'] != 0: + return response['errormsg'] + else: + print ' Outbox message deleting...', messageID + response = api.trashSentMessage(messageID) + if response['error'] != 0: + return response['errormsg'] + + return '\n ' + response['result'] + + +def toReadInbox(cmd='read', trunck=380, withAtta=False): + + global inputShorts, retStrings + + numMessages = 0 + print ' Inbox index fetching...' + response = api.getAllInboxMessageIDs() + if response['error'] != 0: + return response['errormsg'] + + messageIds = response['result'] + numMessages = len(messageIds) + if numMessages < 1: + return ' Zero message found.\n' + + src = retStrings['usercancel'] + if cmd != 'delete': + msgNum = int(inputIndex('Input the index of the message to %s [0-%d]: ' % (cmd, numMessages - 1), numMessages - 1)) + + nextNum = msgNum + ret = '' + while msgNum >= 0: # save, read + nextNum += 1 + messageID = messageIds[msgNum]['msgid'] + if cmd == 'save': + ret = readMsg(cmd, msgNum, messageID, trunck, withAtta) + return ret + + else: + ret = readMsg(cmd, msgNum, messageID) + print ret + + uInput = userInput('Would you like to set this message to unread, (y)es or (N)o?').lower() + if uInput in inputShorts['yes']: + ret = markMessageReadbit(msgNum, messageID, False) + + else: + uInput = userInput('Would you like to (f)orward, (r)eply, (s)ave, (d)ump or Delete this message?').lower() + + if uInput in inputShorts['reply']: + ret = replyMsg(msgNum, messageID, 'reply') + + elif uInput in inputShorts['forward']: + ret = replyMsg(msgNum, messageID, 'forward') + + elif uInput in inputShorts['save']: + ret = readMsg('save', msgNum, messageID, withAtta=False) + + elif uInput in inputShorts['dump']: + ret = readMsg('save', msgNum, messageID, withAtta=True) + + else: + uInput = userInput('Are you sure to delete, (y)es or (N)o?').lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + # nextNum -= 1 + # numMessages -= 1 + ret = delMsg(msgNum, messageID) + + print ret + if nextNum < numMessages: + uInput = userInput('Next message, (n)o or (Y)es?').lower() # Prevent + msgNum = nextNum if uInput not in inputShorts['no'] else -1 + + else: + msgNum = -1 + src = retStrings['indexoutofbound'] + + else: + uInput = inputIndex('Input the index of the message you wish to delete or (A)ll to empty the inbox [0-%d]: ' % (numMessages - 1), numMessages - 1, inputShorts['all'][0]).lower() + + if uInput in inputShorts['all']: + ret = inbox(False) + print ret + uInput = userInput('Are you sure to delete all this %d message(s), (y)es or (N)o?' % numMessages).lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + for msgNum in range(0, numMessages): # processes all of the messages in the outbox + ret = delMsg(msgNum, messageIds[msgNum]['msgid']) + print ret + src = '' + + else: + nextNum = msgNum = int(uInput) + while msgNum >= 0: # save, read + nextNum += 1 + messageID = messageIds[msgNum]['msgid'] + ret = readMsg(cmd, msgNum, messageID) + print ret + + uInput = userInput('Are you sure to delete, (y)es or (N)o?').lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + # nextNum -= 1 + # numMessages -= 1 + ret = delMsg(msgNum, messageID) + print ret + + if nextNum < numMessages: + uInput = userInput('Next message, (n)o or (Y)es?').lower() # Prevent + msgNum = nextNum if uInput not in inputShorts['no'] else -1 + + else: + msgNum = -1 + src = retStrings['indexoutofbound'] + + return src + + +def toReadOutbox(cmd='read', trunck=380, withAtta=False): + + global inputShorts, retStrings + + print ' Outbox index fetching...' + response = api.getAllSentMessageIDs() + if response['error'] != 0: + return response['errormsg'] + + messageIds = response['result'] + numMessages = len(messageIds) + if numMessages < 1: + return ' Zero message found.\n' + + src = retStrings['usercancel'] + if cmd != 'delete': + msgNum = int(inputIndex('Input the index of the message open [0-%d]: ' % (numMessages - 1), numMessages - 1)) + + nextNum = msgNum + ret = '' + while msgNum >= 0: # save, read + nextNum += 1 + messageID = messageIds[msgNum]['msgid'] + if cmd == 'save': + ret = readSentMsg(cmd, msgNum, messageID, trunck, withAtta) + return ret + + else: + ret = readSentMsg(cmd, msgNum, messageID) + + print ret + # Gives the user the option to delete the message + uInput = userInput('Would you like to (s)ave, (d)ump or Delete this message directly?').lower() + + if uInput in inputShorts['save']: + ret = readSentMsg('save', msgNum, messageID, withAtta=False) + + elif uInput in inputShorts['dump']: + ret = readSentMsg('save', msgNum, messageID, withAtta=True) + + else: + uInput = userInput('Are you sure to delete, (y)es or (N)o?').lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + nextNum -= 1 + numMessages -= 1 + ret = delSentMsg(msgNum, messageID) + + print ret + if nextNum < numMessages: + uInput = userInput('Next message, (n)o or (Y)es?').lower() # Prevent + msgNum = nextNum if uInput not in inputShorts['no'] else -1 + + else: + msgNum = -1 + src = retStrings['indexoutofbound'] + + else: + uInput = inputIndex('Input the index of the message you wish to delete or (A)ll to empty the outbox [0-%d]: ' % (numMessages - 1), numMessages - 1, inputShorts['all'][0]).lower() + + if uInput in inputShorts['all']: + ret = outbox() + print ret + uInput = userInput('Are you sure to delete all this %d message(s), (y)es or (N)o?' % numMessages).lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + for msgNum in range(0, numMessages): # processes all of the messages in the outbox + ret = delSentMsg(msgNum, messageIds[msgNum]['msgid']) + print ret + src = '' + + else: + nextNum = msgNum = int(uInput) + while msgNum >= 0: # save, read + nextNum += 1 + + messageID = messageIds[msgNum]['msgid'] + ret = readSentMsg(cmd, msgNum, messageID) + print ret + + uInput = userInput('Are you sure to delete this message, (y)es or (N)o?').lower() # Prevent accidental deletion + if uInput in inputShorts['yes']: + nextNum -= 1 + numMessages -= 1 + ret = delSentMsg(msgNum, messageID) + print ret + + if nextNum < numMessages: + uInput = userInput('Next message, (n)o or (Y)es?').lower() # Prevent + msgNum = nextNum if uInput not in inputShorts['no'] else -1 + + else: + msgNum = -1 + src = retStrings['indexoutofbound'] + + return src def getLabelForAddress(address): """Get label for an address""" - if address in knownAddresses: - return knownAddresses[address] - else: - buildKnownAddresses() - if address in knownAddresses: - return knownAddresses[address] + for entry in knownAddresses['addresses']: + if entry['address'] == address: + return "%s (%s)" % (entry['label'], entry['address']) return address -def buildKnownAddresses(): +def getLabel(): """Build known addresses""" - global usrPrompt - # add from address book - try: - response = api.listAddressBookEntries() - # if api is too old then fail - if "API Error 0020" in response: - return - addressBook = json.loads(response) - for entry in addressBook['addresses']: - if entry['address'] not in knownAddresses: - knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + errors = '' + newentry = [] + print ' Retrieving...', 'Contacts' + addresses = api.listAddressBookEntries() + if addresses['error'] != 0: + print addresses['errormsg'] + else: + for entry in addresses['result']: + isnew = True + for old in knownAddresses['addresses']: + if entry['address'] == old['address']: + isnew = False + break + if isnew is True: + newentry.append({'label': entry['label'].decode('base64').encode('utf-8'), 'address': entry['address']}) + if any(newentry): + for new in newentry: + knownAddresses['addresses'].append(new) - # add from my addresses - try: - response = api.listAddresses2() - # if api is too old just return then fail - if "API Error 0020" in response: - return - addresses = json.loads(response) - for entry in addresses['addresses']: - if entry['address'] not in knownAddresses: - knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address']) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + newentry = [] + print ' Retrieving...', 'Senders' + addresses = api.listAddresses() + if addresses['error'] != 0: + print addresses['errormsg'] + else: + for entry in addresses['result']: + isnew = True + for old in knownAddresses['addresses']: + if entry['address'] == old['address']: + isnew = False + break + if isnew is True: + newentry.append({'label': entry['label'].encode('utf-8'), 'address': entry['address']}) + if any(newentry): + for new in newentry: + knownAddresses['addresses'].append(new) + + return '' -def listAddressBookEntries(): +def listAddressBK(printKnown=False): """List addressbook entries""" - global usrPrompt - - try: + if not printKnown: + print ' Retrieving...', 'Contacts' response = api.listAddressBookEntries() - if "API Error" in response: - return getAPIErrorCode(response) - addressBook = json.loads(response) - print - print ' --------------------------------------------------------------' - print ' | Label | Address |' - print ' |--------------------|---------------------------------------|' - for entry in addressBook['addresses']: - label = entry['label'].decode('base64') - address = entry['address'] - if len(label) > 19: - label = label[:16] + '...' - print ' | ' + label.ljust(19) + '| ' + address.ljust(37) + ' |' - print ' --------------------------------------------------------------' - print + if response['error'] != 0: + return response['errormsg'] + addressBook = response['result'] + else: + addressBook = knownAddresses['addresses'] - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + numAddresses = len(addressBook) + print + print ' ------------------------------------------------------------------' + print ' | # | Label | Address |' + print ' |----|--------------------|--------------------------------------|' + for addNum in range(0, numAddresses): # processes all of the addresses and lists them out + entry = addressBook[addNum] + label = entry['label'].decode('base64').encode('utf-8') if not printKnown else entry['label'] + address = entry['address'] + if len(label) > 19: + label = label[:16] + '...' + print '| '.join([' ', str(addNum).ljust(3), label.ljust(19), address.ljust(37), '', ]) + print ''.join([' ', 66 * '-', '\n', ]) + + return '' def addAddressToAddressBook(address, label): """Add an address to an addressbook""" - global usrPrompt + print ' Adding...', label + response = api.addAddressBK(address, label.encode('base64')) + if response['error'] != 0: + return response['errormsg'] - try: - response = api.addAddressBookEntry(address, label.encode('base64')) - if "API Error" in response: - return getAPIErrorCode(response) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + return '\n ' + response['result'] def deleteAddressFromAddressBook(address): """Delete an address from an addressbook""" - global usrPrompt + print ' Deleting...', address + response = api.delAddressBK(address) + if response['error'] != 0: + return response['errormsg'] - try: - response = api.deleteAddressBookEntry(address) - if "API Error" in response: - return getAPIErrorCode(response) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + return '\n ' + response['result'] def getAPIErrorCode(response): @@ -1321,563 +2138,423 @@ def getAPIErrorCode(response): return int(response.split()[2][:-1]) -def markMessageRead(messageID): - """Mark a message as read""" +def markMessageReadbit(msgNum=-1, messageID=None, read=False): + """Mark a mesasge as unread/read""" - global usrPrompt + print ' Marking...', str(msgNum), + response = api.getInboxMessageByID(messageID, read) + if response['error'] != 0: + print 'Failed.' + return response['errormsg'] - try: - response = api.getInboxMessageByID(messageID, True) - if "API Error" in response: - return getAPIErrorCode(response) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + print 'OK.' + return '' -def markMessageUnread(messageID): - """Mark a mesasge as unread""" +def markAllMessagesReadbit(read=False): + """Mark all messages as unread/read""" - global usrPrompt + print ' Inbox index fetching...', 'mark' + response = api.getAllInboxMessageIDs() + if response['error'] != 0: + return response['errormsg'] - try: - response = api.getInboxMessageByID(messageID, False) - if "API Error" in response: - return getAPIErrorCode(response) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + messageIds = response['result'] + numMessages = len(messageIds) + if numMessages < 1: + return ' Zero message found.\n' + + for msgNum in range(0, numMessages): # processes all of the messages in the inbox + src = markMessageReadbit(msgNum, messageIds[msgNum]['msgid'], read) + print src + + return '' -def markAllMessagesRead(): - """Mark all messages as read""" +def addInfo(address): - global usrPrompt + print ' Address decoding...', address + response = api.decodeAddress(address) + if response['error'] != 0: + return response['errormsg'] - try: - inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages'] - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() - for message in inboxMessages: - if not message['read']: - markMessageRead(message['msgid']) + addinfo = response['result'] + print ' ------------------------------' + if 'success' in addinfo['status'].lower(): + print ' Valid Address' + print ' Address Version: %s' % str(addinfo['addressVersion']) + print ' Stream Number: %s\n' % str(addinfo['streamNumber']) + else: + print '\n Invalid Address !\n' - -def markAllMessagesUnread(): - """Mark all messages as unread""" - - global usrPrompt - - try: - inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages'] - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() - for message in inboxMessages: - if message['read']: - markMessageUnread(message['msgid']) + return '' def clientStatus(): """Print the client status""" - global usrPrompt + print ' Client status fetching...' + print ' ------------------------------' - try: - client_status = json.loads(api.clientStatus()) - except: - print '\n Connection Error\n' - usrPrompt = 0 - main() + response = api.clientStatus() + if response['error'] == 0: + client_status = response['result'] + for key in client_status.keys(): + print ' ', key, ':', str(client_status[key]) + else: + print response['errormsg'] - print "\nnetworkStatus: " + client_status['networkStatus'] + "\n" - print "\nnetworkConnections: " + str(client_status['networkConnections']) + "\n" - print "\nnumberOfPubkeysProcessed: " + str(client_status['numberOfPubkeysProcessed']) + "\n" - print "\nnumberOfMessagesProcessed: " + str(client_status['numberOfMessagesProcessed']) + "\n" - print "\nnumberOfBroadcastsProcessed: " + str(client_status['numberOfBroadcastsProcessed']) + "\n" + response = api.getAllInboxMessageIDs() + if response['error'] == 0: + inboxMessageIds = response['result'] + inumMessages = len(inboxMessageIds) + print ' InboxMessages:', str(inumMessages) + else: + print response['errormsg'] + + response = api.getAllSentMessageIDs() + if response['error'] == 0: + outboxMessageIds = response['result'] + onumMessages = len(outboxMessageIds) + print ' OutboxMessages:', str(onumMessages) + else: + print response['errormsg'] + # print ' Message.dat:', str(boxSize) + # print ' knownNodes.dat:', str(knownNodes) + # print ' debug.log:', str(debugSize) + + print ' ------------------------------' + + return '' def shutdown(): """Shutdown the API""" - try: - api.shutdown() - except socket.error: - pass - print "\nShutdown command relayed\n" + print ' Shutdown command sending...' + response = api.shutdown() + if response['error'] != 0: + return response['errormsg'] + + return '\n ' + response['result'] -def UI(usrInput): +def start_daemon(uri=''): + + start_dir = os.path.dirname(os.path.abspath(__file__)) + mainfile = os.path.join(start_dir, 'bitmessagemain.py') + print " Try to start daemon: %s..." % uri + if os.path.isfile(mainfile): + cmd = ' '.join([sys.executable, mainfile, '--daemon']) + print '\n Exec:', cmd, uri + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) + out, err = p.communicate() + result = out.split('\n') + for line in result: + print ' ' + line + else: + print ' ' + retStrings['nomain'] + + +def UI(cmdInput=None): """Main user menu""" - global usrPrompt + global usrPrompt, inputShorts, cmdShorts, retStrings, bms_allow - if usrInput == "help" or usrInput == "h" or usrInput == "?": - print ' ' - print ' -------------------------------------------------------------------------' - print ' | https://github.com/Dokument/PyBitmessage-Daemon |' - print ' |-----------------------------------------------------------------------|' - print ' | Command | Description |' - print ' |------------------------|----------------------------------------------|' - print ' | help | This help file. |' - print ' | apiTest | Tests the API |' - print ' | addInfo | Returns address information (If valid) |' - print ' | bmSettings | BitMessage settings |' - print ' | exit | Use anytime to return to main menu |' - print ' | quit | Quits the program |' - print ' |------------------------|----------------------------------------------|' - print ' | listAddresses | Lists all of the users addresses |' - print ' | generateAddress | Generates a new address |' - print ' | getAddress | Get determinist address from passphrase |' - print ' |------------------------|----------------------------------------------|' - print ' | listAddressBookEntries | Lists entries from the Address Book |' - print ' | addAddressBookEntry | Add address to the Address Book |' - print ' | deleteAddressBookEntry | Deletes address from the Address Book |' - print ' |------------------------|----------------------------------------------|' - print ' | subscribe | Subscribes to an address |' - print ' | unsubscribe | Unsubscribes from an address |' - print ' |------------------------|----------------------------------------------|' - print ' | create | Creates a channel |' - print ' | join | Joins a channel |' - print ' | leave | Leaves a channel |' - print ' |------------------------|----------------------------------------------|' - print ' | inbox | Lists the message information for the inbox |' - print ' | outbox | Lists the message information for the outbox |' - print ' | send | Send a new message or broadcast |' - print ' | unread | Lists all unread inbox messages |' - print ' | read | Reads a message from the inbox or outbox |' - print ' | save | Saves message to text file |' - print ' | delete | Deletes a message or all messages |' - print ' -------------------------------------------------------------------------' - print ' ' - main() + src = 'MUST WRONG' + uInput = '' - elif usrInput == "apitest": # tests the API Connection. - if apiTest(): - print '\n API connection test has: PASSED\n' + if not any(cmdShorts): + if not cmdGuess(): + raise SystemExit('\n Bye\n') + + if cmdInput in cmdShorts['help']: + src = showCmdTbl() + + elif cmdInput in cmdShorts['daemon']: + src = start_daemon(getattr(config, 'start_daemon', '')) + + elif cmdInput in cmdShorts['apiTest']: # tests the API Connection. + print ' API connection test has:', + print 'PASSED' if apiTest() else 'FAILED\n' + src = '' + + elif cmdInput in cmdShorts['addInfo']: + while uInput == '': + uInput = userInput('Input the Bitmessage Address.') + src = addInfo(uInput) + + elif cmdInput in cmdShorts['bmSettings']: # tests the API Connection. + if bms_allow is True: + if hasattr(conninit, 'main') and hasattr(conninit, 'bmSettings'): + src = conninit.bmSettings() + else: + src = '\n Depends moudle changed, calling dismissed.' else: - print '\n API connection test has: FAILED\n' - main() + src = ' ' + retStrings['bmsnotallow'] - elif usrInput == "addinfo": - tmp_address = userInput('\nEnter the Bitmessage Address.') - address_information = json.loads(api.decodeAddress(tmp_address)) + elif cmdInput in cmdShorts['quit']: # Quits the application + raise SystemExit('\n Bye\n') - print '\n------------------------------' + elif cmdInput in cmdShorts['listAddresses']: # Lists all of the identities in the addressbook + src = listAdd() - if 'success' in str(address_information['status']).lower(): - print ' Valid Address' - print ' Address Version: %s' % str(address_information['addressVersion']) - print ' Stream Number: %s' % str(address_information['streamNumber']) - else: - print ' Invalid Address !' + elif cmdInput in cmdShorts['newAddress']: # Generates a new address + uInput = userInput('Would you like to create a (d)eterministic or (R)andom address?').lower() - print '------------------------------\n' - main() - - elif usrInput == "bmsettings": # tests the API Connection. - bmSettings() - print ' ' - main() - - elif usrInput == "quit": # Quits the application - print '\n Bye\n' - sys.exit(0) - - elif usrInput == "listaddresses": # Lists all of the identities in the addressbook - listAdd() - main() - - elif usrInput == "generateaddress": # Generates a new address - uInput = userInput('\nWould you like to create a (D)eterministic or (R)andom address?').lower() - - if uInput in ["d", "deterministic"]: # Creates a deterministic address + if uInput in inputShorts['deterministic']: # Creates a deterministic address deterministic = True lbl = '' - passphrase = userInput('Enter the Passphrase.') # .encode('base64') + passphrase = userInput('Input the Passphrase.') # .encode('base64') numOfAdd = int(userInput('How many addresses would you like to generate?')) addVNum = 3 streamNum = 1 - isRipe = userInput('Shorten the address, (Y)es or (N)o?').lower() + isRipe = userInput('Shorten the address, (Y)es or no?').lower() - if isRipe == "y": + if isRipe in inputShorts['yes']: ripe = True - print genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) - main() - elif isRipe == "n": + src = genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) + + else: ripe = False - print genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) - main() - elif isRipe == "exit": - usrPrompt = 1 - main() - else: - print '\n Invalid input\n' - main() + src = genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) - elif uInput == "r" or uInput == "random": # Creates a random address with user-defined label + else: # Creates a random address with user-defined label deterministic = False - null = '' - lbl = userInput('Enter the label for the new address.') + lbl = null = None + while lbl is None: + lbl = userInput('Input the label for the new address.') + src = genAdd(lbl, deterministic, null, null, null, null, null) - print genAdd(lbl, deterministic, null, null, null, null, null) - main() + elif cmdInput in cmdShorts['getAddress']: # Gets the address for/from a passphrase + while len(uInput) < 6: + uInput = userInput('Input a strong address passphrase.[6-]') + src = getAddress(uInput, 4, 1) + + elif cmdInput in cmdShorts['subscribe']: # Subsribe to an address + address = inputAddress('What address would you like to subscribe?') + while uInput == '': + uInput = userInput('Enter a label for this address.') + src = subscribe(address, uInput) + + elif cmdInput in cmdShorts['unsubscribe']: # Unsubscribe from an address + address = inputAddress("What address would you like to unsubscribe from?") + uInput = userInput('Are you sure to unsubscribe: [%s]?' % address) + if uInput in inputShorts['yes']: + src = unsubscribe(address) + + elif cmdInput in cmdShorts['listsubscrips']: # Unsubscribe from an address + src = listSubscriptions() + + elif cmdInput in cmdShorts['create']: + while uInput == '': + uInput = userInput('Enter channel name') + src = createChan(uInput) + + elif cmdInput in cmdShorts['join']: + src = joinChan() + + elif cmdInput in cmdShorts['leave']: + src = leaveChan() + + elif cmdInput in cmdShorts['getLabel']: # Retrieve all of the addressbooks + src = getLabel() + print src, + src = listAddressBK(True) + + elif cmdInput in cmdShorts['inbox']: + src = inbox(False) + + elif cmdInput in cmdShorts['news']: + src = inbox(True) + + elif cmdInput in cmdShorts['outbox']: + src = outbox() + + elif cmdInput in cmdShorts['send']: # Sends a message or broadcast + uInput = userInput('Would you like to send a (b)roadcast or (M)essage?').lower() + null = '' + if uInput in inputShorts['broadcast']: + src = sendBrd(null, null, null) + else: + src = sendMsg(null, null, null, null) + + elif cmdInput in cmdShorts['delete']: + withAtta = True + uInput = userInput('Would you like to delete message(s) from the (i)nbox or (O)utbox?').lower() + + if uInput in inputShorts['inbox']: + src = toReadInbox(cmd='delete', withAtta=withAtta) else: - print '\n Invalid input\n' - main() + src = toReadOutbox(cmd='delete', withAtta=withAtta) - elif usrInput == "getaddress": # Gets the address for/from a passphrase - phrase = userInput("Enter the address passphrase.") - print '\n Working...\n' - address = getAddress(phrase, 4, 1) # ,vNumber,sNumber) - print '\n Address: ' + address + '\n' - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['read']: # Opens a message from the inbox for viewing. + withAtta = False + uInput = userInput('Would you like to read a message from the (i)nbox or (O)utbox?').lower() - elif usrInput == "subscribe": # Subsribe to an address - subscribe() - usrPrompt = 1 - main() + if uInput in inputShorts['inbox']: + src = toReadInbox(cmd='read', withAtta=withAtta) - elif usrInput == "unsubscribe": # Unsubscribe from an address - unsubscribe() - usrPrompt = 1 - main() - - elif usrInput == "listsubscriptions": # Unsubscribe from an address - listSubscriptions() - usrPrompt = 1 - main() - - elif usrInput == "create": - createChan() - usrPrompt = 1 - main() - - elif usrInput == "join": - joinChan() - usrPrompt = 1 - main() - - elif usrInput == "leave": - leaveChan() - usrPrompt = 1 - main() - - elif usrInput == "inbox": - print '\n Loading...\n' - inbox() - main() - - elif usrInput == "unread": - print '\n Loading...\n' - inbox(True) - main() - - elif usrInput == "outbox": - print '\n Loading...\n' - outbox() - main() - - elif usrInput == 'send': # Sends a message or broadcast - uInput = userInput('Would you like to send a (M)essage or (B)roadcast?').lower() - - if (uInput == 'm' or uInput == 'message'): - null = '' - sendMsg(null, null, null, null) - main() - elif (uInput == 'b' or uInput == 'broadcast'): - null = '' - sendBrd(null, null, null) - main() - - elif usrInput == "read": # Opens a message from the inbox for viewing. - - uInput = userInput("Would you like to read a message from the (I)nbox or (O)utbox?").lower() - - if (uInput != 'i' and uInput != 'inbox' and uInput != 'o' and uInput != 'outbox'): - print '\n Invalid Input.\n' - usrPrompt = 1 - main() - - msgNum = int(userInput("What is the number of the message you wish to open?")) - - if (uInput == 'i' or uInput == 'inbox'): - print '\n Loading...\n' - messageID = readMsg(msgNum) - - uInput = userInput("\nWould you like to keep this message unread, (Y)es or (N)o?").lower() - - if not (uInput == 'y' or uInput == 'yes'): - markMessageRead(messageID) - usrPrompt = 1 - - uInput = userInput("\nWould you like to (D)elete, (F)orward, (R)eply to, or (Exit) this message?").lower() - - if uInput in ['r', 'reply']: - print '\n Loading...\n' - print ' ' - replyMsg(msgNum, 'reply') - usrPrompt = 1 - - elif uInput == 'f' or uInput == 'forward': - print '\n Loading...\n' - print ' ' - replyMsg(msgNum, 'forward') - usrPrompt = 1 - - elif uInput in ["d", 'delete']: - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion - - if uInput == "y": - delMsg(msgNum) - print '\n Message Deleted.\n' - usrPrompt = 1 - else: - usrPrompt = 1 - else: - print '\n Invalid entry\n' - usrPrompt = 1 - - elif (uInput == 'o' or uInput == 'outbox'): - readSentMsg(msgNum) - - # Gives the user the option to delete the message - uInput = userInput("Would you like to (D)elete, or (Exit) this message?").lower() - - if (uInput == "d" or uInput == 'delete'): - uInput = userInput('Are you sure, (Y)es or (N)o?').lower() # Prevent accidental deletion - - if uInput == "y": - delSentMsg(msgNum) - print '\n Message Deleted.\n' - usrPrompt = 1 - else: - usrPrompt = 1 - else: - print '\n Invalid Entry\n' - usrPrompt = 1 - - main() - - elif usrInput == "save": - - uInput = userInput("Would you like to save a message from the (I)nbox or (O)utbox?").lower() - - if uInput not in ['i', 'inbox', 'o', 'outbox']: - print '\n Invalid Input.\n' - usrPrompt = 1 - main() - - if uInput in ['i', 'inbox']: - inboxMessages = json.loads(api.getAllInboxMessages()) - numMessages = len(inboxMessages['inboxMessages']) - - while True: - msgNum = int(userInput("What is the number of the message you wish to save?")) - - if msgNum >= numMessages: - print '\n Invalid Message Number.\n' - else: - break - - subject = inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64') - # Don't decode since it is done in the saveFile function - message = inboxMessages['inboxMessages'][msgNum]['message'] - - elif uInput == 'o' or uInput == 'outbox': - outboxMessages = json.loads(api.getAllSentMessages()) - numMessages = len(outboxMessages['sentMessages']) - - while True: - msgNum = int(userInput("What is the number of the message you wish to save?")) - - if msgNum >= numMessages: - print '\n Invalid Message Number.\n' - else: - break - - subject = outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') - # Don't decode since it is done in the saveFile function - message = outboxMessages['sentMessages'][msgNum]['message'] - - subject = subject + '.txt' - saveFile(subject, message) - - usrPrompt = 1 - main() - - elif usrInput == "delete": # will delete a message from the system, not reflected on the UI. - - uInput = userInput("Would you like to delete a message from the (I)nbox or (O)utbox?").lower() - - if uInput in ['i', 'inbox']: - inboxMessages = json.loads(api.getAllInboxMessages()) - numMessages = len(inboxMessages['inboxMessages']) - - while True: - msgNum = userInput( - 'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() - - if msgNum == 'a' or msgNum == 'all': - break - elif int(msgNum) >= numMessages: - print '\n Invalid Message Number.\n' - else: - break - - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion - - if uInput == "y": - if msgNum in ['a', 'all']: - print ' ' - for msgNum in range(0, numMessages): # processes all of the messages in the inbox - print ' Deleting message ', msgNum + 1, ' of ', numMessages - delMsg(0) - - print '\n Inbox is empty.' - usrPrompt = 1 - else: - delMsg(int(msgNum)) - - print '\n Notice: Message numbers may have changed.\n' - main() - else: - usrPrompt = 1 - - elif uInput in ['o', 'outbox']: - outboxMessages = json.loads(api.getAllSentMessages()) - numMessages = len(outboxMessages['sentMessages']) - - while True: - msgNum = userInput( - 'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() - - if msgNum in ['a', 'all']: - break - elif int(msgNum) >= numMessages: - print '\n Invalid Message Number.\n' - else: - break - - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion - - if uInput == "y": - if msgNum in ['a', 'all']: - print ' ' - for msgNum in range(0, numMessages): # processes all of the messages in the outbox - print ' Deleting message ', msgNum + 1, ' of ', numMessages - delSentMsg(0) - - print '\n Outbox is empty.' - usrPrompt = 1 - else: - delSentMsg(int(msgNum)) - print '\n Notice: Message numbers may have changed.\n' - main() - else: - usrPrompt = 1 else: - print '\n Invalid Entry.\n' - usrPrompt = 1 - main() + src = toReadOutbox(cmd='read', withAtta=withAtta) - elif usrInput == "exit": - print '\n You are already at the main menu. Use "quit" to quit.\n' - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['save']: + uInput = userInput('Would you like to save a message from the (i)nbox or (O)utbox?').lower() - elif usrInput == "listaddressbookentries": - res = listAddressBookEntries() - if res == 20: - print '\n Error: API function not supported.\n' - usrPrompt = 1 - main() + if uInput in inputShorts['inbox']: + withAtta = True + uInput = userInput('Would you like to decode and (s)ave or (D)ump directly?').lower() + if uInput in inputShorts['save']: + withAtta = False + src = toReadInbox(cmd='save', trunck=-1, withAtta=withAtta) - elif usrInput == "addaddressbookentry": - address = userInput('Enter address') - label = userInput('Enter label') - res = addAddressToAddressBook(address, label) - if res == 16: - print '\n Error: Address already exists in Address Book.\n' - if res == 20: - print '\n Error: API function not supported.\n' - usrPrompt = 1 - main() + else: + withAtta = True + uInput = userInput('Would you like to decode and (s)ave or (D)ump directly?').lower() + if uInput in inputShorts['save']: + withAtta = False - elif usrInput == "deleteaddressbookentry": - address = userInput('Enter address') - res = deleteAddressFromAddressBook(address) - if res == 20: - print '\n Error: API function not supported.\n' - usrPrompt = 1 - main() + src = toReadOutbox(cmd='save', trunck=-1, withAtta=withAtta) - elif usrInput == "markallmessagesread": - markAllMessagesRead() - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['quit']: + src = '\n You are already at the main menu. Use "quit" to quit.\n' - elif usrInput == "markallmessagesunread": - markAllMessagesUnread() - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['listAddressBK']: + src = listAddressBK() - elif usrInput == "status": - clientStatus() - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['addAddressBK']: + label = '' + while uInput == '': + uInput = userInput('Enter address to add.') + while label == '': + label = userInput('Enter label') + src = addAddressToAddressBook(uInput, label) - elif usrInput == "shutdown": - shutdown() - usrPrompt = 1 - main() + elif cmdInput in cmdShorts['delAddressBK']: + while uInput == '': + uInput = userInput('Enter address to delete.') + src = deleteAddressFromAddressBook(uInput) + + elif cmdInput in cmdShorts['readAll']: + src = markAllMessagesReadbit(True) + + elif cmdInput in cmdShorts['unreadAll']: + src = markAllMessagesReadbit(False) + + elif cmdInput in cmdShorts['status']: + src = clientStatus() + + elif cmdInput in cmdShorts['shutdown']: + src = shutdown() else: - print '\n "', usrInput, '" is not a command.\n' + src = '\n "' + cmdInput + '" is not a command.\n' + + if src is None or src == '': + src = retStrings['none'] usrPrompt = 1 - main() + elif 'Connection' in src or 'ProtocolError' in src: + usrPrompt = 0 + else: + usrPrompt = 1 + + print src -def main(): +def CLI(): """Entrypoint for the CLI app""" - global api - global usrPrompt + global usrPrompt, config, api, cmdStr if usrPrompt == 0: - print '\n ------------------------------' - print ' | Bitmessage Daemon by .dok |' - print ' | Version 0.3.1 for BM 0.6.2 |' - print ' ------------------------------' - api = xmlrpclib.ServerProxy(apiData()) # Connect to BitMessage using these api credentials + if config.conn: + api = BMAPIWrapper(config.conn, config.proxy) + # api.set_proxy(config.proxy) + if apiTest() is False: + print + print ' ****************************************************************' + print ' WARNING: You are not connected to the Bitmessage API service.' + print ' Either Bitmessage is not running or your settings are incorrect.' + print ' Use the command "apiTest" or "bmSettings" to resolve this issue.' + print ' ****************************************************************\n' - if apiTest() is False: - print '\n ****************************************************************' - print ' WARNING: You are not connected to the Bitmessage client.' - print ' Either Bitmessage is not running or your settings are incorrect.' - print ' Use the command "apiTest" or "bmSettings" to resolve this issue.' - print ' ****************************************************************\n' - - print 'Type (H)elp for a list of commands.' # Startup message - usrPrompt = 2 + print '\nType (H)elp for a list of commands.\nPress Enter for default cmd [%s]: ' % cmdStr # Startup message + usrPrompt = 2 + else: + print + print ' *****************************************************' + print ' WARNING: API not functionable till you finish the' + print ' configuration.' + print ' *****************************************************\n' + print '\nType (H)elp for a list of commands.\nPress Enter for default cmd [%s]: ' % cmdStr # Startup message + usrPrompt = 0 elif usrPrompt == 1: - print '\nType (H)elp for a list of commands.' # Startup message + print '\nType (H)elp for a list of commands.\nPress Enter for default cmd [%s]: ' % cmdStr # Startup message usrPrompt = 2 try: - UI((raw_input('>').lower()).replace(" ", "")) + cmdInput = raw_input('>').lower().replace(" ", "") + if cmdInput != '': + cmdStr = cmdInput # save as last cmd for replay + UI(cmdStr) + except EOFError: UI("quit") if __name__ == "__main__": - main() + config = Config(sys.argv) + config.parse() + bms_allow = False + socks_allow = False + if not config.arguments: # Config parse failed, show the help screen and exit + config.parse() + + if config.action == 'main': + try: + print '- Try to get API connect uri from BMs setting file "keys.dat"...' + import bmsettings as conninit + bms_allow = True + if hasattr(conninit, 'apiData'): + config.conn = conninit.apiData() + print ' BMs host uri:', config.conn, '\n' + except Exception as err: + print ' Depends check failed, command "bmSettings" disabled. (%s)' % err + pass + + try: + print '- Try to get socks module for proxied...' + from socks import ProxyError, GeneralProxyError, Socks5AuthError, Socks5Error, Socks4Error, HTTPError + import socks + if hasattr(socks, 'setdefaultproxy'): + socks_allow = True + else: + print ' Not the correct "socks" module imported.' + except ImportError as err: + print ' Depends check failed, "SOCKS" type proxy disabled. (%s)' % err + + if getattr(config, 'start_daemon'): + start_daemon(getattr(config, 'start_daemon')) + + socks_type = getattr(config, 'proxy_type', 'none') + proxy = dict() + if socks_type != 'none': + if socks_allow is False and 'SOCKS' in socks_type: + print ' Socks type proxy disabled.' + else: + for key in ['proxy_type', 'proxy_path', 'proxy_username', 'proxy_password', 'proxy_remotedns', 'proxy_timeout']: + proxy[key] = getattr(config, key) + config.proxy = proxy + if getattr(config, 'api_path', None): + if getattr(config, 'api_username', None) and getattr(config, 'api_password', None): + config.conn = '%s://%s:%s@%s/' % (config.api_type, config.api_username, config.api_password, config.api_path) + else: + config.conn = '%s://%s/' % (config.api_type, config.api_path) + + actions = Actions() + start() + print 'bye' + sys.exit() diff --git a/src/bmsettings.py b/src/bmsettings.py new file mode 100644 index 00000000..3ffbe8b4 --- /dev/null +++ b/src/bmsettings.py @@ -0,0 +1,373 @@ +#!/usr/bin/python2.7 +# -*- coding: utf-8 -*- +# pylint: disable=too-many-lines,global-statement,too-many-branches,too-many-statements,inconsistent-return-statements +# pylint: disable=too-many-nested-blocks,too-many-locals,protected-access,too-many-arguments,too-many-function-args +# pylint: disable=no-member +""" +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 + +TODO: fix the following (currently ignored) violations: + +""" + +import sys +import os + +from bmconfigparser import BMConfigParser +import ConfigParser + +keysName = 'keys.dat' +keysPath = 'keys.dat' + + +def userInput(message): + """Checks input for exit or quit. Also formats for input, etc""" + + print message + uInput = raw_input('> ') + + return uInput + + +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 ' {0} Initalized in the same directory as this CLI.\n' \ + ' You will now need to configure the {0} file.\n'.format(keysName) + + +def restartBmNotify(): + """Prompt the user to restart Bitmessage""" + + print + print ' *******************************************************************' + print ' WARNING: If Bitmessage is running locally, you must restart it now.' + print ' *******************************************************************\n' + + +def apiInit(apiEnabled): + """Initialise the API""" + + global keysPath + BMConfigParser().read(keysPath) + isValid = False + + 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() + isValid = True + + elif uInput == "n": + print + print ' ************************************************************' + 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' + + else: + print '\n Invalid Entry\n' + + elif apiEnabled: # API correctly setup + # Everything is as it should be + isValid = 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' + + # 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 ' Finished configuring the keys.dat file with API information.\n' + restartBmNotify() + isValid = True + + elif uInput == "n": + print + print ' ***********************************************************' + print ' Please refer to the Bitmessage Wiki on how to setup the API.' + print ' ***********************************************************\n' + + else: + print ' \nInvalid entry\n' + + return isValid + + +def apiData(): + """TBC""" + + global keysName + global keysPath + + print '\n Configure file searching:', os.path.realpath(keysPath) + 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 (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as err: + pass + # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory. + appDataFolder = lookupAppdataFolder() + keysPath = appDataFolder + keysPath + print '\n Configure file searching:', os.path.realpath(keysPath) + BMConfigParser().read(keysPath) + + try: + BMConfigParser().get('bitmessagesettings', 'port') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as err: + # keys.dat was not there either, something is wrong. + print + print ' *************************************************************' + print ' WARNING: There was a problem on accessing congfigure file.' + print ' Make sure that daemon is in the same directory as Bitmessage. ' + print ' *************************************************************\n' + + finalCheck = False + uInput = userInput('Would you like to create a "keys.dat" file in current working directory, (Y)es or (N)o?').lower() + + if (uInput == "y" or uInput == "yes"): + configInit() + keysPath = keysName + f = True + + elif (uInput == "n" or uInput == "no"): + print '\n Trying Again.\n' + + else: + print '\n Invalid Input.\n' + + if not finalCheck: + return '' + + 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 (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as err: + isValid = apiInit("") # Initalize the keys.dat file with API information + if not isValid: + print '\n Config file not valid.\n' + return '' + + # 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') + + ret = "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface + ":" + str(apiPort) + "/" + print '\n API data successfully imported.' + + # Build the api credentials + return ret + + +def bmSettings(): + """Allows the viewing and modification of keys.dat settings.""" + + global keysPath + global usrPrompt + + BMConfigParser().read(keysPath) # Read the keys.dat + try: + print ' Configure file loading:', os.path.realpath(keysPath) + port = BMConfigParser().get('bitmessagesettings', 'port') + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as err: + print '\n File not found.\n' + return '' + + 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 + print ' -----------------------------------' + 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 ' ------------------------------------' + 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, (n)o or (Y)es?').lower() + + if uInput in ['y', 'yes']: + 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("Input the new port number.") + BMConfigParser().set('bitmessagesettings', 'port', str(uInput)) + elif uInput == "startonlogon": + print ' Current status: ' + str(startonlogon) + uInput = userInput("Input the new status.") + BMConfigParser().set('bitmessagesettings', 'startonlogon', str(uInput)) + elif uInput == "minimizetotray": + print ' Current status: ' + str(minimizetotray) + uInput = userInput("Input the new status.") + BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(uInput)) + elif uInput == "showtraynotifications": + print ' Current status: ' + str(showtraynotifications) + uInput = userInput("Input the new status.") + BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str(uInput)) + elif uInput == "startintray": + print ' Current status: ' + str(startintray) + uInput = userInput("Input the new status.") + BMConfigParser().set('bitmessagesettings', 'startintray', str(uInput)) + elif uInput == "defaultnoncetrialsperbyte": + print ' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte + uInput = userInput("Input the new defaultnoncetrialsperbyte.") + BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) + elif uInput == "defaultpayloadlengthextrabytes": + print ' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes + uInput = userInput("Input the new defaultpayloadlengthextrabytes.") + BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) + elif uInput == "daemon": + print ' Current status: ' + str(daemon) + uInput = userInput("Input 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("Input the new socksproxytype.") + BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(uInput)) + elif uInput == "sockshostname": + print ' Current socks host name: ' + sockshostname + uInput = userInput("Input the new sockshostname.") + BMConfigParser().set('bitmessagesettings', 'sockshostname', str(uInput)) + elif uInput == "socksport": + print ' Current socks port number: ' + socksport + uInput = userInput("Input the new socksport.") + BMConfigParser().set('bitmessagesettings', 'socksport', str(uInput)) + elif uInput == "socksauthentication": + print ' Current status: ' + str(socksauthentication) + uInput = userInput("Input the new status.") + BMConfigParser().set('bitmessagesettings', 'socksauthentication', str(uInput)) + elif uInput == "socksusername": + print ' Current socks username: ' + socksusername + uInput = userInput("Input the new socksusername.") + BMConfigParser().set('bitmessagesettings', 'socksusername', str(uInput)) + elif uInput == "sockspassword": + print ' Current socks password: ' + sockspassword + uInput = userInput("Input the new password.") + BMConfigParser().set('bitmessagesettings', 'sockspassword', str(uInput)) + else: + print "\n Invalid field. 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, (n)o or (Y)es?").lower() + + if uInput in ['n', 'no']: + with open(keysPath, 'wb') as configfile: + src = BMConfigParser().write(configfile) + restartBmNotify() + break + + +def main(): + apiData() # configuration file "keys.dat" searching + bmSettings() + + +if __name__ == "__main__": + main()