/ src / bitmessagecli.py
bitmessagecli.py
   1  #!/usr/bin/python2.7
   2  # -*- coding: utf-8 -*-
   3  # pylint: disable=too-many-lines,global-statement,too-many-branches,too-many-statements,inconsistent-return-statements
   4  # pylint: disable=too-many-nested-blocks,too-many-locals,protected-access,too-many-arguments,too-many-function-args
   5  # pylint: disable=no-member
   6  """
   7  Created by Adam Melton (.dok) referenceing https://bitmessage.org/wiki/API_Reference for API documentation
   8  Distributed under the MIT/X11 software license. See http://www.opensource.org/licenses/mit-license.php.
   9  
  10  This is an example of a daemon client for PyBitmessage 0.6.2, by .dok (Version 0.3.1) , modified
  11  
  12  TODO: fix the following (currently ignored) violations:
  13  
  14  """
  15  
  16  import datetime
  17  import imghdr
  18  import json
  19  import ntpath
  20  import os
  21  import socket
  22  import sys
  23  import time
  24  
  25  from six.moves import input as raw_input
  26  from six.moves import xmlrpc_client as xmlrpclib
  27  
  28  from .bmconfigparser import config
  29  
  30  
  31  api = ''
  32  keysName = 'keys.dat'
  33  keysPath = 'keys.dat'
  34  usrPrompt = 0  # 0 = First Start, 1 = prompt, 2 = no prompt if the program is starting up
  35  knownAddresses = dict()
  36  
  37  
  38  def userInput(message):
  39      """Checks input for exit or quit. Also formats for input, etc"""
  40  
  41      global usrPrompt
  42  
  43      print(('\n' + message))
  44      uInput = input('> ')
  45  
  46      if uInput.lower() == 'exit':  # Returns the user to the main menu
  47          usrPrompt = 1
  48          main()
  49  
  50      elif uInput.lower() == 'quit':  # Quits the program
  51          print('\n     Bye\n')
  52          sys.exit(0)
  53  
  54      else:
  55          return uInput
  56  
  57  
  58  def restartBmNotify():
  59      """Prompt the user to restart Bitmessage"""
  60      print('\n     *******************************************************************')
  61      print('     WARNING: If Bitmessage is running locally, you must restart it now.')
  62      print('     *******************************************************************\n')
  63  
  64  
  65  # Begin keys.dat interactions
  66  
  67  
  68  def lookupAppdataFolder():
  69      """gets the appropriate folders for the .dat files depending on the OS. Taken from bitmessagemain.py"""
  70  
  71      APPNAME = "PyBitmessage"
  72      if sys.platform == 'darwin':
  73          if "HOME" in os.environ:
  74              dataFolder = os.path.join(os.environ["HOME"], "Library/Application support/", APPNAME) + '/'
  75          else:
  76              print(
  77                  '     Could not find home folder, please report '
  78                  'this message and your OS X version to the Daemon Github.')
  79              sys.exit(1)
  80  
  81      elif 'win32' in sys.platform or 'win64' in sys.platform:
  82          dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + '\\'
  83      else:
  84          dataFolder = os.path.expanduser(os.path.join("~", ".config/" + APPNAME + "/"))
  85      return dataFolder
  86  
  87  
  88  def configInit():
  89      """Initialised the configuration"""
  90  
  91      config.add_section('bitmessagesettings')
  92      # Sets the bitmessage port to stop the warning about the api not properly
  93      # being setup. This is in the event that the keys.dat is in a different
  94      # directory or is created locally to connect to a machine remotely.
  95      config.set('bitmessagesettings', 'port', '8444')
  96      config.set('bitmessagesettings', 'apienabled', 'true')  # Sets apienabled to true in keys.dat
  97  
  98      with open(keysName, 'wb') as configfile:
  99          config.write(configfile)
 100  
 101      print(('\n     ' + str(keysName) + ' Initalized in the same directory as daemon.py'))
 102      print(('     You will now need to configure the ' + str(keysName) + ' file.\n'))
 103  
 104  
 105  def apiInit(apiEnabled):
 106      """Initialise the API"""
 107  
 108      global usrPrompt
 109      config.read(keysPath)
 110  
 111      if apiEnabled is False:  # API information there but the api is disabled.
 112          uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower()
 113  
 114          if uInput == "y":
 115              config.set('bitmessagesettings', 'apienabled', 'true')  # Sets apienabled to true in keys.dat
 116              with open(keysPath, 'wb') as configfile:
 117                  config.write(configfile)
 118  
 119              print('Done')
 120              restartBmNotify()
 121              return True
 122  
 123          elif uInput == "n":
 124              print('     \n************************************************************')
 125              print('            Daemon will not work when the API is disabled.       ')
 126              print('     Please refer to the Bitmessage Wiki on how to setup the API.')
 127              print('     ************************************************************\n')
 128              usrPrompt = 1
 129              main()
 130  
 131          else:
 132              print('\n     Invalid Entry\n')
 133              usrPrompt = 1
 134              main()
 135  
 136      elif apiEnabled:  # API correctly setup
 137          # Everything is as it should be
 138          return True
 139  
 140      else:  # API information was not present.
 141          print(('\n     ' + str(keysPath) + ' not properly configured!\n'))
 142          uInput = userInput("Would you like to do this now, (Y)es or (N)o?").lower()
 143  
 144          if uInput == "y":  # User said yes, initalize the api by writing these values to the keys.dat file
 145              print(' ')
 146  
 147              apiUsr = userInput("API Username")
 148              apiPwd = userInput("API Password")
 149              apiPort = userInput("API Port")
 150              apiEnabled = userInput("API Enabled? (True) or (False)").lower()
 151              daemon = userInput("Daemon mode Enabled? (True) or (False)").lower()
 152  
 153              if (daemon != 'true' and daemon != 'false'):
 154                  print('\n     Invalid Entry for Daemon.\n')
 155                  uInput = 1
 156                  main()
 157  
 158              print('     -----------------------------------\n')
 159  
 160              # sets the bitmessage port to stop the warning about the api not properly
 161              # being setup. This is in the event that the keys.dat is in a different
 162              # directory or is created locally to connect to a machine remotely.
 163              config.set('bitmessagesettings', 'port', '8444')
 164              config.set('bitmessagesettings', 'apienabled', 'true')
 165              config.set('bitmessagesettings', 'apiport', apiPort)
 166              config.set('bitmessagesettings', 'apiinterface', '127.0.0.1')
 167              config.set('bitmessagesettings', 'apiusername', apiUsr)
 168              config.set('bitmessagesettings', 'apipassword', apiPwd)
 169              config.set('bitmessagesettings', 'daemon', daemon)
 170              with open(keysPath, 'wb') as configfile:
 171                  config.write(configfile)
 172  
 173              print('\n     Finished configuring the keys.dat file with API information.\n')
 174              restartBmNotify()
 175              return True
 176  
 177          elif uInput == "n":
 178              print('\n     ***********************************************************')
 179              print('     Please refer to the Bitmessage Wiki on how to setup the API.')
 180              print('     ***********************************************************\n')
 181              usrPrompt = 1
 182              main()
 183          else:
 184              print('     \nInvalid entry\n')
 185              usrPrompt = 1
 186              main()
 187  
 188  
 189  def apiData():
 190      """TBC"""
 191  
 192      global keysName
 193      global keysPath
 194      global usrPrompt
 195  
 196      config.read(keysPath)  # First try to load the config file (the keys.dat file) from the program directory
 197  
 198      try:
 199          config.get('bitmessagesettings', 'port')
 200          appDataFolder = ''
 201      except:  # noqa:E722
 202          # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory.
 203          appDataFolder = lookupAppdataFolder()
 204          keysPath = appDataFolder + keysPath
 205          config.read(keysPath)
 206  
 207          try:
 208              config.get('bitmessagesettings', 'port')
 209          except:  # noqa:E722
 210              # keys.dat was not there either, something is wrong.
 211              print('\n     ******************************************************************')
 212              print('     There was a problem trying to access the Bitmessage keys.dat file')
 213              print('                    or keys.dat is not set up correctly')
 214              print('       Make sure that daemon is in the same directory as Bitmessage. ')
 215              print('     ******************************************************************\n')
 216  
 217              uInput = userInput("Would you like to create a keys.dat in the local directory, (Y)es or (N)o?").lower()
 218  
 219              if uInput in ("y", "yes"):
 220                  configInit()
 221                  keysPath = keysName
 222                  usrPrompt = 0
 223                  main()
 224              elif uInput in ("n", "no"):
 225                  print('\n     Trying Again.\n')
 226                  usrPrompt = 0
 227                  main()
 228              else:
 229                  print('\n     Invalid Input.\n')
 230  
 231              usrPrompt = 1
 232              main()
 233  
 234      try:  # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after
 235          config.get('bitmessagesettings', 'apiport')
 236          config.get('bitmessagesettings', 'apiinterface')
 237          config.get('bitmessagesettings', 'apiusername')
 238          config.get('bitmessagesettings', 'apipassword')
 239  
 240      except:  # noqa:E722
 241          apiInit("")  # Initalize the keys.dat file with API information
 242  
 243      # keys.dat file was found or appropriately configured, allow information retrieval
 244      # apiEnabled =
 245      # apiInit(config.safeGetBoolean('bitmessagesettings','apienabled'))
 246      # #if false it will prompt the user, if true it will return true
 247  
 248      config.read(keysPath)  # read again since changes have been made
 249      apiPort = int(config.get('bitmessagesettings', 'apiport'))
 250      apiInterface = config.get('bitmessagesettings', 'apiinterface')
 251      apiUsername = config.get('bitmessagesettings', 'apiusername')
 252      apiPassword = config.get('bitmessagesettings', 'apipassword')
 253  
 254      print('\n     API data successfully imported.\n')
 255  
 256      # Build the api credentials
 257      return "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface + ":" + str(apiPort) + "/"
 258  
 259  
 260  # End keys.dat interactions
 261  
 262  
 263  def apiTest():
 264      """Tests the API connection to bitmessage. Returns true if it is connected."""
 265  
 266      try:
 267          result = api.add(2, 3)
 268      except:  # noqa:E722
 269          return False
 270  
 271      return result == 5
 272  
 273  
 274  def bmSettings():
 275      """Allows the viewing and modification of keys.dat settings."""
 276  
 277      global keysPath
 278      global usrPrompt
 279  
 280      keysPath = 'keys.dat'
 281  
 282      config.read(keysPath)  # Read the keys.dat
 283      try:
 284          port = config.get('bitmessagesettings', 'port')
 285      except:  # noqa:E722
 286          print('\n     File not found.\n')
 287          usrPrompt = 0
 288          main()
 289  
 290      startonlogon = config.safeGetBoolean('bitmessagesettings', 'startonlogon')
 291      minimizetotray = config.safeGetBoolean('bitmessagesettings', 'minimizetotray')
 292      showtraynotifications = config.safeGetBoolean('bitmessagesettings', 'showtraynotifications')
 293      startintray = config.safeGetBoolean('bitmessagesettings', 'startintray')
 294      defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte')
 295      defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes')
 296      daemon = config.safeGetBoolean('bitmessagesettings', 'daemon')
 297  
 298      socksproxytype = config.get('bitmessagesettings', 'socksproxytype')
 299      sockshostname = config.get('bitmessagesettings', 'sockshostname')
 300      socksport = config.get('bitmessagesettings', 'socksport')
 301      socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication')
 302      socksusername = config.get('bitmessagesettings', 'socksusername')
 303      sockspassword = config.get('bitmessagesettings', 'sockspassword')
 304  
 305      print('\n     -----------------------------------')
 306      print('     |   Current Bitmessage Settings   |')
 307      print('     -----------------------------------')
 308      print(('     port = ' + port))
 309      print(('     startonlogon = ' + str(startonlogon)))
 310      print(('     minimizetotray = ' + str(minimizetotray)))
 311      print(('     showtraynotifications = ' + str(showtraynotifications)))
 312      print(('     startintray = ' + str(startintray)))
 313      print(('     defaultnoncetrialsperbyte = ' + defaultnoncetrialsperbyte))
 314      print(('     defaultpayloadlengthextrabytes = ' + defaultpayloadlengthextrabytes))
 315      print(('     daemon = ' + str(daemon)))
 316      print('\n     ------------------------------------')
 317      print('     |   Current Connection Settings   |')
 318      print('     -----------------------------------')
 319      print(('     socksproxytype = ' + socksproxytype))
 320      print(('     sockshostname = ' + sockshostname))
 321      print(('     socksport = ' + socksport))
 322      print(('     socksauthentication = ' + str(socksauthentication)))
 323      print(('     socksusername = ' + socksusername))
 324      print(('     sockspassword = ' + sockspassword))
 325      print(' ')
 326  
 327      uInput = userInput("Would you like to modify any of these settings, (Y)es or (N)o?").lower()
 328  
 329      if uInput == "y":
 330          while True:  # loops if they mistype the setting name, they can exit the loop with 'exit'
 331              invalidInput = False
 332              uInput = userInput("What setting would you like to modify?").lower()
 333              print(' ')
 334  
 335              if uInput == "port":
 336                  print(('     Current port number: ' + port))
 337                  uInput = userInput("Enter the new port number.")
 338                  config.set('bitmessagesettings', 'port', str(uInput))
 339              elif uInput == "startonlogon":
 340                  print(('     Current status: ' + str(startonlogon)))
 341                  uInput = userInput("Enter the new status.")
 342                  config.set('bitmessagesettings', 'startonlogon', str(uInput))
 343              elif uInput == "minimizetotray":
 344                  print(('     Current status: ' + str(minimizetotray)))
 345                  uInput = userInput("Enter the new status.")
 346                  config.set('bitmessagesettings', 'minimizetotray', str(uInput))
 347              elif uInput == "showtraynotifications":
 348                  print(('     Current status: ' + str(showtraynotifications)))
 349                  uInput = userInput("Enter the new status.")
 350                  config.set('bitmessagesettings', 'showtraynotifications', str(uInput))
 351              elif uInput == "startintray":
 352                  print(('     Current status: ' + str(startintray)))
 353                  uInput = userInput("Enter the new status.")
 354                  config.set('bitmessagesettings', 'startintray', str(uInput))
 355              elif uInput == "defaultnoncetrialsperbyte":
 356                  print(('     Current default nonce trials per byte: ' + defaultnoncetrialsperbyte))
 357                  uInput = userInput("Enter the new defaultnoncetrialsperbyte.")
 358                  config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput))
 359              elif uInput == "defaultpayloadlengthextrabytes":
 360                  print(('     Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes))
 361                  uInput = userInput("Enter the new defaultpayloadlengthextrabytes.")
 362                  config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput))
 363              elif uInput == "daemon":
 364                  print(('     Current status: ' + str(daemon)))
 365                  uInput = userInput("Enter the new status.").lower()
 366                  config.set('bitmessagesettings', 'daemon', str(uInput))
 367              elif uInput == "socksproxytype":
 368                  print(('     Current socks proxy type: ' + socksproxytype))
 369                  print("Possibilities: 'none', 'SOCKS4a', 'SOCKS5'.")
 370                  uInput = userInput("Enter the new socksproxytype.")
 371                  config.set('bitmessagesettings', 'socksproxytype', str(uInput))
 372              elif uInput == "sockshostname":
 373                  print(('     Current socks host name: ' + sockshostname))
 374                  uInput = userInput("Enter the new sockshostname.")
 375                  config.set('bitmessagesettings', 'sockshostname', str(uInput))
 376              elif uInput == "socksport":
 377                  print(('     Current socks port number: ' + socksport))
 378                  uInput = userInput("Enter the new socksport.")
 379                  config.set('bitmessagesettings', 'socksport', str(uInput))
 380              elif uInput == "socksauthentication":
 381                  print(('     Current status: ' + str(socksauthentication)))
 382                  uInput = userInput("Enter the new status.")
 383                  config.set('bitmessagesettings', 'socksauthentication', str(uInput))
 384              elif uInput == "socksusername":
 385                  print(('     Current socks username: ' + socksusername))
 386                  uInput = userInput("Enter the new socksusername.")
 387                  config.set('bitmessagesettings', 'socksusername', str(uInput))
 388              elif uInput == "sockspassword":
 389                  print(('     Current socks password: ' + sockspassword))
 390                  uInput = userInput("Enter the new password.")
 391                  config.set('bitmessagesettings', 'sockspassword', str(uInput))
 392              else:
 393                  print("\n     Invalid input. Please try again.\n")
 394                  invalidInput = True
 395  
 396              if invalidInput is not True:  # don't prompt if they made a mistake.
 397                  uInput = userInput("Would you like to change another setting, (Y)es or (N)o?").lower()
 398  
 399                  if uInput != "y":
 400                      print('\n     Changes Made.\n')
 401                      with open(keysPath, 'wb') as configfile:
 402                          config.write(configfile)
 403                      restartBmNotify()
 404                      break
 405  
 406      elif uInput == "n":
 407          usrPrompt = 1
 408          main()
 409      else:
 410          print("Invalid input.")
 411          usrPrompt = 1
 412          main()
 413  
 414  
 415  def validAddress(address):
 416      """Predicate to test address validity"""
 417      address_information = json.loads(api.decodeAddress(address))
 418  
 419      return 'success' in str(address_information['status']).lower()
 420  
 421  
 422  def getAddress(passphrase, vNumber, sNumber):
 423      """Get a deterministic address"""
 424      passphrase = passphrase.encode('base64')  # passphrase must be encoded
 425  
 426      return api.getDeterministicAddress(passphrase, vNumber, sNumber)
 427  
 428  
 429  def subscribe():
 430      """Subscribe to an address"""
 431      global usrPrompt
 432  
 433      while True:
 434          address = userInput("What address would you like to subscribe to?")
 435  
 436          if address == "c":
 437              usrPrompt = 1
 438              print(' ')
 439              main()
 440          elif validAddress(address) is False:
 441              print('\n     Invalid. "c" to cancel. Please try again.\n')
 442          else:
 443              break
 444  
 445      label = userInput("Enter a label for this address.")
 446      label = label.encode('base64')
 447  
 448      api.addSubscription(address, label)
 449      print(('\n     You are now subscribed to: ' + address + '\n'))
 450  
 451  
 452  def unsubscribe():
 453      """Unsusbcribe from an address"""
 454      global usrPrompt
 455  
 456      while True:
 457          address = userInput("What address would you like to unsubscribe from?")
 458  
 459          if address == "c":
 460              usrPrompt = 1
 461              print(' ')
 462              main()
 463          elif validAddress(address) is False:
 464              print('\n     Invalid. "c" to cancel. Please try again.\n')
 465          else:
 466              break
 467  
 468      userInput("Are you sure, (Y)es or (N)o?").lower()  # uInput =
 469  
 470      api.deleteSubscription(address)
 471      print(('\n     You are now unsubscribed from: ' + address + '\n'))
 472  
 473  
 474  def listSubscriptions():
 475      """List subscriptions"""
 476  
 477      global usrPrompt
 478      print('\nLabel, Address, Enabled\n')
 479      try:
 480          print((api.listSubscriptions()))
 481      except:  # noqa:E722
 482          print('\n     Connection Error\n')
 483          usrPrompt = 0
 484          main()
 485      print(' ')
 486  
 487  
 488  def createChan():
 489      """Create a channel"""
 490  
 491      global usrPrompt
 492      password = userInput("Enter channel name")
 493      password = password.encode('base64')
 494      try:
 495          print((api.createChan(password)))
 496      except:  # noqa:E722
 497          print('\n     Connection Error\n')
 498          usrPrompt = 0
 499          main()
 500  
 501  
 502  def joinChan():
 503      """Join a channel"""
 504  
 505      global usrPrompt
 506      while True:
 507          address = userInput("Enter channel address")
 508  
 509          if address == "c":
 510              usrPrompt = 1
 511              print(' ')
 512              main()
 513          elif validAddress(address) is False:
 514              print('\n     Invalid. "c" to cancel. Please try again.\n')
 515          else:
 516              break
 517  
 518      password = userInput("Enter channel name")
 519      password = password.encode('base64')
 520      try:
 521          print((api.joinChan(password, address)))
 522      except:  # noqa:E722
 523          print('\n     Connection Error\n')
 524          usrPrompt = 0
 525          main()
 526  
 527  
 528  def leaveChan():
 529      """Leave a channel"""
 530  
 531      global usrPrompt
 532      while True:
 533          address = userInput("Enter channel address")
 534  
 535          if address == "c":
 536              usrPrompt = 1
 537              print(' ')
 538              main()
 539          elif validAddress(address) is False:
 540              print('\n     Invalid. "c" to cancel. Please try again.\n')
 541          else:
 542              break
 543  
 544      try:
 545          print((api.leaveChan(address)))
 546      except:  # noqa:E722
 547          print('\n     Connection Error\n')
 548          usrPrompt = 0
 549          main()
 550  
 551  
 552  def listAdd():
 553      """List all of the addresses and their info"""
 554      global usrPrompt
 555      try:
 556          jsonAddresses = json.loads(api.listAddresses())
 557          numAddresses = len(jsonAddresses['addresses'])  # Number of addresses
 558      except:  # noqa:E722
 559          print('\n     Connection Error\n')
 560          usrPrompt = 0
 561          main()
 562  
 563      # print('\nAddress Number,Label,Address,Stream,Enabled\n')
 564      print('\n     --------------------------------------------------------------------------')
 565      print('     | # |       Label       |               Address               |S#|Enabled|')
 566      print('     |---|-------------------|-------------------------------------|--|-------|')
 567      for addNum in range(0, numAddresses):  # processes all of the addresses and lists them out
 568          label = (jsonAddresses['addresses'][addNum]['label']).encode(
 569              'utf')  # may still misdiplay in some consoles
 570          address = str(jsonAddresses['addresses'][addNum]['address'])
 571          stream = str(jsonAddresses['addresses'][addNum]['stream'])
 572          enabled = str(jsonAddresses['addresses'][addNum]['enabled'])
 573  
 574          if len(label) > 19:
 575              label = label[:16] + '...'
 576  
 577          print((''.join([
 578              '     |',
 579              str(addNum).ljust(3),
 580              '|',
 581              label.ljust(19),
 582              '|',
 583              address.ljust(37),
 584              '|',
 585              stream.ljust(1),
 586              '|',
 587              enabled.ljust(7),
 588              '|',
 589          ])))
 590  
 591      print((''.join([
 592          '     ',
 593          74 * '-',
 594          '\n',
 595      ])))
 596  
 597  
 598  def genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe):
 599      """Generate address"""
 600  
 601      global usrPrompt
 602  
 603      if deterministic is False:  # Generates a new address with the user defined label. non-deterministic
 604          addressLabel = lbl.encode('base64')
 605          try:
 606              generatedAddress = api.createRandomAddress(addressLabel)
 607          except:  # noqa:E722
 608              print('\n     Connection Error\n')
 609              usrPrompt = 0
 610              main()
 611  
 612          return generatedAddress
 613  
 614      elif deterministic:  # Generates a new deterministic address with the user inputs.
 615          passphrase = passphrase.encode('base64')
 616          try:
 617              generatedAddress = api.createDeterministicAddresses(passphrase, numOfAdd, addVNum, streamNum, ripe)
 618          except:  # noqa:E722
 619              print('\n     Connection Error\n')
 620              usrPrompt = 0
 621              main()
 622          return generatedAddress
 623  
 624      return 'Entry Error'
 625  
 626  
 627  def saveFile(fileName, fileData):
 628      """Allows attachments and messages/broadcats to be saved"""
 629  
 630      # This section finds all invalid characters and replaces them with ~
 631      fileName = fileName.replace(" ", "")
 632      fileName = fileName.replace("/", "~")
 633      # fileName = fileName.replace("\\", "~") How do I get this to work...?
 634      fileName = fileName.replace(":", "~")
 635      fileName = fileName.replace("*", "~")
 636      fileName = fileName.replace("?", "~")
 637      fileName = fileName.replace('"', "~")
 638      fileName = fileName.replace("<", "~")
 639      fileName = fileName.replace(">", "~")
 640      fileName = fileName.replace("|", "~")
 641  
 642      directory = os.path.abspath('attachments')
 643  
 644      if not os.path.exists(directory):
 645          os.makedirs(directory)
 646  
 647      filePath = os.path.join(directory, fileName)
 648  
 649      with open(filePath, 'wb+') as path_to_file:
 650          path_to_file.write(fileData.decode("base64"))
 651      print(('\n     Successfully saved ' + filePath + '\n'))
 652  
 653  
 654  def attachment():
 655      """Allows users to attach a file to their message or broadcast"""
 656  
 657      theAttachmentS = ''
 658  
 659      while True:
 660  
 661          isImage = False
 662          theAttachment = ''
 663  
 664          while True:  # loops until valid path is entered
 665              filePath = userInput(
 666                  '\nPlease enter the path to the attachment or just the attachment name if in this folder.')
 667  
 668              try:
 669                  with open(filePath):
 670                      break
 671              except IOError:
 672                  print(('\n     %s was not found on your filesystem or can not be opened.\n' % filePath))
 673  
 674          # print(filesize, and encoding estimate with confirmation if file is over X size(1mb?))
 675          invSize = os.path.getsize(filePath)
 676          invSize = (invSize / 1024)  # Converts to kilobytes
 677          round(invSize, 2)  # Rounds to two decimal places
 678  
 679          if invSize > 500.0:  # If over 500KB
 680              print((''.join([
 681                  '\n     WARNING:The file that you are trying to attach is ',
 682                  invSize,
 683                  'KB and will take considerable time to send.\n'
 684              ])))
 685              uInput = userInput('Are you sure you still want to attach it, (Y)es or (N)o?').lower()
 686  
 687              if uInput != "y":
 688                  print('\n     Attachment discarded.\n')
 689                  return ''
 690          elif invSize > 184320.0:  # If larger than 180MB, discard.
 691              print('\n     Attachment too big, maximum allowed size:180MB\n')
 692              main()
 693  
 694          pathLen = len(str(ntpath.basename(filePath)))  # Gets the length of the filepath excluding the filename
 695          fileName = filePath[(len(str(filePath)) - pathLen):]  # reads the filename
 696  
 697          filetype = imghdr.what(filePath)  # Tests if it is an image file
 698          if filetype is not None:
 699              print('\n     ---------------------------------------------------')
 700              print('     Attachment detected as an Image.')
 701              print('     <img> tags will automatically be included,')
 702              print('     allowing the recipient to view the image')
 703              print('     using the "View HTML code..." option in Bitmessage.')
 704              print('     ---------------------------------------------------\n')
 705              isImage = True
 706              time.sleep(2)
 707  
 708          # Alert the user that the encoding process may take some time.
 709          print('\n     Encoding Attachment, Please Wait ...\n')
 710  
 711          with open(filePath, 'rb') as f:  # Begin the actual encoding
 712              data = f.read(188743680)  # Reads files up to 180MB, the maximum size for Bitmessage.
 713              data = data.encode("base64")
 714  
 715          if isImage:  # If it is an image, include image tags in the message
 716              theAttachment = """
 717  <!-- Note: Image attachment below. Please use the right click "View HTML code ..." option to view it. -->
 718  <!-- Sent using Bitmessage Daemon. https://github.com/Dokument/PyBitmessage-Daemon -->
 719  
 720  Filename:%s
 721  Filesize:%sKB
 722  Encoding:base64
 723  
 724  <center>
 725      <div id="image">
 726          <img alt = "%s" src='data:image/%s;base64, %s' />
 727      </div>
 728  </center>""" % (fileName, invSize, fileName, filetype, data)
 729          else:  # Else it is not an image so do not include the embedded image code.
 730              theAttachment = """
 731  <!-- Note: File attachment below. Please use a base64 decoder, or Daemon, to save it. -->
 732  <!-- Sent using Bitmessage Daemon. https://github.com/Dokument/PyBitmessage-Daemon -->
 733  
 734  Filename:%s
 735  Filesize:%sKB
 736  Encoding:base64
 737  
 738  <attachment alt = "%s" src='data:file/%s;base64, %s' />""" % (fileName, invSize, fileName, fileName, data)
 739  
 740          uInput = userInput('Would you like to add another attachment, (Y)es or (N)o?').lower()
 741  
 742          if uInput in ('y', 'yes'):  # Allows multiple attachments to be added to one message
 743              theAttachmentS = str(theAttachmentS) + str(theAttachment) + '\n\n'
 744          elif uInput in ('n', 'no'):
 745              break
 746  
 747      theAttachmentS = theAttachmentS + theAttachment
 748      return theAttachmentS
 749  
 750  
 751  def sendMsg(toAddress, fromAddress, subject, message):
 752      """
 753      With no arguments sent, sendMsg fills in the blanks.
 754      subject and message must be encoded before they are passed.
 755      """
 756  
 757      global usrPrompt
 758      if validAddress(toAddress) is False:
 759          while True:
 760              toAddress = userInput("What is the To Address?")
 761  
 762              if toAddress == "c":
 763                  usrPrompt = 1
 764                  print(' ')
 765                  main()
 766              elif validAddress(toAddress) is False:
 767                  print('\n     Invalid Address. "c" to cancel. Please try again.\n')
 768              else:
 769                  break
 770  
 771      if validAddress(fromAddress) is False:
 772          try:
 773              jsonAddresses = json.loads(api.listAddresses())
 774              numAddresses = len(jsonAddresses['addresses'])  # Number of addresses
 775          except:  # noqa:E722
 776              print('\n     Connection Error\n')
 777              usrPrompt = 0
 778              main()
 779  
 780          if numAddresses > 1:  # Ask what address to send from if multiple addresses
 781              found = False
 782              while True:
 783                  print(' ')
 784                  fromAddress = userInput("Enter an Address or Address Label to send from.")
 785  
 786                  if fromAddress == "exit":
 787                      usrPrompt = 1
 788                      main()
 789  
 790                  for addNum in range(0, numAddresses):  # processes all of the addresses
 791                      label = jsonAddresses['addresses'][addNum]['label']
 792                      address = jsonAddresses['addresses'][addNum]['address']
 793                      if fromAddress == label:  # address entered was a label and is found
 794                          fromAddress = address
 795                          found = True
 796                          break
 797  
 798                  if found is False:
 799                      if validAddress(fromAddress) is False:
 800                          print('\n     Invalid Address. Please try again.\n')
 801  
 802                      else:
 803                          for addNum in range(0, numAddresses):  # processes all of the addresses
 804                              address = jsonAddresses['addresses'][addNum]['address']
 805                              if fromAddress == address:  # address entered was a found in our addressbook.
 806                                  found = True
 807                                  break
 808  
 809                          if found is False:
 810                              print('\n     The address entered is not one of yours. Please try again.\n')
 811  
 812                  if found:
 813                      break  # Address was found
 814  
 815          else:  # Only one address in address book
 816              print('\n     Using the only address in the addressbook to send from.\n')
 817              fromAddress = jsonAddresses['addresses'][0]['address']
 818  
 819      if not subject:
 820          subject = userInput("Enter your Subject.")
 821          subject = subject.encode('base64')
 822      if not message:
 823          message = userInput("Enter your Message.")
 824  
 825          uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower()
 826          if uInput == "y":
 827              message = message + '\n\n' + attachment()
 828  
 829          message = message.encode('base64')
 830  
 831      try:
 832          ackData = api.sendMessage(toAddress, fromAddress, subject, message)
 833          print(('\n     Message Status:', api.getStatus(ackData), '\n'))
 834      except:  # noqa:E722
 835          print('\n     Connection Error\n')
 836          usrPrompt = 0
 837          main()
 838  
 839  
 840  def sendBrd(fromAddress, subject, message):
 841      """Send a broadcast"""
 842  
 843      global usrPrompt
 844      if not fromAddress:
 845  
 846          try:
 847              jsonAddresses = json.loads(api.listAddresses())
 848              numAddresses = len(jsonAddresses['addresses'])  # Number of addresses
 849          except:  # noqa:E722
 850              print('\n     Connection Error\n')
 851              usrPrompt = 0
 852              main()
 853  
 854          if numAddresses > 1:  # Ask what address to send from if multiple addresses
 855              found = False
 856              while True:
 857                  fromAddress = userInput("\nEnter an Address or Address Label to send from.")
 858  
 859                  if fromAddress == "exit":
 860                      usrPrompt = 1
 861                      main()
 862  
 863                  for addNum in range(0, numAddresses):  # processes all of the addresses
 864                      label = jsonAddresses['addresses'][addNum]['label']
 865                      address = jsonAddresses['addresses'][addNum]['address']
 866                      if fromAddress == label:  # address entered was a label and is found
 867                          fromAddress = address
 868                          found = True
 869                          break
 870  
 871                  if found is False:
 872                      if validAddress(fromAddress) is False:
 873                          print('\n     Invalid Address. Please try again.\n')
 874  
 875                      else:
 876                          for addNum in range(0, numAddresses):  # processes all of the addresses
 877                              address = jsonAddresses['addresses'][addNum]['address']
 878                              if fromAddress == address:  # address entered was a found in our addressbook.
 879                                  found = True
 880                                  break
 881  
 882                          if found is False:
 883                              print('\n     The address entered is not one of yours. Please try again.\n')
 884  
 885                  if found:
 886                      break  # Address was found
 887  
 888          else:  # Only one address in address book
 889              print('\n     Using the only address in the addressbook to send from.\n')
 890              fromAddress = jsonAddresses['addresses'][0]['address']
 891  
 892      if not subject:
 893          subject = userInput("Enter your Subject.")
 894          subject = subject.encode('base64')
 895      if not message:
 896          message = userInput("Enter your Message.")
 897  
 898          uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower()
 899          if uInput == "y":
 900              message = message + '\n\n' + attachment()
 901  
 902          message = message.encode('base64')
 903  
 904      try:
 905          ackData = api.sendBroadcast(fromAddress, subject, message)
 906          print(('\n     Message Status:', api.getStatus(ackData), '\n'))
 907      except:  # noqa:E722
 908          print('\n     Connection Error\n')
 909          usrPrompt = 0
 910          main()
 911  
 912  
 913  def inbox(unreadOnly=False):
 914      """Lists the messages by: Message Number, To Address Label, From Address Label, Subject, Received Time)"""
 915  
 916      global usrPrompt
 917      try:
 918          inboxMessages = json.loads(api.getAllInboxMessages())
 919          numMessages = len(inboxMessages['inboxMessages'])
 920      except:  # noqa:E722
 921          print('\n     Connection Error\n')
 922          usrPrompt = 0
 923          main()
 924  
 925      messagesPrinted = 0
 926      messagesUnread = 0
 927      for msgNum in range(0, numMessages):  # processes all of the messages in the inbox
 928          message = inboxMessages['inboxMessages'][msgNum]
 929          # if we are displaying all messages or if this message is unread then display it
 930          if not unreadOnly or not message['read']:
 931              print('     -----------------------------------\n')
 932              print(('     Message Number:', msgNum))  # Message Number)
 933              print(('     To:', getLabelForAddress(message['toAddress'])))  # Get the to address)
 934              print(('     From:', getLabelForAddress(message['fromAddress'])))  # Get the from address)
 935              print(('     Subject:', message['subject'].decode('base64')))  # Get the subject)
 936              print((''.join([
 937                  '     Received:',
 938                  datetime.datetime.fromtimestamp(
 939                      float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'),
 940              ])))
 941              messagesPrinted += 1
 942              if not message['read']:
 943                  messagesUnread += 1
 944  
 945          if messagesPrinted % 20 == 0 and messagesPrinted != 0:
 946              userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower()  # uInput =
 947  
 948      print('\n     -----------------------------------')
 949      print(('     There are %d unread messages of %d messages in the inbox.' % (messagesUnread, numMessages)))
 950      print('     -----------------------------------\n')
 951  
 952  
 953  def outbox():
 954      """TBC"""
 955  
 956      global usrPrompt
 957      try:
 958          outboxMessages = json.loads(api.getAllSentMessages())
 959          numMessages = len(outboxMessages['sentMessages'])
 960      except:  # noqa:E722
 961          print('\n     Connection Error\n')
 962          usrPrompt = 0
 963          main()
 964  
 965      for msgNum in range(0, numMessages):  # processes all of the messages in the outbox
 966          print('\n     -----------------------------------\n')
 967          print(('     Message Number:', msgNum))  # Message Number)
 968          # print('     Message ID:', outboxMessages['sentMessages'][msgNum]['msgid'])
 969          print(('     To:', getLabelForAddress(
 970              outboxMessages['sentMessages'][msgNum]['toAddress']
 971          )))  # Get the to address)
 972          # Get the from address
 973          print(('     From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress'])))
 974          print(('     Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64')))  # Get the subject)
 975          print(('     Status:', outboxMessages['sentMessages'][msgNum]['status']))  # Get the subject)
 976  
 977          # print(''.join([
 978          #     '     Last Action Time:',
 979          #     datetime.datetime.fromtimestamp(
 980          #         float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'),
 981          # ]))
 982          print((''.join([
 983              '     Last Action Time:',
 984              datetime.datetime.fromtimestamp(
 985                  float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'),
 986          ])))
 987  
 988          if msgNum % 20 == 0 and msgNum != 0:
 989              userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower()  # uInput =
 990  
 991      print('\n     -----------------------------------')
 992      print(('     There are ', numMessages, ' messages in the outbox.'))
 993      print('     -----------------------------------\n')
 994  
 995  
 996  def readSentMsg(msgNum):
 997      """Opens a sent message for reading"""
 998  
 999      global usrPrompt
1000      try:
1001          outboxMessages = json.loads(api.getAllSentMessages())
1002          numMessages = len(outboxMessages['sentMessages'])
1003      except:  # noqa:E722
1004          print('\n     Connection Error\n')
1005          usrPrompt = 0
1006          main()
1007  
1008      print(' ')
1009  
1010      if msgNum >= numMessages:
1011          print('\n     Invalid Message Number.\n')
1012          main()
1013  
1014      # Begin attachment detection
1015      message = outboxMessages['sentMessages'][msgNum]['message'].decode('base64')
1016  
1017      while True:  # Allows multiple messages to be downloaded/saved
1018          if ';base64,' in message:  # Found this text in the message, there is probably an attachment.
1019              attPos = message.index(";base64,")  # Finds the attachment position
1020              attEndPos = message.index("' />")  # Finds the end of the attachment
1021              # attLen = attEndPos - attPos #Finds the length of the message
1022  
1023              if 'alt = "' in message:  # We can get the filename too
1024                  fnPos = message.index('alt = "')  # Finds position of the filename
1025                  fnEndPos = message.index('" src=')  # Finds the end position
1026                  # fnLen = fnEndPos - fnPos #Finds the length of the filename
1027  
1028                  fileName = message[fnPos + 7:fnEndPos]
1029              else:
1030                  fnPos = attPos
1031                  fileName = 'Attachment'
1032  
1033              uInput = userInput(
1034                  '\n     Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower()
1035              if uInput in ("y", 'yes'):
1036  
1037                  this_attachment = message[attPos + 9:attEndPos]
1038                  saveFile(fileName, this_attachment)
1039  
1040              message = message[:fnPos] + '~<Attachment data removed for easier viewing>~' + message[(attEndPos + 4):]
1041  
1042          else:
1043              break
1044  
1045      # End attachment Detection
1046  
1047      print(('\n     To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress'])))  # Get the to address)
1048      # Get the from address
1049      print(('     From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress'])))
1050      print(('     Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64')))  # Get the subject)
1051      print(('     Status:', outboxMessages['sentMessages'][msgNum]['status']))  # Get the subject)
1052      print((''.join([
1053          '     Last Action Time:',
1054          datetime.datetime.fromtimestamp(
1055              float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'),
1056      ])))
1057      print('     Message:\n')
1058      print(message)  # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64'))
1059      print(' ')
1060  
1061  
1062  def readMsg(msgNum):
1063      """Open a message for reading"""
1064      global usrPrompt
1065      try:
1066          inboxMessages = json.loads(api.getAllInboxMessages())
1067          numMessages = len(inboxMessages['inboxMessages'])
1068      except:  # noqa:E722
1069          print('\n     Connection Error\n')
1070          usrPrompt = 0
1071          main()
1072  
1073      if msgNum >= numMessages:
1074          print('\n     Invalid Message Number.\n')
1075          main()
1076  
1077      # Begin attachment detection
1078      message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64')
1079  
1080      while True:  # Allows multiple messages to be downloaded/saved
1081          if ';base64,' in message:  # Found this text in the message, there is probably an attachment.
1082              attPos = message.index(";base64,")  # Finds the attachment position
1083              attEndPos = message.index("' />")  # Finds the end of the attachment
1084              # attLen = attEndPos - attPos #Finds the length of the message
1085  
1086              if 'alt = "' in message:  # We can get the filename too
1087                  fnPos = message.index('alt = "')  # Finds position of the filename
1088                  fnEndPos = message.index('" src=')  # Finds the end position
1089                  # fnLen = fnEndPos - fnPos #Finds the length of the filename
1090  
1091                  fileName = message[fnPos + 7:fnEndPos]
1092              else:
1093                  fnPos = attPos
1094                  fileName = 'Attachment'
1095  
1096              uInput = userInput(
1097                  '\n     Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower()
1098              if uInput in ("y", 'yes'):
1099  
1100                  this_attachment = message[attPos + 9:attEndPos]
1101                  saveFile(fileName, this_attachment)
1102  
1103              message = message[:fnPos] + '~<Attachment data removed for easier viewing>~' + message[attEndPos + 4:]
1104  
1105          else:
1106              break
1107  
1108      # End attachment Detection
1109      print(('\n     To:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['toAddress'])))  # Get the to address)
1110      # Get the from address
1111      print(('     From:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['fromAddress'])))
1112      print(('     Subject:', inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64')))  # Get the subject)
1113      print((''.join([
1114          '     Received:', datetime.datetime.fromtimestamp(
1115              float(inboxMessages['inboxMessages'][msgNum]['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'),
1116      ])))
1117      print('     Message:\n')
1118      print(message)  # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64'))
1119      print(' ')
1120      return inboxMessages['inboxMessages'][msgNum]['msgid']
1121  
1122  
1123  def replyMsg(msgNum, forwardORreply):
1124      """Allows you to reply to the message you are currently on. Saves typing in the addresses and subject."""
1125  
1126      global usrPrompt
1127      forwardORreply = forwardORreply.lower()  # makes it lowercase
1128      try:
1129          inboxMessages = json.loads(api.getAllInboxMessages())
1130      except:  # noqa:E722
1131          print('\n     Connection Error\n')
1132          usrPrompt = 0
1133          main()
1134  
1135      fromAdd = inboxMessages['inboxMessages'][msgNum]['toAddress']  # Address it was sent To, now the From address
1136      message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64')  # Message that you are replying too.
1137  
1138      subject = inboxMessages['inboxMessages'][msgNum]['subject']
1139      subject = subject.decode('base64')
1140  
1141      if forwardORreply == 'reply':
1142          toAdd = inboxMessages['inboxMessages'][msgNum]['fromAddress']  # Address it was From, now the To address
1143          subject = "Re: " + subject
1144  
1145      elif forwardORreply == 'forward':
1146          subject = "Fwd: " + subject
1147  
1148          while True:
1149              toAdd = userInput("What is the To Address?")
1150  
1151              if toAdd == "c":
1152                  usrPrompt = 1
1153                  print(' ')
1154                  main()
1155              elif validAddress(toAdd) is False:
1156                  print('\n     Invalid Address. "c" to cancel. Please try again.\n')
1157              else:
1158                  break
1159      else:
1160          print('\n     Invalid Selection. Reply or Forward only')
1161          usrPrompt = 0
1162          main()
1163  
1164      subject = subject.encode('base64')
1165  
1166      newMessage = userInput("Enter your Message.")
1167  
1168      uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower()
1169      if uInput == "y":
1170          newMessage = newMessage + '\n\n' + attachment()
1171  
1172      newMessage = newMessage + '\n\n------------------------------------------------------\n'
1173      newMessage = newMessage + message
1174      newMessage = newMessage.encode('base64')
1175  
1176      sendMsg(toAdd, fromAdd, subject, newMessage)
1177  
1178      main()
1179  
1180  
1181  def delMsg(msgNum):
1182      """Deletes a specified message from the inbox"""
1183  
1184      global usrPrompt
1185      try:
1186          inboxMessages = json.loads(api.getAllInboxMessages())
1187          # gets the message ID via the message index number
1188          msgId = inboxMessages['inboxMessages'][int(msgNum)]['msgid']
1189  
1190          msgAck = api.trashMessage(msgId)
1191      except:  # noqa:E722
1192          print('\n     Connection Error\n')
1193          usrPrompt = 0
1194          main()
1195  
1196      return msgAck
1197  
1198  
1199  def delSentMsg(msgNum):
1200      """Deletes a specified message from the outbox"""
1201  
1202      global usrPrompt
1203      try:
1204          outboxMessages = json.loads(api.getAllSentMessages())
1205          # gets the message ID via the message index number
1206          msgId = outboxMessages['sentMessages'][int(msgNum)]['msgid']
1207          msgAck = api.trashSentMessage(msgId)
1208      except:  # noqa:E722
1209          print('\n     Connection Error\n')
1210          usrPrompt = 0
1211          main()
1212  
1213      return msgAck
1214  
1215  
1216  def getLabelForAddress(address):
1217      """Get label for an address"""
1218  
1219      if address in knownAddresses:
1220          return knownAddresses[address]
1221      else:
1222          buildKnownAddresses()
1223          if address in knownAddresses:
1224              return knownAddresses[address]
1225  
1226      return address
1227  
1228  
1229  def buildKnownAddresses():
1230      """Build known addresses"""
1231  
1232      global usrPrompt
1233  
1234      # add from address book
1235      try:
1236          response = api.listAddressBookEntries()
1237          # if api is too old then fail
1238          if "API Error 0020" in response:
1239              return
1240          addressBook = json.loads(response)
1241          for entry in addressBook['addresses']:
1242              if entry['address'] not in knownAddresses:
1243                  knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address'])
1244      except:  # noqa:E722
1245          print('\n     Connection Error\n')
1246          usrPrompt = 0
1247          main()
1248  
1249      # add from my addresses
1250      try:
1251          response = api.listAddresses2()
1252          # if api is too old just return then fail
1253          if "API Error 0020" in response:
1254              return
1255          addresses = json.loads(response)
1256          for entry in addresses['addresses']:
1257              if entry['address'] not in knownAddresses:
1258                  knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address'])
1259      except:  # noqa:E722
1260          print('\n     Connection Error\n')
1261          usrPrompt = 0
1262          main()
1263  
1264  
1265  def listAddressBookEntries():
1266      """List addressbook entries"""
1267  
1268      global usrPrompt
1269  
1270      try:
1271          response = api.listAddressBookEntries()
1272          if "API Error" in response:
1273              return getAPIErrorCode(response)
1274          addressBook = json.loads(response)
1275          print('     --------------------------------------------------------------')
1276          print('     |        Label       |                Address                |')
1277          print('     |--------------------|---------------------------------------|')
1278          for entry in addressBook['addresses']:
1279              label = entry['label'].decode('base64')
1280              address = entry['address']
1281              if len(label) > 19:
1282                  label = label[:16] + '...'
1283              print(('     | ' + label.ljust(19) + '| ' + address.ljust(37) + ' |'))
1284          print('     --------------------------------------------------------------')
1285      except:  # noqa:E722
1286          print('\n     Connection Error\n')
1287          usrPrompt = 0
1288          main()
1289  
1290  
1291  def addAddressToAddressBook(address, label):
1292      """Add an address to an addressbook"""
1293  
1294      global usrPrompt
1295  
1296      try:
1297          response = api.addAddressBookEntry(address, label.encode('base64'))
1298          if "API Error" in response:
1299              return getAPIErrorCode(response)
1300      except:  # noqa:E722
1301          print('\n     Connection Error\n')
1302          usrPrompt = 0
1303          main()
1304  
1305  
1306  def deleteAddressFromAddressBook(address):
1307      """Delete an address from an addressbook"""
1308  
1309      global usrPrompt
1310  
1311      try:
1312          response = api.deleteAddressBookEntry(address)
1313          if "API Error" in response:
1314              return getAPIErrorCode(response)
1315      except:  # noqa:E722
1316          print('\n     Connection Error\n')
1317          usrPrompt = 0
1318          main()
1319  
1320  
1321  def getAPIErrorCode(response):
1322      """Get API error code"""
1323  
1324      if "API Error" in response:
1325          # if we got an API error return the number by getting the number
1326          # after the second space and removing the trailing colon
1327          return int(response.split()[2][:-1])
1328  
1329  
1330  def markMessageRead(messageID):
1331      """Mark a message as read"""
1332  
1333      global usrPrompt
1334  
1335      try:
1336          response = api.getInboxMessageByID(messageID, True)
1337          if "API Error" in response:
1338              return getAPIErrorCode(response)
1339      except:  # noqa:E722
1340          print('\n     Connection Error\n')
1341          usrPrompt = 0
1342          main()
1343  
1344  
1345  def markMessageUnread(messageID):
1346      """Mark a mesasge as unread"""
1347  
1348      global usrPrompt
1349  
1350      try:
1351          response = api.getInboxMessageByID(messageID, False)
1352          if "API Error" in response:
1353              return getAPIErrorCode(response)
1354      except:  # noqa:E722
1355          print('\n     Connection Error\n')
1356          usrPrompt = 0
1357          main()
1358  
1359  
1360  def markAllMessagesRead():
1361      """Mark all messages as read"""
1362  
1363      global usrPrompt
1364  
1365      try:
1366          inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages']
1367      except:  # noqa:E722
1368          print('\n     Connection Error\n')
1369          usrPrompt = 0
1370          main()
1371      for message in inboxMessages:
1372          if not message['read']:
1373              markMessageRead(message['msgid'])
1374  
1375  
1376  def markAllMessagesUnread():
1377      """Mark all messages as unread"""
1378  
1379      global usrPrompt
1380  
1381      try:
1382          inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages']
1383      except:  # noqa:E722
1384          print('\n     Connection Error\n')
1385          usrPrompt = 0
1386          main()
1387      for message in inboxMessages:
1388          if message['read']:
1389              markMessageUnread(message['msgid'])
1390  
1391  
1392  def clientStatus():
1393      """Print (the client status"""
1394  
1395      global usrPrompt
1396  
1397      try:
1398          client_status = json.loads(api.clientStatus())
1399      except:  # noqa:E722
1400          print('\n     Connection Error\n')
1401          usrPrompt = 0
1402          main()
1403  
1404      print(("\nnetworkStatus: " + client_status['networkStatus'] + "\n"))
1405      print(("\nnetworkConnections: " + str(client_status['networkConnections']) + "\n"))
1406      print(("\nnumberOfPubkeysProcessed: " + str(client_status['numberOfPubkeysProcessed']) + "\n"))
1407      print(("\nnumberOfMessagesProcessed: " + str(client_status['numberOfMessagesProcessed']) + "\n"))
1408      print(("\nnumberOfBroadcastsProcessed: " + str(client_status['numberOfBroadcastsProcessed']) + "\n"))
1409  
1410  
1411  def shutdown():
1412      """Shutdown the API"""
1413  
1414      try:
1415          api.shutdown()
1416      except socket.error:
1417          pass
1418      print("\nShutdown command relayed\n")
1419  
1420  
1421  def UI(usrInput):
1422      """Main user menu"""
1423  
1424      global usrPrompt
1425  
1426      if usrInput in ("help", "h", "?"):
1427          print(' ')
1428          print('     -------------------------------------------------------------------------')
1429          print('     |        https://github.com/Dokument/PyBitmessage-Daemon                |')
1430          print('     |-----------------------------------------------------------------------|')
1431          print('     | Command                | Description                                  |')
1432          print('     |------------------------|----------------------------------------------|')
1433          print('     | help                   | This help file.                              |')
1434          print('     | apiTest                | Tests the API                                |')
1435          print('     | addInfo                | Returns address information (If valid)       |')
1436          print('     | bmSettings             | BitMessage settings                          |')
1437          print('     | exit                   | Use anytime to return to main menu           |')
1438          print('     | quit                   | Quits the program                            |')
1439          print('     |------------------------|----------------------------------------------|')
1440          print('     | listAddresses          | Lists all of the users addresses             |')
1441          print('     | generateAddress        | Generates a new address                      |')
1442          print('     | getAddress             | Get determinist address from passphrase      |')
1443          print('     |------------------------|----------------------------------------------|')
1444          print('     | listAddressBookEntries | Lists entries from the Address Book          |')
1445          print('     | addAddressBookEntry    | Add address to the Address Book              |')
1446          print('     | deleteAddressBookEntry | Deletes address from the Address Book        |')
1447          print('     |------------------------|----------------------------------------------|')
1448          print('     | subscribe              | Subscribes to an address                     |')
1449          print('     | unsubscribe            | Unsubscribes from an address                 |')
1450          print('     |------------------------|----------------------------------------------|')
1451          print('     | create                 | Creates a channel                            |')
1452          print('     | join                   | Joins a channel                              |')
1453          print('     | leave                  | Leaves a channel                             |')
1454          print('     |------------------------|----------------------------------------------|')
1455          print('     | inbox                  | Lists the message information for the inbox  |')
1456          print('     | outbox                 | Lists the message information for the outbox |')
1457          print('     | send                   | Send a new message or broadcast              |')
1458          print('     | unread                 | Lists all unread inbox messages              |')
1459          print('     | read                   | Reads a message from the inbox or outbox     |')
1460          print('     | save                   | Saves message to text file                   |')
1461          print('     | delete                 | Deletes a message or all messages            |')
1462          print('     -------------------------------------------------------------------------')
1463          print(' ')
1464          main()
1465  
1466      elif usrInput == "apitest":  # tests the API Connection.
1467          if apiTest():
1468              print('\n     API connection test has: PASSED\n')
1469          else:
1470              print('\n     API connection test has: FAILED\n')
1471          main()
1472  
1473      elif usrInput == "addinfo":
1474          tmp_address = userInput('\nEnter the Bitmessage Address.')
1475          address_information = json.loads(api.decodeAddress(tmp_address))
1476  
1477          print('\n------------------------------')
1478  
1479          if 'success' in str(address_information['status']).lower():
1480              print(' Valid Address')
1481              print((' Address Version: %s' % str(address_information['addressVersion'])))
1482              print((' Stream Number: %s' % str(address_information['streamNumber'])))
1483          else:
1484              print(' Invalid Address !')
1485  
1486          print('------------------------------\n')
1487          main()
1488  
1489      elif usrInput == "bmsettings":  # tests the API Connection.
1490          bmSettings()
1491          print(' ')
1492          main()
1493  
1494      elif usrInput == "quit":  # Quits the application
1495          print('\n     Bye\n')
1496          sys.exit(0)
1497  
1498      elif usrInput == "listaddresses":  # Lists all of the identities in the addressbook
1499          listAdd()
1500          main()
1501  
1502      elif usrInput == "generateaddress":  # Generates a new address
1503          uInput = userInput('\nWould you like to create a (D)eterministic or (R)andom address?').lower()
1504  
1505          if uInput in ("d", "deterministic"):  # Creates a deterministic address
1506              deterministic = True
1507  
1508              lbl = ''
1509              passphrase = userInput('Enter the Passphrase.')  # .encode('base64')
1510              numOfAdd = int(userInput('How many addresses would you like to generate?'))
1511              addVNum = 3
1512              streamNum = 1
1513              isRipe = userInput('Shorten the address, (Y)es or (N)o?').lower()
1514  
1515              if isRipe == "y":
1516                  ripe = True
1517                  print((genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe)))
1518                  main()
1519              elif isRipe == "n":
1520                  ripe = False
1521                  print((genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe)))
1522                  main()
1523              elif isRipe == "exit":
1524                  usrPrompt = 1
1525                  main()
1526              else:
1527                  print('\n     Invalid input\n')
1528                  main()
1529  
1530          elif uInput == "r" or uInput == "random":  # Creates a random address with user-defined label
1531              deterministic = False
1532              null = ''
1533              lbl = userInput('Enter the label for the new address.')
1534  
1535              print((genAdd(lbl, deterministic, null, null, null, null, null)))
1536              main()
1537  
1538          else:
1539              print('\n     Invalid input\n')
1540              main()
1541  
1542      elif usrInput == "getaddress":  # Gets the address for/from a passphrase
1543          phrase = userInput("Enter the address passphrase.")
1544          print('\n     Working...\n')
1545          address = getAddress(phrase, 4, 1)  # ,vNumber,sNumber)
1546          print(('\n     Address: ' + address + '\n'))
1547          usrPrompt = 1
1548          main()
1549  
1550      elif usrInput == "subscribe":  # Subsribe to an address
1551          subscribe()
1552          usrPrompt = 1
1553          main()
1554  
1555      elif usrInput == "unsubscribe":  # Unsubscribe from an address
1556          unsubscribe()
1557          usrPrompt = 1
1558          main()
1559  
1560      elif usrInput == "listsubscriptions":  # Unsubscribe from an address
1561          listSubscriptions()
1562          usrPrompt = 1
1563          main()
1564  
1565      elif usrInput == "create":
1566          createChan()
1567          usrPrompt = 1
1568          main()
1569  
1570      elif usrInput == "join":
1571          joinChan()
1572          usrPrompt = 1
1573          main()
1574  
1575      elif usrInput == "leave":
1576          leaveChan()
1577          usrPrompt = 1
1578          main()
1579  
1580      elif usrInput == "inbox":
1581          print('\n     Loading...\n')
1582          inbox()
1583          main()
1584  
1585      elif usrInput == "unread":
1586          print('\n     Loading...\n')
1587          inbox(True)
1588          main()
1589  
1590      elif usrInput == "outbox":
1591          print('\n     Loading...\n')
1592          outbox()
1593          main()
1594  
1595      elif usrInput == 'send':  # Sends a message or broadcast
1596          uInput = userInput('Would you like to send a (M)essage or (B)roadcast?').lower()
1597  
1598          if uInput in ('m', 'message'):
1599              null = ''
1600              sendMsg(null, null, null, null)
1601              main()
1602          elif uInput in ('b', 'broadcast'):
1603              null = ''
1604              sendBrd(null, null, null)
1605              main()
1606  
1607      elif usrInput == "read":  # Opens a message from the inbox for viewing.
1608  
1609          uInput = userInput("Would you like to read a message from the (I)nbox or (O)utbox?").lower()
1610  
1611          if uInput not in ('i', 'inbox', 'o', 'outbox'):
1612              print('\n     Invalid Input.\n')
1613              usrPrompt = 1
1614              main()
1615  
1616          msgNum = int(userInput("What is the number of the message you wish to open?"))
1617  
1618          if uInput in ('i', 'inbox'):
1619              print('\n     Loading...\n')
1620              messageID = readMsg(msgNum)
1621  
1622              uInput = userInput("\nWould you like to keep this message unread, (Y)es or (N)o?").lower()
1623  
1624              if uInput not in ('y', 'yes'):
1625                  markMessageRead(messageID)
1626                  usrPrompt = 1
1627  
1628              uInput = userInput("\nWould you like to (D)elete, (F)orward, (R)eply to, or (Exit) this message?").lower()
1629  
1630              if uInput in ('r', 'reply'):
1631                  print('\n     Loading...\n')
1632                  print(' ')
1633                  replyMsg(msgNum, 'reply')
1634                  usrPrompt = 1
1635  
1636              elif uInput in ('f', 'forward'):
1637                  print('\n     Loading...\n')
1638                  print(' ')
1639                  replyMsg(msgNum, 'forward')
1640                  usrPrompt = 1
1641  
1642              elif uInput in ("d", 'delete'):
1643                  uInput = userInput("Are you sure, (Y)es or (N)o?").lower()  # Prevent accidental deletion
1644  
1645                  if uInput == "y":
1646                      delMsg(msgNum)
1647                      print('\n     Message Deleted.\n')
1648                      usrPrompt = 1
1649                  else:
1650                      usrPrompt = 1
1651              else:
1652                  print('\n     Invalid entry\n')
1653                  usrPrompt = 1
1654  
1655          elif uInput in ('o', 'outbox'):
1656              readSentMsg(msgNum)
1657  
1658              # Gives the user the option to delete the message
1659              uInput = userInput("Would you like to (D)elete, or (Exit) this message?").lower()
1660  
1661              if uInput in ("d", 'delete'):
1662                  uInput = userInput('Are you sure, (Y)es or (N)o?').lower()  # Prevent accidental deletion
1663  
1664                  if uInput == "y":
1665                      delSentMsg(msgNum)
1666                      print('\n     Message Deleted.\n')
1667                      usrPrompt = 1
1668                  else:
1669                      usrPrompt = 1
1670              else:
1671                  print('\n     Invalid Entry\n')
1672                  usrPrompt = 1
1673  
1674          main()
1675  
1676      elif usrInput == "save":
1677  
1678          uInput = userInput("Would you like to save a message from the (I)nbox or (O)utbox?").lower()
1679  
1680          if uInput not in ('i', 'inbox', 'o', 'outbox'):
1681              print('\n     Invalid Input.\n')
1682              usrPrompt = 1
1683              main()
1684  
1685          if uInput in ('i', 'inbox'):
1686              inboxMessages = json.loads(api.getAllInboxMessages())
1687              numMessages = len(inboxMessages['inboxMessages'])
1688  
1689              while True:
1690                  msgNum = int(userInput("What is the number of the message you wish to save?"))
1691  
1692                  if msgNum >= numMessages:
1693                      print('\n     Invalid Message Number.\n')
1694                  else:
1695                      break
1696  
1697              subject = inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64')
1698              # Don't decode since it is done in the saveFile function
1699              message = inboxMessages['inboxMessages'][msgNum]['message']
1700  
1701          elif uInput == 'o' or uInput == 'outbox':
1702              outboxMessages = json.loads(api.getAllSentMessages())
1703              numMessages = len(outboxMessages['sentMessages'])
1704  
1705              while True:
1706                  msgNum = int(userInput("What is the number of the message you wish to save?"))
1707  
1708                  if msgNum >= numMessages:
1709                      print('\n     Invalid Message Number.\n')
1710                  else:
1711                      break
1712  
1713              subject = outboxMessages['sentMessages'][msgNum]['subject'].decode('base64')
1714              # Don't decode since it is done in the saveFile function
1715              message = outboxMessages['sentMessages'][msgNum]['message']
1716  
1717          subject = subject + '.txt'
1718          saveFile(subject, message)
1719  
1720          usrPrompt = 1
1721          main()
1722  
1723      elif usrInput == "delete":  # will delete a message from the system, not reflected on the UI.
1724  
1725          uInput = userInput("Would you like to delete a message from the (I)nbox or (O)utbox?").lower()
1726  
1727          if uInput in ('i', 'inbox'):
1728              inboxMessages = json.loads(api.getAllInboxMessages())
1729              numMessages = len(inboxMessages['inboxMessages'])
1730  
1731              while True:
1732                  msgNum = userInput(
1733                      'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower()
1734  
1735                  if msgNum == 'a' or msgNum == 'all':
1736                      break
1737                  elif int(msgNum) >= numMessages:
1738                      print('\n     Invalid Message Number.\n')
1739                  else:
1740                      break
1741  
1742              uInput = userInput("Are you sure, (Y)es or (N)o?").lower()  # Prevent accidental deletion
1743  
1744              if uInput == "y":
1745                  if msgNum in ('a', 'all'):
1746                      print(' ')
1747                      for msgNum in range(0, numMessages):  # processes all of the messages in the inbox
1748                          print(('     Deleting message ', msgNum + 1, ' of ', numMessages))
1749                          delMsg(0)
1750  
1751                      print('\n     Inbox is empty.')
1752                      usrPrompt = 1
1753                  else:
1754                      delMsg(int(msgNum))
1755  
1756                  print('\n     Notice: Message numbers may have changed.\n')
1757                  main()
1758              else:
1759                  usrPrompt = 1
1760  
1761          elif uInput in ('o', 'outbox'):
1762              outboxMessages = json.loads(api.getAllSentMessages())
1763              numMessages = len(outboxMessages['sentMessages'])
1764  
1765              while True:
1766                  msgNum = userInput(
1767                      'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower()
1768  
1769                  if msgNum in ('a', 'all'):
1770                      break
1771                  elif int(msgNum) >= numMessages:
1772                      print('\n     Invalid Message Number.\n')
1773                  else:
1774                      break
1775  
1776              uInput = userInput("Are you sure, (Y)es or (N)o?").lower()  # Prevent accidental deletion
1777  
1778              if uInput == "y":
1779                  if msgNum in ('a', 'all'):
1780                      print(' ')
1781                      for msgNum in range(0, numMessages):  # processes all of the messages in the outbox
1782                          print(('     Deleting message ', msgNum + 1, ' of ', numMessages))
1783                          delSentMsg(0)
1784  
1785                      print('\n     Outbox is empty.')
1786                      usrPrompt = 1
1787                  else:
1788                      delSentMsg(int(msgNum))
1789                  print('\n     Notice: Message numbers may have changed.\n')
1790                  main()
1791              else:
1792                  usrPrompt = 1
1793          else:
1794              print('\n     Invalid Entry.\n')
1795              usrPrompt = 1
1796              main()
1797  
1798      elif usrInput == "exit":
1799          print('\n     You are already at the main menu. Use "quit" to quit.\n')
1800          usrPrompt = 1
1801          main()
1802  
1803      elif usrInput == "listaddressbookentries":
1804          res = listAddressBookEntries()
1805          if res == 20:
1806              print('\n     Error: API function not supported.\n')
1807          usrPrompt = 1
1808          main()
1809  
1810      elif usrInput == "addaddressbookentry":
1811          address = userInput('Enter address')
1812          label = userInput('Enter label')
1813          res = addAddressToAddressBook(address, label)
1814          if res == 16:
1815              print('\n     Error: Address already exists in Address Book.\n')
1816          if res == 20:
1817              print('\n     Error: API function not supported.\n')
1818          usrPrompt = 1
1819          main()
1820  
1821      elif usrInput == "deleteaddressbookentry":
1822          address = userInput('Enter address')
1823          res = deleteAddressFromAddressBook(address)
1824          if res == 20:
1825              print('\n     Error: API function not supported.\n')
1826          usrPrompt = 1
1827          main()
1828  
1829      elif usrInput == "markallmessagesread":
1830          markAllMessagesRead()
1831          usrPrompt = 1
1832          main()
1833  
1834      elif usrInput == "markallmessagesunread":
1835          markAllMessagesUnread()
1836          usrPrompt = 1
1837          main()
1838  
1839      elif usrInput == "status":
1840          clientStatus()
1841          usrPrompt = 1
1842          main()
1843  
1844      elif usrInput == "shutdown":
1845          shutdown()
1846          usrPrompt = 1
1847          main()
1848  
1849      else:
1850          print(('\n     "', usrInput, '" is not a command.\n'))
1851          usrPrompt = 1
1852          main()
1853  
1854  
1855  def main():
1856      """Entrypoint for the CLI app"""
1857  
1858      global api
1859      global usrPrompt
1860  
1861      if usrPrompt == 0:
1862          print('\n     ------------------------------')
1863          print('     | Bitmessage Daemon by .dok  |')
1864          print('     | Version 0.3.1 for BM 0.6.2 |')
1865          print('     ------------------------------')
1866          api = xmlrpclib.ServerProxy(apiData())  # Connect to BitMessage using these api credentials
1867  
1868          if apiTest() is False:
1869              print('\n     ****************************************************************')
1870              print('        WARNING: You are not connected to the Bitmessage client.')
1871              print('     Either Bitmessage is not running or your settings are incorrect.')
1872              print('     Use the command "apiTest" or "bmSettings" to resolve this issue.')
1873              print('     ****************************************************************\n')
1874  
1875          print('Type (H)elp for a list of commands.')  # Startup message)
1876          usrPrompt = 2
1877  
1878      elif usrPrompt == 1:
1879          print('\nType (H)elp for a list of commands.')  # Startup message)
1880          usrPrompt = 2
1881  
1882      try:
1883          UI((input('>').lower()).replace(" ", ""))
1884      except EOFError:
1885          UI("quit")
1886  
1887  
1888  if __name__ == "__main__":
1889      main()