#!/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