Formatted lines for PEP8, handled pylint warnings, added docstrings

This commit is contained in:
Dmitri Bogomolov 2018-11-05 18:35:38 +02:00
parent 3a8e842e60
commit ef5593b3d5
Signed by untrusted user: g1itch
GPG Key ID: 720A756F18DEED13

View File

@ -3,9 +3,58 @@
# pylint: disable=too-many-lines,no-self-use,unused-variable,unused-argument # pylint: disable=too-many-lines,no-self-use,unused-variable,unused-argument
""" """
This is not what you run to run the Bitmessage API. Instead, enable the API This is not what you run to start the Bitmessage API.
( https://bitmessage.org/wiki/API ) and optionally enable daemon mode Instead, `enable the API <https://bitmessage.org/wiki/API>`_
( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py. and optionally `enable daemon mode <https://bitmessage.org/wiki/Daemon>`_
then run the PyBitmessage.
The PyBitmessage API is provided either as
`XML-RPC <http://xmlrpc.scripting.com/spec.html>`_ or
`JSON-RPC <https://www.jsonrpc.org/specification>`_ like in bitcoin.
It's selected according to 'apivariant' setting in config file.
Special value ``apivariant=legacy`` is to mimic the old pre 0.6.3
behaviour when any results are returned as strings of json.
.. list-table:: All config settings related to API:
:header-rows: 0
* - apienabled = true
- if 'false' the `singleAPI` wont start
* - apiinterface = 127.0.0.1
- this is the recommended default
* - apiport = 8442
- the API listens apiinterface:apiport if apiport is not used,
random in range (32767, 65535) otherwice
* - apivariant = xml
- current default for backward compatibility, 'json' is recommended
* - apiusername = username
- set the username
* - apipassword = password
- and the password
* - apinotifypath =
- not really the API setting, this sets a path for the executable to be ran
when certain internal event happens
To use the API concider such simple example:
.. code-block:: python
import jsonrpclib
from pybitmessage import bmconfigparser, helper_startup
helper_startup.loadConfig() # find and load local config file
conf = bmconfigparser.BMConfigParser()
api_uri = "http://%s:%s@127.0.0.1:8442/" % (
conf.safeGet('bitmessagesettings', 'apiusername'),
conf.safeGet('bitmessagesettings', 'apipassword')
)
api = jsonrpclib.ServerProxy(api_uri)
print(api.clientStatus())
For further examples please reference `.tests.test_api`.
""" """
import base64 import base64
@ -86,6 +135,11 @@ class singleAPI(StoppableThread):
pass pass
def run(self): def run(self):
"""
The instance of `SimpleXMLRPCServer.SimpleXMLRPCServer` or
:class:`jsonrpclib.SimpleJSONRPCServer` is created and started here
with `BMRPCDispatcher` dispatcher.
"""
port = BMConfigParser().getint('bitmessagesettings', 'apiport') port = BMConfigParser().getint('bitmessagesettings', 'apiport')
try: try:
getattr(errno, 'WSAEADDRINUSE') getattr(errno, 'WSAEADDRINUSE')
@ -185,7 +239,7 @@ class CommandHandler(type):
return result return result
class command(object): class command(object): # pylint: disable=too-few-public-methods
"""Decorator for API command method""" """Decorator for API command method"""
def __init__(self, *aliases): def __init__(self, *aliases):
self.aliases = aliases self.aliases = aliases
@ -194,6 +248,10 @@ class command(object):
if BMConfigParser().safeGet( if BMConfigParser().safeGet(
'bitmessagesettings', 'apivariant') == 'legacy': 'bitmessagesettings', 'apivariant') == 'legacy':
def wrapper(*args): def wrapper(*args):
"""
A wrapper for legacy apivariant which dumps the result
into string of json
"""
result = func(*args) result = func(*args)
return result if isinstance(result, (int, str)) \ return result if isinstance(result, (int, str)) \
else json.dumps(result, indent=4) else json.dumps(result, indent=4)
@ -202,6 +260,9 @@ class command(object):
wrapper = func wrapper = func
# pylint: disable=protected-access # pylint: disable=protected-access
wrapper._cmd = self.aliases wrapper._cmd = self.aliases
wrapper.__doc__ = """Commands: *%s*
""" % ', '.join(self.aliases) + wrapper.__doc__.lstrip()
return wrapper return wrapper
@ -213,6 +274,7 @@ class command(object):
class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
"""The main API handler""" """The main API handler"""
# pylint: disable=protected-access
def do_POST(self): def do_POST(self):
""" """
Handles the HTTP POST request. Handles the HTTP POST request.
@ -220,8 +282,9 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
Attempts to interpret all HTTP POST requests as XML-RPC calls, Attempts to interpret all HTTP POST requests as XML-RPC calls,
which are forwarded to the server's _dispatch method for handling. which are forwarded to the server's _dispatch method for handling.
Note: this method is the same as in SimpleXMLRPCRequestHandler, .. note:: this method is the same as in
just hacked to handle cookies `SimpleXMLRPCServer.SimpleXMLRPCRequestHandler`,
just hacked to handle cookies
""" """
# Check that the path is legal # Check that the path is legal
@ -260,7 +323,7 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
# SimpleXMLRPCDispatcher. To maintain backwards compatibility, # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
# check to see if a subclass implements _dispatch and dispatch # check to see if a subclass implements _dispatch and dispatch
# using that method if present. # using that method if present.
# pylint: disable=protected-access
response = self.server._marshaled_dispatch( response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None) data, getattr(self, '_dispatch', None)
) )
@ -292,18 +355,19 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
shutdown.doCleanShutdown() shutdown.doCleanShutdown()
def APIAuthenticateClient(self): def APIAuthenticateClient(self):
"""Predicate to check for valid API credentials in the request header""" """
Predicate to check for valid API credentials in the request header
"""
if 'Authorization' in self.headers: if 'Authorization' in self.headers:
# handle Basic authentication # handle Basic authentication
encstr = self.headers.get('Authorization').split()[1] encstr = self.headers.get('Authorization').split()[1]
emailid, password = encstr.decode('base64').split(':') emailid, password = encstr.decode('base64').split(':')
return ( return (
emailid == self.config.get( emailid == BMConfigParser().get(
'bitmessagesettings', 'apiusername') 'bitmessagesettings', 'apiusername'
and password == self.config.get( ) and password == BMConfigParser().get(
'bitmessagesettings', 'apipassword') 'bitmessagesettings', 'apipassword'))
)
else: else:
logger.warning( logger.warning(
'Authentication failed because header lacks' 'Authentication failed because header lacks'
@ -313,11 +377,13 @@ class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
return False return False
# pylint: disable=no-self-use,no-member,too-many-public-methods
class BMRPCDispatcher(object): class BMRPCDispatcher(object):
"""This class is used to dispatch API commands""" """This class is used to dispatch API commands"""
__metaclass__ = CommandHandler __metaclass__ = CommandHandler
def _decode(self, text, decode_type): @staticmethod
def _decode(text, decode_type):
try: try:
if decode_type == 'hex': if decode_type == 'hex':
return unhexlify(text) return unhexlify(text)
@ -328,7 +394,6 @@ class BMRPCDispatcher(object):
22, 'Decode error - %s. Had trouble while decoding string: %r' 22, 'Decode error - %s. Had trouble while decoding string: %r'
% (e, text) % (e, text)
) )
return None
def _verifyAddress(self, address): def _verifyAddress(self, address):
status, addressVersionNumber, streamNumber, ripe = \ status, addressVersionNumber, streamNumber, ripe = \
@ -340,9 +405,8 @@ class BMRPCDispatcher(object):
raise APIError(9, 'Invalid characters in address: ' + address) raise APIError(9, 'Invalid characters in address: ' + address)
if status == 'versiontoohigh': if status == 'versiontoohigh':
raise APIError( raise APIError(
10, 10, 'Address version number too high (or zero) in address: '
'Address version number too high (or zero) in address: ' + + address)
address)
if status == 'varintmalformed': if status == 'varintmalformed':
raise APIError(26, 'Malformed varint in address: ' + address) raise APIError(26, 'Malformed varint in address: ' + address)
raise APIError( raise APIError(
@ -367,7 +431,7 @@ class BMRPCDispatcher(object):
status, addressVersionNumber, streamNumber, ripe) status, addressVersionNumber, streamNumber, ripe)
@staticmethod @staticmethod
def _dump_inbox_message( def _dump_inbox_message( # pylint: disable=too-many-arguments
msgid, toAddress, fromAddress, subject, received, msgid, toAddress, fromAddress, subject, received,
message, encodingtype, read): message, encodingtype, read):
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
@ -384,7 +448,7 @@ class BMRPCDispatcher(object):
} }
@staticmethod @staticmethod
def _dump_sent_message( def _dump_sent_message( # pylint: disable=too-many-arguments
msgid, toAddress, fromAddress, subject, lastactiontime, msgid, toAddress, fromAddress, subject, lastactiontime,
message, encodingtype, status, ackdata): message, encodingtype, status, ackdata):
subject = shared.fixPotentiallyInvalidUTF8Data(subject) subject = shared.fixPotentiallyInvalidUTF8Data(subject)
@ -405,10 +469,18 @@ class BMRPCDispatcher(object):
@command('decodeAddress') @command('decodeAddress')
def HandleDecodeAddress(self, address): def HandleDecodeAddress(self, address):
"""
Decode given address and return dict with
status, addressVersion, streamNumber and ripe keys
"""
return self._verifyAddress(address) return self._verifyAddress(address)
@command('listAddresses', 'listAddresses2') @command('listAddresses', 'listAddresses2')
def HandleListAddresses(self): def HandleListAddresses(self):
"""
Returns dict with a list of all used addresses with their properties
in the *addresses* key.
"""
data = [] data = []
for address in self.config.addresses(): for address in self.config.addresses():
streamNumber = decodeAddress(address)[2] streamNumber = decodeAddress(address)[2]
@ -427,6 +499,10 @@ class BMRPCDispatcher(object):
# the listAddressbook alias should be removed eventually. # the listAddressbook alias should be removed eventually.
@command('listAddressBookEntries', 'legacy:listAddressbook') @command('listAddressBookEntries', 'legacy:listAddressbook')
def HandleListAddressBookEntries(self, label=None): def HandleListAddressBookEntries(self, label=None):
"""
Returns dict with a list of all address book entries (address and label)
in the *addresses* key.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT label, address from addressbook WHERE label = ?", "SELECT label, address from addressbook WHERE label = ?",
label label
@ -443,6 +519,7 @@ class BMRPCDispatcher(object):
# the addAddressbook alias should be deleted eventually. # the addAddressbook alias should be deleted eventually.
@command('addAddressBookEntry', 'legacy:addAddressbook') @command('addAddressBookEntry', 'legacy:addAddressbook')
def HandleAddAddressBookEntry(self, address, label): def HandleAddAddressBookEntry(self, address, label):
"""Add an entry to address book. label must be base64 encoded."""
label = self._decode(label, "base64") label = self._decode(label, "base64")
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
self._verifyAddress(address) self._verifyAddress(address)
@ -462,6 +539,7 @@ class BMRPCDispatcher(object):
# the deleteAddressbook alias should be deleted eventually. # the deleteAddressbook alias should be deleted eventually.
@command('deleteAddressBookEntry', 'legacy:deleteAddressbook') @command('deleteAddressBookEntry', 'legacy:deleteAddressbook')
def HandleDeleteAddressBookEntry(self, address): def HandleDeleteAddressBookEntry(self, address):
"""Delete an entry from address book."""
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
self._verifyAddress(address) self._verifyAddress(address)
sqlExecute('DELETE FROM addressbook WHERE address=?', address) sqlExecute('DELETE FROM addressbook WHERE address=?', address)
@ -475,23 +553,30 @@ class BMRPCDispatcher(object):
self, label, eighteenByteRipe=False, totalDifficulty=0, self, label, eighteenByteRipe=False, totalDifficulty=0,
smallMessageDifficulty=0 smallMessageDifficulty=0
): ):
"""Handle a request to create a random address""" """
Create one address using the random number generator.
:param str label: base64 encoded label for the address
:param bool eighteenByteRipe: is telling Bitmessage whether to
generate an address with an 18 byte RIPE hash
(as opposed to a 19 byte hash).
"""
nonceTrialsPerByte = self.config.get( nonceTrialsPerByte = self.config.get(
'bitmessagesettings', 'defaultnoncetrialsperbyte' 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int( ) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte * defaults.networkDefaultProofOfWorkNonceTrialsPerByte
totalDifficulty) * totalDifficulty)
payloadLengthExtraBytes = self.config.get( payloadLengthExtraBytes = self.config.get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes' 'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int( ) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes * defaults.networkDefaultPayloadLengthExtraBytes
smallMessageDifficulty) * smallMessageDifficulty)
if not isinstance(eighteenByteRipe, bool): if not isinstance(eighteenByteRipe, bool):
raise APIError( raise APIError(
23, 'Bool expected in eighteenByteRipe, saw %s instead' % 23, 'Bool expected in eighteenByteRipe, saw %s instead'
type(eighteenByteRipe)) % type(eighteenByteRipe))
label = self._decode(label, "base64") label = self._decode(label, "base64")
try: try:
unicode(label, 'utf-8') unicode(label, 'utf-8')
@ -506,32 +591,42 @@ class BMRPCDispatcher(object):
)) ))
return queues.apiAddressGeneratorReturnQueue.get() return queues.apiAddressGeneratorReturnQueue.get()
# pylint: disable=too-many-arguments
@command('createDeterministicAddresses') @command('createDeterministicAddresses')
def HandleCreateDeterministicAddresses( def HandleCreateDeterministicAddresses(
self, passphrase, numberOfAddresses=1, addressVersionNumber=0, self, passphrase, numberOfAddresses=1, addressVersionNumber=0,
streamNumber=0, eighteenByteRipe=False, totalDifficulty=0, streamNumber=0, eighteenByteRipe=False, totalDifficulty=0,
smallMessageDifficulty=0 smallMessageDifficulty=0
): ):
"""Handle a request to create a deterministic address""" """
# pylint: disable=too-many-branches, too-many-statements Create many addresses deterministically using the passphrase.
:param str passphrase: base64 encoded passphrase
:param int numberOfAddresses: number of addresses to create,
up to 999
*addressVersionNumber* and *streamNumber* may be set to 0
which will tell Bitmessage to use the most up-to-date
address version and the most available stream.
"""
nonceTrialsPerByte = self.config.get( nonceTrialsPerByte = self.config.get(
'bitmessagesettings', 'defaultnoncetrialsperbyte' 'bitmessagesettings', 'defaultnoncetrialsperbyte'
) if not totalDifficulty else int( ) if not totalDifficulty else int(
defaults.networkDefaultProofOfWorkNonceTrialsPerByte * defaults.networkDefaultProofOfWorkNonceTrialsPerByte
totalDifficulty) * totalDifficulty)
payloadLengthExtraBytes = self.config.get( payloadLengthExtraBytes = self.config.get(
'bitmessagesettings', 'defaultpayloadlengthextrabytes' 'bitmessagesettings', 'defaultpayloadlengthextrabytes'
) if not smallMessageDifficulty else int( ) if not smallMessageDifficulty else int(
defaults.networkDefaultPayloadLengthExtraBytes * defaults.networkDefaultPayloadLengthExtraBytes
smallMessageDifficulty) * smallMessageDifficulty)
if not passphrase: if not passphrase:
raise APIError(1, 'The specified passphrase is blank.') raise APIError(1, 'The specified passphrase is blank.')
if not isinstance(eighteenByteRipe, bool): if not isinstance(eighteenByteRipe, bool):
raise APIError( raise APIError(
23, 'Bool expected in eighteenByteRipe, saw %s instead' % 23, 'Bool expected in eighteenByteRipe, saw %s instead'
type(eighteenByteRipe)) % type(eighteenByteRipe))
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
# 0 means "just use the proper addressVersionNumber" # 0 means "just use the proper addressVersionNumber"
if addressVersionNumber == 0: if addressVersionNumber == 0:
@ -539,8 +634,8 @@ class BMRPCDispatcher(object):
if addressVersionNumber not in (3, 4): if addressVersionNumber not in (3, 4):
raise APIError( raise APIError(
2, 'The address version number currently must be 3, 4, or 0' 2, 'The address version number currently must be 3, 4, or 0'
' (which means auto-select). %i isn\'t supported.' % ' (which means auto-select). %i isn\'t supported.'
addressVersionNumber) % addressVersionNumber)
if streamNumber == 0: # 0 means "just use the most available stream" if streamNumber == 0: # 0 means "just use the most available stream"
streamNumber = 1 # FIXME hard coded stream no streamNumber = 1 # FIXME hard coded stream no
if streamNumber != 1: if streamNumber != 1:
@ -573,7 +668,11 @@ class BMRPCDispatcher(object):
@command('getDeterministicAddress') @command('getDeterministicAddress')
def HandleGetDeterministicAddress( def HandleGetDeterministicAddress(
self, passphrase, addressVersionNumber, streamNumber): self, passphrase, addressVersionNumber, streamNumber):
"""Handle a request to get a deterministic address""" """
Similar to *createDeterministicAddresses* except that the one
address that is returned will not be added to the Bitmessage
user interface or the keys.dat file.
"""
numberOfAddresses = 1 numberOfAddresses = 1
eighteenByteRipe = False eighteenByteRipe = False
@ -600,7 +699,10 @@ class BMRPCDispatcher(object):
@command('createChan') @command('createChan')
def HandleCreateChan(self, passphrase): def HandleCreateChan(self, passphrase):
"""Handle a request to create a chan""" """
Creates a new chan. passphrase must be base64 encoded.
Returns the corresponding Bitmessage address.
"""
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
if not passphrase: if not passphrase:
@ -630,7 +732,9 @@ class BMRPCDispatcher(object):
@command('joinChan') @command('joinChan')
def HandleJoinChan(self, passphrase, suppliedAddress): def HandleJoinChan(self, passphrase, suppliedAddress):
"""Handle a request to join a chan""" """
Join a chan. passphrase must be base64 encoded. Returns 'success'.
"""
passphrase = self._decode(passphrase, "base64") passphrase = self._decode(passphrase, "base64")
if not passphrase: if not passphrase:
@ -660,7 +764,12 @@ class BMRPCDispatcher(object):
@command('leaveChan') @command('leaveChan')
def HandleLeaveChan(self, address): def HandleLeaveChan(self, address):
"""Handle a request to leave a chan""" """
Leave a chan. Returns 'success'.
.. note:: at this time, the address is still shown in the UI
until a restart.
"""
self._verifyAddress(address) self._verifyAddress(address)
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
if not self.config.safeGetBoolean(address, 'chan'): if not self.config.safeGetBoolean(address, 'chan'):
@ -679,7 +788,9 @@ class BMRPCDispatcher(object):
@command('deleteAddress') @command('deleteAddress')
def HandleDeleteAddress(self, address): def HandleDeleteAddress(self, address):
"""Handle a request to delete an address""" """
Permanently delete the address from keys.dat file. Returns 'success'.
"""
self._verifyAddress(address) self._verifyAddress(address)
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
try: try:
@ -694,7 +805,14 @@ class BMRPCDispatcher(object):
@command('getAllInboxMessages') @command('getAllInboxMessages')
def HandleGetAllInboxMessages(self): def HandleGetAllInboxMessages(self):
"""Handle a request to get all inbox messages""" """
Returns a dict with all inbox messages in the *inboxMessages* key.
The message is a dict with such keys:
*msgid*, *toAddress*, *fromAddress*, *subject*, *message*,
*encodingType*, *receivedTime*, *read*.
*msgid* is hex encoded string.
*subject* and *message* are base64 encoded.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received, message," "SELECT msgid, toaddress, fromaddress, subject, received, message,"
@ -707,7 +825,10 @@ class BMRPCDispatcher(object):
@command('getAllInboxMessageIds', 'getAllInboxMessageIDs') @command('getAllInboxMessageIds', 'getAllInboxMessageIDs')
def HandleGetAllInboxMessageIds(self): def HandleGetAllInboxMessageIds(self):
"""Handle a request to get all inbox message IDs""" """
The same as *getAllInboxMessages* but returns only *msgid*s,
result key - *inboxMessageIds*.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid FROM inbox where folder='inbox' ORDER BY received") "SELECT msgid FROM inbox where folder='inbox' ORDER BY received")
@ -718,14 +839,20 @@ class BMRPCDispatcher(object):
@command('getInboxMessageById', 'getInboxMessageByID') @command('getInboxMessageById', 'getInboxMessageByID')
def HandleGetInboxMessageById(self, hid, readStatus=None): def HandleGetInboxMessageById(self, hid, readStatus=None):
"""Handle a request to get an inbox messsage by ID""" """
Returns a dict with list containing single message in the result
key *inboxMessage*. May also return None if message was not found.
:param str hid: hex encoded msgid
:param bool readStatus: sets the message's read status if present
"""
msgid = self._decode(hid, "hex") msgid = self._decode(hid, "hex")
if readStatus is not None: if readStatus is not None:
if not isinstance(readStatus, bool): if not isinstance(readStatus, bool):
raise APIError( raise APIError(
23, 'Bool expected in readStatus, saw %s instead.' % 23, 'Bool expected in readStatus, saw %s instead.'
type(readStatus)) % type(readStatus))
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT read FROM inbox WHERE msgid=?", msgid) "SELECT read FROM inbox WHERE msgid=?", msgid)
# UPDATE is slow, only update if status is different # UPDATE is slow, only update if status is different
@ -749,7 +876,13 @@ class BMRPCDispatcher(object):
@command('getAllSentMessages') @command('getAllSentMessages')
def HandleGetAllSentMessages(self): def HandleGetAllSentMessages(self):
"""Handle a request to get all sent messages""" """
The same as *getAllInboxMessages* but for sent,
result key - *sentMessages*. Message dict keys are:
*msgid*, *toAddress*, *fromAddress*, *subject*, *message*,
*encodingType*, *lastActionTime*, *status*, *ackData*.
*ackData* is also a hex encoded string.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime," "SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
@ -762,7 +895,10 @@ class BMRPCDispatcher(object):
@command('getAllSentMessageIds', 'getAllSentMessageIDs') @command('getAllSentMessageIds', 'getAllSentMessageIDs')
def HandleGetAllSentMessageIds(self): def HandleGetAllSentMessageIds(self):
"""Handle a request to get all sent message IDs""" """
The same as *getAllInboxMessageIds* but for sent,
result key - *sentMessageIds*.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid FROM sent WHERE folder='sent'" "SELECT msgid FROM sent WHERE folder='sent'"
@ -775,7 +911,10 @@ class BMRPCDispatcher(object):
# after some time getInboxMessagesByAddress should be removed # after some time getInboxMessagesByAddress should be removed
@command('getInboxMessagesByReceiver', 'legacy:getInboxMessagesByAddress') @command('getInboxMessagesByReceiver', 'legacy:getInboxMessagesByAddress')
def HandleInboxMessagesByReceiver(self, toAddress): def HandleInboxMessagesByReceiver(self, toAddress):
"""Handle a request to get inbox messages by receiver""" """
The same as *getAllInboxMessages* but returns only messages
for toAddress.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, received," "SELECT msgid, toaddress, fromaddress, subject, received,"
@ -787,7 +926,11 @@ class BMRPCDispatcher(object):
@command('getSentMessageById', 'getSentMessageByID') @command('getSentMessageById', 'getSentMessageByID')
def HandleGetSentMessageById(self, hid): def HandleGetSentMessageById(self, hid):
"""Handle a request to get a sent message by ID""" """
Similiar to *getInboxMessageById* but doesn't change message's
read status (sent messages have no such field).
Result key is *sentMessage*
"""
msgid = self._decode(hid, "hex") msgid = self._decode(hid, "hex")
queryreturn = sqlQuery( queryreturn = sqlQuery(
@ -804,7 +947,10 @@ class BMRPCDispatcher(object):
@command('getSentMessagesByAddress', 'getSentMessagesBySender') @command('getSentMessagesByAddress', 'getSentMessagesBySender')
def HandleGetSentMessagesByAddress(self, fromAddress): def HandleGetSentMessagesByAddress(self, fromAddress):
"""Handle a request to get sent messages by address""" """
The same as *getAllSentMessages* but returns only messages
from fromAddress.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime," "SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
@ -818,7 +964,10 @@ class BMRPCDispatcher(object):
@command('getSentMessageByAckData') @command('getSentMessageByAckData')
def HandleGetSentMessagesByAckData(self, ackData): def HandleGetSentMessagesByAckData(self, ackData):
"""Handle a request to get sent messages by ack data""" """
Similiar to *getSentMessageById* but searches by ackdata
(also hex encoded).
"""
ackData = self._decode(ackData, "hex") ackData = self._decode(ackData, "hex")
queryreturn = sqlQuery( queryreturn = sqlQuery(
@ -836,7 +985,11 @@ class BMRPCDispatcher(object):
@command('trashMessage') @command('trashMessage')
def HandleTrashMessage(self, msgid): def HandleTrashMessage(self, msgid):
"""Handle a request to trash a message by ID""" """
Trash message by msgid (encoded in hex). Returns a simple message
saying that the message was trashed assuming it ever even existed.
Prior existence is not checked.
"""
msgid = self._decode(msgid, "hex") msgid = self._decode(msgid, "hex")
# Trash if in inbox table # Trash if in inbox table
helper_inbox.trash(msgid) helper_inbox.trash(msgid)
@ -846,14 +999,14 @@ class BMRPCDispatcher(object):
@command('trashInboxMessage') @command('trashInboxMessage')
def HandleTrashInboxMessage(self, msgid): def HandleTrashInboxMessage(self, msgid):
"""Handle a request to trash an inbox message by ID""" """Trash inbox message by msgid (encoded in hex)."""
msgid = self._decode(msgid, "hex") msgid = self._decode(msgid, "hex")
helper_inbox.trash(msgid) helper_inbox.trash(msgid)
return 'Trashed inbox message (assuming message existed).' return 'Trashed inbox message (assuming message existed).'
@command('trashSentMessage') @command('trashSentMessage')
def HandleTrashSentMessage(self, msgid): def HandleTrashSentMessage(self, msgid):
"""Handle a request to trash a sent message by ID""" """Trash sent message by msgid (encoded in hex)."""
msgid = self._decode(msgid, "hex") msgid = self._decode(msgid, "hex")
sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid)
return 'Trashed sent message (assuming message existed).' return 'Trashed sent message (assuming message existed).'
@ -863,8 +1016,14 @@ class BMRPCDispatcher(object):
self, toAddress, fromAddress, subject, message, self, toAddress, fromAddress, subject, message,
encodingType=2, TTL=4 * 24 * 60 * 60 encodingType=2, TTL=4 * 24 * 60 * 60
): ):
"""Handle a request to send a message""" """
Send the message and return ackdata (hex encoded string).
subject and message must be encoded in base64 which may optionally
include line breaks. TTL is specified in seconds; values outside
the bounds of 3600 to 2419200 will be moved to be within those
bounds. TTL defaults to 4 days.
"""
# pylint: disable=too-many-locals
if encodingType not in (2, 3): if encodingType not in (2, 3):
raise APIError(6, 'The encoding type must be 2 or 3.') raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64") subject = self._decode(subject, "base64")
@ -927,7 +1086,7 @@ class BMRPCDispatcher(object):
def HandleSendBroadcast( def HandleSendBroadcast(
self, fromAddress, subject, message, encodingType=2, self, fromAddress, subject, message, encodingType=2,
TTL=4 * 24 * 60 * 60): TTL=4 * 24 * 60 * 60):
"""Handle a request to send a broadcast message""" """Send the broadcast message. Similiar to *sendMessage*."""
if encodingType not in (2, 3): if encodingType not in (2, 3):
raise APIError(6, 'The encoding type must be 2 or 3.') raise APIError(6, 'The encoding type must be 2 or 3.')
@ -978,7 +1137,12 @@ class BMRPCDispatcher(object):
@command('getStatus') @command('getStatus')
def HandleGetStatus(self, ackdata): def HandleGetStatus(self, ackdata):
"""Handle a request to get the status of a sent message""" """
Get the status of sent message by its ackdata (hex encoded).
Returns one of these strings: notfound, msgqueued,
broadcastqueued, broadcastsent, doingpubkeypow, awaitingpubkey,
doingmsgpow, forcepow, msgsent, msgsentnoackexpected or ackreceived.
"""
if len(ackdata) < 76: if len(ackdata) < 76:
# The length of ackData should be at least 38 bytes (76 hex digits) # The length of ackData should be at least 38 bytes (76 hex digits)
@ -993,7 +1157,7 @@ class BMRPCDispatcher(object):
@command('addSubscription') @command('addSubscription')
def HandleAddSubscription(self, address, label=''): def HandleAddSubscription(self, address, label=''):
"""Handle a request to add a subscription""" """Subscribe to the address. label must be base64 encoded."""
if label: if label:
label = self._decode(label, "base64") label = self._decode(label, "base64")
@ -1018,7 +1182,10 @@ class BMRPCDispatcher(object):
@command('deleteSubscription') @command('deleteSubscription')
def HandleDeleteSubscription(self, address): def HandleDeleteSubscription(self, address):
"""Handle a request to delete a subscription""" """
Unsubscribe from the address. The program does not check whether
you were subscribed in the first place.
"""
address = addBMIfNotPresent(address) address = addBMIfNotPresent(address)
sqlExecute("DELETE FROM subscriptions WHERE address=?", address) sqlExecute("DELETE FROM subscriptions WHERE address=?", address)
@ -1029,7 +1196,10 @@ class BMRPCDispatcher(object):
@command('listSubscriptions') @command('listSubscriptions')
def ListSubscriptions(self): def ListSubscriptions(self):
"""Handle a request to list susbcriptions""" """
Returns dict with a list of all subscriptions
in the *subscriptions* key.
"""
queryreturn = sqlQuery( queryreturn = sqlQuery(
"SELECT label, address, enabled FROM subscriptions") "SELECT label, address, enabled FROM subscriptions")
@ -1092,7 +1262,7 @@ class BMRPCDispatcher(object):
@command('trashSentMessageByAckData') @command('trashSentMessageByAckData')
def HandleTrashSentMessageByAckDAta(self, ackdata): def HandleTrashSentMessageByAckDAta(self, ackdata):
"""Handle a request to trash a sent message by ackdata""" """Trash a sent message by ackdata (hex encoded)"""
# This API method should only be used when msgid is not available # This API method should only be used when msgid is not available
ackdata = self._decode(ackdata, "hex") ackdata = self._decode(ackdata, "hex")
sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata) sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata)
@ -1181,7 +1351,15 @@ class BMRPCDispatcher(object):
@command('clientStatus') @command('clientStatus')
def HandleClientStatus(self): def HandleClientStatus(self):
"""Handle a request to get the status of the client""" """
Returns the bitmessage status as dict with keys *networkConnections*,
*numberOfMessagesProcessed*, *numberOfBroadcastsProcessed*,
*numberOfPubkeysProcessed*, *networkStatus*,
*softwareName*, *softwareVersion*. *networkStatus* will be one
of these strings: "notConnected",
"connectedButHaveNotReceivedIncomingConnections",
or "connectedAndReceivingIncomingConnections".
"""
connections_num = len(network.stats.connectedHostsList()) connections_num = len(network.stats.connectedHostsList())
if connections_num == 0: if connections_num == 0:
@ -1212,18 +1390,18 @@ class BMRPCDispatcher(object):
@command('statusBar') @command('statusBar')
def HandleStatusBar(self, message): def HandleStatusBar(self, message):
"""Handle a request to update the status bar""" """Update GUI statusbar message"""
queues.UISignalQueue.put(('updateStatusBar', message)) queues.UISignalQueue.put(('updateStatusBar', message))
@command('deleteAndVacuum') @command('deleteAndVacuum')
def HandleDeleteAndVacuum(self): def HandleDeleteAndVacuum(self):
"""Handle a request to run the deleteandvacuum stored procedure""" """Cleanup trashes and vacuum messages database"""
sqlStoredProcedure('deleteandvacuume') sqlStoredProcedure('deleteandvacuume')
return 'done' return 'done'
@command('shutdown') @command('shutdown')
def HandleShutdown(self): def HandleShutdown(self):
"""Handle a request to shutdown the node""" """Shutdown the bitmessage. Returns 'done'."""
# backward compatible trick because False == 0 is True # backward compatible trick because False == 0 is True
state.shutdown = False state.shutdown = False
return 'done' return 'done'
@ -1244,14 +1422,14 @@ class BMRPCDispatcher(object):
maxcount = func.func_code.co_argcount maxcount = func.func_code.co_argcount
if argcount > maxcount: if argcount > maxcount:
msg = ( msg = (
'Command %s takes at most %s parameters (%s given)' % 'Command %s takes at most %s parameters (%s given)'
(method, maxcount, argcount)) % (method, maxcount, argcount))
else: else:
mincount = maxcount - len(func.func_defaults or []) mincount = maxcount - len(func.func_defaults or [])
if argcount < mincount: if argcount < mincount:
msg = ( msg = (
'Command %s takes at least %s parameters (%s given)' % 'Command %s takes at least %s parameters (%s given)'
(method, mincount, argcount)) % (method, mincount, argcount))
raise APIError(0, msg) raise APIError(0, msg)
finally: finally:
state.last_api_response = time.time() state.last_api_response = time.time()
@ -1266,8 +1444,7 @@ class BMRPCDispatcher(object):
except varintDecodeError as e: except varintDecodeError as e:
logger.error(e) logger.error(e)
_fault = APIError( _fault = APIError(
26, 'Data contains a malformed varint. Some details: %s' % 26, 'Data contains a malformed varint. Some details: %s' % e)
e)
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
_fault = APIError(21, 'Unexpected API Failure - %s' % e) _fault = APIError(21, 'Unexpected API Failure - %s' % e)