#!/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 datetime
import imghdr
import json
import ntpath
import os
import socket
import sys
import time
import xmlrpclib
from bmconfigparser import BMConfigParser
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()
def userInput(message):
"""Checks input for exit or quit. Also formats for input, etc"""
global usrPrompt
print ('\n' + message)
uInput = raw_input('> ')
if uInput.lower() == 'exit': # Returns the user to the main menu
usrPrompt = 1
main()
elif uInput.lower() == 'quit': # Quits the program
print ('\n Bye\n')
sys.exit(0)
else:
return 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
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()
def validAddress(address):
"""Predicate to test address validity"""
address_information = json.loads(api.decodeAddress(address))
return 'success' in str(address_information['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)
def subscribe():
"""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')
api.addSubscription(address, label)
print ('\n You are now subscribed to: ' + address + '\n')
def unsubscribe():
"""Unsusbcribe from an address"""
global usrPrompt
while True:
address = userInput("What address would you like to unsubscribe from?")
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')
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 (' ')
def createChan():
"""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()
def joinChan():
"""Join a channel"""
global usrPrompt
while True:
address = userInput("Enter channel address")
if address == "c":
usrPrompt = 1
print (' ')
main()
elif validAddress(address) is False:
print ('\n Invalid. "c" to cancel. Please try again.\n')
else:
break
password = userInput("Enter channel name")
password = password.encode('base64')
try:
print (api.joinChan(password, address))
except:
print ('\n Connection Error\n')
usrPrompt = 0
main()
def leaveChan():
"""Leave a channel"""
global usrPrompt
while True:
address = userInput("Enter channel address")
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()
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 (' |---|-------------------|-------------------------------------|--|-------|')
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'])
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([
' ',
74 * '-',
'\n',
]))
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()
return generatedAddress
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'
def saveFile(fileName, fileData):
"""Allows attachments and messages/broadcats to be saved"""
# 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("|", "~")
directory = os.path.abspath('attachments')
if not os.path.exists(directory):
os.makedirs(directory)
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')
def attachment():
"""Allows users to attach a file to their message or broadcast"""
theAttachmentS = ''
while True:
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.')
try:
with open(filePath):
break
except IOError:
print ('\n %s was not found on your filesystem or can not be opened.\n' % filePath)
# print (filesize, and encoding estimate with confirmation if file is over X size (1mb?))
invSize = os.path.getsize(filePath)
invSize = (invSize / 1024) # Converts to kilobytes
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()
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()
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 (' Attachment detected as an Image.')
print (' tags will automatically be included,')
print (' allowing the recipient to view the image')
print (' using the "View HTML code..." option in Bitmessage.')
print (' ---------------------------------------------------\n')
isImage = True
time.sleep(2)
# Alert the user that the encoding process may take some time.
print ('\n Encoding Attachment, Please Wait ...\n')
with open(filePath, 'rb') as f: # Begin the actual encoding
data = f.read(188743680) # Reads files up to 180MB, the maximum size for Bitmessage.
data = data.encode("base64")
if isImage: # If it is an image, include image tags in the message
theAttachment = """
Filename:%s
Filesize:%sKB
Encoding:base64