diff --git a/onionr/communicator2.py b/onionr/communicator2.py
index 76359e42..b4a32649 100755
--- a/onionr/communicator2.py
+++ b/onionr/communicator2.py
@@ -25,7 +25,6 @@ from defusedxml import minidom
class OnionrCommunicatorDaemon:
def __init__(self, debug, developmentMode):
- logger.warn('New (unstable) communicator is being used.')
# list of timer instances
self.timers = []
@@ -57,6 +56,9 @@ class OnionrCommunicatorDaemon:
# list of new blocks to download, added to when new block lists are fetched from peers
self.blockQueue = []
+ # list of blocks currently downloading, avoid s
+ self.currentDownloading = []
+
# Clear the daemon queue for any dead messages
if os.path.exists(self._core.queueDB):
self._core.clearDaemonQueue()
@@ -77,9 +79,9 @@ class OnionrCommunicatorDaemon:
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60)
OnionrCommunicatorTimers(self, self.lookupBlocks, 7, requiresPeer=True)
OnionrCommunicatorTimers(self, self.getBlocks, 10, requiresPeer=True)
- OnionrCommunicatorTimers(self, self.clearOfflinePeer, 120)
- OnionrCommunicatorTimers(self, self.lookupKeys, 125, requiresPeer=True)
- OnionrCommunicatorTimers(self, self.lookupAdders, 600, requiresPeer=True)
+ OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
+ OnionrCommunicatorTimers(self, self.lookupKeys, 60, requiresPeer=True)
+ OnionrCommunicatorTimers(self, self.lookupAdders, 60, requiresPeer=True)
# set loop to execute instantly to load up peer pool (replaced old pool init wait)
peerPoolTimer.count = (peerPoolTimer.frequency - 1)
@@ -122,8 +124,7 @@ class OnionrCommunicatorDaemon:
peer = self.pickOnlinePeer()
newAdders = self.peerAction(peer, action='pex')
self._core._utils.mergeAdders(newAdders)
-
- self.decrementThreadCount('lookupKeys')
+ self.decrementThreadCount('lookupAdders')
def lookupBlocks(self):
'''Lookup new blocks & add them to download queue'''
@@ -154,6 +155,10 @@ class OnionrCommunicatorDaemon:
def getBlocks(self):
'''download new blocks in queue'''
for blockHash in self.blockQueue:
+ if blockHash in self.currentDownloading:
+ logger.debug('ALREADY DOWNLOADING ' + blockHash)
+ continue
+ self.currentDownloading.append(blockHash)
logger.info("Attempting to download %s..." % blockHash)
content = self.peerAction(self.pickOnlinePeer(), 'getData', data=blockHash) # block content from random peer (includes metadata)
if content != False:
@@ -171,7 +176,7 @@ class OnionrCommunicatorDaemon:
content = content.decode() # decode here because sha3Hash needs bytes above
metas = self._core._utils.getBlockMetadataFromData(content) # returns tuple(metadata, meta), meta is also in metadata
metadata = metas[0]
- meta = metas[1]
+ #meta = metas[1]
if self._core._utils.validateMetadata(metadata): # check if metadata is valid
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
logger.info('Block passed proof, saving.')
@@ -191,6 +196,7 @@ class OnionrCommunicatorDaemon:
pass
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
self.blockQueue.remove(blockHash) # remove from block queue both if success or false
+ self.currentDownloading.remove(blockHash)
self.decrementThreadCount('getBlocks')
return
diff --git a/onionr/core.py b/onionr/core.py
index 16502d25..fbe9cc80 100644
--- a/onionr/core.py
+++ b/onionr/core.py
@@ -628,13 +628,16 @@ class Core:
return None
- def getBlocksByType(self, blockType):
+ def getBlocksByType(self, blockType, orderDate=True):
'''
Returns a list of blocks by the type
'''
conn = sqlite3.connect(self.blockDB)
c = conn.cursor()
- execute = 'SELECT hash FROM hashes WHERE dataType=?;'
+ if orderDate:
+ execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
+ else:
+ execute = 'SELECT hash FROM hashes WHERE dataType=?;'
args = (blockType,)
rows = list()
for row in c.execute(execute, args):
@@ -719,7 +722,7 @@ class Core:
# sign before encrypt, as unauthenticated crypto should not be a problem here
if sign:
signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True)
- signer = self._crypto.pubKeyHashID()
+ signer = self._crypto.pubKey
if len(jsonMeta) > 1000:
raise onionrexceptions.InvalidMetadata('meta in json encoded form must not exceed 1000 bytes')
@@ -728,15 +731,16 @@ class Core:
if encryptType == 'sym':
if len(symKey) < self.requirements.passwordLength:
raise onionrexceptions.SecurityError('Weak encryption key')
- jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True)
- data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True)
- signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True)
- signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True)
+ jsonMeta = self._crypto.symmetricEncrypt(jsonMeta, key=symKey, returnEncoded=True).decode()
+ data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode()
+ signature = self._crypto.symmetricEncrypt(signature, key=symKey, returnEncoded=True).decode()
+ signer = self._crypto.symmetricEncrypt(signer, key=symKey, returnEncoded=True).decode()
elif encryptType == 'asym':
if self._utils.validatePubKey(asymPeer):
- jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True)
- data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True)
- signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True)
+ jsonMeta = self._crypto.pubKeyEncrypt(jsonMeta, asymPeer, encodedData=True, anonymous=True).decode()
+ data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True, anonymous=True).decode()
+ signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True, anonymous=True).decode()
+ signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True, anonymous=True).decode()
else:
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')
diff --git a/onionr/logger.py b/onionr/logger.py
index e2e09d03..1c299054 100644
--- a/onionr/logger.py
+++ b/onionr/logger.py
@@ -134,7 +134,7 @@ def raw(data, fd = sys.stdout):
with open(_outputfile, "a+") as f:
f.write(colors.filter(data) + '\n')
-def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
+def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True):
'''
Logs the data
prefix : The prefix to the output
@@ -145,7 +145,7 @@ def log(prefix, data, color = '', timestamp=True, fd = sys.stdout):
if timestamp:
curTime = time.strftime("%m-%d %H:%M:%S") + ' '
- output = colors.reset + str(color) + '[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' + curTime + str(data) + colors.reset
+ output = colors.reset + str(color) + ('[' + colors.bold + str(prefix) + colors.reset + str(color) + '] ' if prompt is True else '') + curTime + str(data) + colors.reset
if not get_settings() & USE_ANSI:
output = colors.filter(output)
@@ -201,31 +201,37 @@ def confirm(default = 'y', message = 'Are you sure %s? '):
return default == 'y'
# debug: when there is info that could be useful for debugging purposes only
-def debug(data, timestamp=True):
+def debug(data, error = None, timestamp = True, prompt = True):
if get_level() <= LEVEL_DEBUG:
- log('/', data, timestamp=timestamp)
+ log('/', data, timestamp=timestamp, prompt = prompt)
+ if not error is None:
+ debug('Error: ' + str(error) + parse_error())
# info: when there is something to notify the user of, such as the success of a process
-def info(data, timestamp=False):
+def info(data, timestamp = False, prompt = True):
if get_level() <= LEVEL_INFO:
- log('+', data, colors.fg.green, timestamp=timestamp)
+ log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt)
# warn: when there is a potential for something bad to happen
-def warn(data, timestamp=True):
+def warn(data, error = None, timestamp = True, prompt = True):
+ if not error is None:
+ debug('Error: ' + str(error) + parse_error())
if get_level() <= LEVEL_WARN:
- log('!', data, colors.fg.orange, timestamp=timestamp)
+ log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt)
# error: when only one function, module, or process of the program encountered a problem and must stop
-def error(data, error=None, timestamp=True):
+def error(data, error = None, timestamp = True, prompt = True):
if get_level() <= LEVEL_ERROR:
- log('-', data, colors.fg.red, timestamp=timestamp, fd = sys.stderr)
+ log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt)
if not error is None:
debug('Error: ' + str(error) + parse_error())
# fatal: when the something so bad has happened that the program must stop
-def fatal(data, timestamp=True):
+def fatal(data, error = None, timestamp=True, prompt = True):
+ if not error is None:
+ debug('Error: ' + str(error) + parse_error())
if get_level() <= LEVEL_FATAL:
- log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr)
+ log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr, prompt = prompt)
# returns a formatted error message
def parse_error():
diff --git a/onionr/onionr.py b/onionr/onionr.py
index 40c267ac..10aa54ce 100755
--- a/onionr/onionr.py
+++ b/onionr/onionr.py
@@ -50,7 +50,6 @@ class Onionr:
Main Onionr class. This is for the CLI program, and does not handle much of the logic.
In general, external programs and plugins should not use this class.
'''
-
try:
os.chdir(sys.path[0])
except FileNotFoundError:
@@ -181,15 +180,6 @@ class Onionr:
'listkeys': self.listKeys,
'list-keys': self.listKeys,
- 'addmsg': self.addMessage,
- 'addmessage': self.addMessage,
- 'add-msg': self.addMessage,
- 'add-message': self.addMessage,
- 'pm': self.sendEncrypt,
-
- 'getpms': self.getPMs,
- 'get-pms': self.getPMs,
-
'addpeer': self.addPeer,
'add-peer': self.addPeer,
'add-address': self.addAddress,
@@ -226,9 +216,6 @@ class Onionr:
'create-plugin': 'Creates directory structure for a plugin',
'add-peer': 'Adds a peer to database',
'list-peers': 'Displays a list of peers',
- 'add-msg': 'Broadcasts a message to the Onionr network',
- 'pm': 'Adds a private message to block',
- 'get-pms': 'Shows private messages sent to you',
'add-file': 'Create an Onionr block from a file',
'import-blocks': 'import blocks from the disk (Onionr is transport-agnostic!)',
'listconn': 'list connected peers',
@@ -341,32 +328,6 @@ class Onionr:
logger.info('Sending kex to command queue...')
self.onionrCore.daemonQueueAdd('kex')
- def sendEncrypt(self):
- '''
- Create a private message and send it
- '''
-
- invalidID = True
- while invalidID:
- try:
- peer = logger.readline('Peer to send to: ')
- except KeyboardInterrupt:
- break
- else:
- if self.onionrUtils.validatePubKey(peer):
- invalidID = False
- else:
- logger.error('Invalid peer ID')
- else:
- try:
- message = logger.readline("Enter a message: ")
- except KeyboardInterrupt:
- pass
- else:
- logger.info("Sending message to: " + logger.colors.underline + peer)
- self.onionrUtils.sendPM(peer, message)
-
-
def listKeys(self):
'''
Displays a list of keys (used to be called peers) (?)
@@ -447,13 +408,6 @@ class Onionr:
logger.error('Failed to insert block.', timestamp = False)
return
- def getPMs(self):
- '''
- display PMs sent to us
- '''
-
- self.onionrUtils.loadPMs()
-
def enablePlugin(self):
'''
Enables and starts the given plugin
diff --git a/onionr/onionrblockapi.py b/onionr/onionrblockapi.py
index dbb358af..695d41ab 100644
--- a/onionr/onionrblockapi.py
+++ b/onionr/onionrblockapi.py
@@ -18,7 +18,7 @@
along with this program. If not, see .
'''
-import core as onionrcore, logger, config, onionrexceptions
+import core as onionrcore, logger, config, onionrexceptions, nacl.exceptions
import json, os, sys, datetime, base64
class Block:
@@ -42,8 +42,6 @@ class Block:
# initialize variables
self.valid = True
self.raw = None
- self.powHash = None
- self.powToken = None
self.signed = False
self.signature = None
self.signedData = None
@@ -51,23 +49,69 @@ class Block:
self.parent = None
self.bheader = {}
self.bmetadata = {}
+ self.isEncrypted = False
+ self.decrypted = False
+ self.signer = None
+ self.validSig = False
# handle arguments
if self.getCore() is None:
self.core = onionrcore.Core()
-
- if not self.core._utils.validateHash(self.hash):
- raise onionrexceptions.InvalidHexHash('specified block hash is not valid')
# update the blocks' contents if it exists
if not self.getHash() is None:
- if not self.update():
+ if not self.core._utils.validateHash(self.hash):
+ logger.debug('Block hash %s is invalid.' % self.getHash())
+ raise onionrexceptions.InvalidHexHash('Block hash is invalid.')
+ elif not self.update():
logger.debug('Failed to open block %s.' % self.getHash())
else:
- logger.debug('Did not update block')
+ pass
+ #logger.debug('Did not update block.')
# logic
+ def decrypt(self, anonymous=True, encodedData=True):
+ '''Decrypt a block, loading decrypted data into their vars'''
+ if self.decrypted:
+ return True
+ retData = False
+ core = self.getCore()
+ # decrypt data
+ if self.getHeader('encryptType') == 'asym':
+ try:
+ self.bcontent = core._crypto.pubKeyDecrypt(self.bcontent, anonymous=anonymous, encodedData=encodedData)
+ bmeta = core._crypto.pubKeyDecrypt(self.bmetadata, anonymous=anonymous, encodedData=encodedData)
+ try:
+ bmeta = bmeta.decode()
+ except AttributeError:
+ # yet another bytes fix
+ pass
+ self.bmetadata = json.loads(bmeta)
+ self.signature = core._crypto.pubKeyDecrypt(self.signature, anonymous=anonymous, encodedData=encodedData)
+ self.signer = core._crypto.pubKeyDecrypt(self.signer, anonymous=anonymous, encodedData=encodedData)
+ self.signedData = json.dumps(self.bmetadata) + self.bcontent.decode()
+ except nacl.exceptions.CryptoError:
+ pass
+ #logger.debug('Could not decrypt block. Either invalid key or corrupted data')
+ else:
+ retData = True
+ self.decrypted = True
+ else:
+ logger.warn('symmetric decryption is not yet supported by this API')
+ return retData
+
+ def verifySig(self):
+ '''Verify if a block's signature is signed by its claimed signer'''
+ core = self.getCore()
+
+ if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
+ self.validSig = True
+ else:
+ self.validSig = False
+ return self.validSig
+
+
def update(self, data = None, file = None):
'''
Loads data from a block in to the current object.
@@ -118,14 +162,19 @@ class Block:
self.raw = str(blockdata)
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
- self.bmetadata = json.loads(self.getHeader('meta', None))
+ if self.bheader['encryptType'] in ('asym', 'sym'):
+ self.bmetadata = self.getHeader('meta', None)
+ self.isEncrypted = True
+ else:
+ self.bmetadata = json.loads(self.getHeader('meta', None))
self.parent = self.getMetadata('parent', None)
self.btype = self.getMetadata('type', None)
- self.powHash = self.getMetadata('powHash', None)
- self.powToken = self.getMetadata('powToken', None)
self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
+ # TODO: detect if signer is hash of pubkey or not
+ self.signer = self.getHeader('signer', None)
self.signature = self.getHeader('sig', None)
- self.signedData = (None if not self.isSigned() else self.getHeader('meta') + '\n' + self.getContent())
+ # signed data is jsonMeta + block content (no linebreak)
+ self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
self.date = self.getCore().getBlockDate(self.getHash())
if not self.getDate() is None:
@@ -214,7 +263,6 @@ class Block:
Outputs:
- (str): the type of the block
'''
-
return self.btype
def getRaw(self):
@@ -471,6 +519,8 @@ class Block:
if not signer is None:
if isinstance(signer, (str,)):
signer = [signer]
+ if isinstance(signer, (bytes,)):
+ signer = [signer.decode()]
isSigner = False
for key in signer:
@@ -483,12 +533,13 @@ class Block:
if relevant:
relevant_blocks.append(block)
+
if bool(reverse):
relevant_blocks.reverse()
return relevant_blocks
except Exception as e:
- logger.debug(('Failed to get blocks: %s' % str(e)) + logger.parse_error())
+ logger.debug('Failed to get blocks.', error = e)
return list()
diff --git a/onionr/onionrcrypto.py b/onionr/onionrcrypto.py
index 1500cea1..40dca90f 100644
--- a/onionr/onionrcrypto.py
+++ b/onionr/onionrcrypto.py
@@ -114,6 +114,11 @@ class OnionrCrypto:
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
retVal = ''
+ try:
+ pubkey = pubkey.encode()
+ except AttributeError:
+ pass
+
if encodedData:
encoding = nacl.encoding.Base64Encoder
else:
@@ -127,7 +132,11 @@ class OnionrCrypto:
elif anonymous:
key = nacl.signing.VerifyKey(key=pubkey, encoder=nacl.encoding.Base32Encoder).to_curve25519_public_key()
anonBox = nacl.public.SealedBox(key)
- retVal = anonBox.encrypt(data.encode(), encoder=encoding)
+ try:
+ data = data.encode()
+ except AttributeError:
+ pass
+ retVal = anonBox.encrypt(data, encoder=encoding)
return retVal
def pubKeyDecrypt(self, data, pubkey='', anonymous=False, encodedData=False):
diff --git a/onionr/onionrproofs.py b/onionr/onionrproofs.py
index b6a63d8a..194b37e9 100644
--- a/onionr/onionrproofs.py
+++ b/onionr/onionrproofs.py
@@ -159,7 +159,11 @@ class POW:
self.metadata['powRandomToken'] = base64.b64encode(rand).decode()
payload = json.dumps(self.metadata).encode() + b'\n' + self.data
token = myCore._crypto.sha3Hash(payload)
- #print(token)
+ try:
+ # on some versions, token is bytes
+ token = token.decode()
+ except AttributeError:
+ pass
if self.puzzle == token[0:self.difficulty]:
self.hashing = False
iFound = True
diff --git a/onionr/onionrutils.py b/onionr/onionrutils.py
index 077fe302..33e3f51d 100644
--- a/onionr/onionrutils.py
+++ b/onionr/onionrutils.py
@@ -59,7 +59,7 @@ class OnionrUtils:
High level function to encrypt a message to a peer and insert it as a block
'''
- self._core.insertBlock(message, header='pm', sign=True, encryptType='sym', symKey=pubkey)
+ self._core.insertBlock(message, header='pm', sign=True, encryptType='asym', asymPeer=pubkey)
return
@@ -117,7 +117,8 @@ class OnionrUtils:
else:
logger.warn("Failed to add key")
else:
- logger.warn('%s pow failed' % key[0])
+ pass
+ #logger.debug('%s pow failed' % key[0])
return retVal
except Exception as error:
logger.error('Failed to merge keys.', error=error)
@@ -137,7 +138,8 @@ class OnionrUtils:
logger.info('Added %s to db.' % adder, timestamp = True)
retVal = True
else:
- logger.debug('%s is either our address or already in our DB' % adder)
+ pass
+ #logger.debug('%s is either our address or already in our DB' % adder)
return retVal
except Exception as error:
logger.error('Failed to merge adders.', error = error)
@@ -204,18 +206,23 @@ class OnionrUtils:
def getBlockMetadataFromData(self, blockData):
'''
- accepts block contents as string and returns a tuple of metadata, meta (meta being internal metadata)
+ accepts block contents as string, returns a tuple of metadata, meta (meta being internal metadata, which will be returned as an encrypted base64 string if it is encrypted, dict if not).
+
'''
+ meta = {}
try:
blockData = blockData.encode()
except AttributeError:
pass
metadata = json.loads(blockData[:blockData.find(b'\n')].decode())
data = blockData[blockData.find(b'\n'):].decode()
- try:
- meta = json.loads(metadata['meta'])
- except KeyError:
- meta = {}
+
+ if not metadata['encryptType'] in ('asym', 'sym'):
+ try:
+ meta = json.loads(metadata['meta'])
+ except KeyError:
+ pass
+ meta = metadata['meta']
return (metadata, meta, data)
def checkPort(self, port, host=''):
@@ -251,7 +258,14 @@ class OnionrUtils:
Read metadata from a block and cache it to the block database
'''
myBlock = Block(blockHash, self._core)
- self._core.updateBlockInfo(blockHash, 'dataType', myBlock.getType())
+ if myBlock.isEncrypted:
+ myBlock.decrypt()
+ blockType = myBlock.getMetadata('type') # we would use myBlock.getType() here, but it is bugged with encrypted blocks
+ try:
+ if len(blockType) <= 10:
+ self._core.updateBlockInfo(blockHash, 'dataType', blockType)
+ except TypeError:
+ pass
def escapeAnsi(self, line):
'''
@@ -426,52 +440,6 @@ class OnionrUtils:
except:
return False
- def loadPMs(self):
- '''
- Find, decrypt, and return array of PMs (array of dictionary, {from, text})
- '''
- blocks = Block.getBlocks(type = 'pm', core = self._core)
- message = ''
- sender = ''
- for i in blocks:
- try:
- blockContent = i.getContent()
-
- try:
- message = self._core._crypto.pubKeyDecrypt(blockContent, encodedData=True, anonymous=True)
- except nacl.exceptions.CryptoError as e:
- pass
- else:
- try:
- message = message.decode()
- except AttributeError:
- pass
-
- try:
- message = json.loads(message)
- except json.decoder.JSONDecodeError:
- pass
- else:
- logger.debug('Decrypted %s:' % i.getHash())
- logger.info(message["msg"])
-
- signer = message["id"]
- sig = message["sig"]
-
- if self.validatePubKey(signer):
- if self._core._crypto.edVerify(message["msg"], signer, sig, encodedData=True):
- logger.info("Good signature by %s" % signer)
- else:
- logger.warn("Bad signature by %s" % signer)
- else:
- logger.warn('Bad sender id: %s' % signer)
-
- except FileNotFoundError:
- pass
- except Exception as error:
- logger.error('Failed to open block %s.' % i, error=error)
- return
-
def getPeerByHashId(self, hash):
'''
Return the pubkey of the user if known from the hash
diff --git a/onionr/onionrvalues.py b/onionr/onionrvalues.py
index b8c36b05..3f806702 100644
--- a/onionr/onionrvalues.py
+++ b/onionr/onionrvalues.py
@@ -21,4 +21,4 @@
class OnionrValues:
def __init__(self):
self.passwordLength = 20
- self.blockMetadataLengths = {'meta': 1000, 'sig': 88, 'signer': 64, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4}
\ No newline at end of file
+ self.blockMetadataLengths = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10, 'powRandomToken': 1000, 'encryptType': 4} #TODO properly refine values to minimum needed
\ No newline at end of file
diff --git a/onionr/static-data/default-plugins/flow/main.py b/onionr/static-data/default-plugins/flow/main.py
index 2acce082..b2fb1dfa 100644
--- a/onionr/static-data/default-plugins/flow/main.py
+++ b/onionr/static-data/default-plugins/flow/main.py
@@ -47,25 +47,24 @@ class OnionrFlow:
self.flowRunning = False
if len(message) > 0:
- self.myCore.insertBlock(message)
+ Block(content = message, type = 'txt', core = self.myCore).save()
logger.info("Flow is exiting, goodbye")
return
def showOutput(self):
while self.flowRunning:
- for blockHash in self.myCore.getBlocksByType('txt'):
- if blockHash in self.alreadyOutputed:
+ for block in Block.getBlocks(type = 'txt', core = self.myCore):
+ if block.getHash() in self.alreadyOutputed:
continue
if not self.flowRunning:
break
- logger.info('\n------------------------')
- block = Block(blockHash, self.myCore)
+ logger.info('\n------------------------', prompt = False)
content = block.getContent()
# Escape new lines, remove trailing whitespace, and escape ansi sequences
content = self.myCore._utils.escapeAnsi(content.replace('\n', '\\n').replace('\r', '\\r').strip())
- logger.info("\n" + block.getDate().strftime("%m/%d %H:%M") + ' - ' + '\033[0;0m' + content)
- self.alreadyOutputed.append(blockHash)
+ logger.info(block.getDate().strftime("%m/%d %H:%M") + ' - ' + logger.colors.reset + content, prompt = False)
+ self.alreadyOutputed.append(block.getHash())
try:
time.sleep(5)
except KeyboardInterrupt:
@@ -84,6 +83,6 @@ def on_init(api, data = None):
global pluginapi
pluginapi = api
flow = OnionrFlow()
- api.commands.register(['flow'], flow.start)
+ api.commands.register('flow', flow.start)
api.commands.register_help('flow', 'Open the flow messaging interface')
- return
\ No newline at end of file
+ return
diff --git a/onionr/static-data/default-plugins/gui/main.py b/onionr/static-data/default-plugins/gui/main.py
deleted file mode 100644
index 07e5a76e..00000000
--- a/onionr/static-data/default-plugins/gui/main.py
+++ /dev/null
@@ -1,135 +0,0 @@
-#!/usr/bin/python
-'''
- Onionr - P2P Microblogging Platform & Social network
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-'''
-
-# Imports some useful libraries
-import logger, config, core
-import os, sqlite3, threading
-from onionrblockapi import Block
-
-plugin_name = 'gui'
-
-def send():
- global message
- block = Block()
- block.setType('txt')
- block.setContent(message)
- logger.debug('Sent message in block %s.' % block.save(sign = True))
-
-
-def sendMessage():
- global sendEntry
-
- global message
- message = sendEntry.get()
-
- t = threading.Thread(target = send)
- t.start()
-
- sendEntry.delete(0, len(message))
-
-def update():
- global listedBlocks, listbox, runningCheckDelayCount, runningCheckDelay, root, daemonStatus
-
- for i in Block.getBlocks(type = 'txt'):
- if i.getContent().strip() == '' or i.getHash() in listedBlocks:
- continue
- listbox.insert(99999, str(i.getContent()))
- listedBlocks.append(i.getHash())
- listbox.see(99999)
-
- runningCheckDelayCount += 1
-
- if runningCheckDelayCount == runningCheckDelay:
- resp = pluginapi.daemon.local_command('ping')
- if resp == 'pong':
- daemonStatus.config(text = "Onionr Daemon Status: Running")
- else:
- daemonStatus.config(text = "Onionr Daemon Status: Not Running")
- runningCheckDelayCount = 0
- root.after(10000, update)
-
-
-def reallyOpenGUI():
- import tkinter
- global root, runningCheckDelay, runningCheckDelayCount, scrollbar, listedBlocks, nodeInfo, keyInfo, idText, idEntry, pubKeyEntry, listbox, daemonStatus, sendEntry
-
- root = tkinter.Tk()
-
- root.title("Onionr GUI")
-
- runningCheckDelay = 5
- runningCheckDelayCount = 4
-
- scrollbar = tkinter.Scrollbar(root)
- scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
-
- listedBlocks = []
-
- nodeInfo = tkinter.Frame(root)
- keyInfo = tkinter.Frame(root)
-
- hostname = pluginapi.get_onionr().get_hostname()
- logger.debug('Onionr Hostname: %s' % hostname)
- idText = hostname
-
- idEntry = tkinter.Entry(nodeInfo)
- tkinter.Label(nodeInfo, text = "Node Address: ").pack(side=tkinter.LEFT)
- idEntry.pack()
- idEntry.insert(0, idText.strip())
- idEntry.configure(state="readonly")
-
- nodeInfo.pack()
-
- pubKeyEntry = tkinter.Entry(keyInfo)
-
- tkinter.Label(keyInfo, text="Public key: ").pack(side=tkinter.LEFT)
-
- pubKeyEntry.pack()
- pubKeyEntry.insert(0, pluginapi.get_core()._crypto.pubKey)
- pubKeyEntry.configure(state="readonly")
-
- keyInfo.pack()
-
- sendEntry = tkinter.Entry(root)
- sendBtn = tkinter.Button(root, text='Send Message', command=sendMessage)
- sendEntry.pack(side=tkinter.TOP, pady=5)
- sendBtn.pack(side=tkinter.TOP)
-
- listbox = tkinter.Listbox(root, yscrollcommand=tkinter.Scrollbar.set, height=15)
-
- listbox.pack(fill=tkinter.BOTH, pady=25)
-
- daemonStatus = tkinter.Label(root, text="Onionr Daemon Status: unknown")
- daemonStatus.pack()
-
- scrollbar.config(command=tkinter.Listbox.yview)
- root.after(2000, update)
- root.mainloop()
-
-def openGUI():
- t = threading.Thread(target = reallyOpenGUI)
- t.daemon = False
- t.start()
-
-def on_init(api, data = None):
- global pluginapi
- pluginapi = api
-
- api.commands.register(['gui', 'launch-gui', 'open-gui'], openGUI)
- api.commands.register_help('gui', 'Opens a graphical interface for Onionr')
-
- return
diff --git a/onionr/static-data/default-plugins/gui/info.json b/onionr/static-data/default-plugins/pms/info.json
similarity index 71%
rename from onionr/static-data/default-plugins/gui/info.json
rename to onionr/static-data/default-plugins/pms/info.json
index 83d4489a..454b9bd6 100644
--- a/onionr/static-data/default-plugins/gui/info.json
+++ b/onionr/static-data/default-plugins/pms/info.json
@@ -1,5 +1,5 @@
{
- "name" : "gui",
+ "name" : "pms",
"version" : "1.0",
"author" : "onionr"
}
diff --git a/onionr/static-data/default-plugins/pms/main.py b/onionr/static-data/default-plugins/pms/main.py
new file mode 100644
index 00000000..3da4d268
--- /dev/null
+++ b/onionr/static-data/default-plugins/pms/main.py
@@ -0,0 +1,198 @@
+'''
+ Onionr - P2P Microblogging Platform & Social network
+
+ This default plugin handles private messages in an email like fashion
+'''
+'''
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+'''
+
+# Imports some useful libraries
+import logger, config, threading, time, readline, datetime
+from onionrblockapi import Block
+import onionrexceptions
+
+plugin_name = 'pms'
+PLUGIN_VERSION = '0.0.1'
+
+def draw_border(text):
+ #https://stackoverflow.com/a/20757491
+ lines = text.splitlines()
+ width = max(len(s) for s in lines)
+ res = ['┌' + '─' * width + '┐']
+ for s in lines:
+ res.append('│' + (s + ' ' * width)[:width] + '│')
+ res.append('└' + '─' * width + '┘')
+ return '\n'.join(res)
+
+
+class MailStrings:
+ def __init__(self, mailInstance):
+ self.mailInstance = mailInstance
+
+ self.programTag = 'OnionrMail v%s' % (PLUGIN_VERSION)
+ choices = ['view inbox', 'view sentbox', 'send message', 'help', 'quit']
+ self.mainMenuChoices = choices
+ self.mainMenu = '''\n
+-----------------
+1. %s
+2. %s
+3. %s
+4. %s
+5. %s''' % (choices[0], choices[1], choices[2], choices[3], choices[4])
+
+class OnionrMail:
+ def __init__(self, pluginapi):
+ self.myCore = pluginapi.get_core()
+ #self.dataFolder = pluginapi.get_data_folder()
+ self.strings = MailStrings(self)
+
+ return
+
+ def inbox(self):
+ blockCount = 0
+ pmBlockMap = {}
+ pmBlocks = {}
+ logger.info('Decrypting messages...')
+ choice = ''
+
+ # this could use a lot of memory if someone has recieved a lot of messages
+ for blockHash in self.myCore.getBlocksByType('pm'):
+ pmBlocks[blockHash] = Block(blockHash, core=self.myCore)
+ pmBlocks[blockHash].decrypt()
+
+ while choice not in ('-q', 'q', 'quit'):
+ blockCount = 0
+ for blockHash in pmBlocks:
+ if not pmBlocks[blockHash].decrypted:
+ continue
+ blockCount += 1
+ pmBlockMap[blockCount] = blockHash
+ blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M")
+ print('%s. %s: %s' % (blockCount, blockDate, blockHash))
+
+ try:
+ choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
+ except (EOFError, KeyboardInterrupt):
+ choice = '-q'
+
+ if choice in ('-q', 'q', 'quit'):
+ continue
+
+ if choice in ('-r', 'r', 'refresh'):
+ # dirty hack
+ self.inbox()
+ return
+
+ try:
+ choice = int(choice)
+ except ValueError:
+ pass
+ else:
+ try:
+ pmBlockMap[choice]
+ readBlock = pmBlocks[pmBlockMap[choice]]
+ except KeyError:
+ pass
+ else:
+ readBlock.verifySig()
+ print('Message recieved from', readBlock.signer)
+ print('Valid signature:', readBlock.validSig)
+ if not readBlock.validSig:
+ logger.warn('This message has an INVALID signature. Anyone could have sent this message.')
+ logger.readline('Press enter to continue to message.')
+
+ print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
+
+ return
+
+ def draftMessage(self):
+ message = ''
+ newLine = ''
+ recip = ''
+ entering = True
+
+ while entering:
+ try:
+ recip = logger.readline('Enter peer address, or q to stop:').strip()
+ if recip in ('-q', 'q'):
+ raise EOFError
+ if not self.myCore._utils.validatePubKey(recip):
+ raise onionrexceptions.InvalidPubkey('Must be a valid ed25519 base32 encoded public key')
+ except onionrexceptions.InvalidPubkey:
+ logger.warn('Invalid public key')
+ except (KeyboardInterrupt, EOFError):
+ entering = False
+ else:
+ break
+ else:
+ # if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
+ return
+
+ print('Enter your message, stop by entering -q on a new line.')
+ while newLine != '-q':
+ try:
+ newLine = input()
+ except (KeyboardInterrupt, EOFError):
+ pass
+ if newLine == '-q':
+ continue
+ newLine += '\n'
+ message += newLine
+
+ print('Inserting encrypted message as Onionr block....')
+
+ self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
+
+ def menu(self):
+ choice = ''
+ while True:
+
+ print(self.strings.programTag + '\n\nOur ID: ' + self.myCore._crypto.pubKey + self.strings.mainMenu.title()) # print out main menu
+
+ try:
+ choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
+ except (KeyboardInterrupt, EOFError):
+ choice = '5'
+
+ if choice in (self.strings.mainMenuChoices[0], '1'):
+ self.inbox()
+ elif choice in (self.strings.mainMenuChoices[1], '2'):
+ logger.warn('not implemented yet')
+ elif choice in (self.strings.mainMenuChoices[2], '3'):
+ self.draftMessage()
+ elif choice in (self.strings.mainMenuChoices[3], '4'):
+ logger.warn('not implemented yet')
+ elif choice in (self.strings.mainMenuChoices[4], '5'):
+ logger.info('Goodbye.')
+ break
+ elif choice == '':
+ pass
+ else:
+ logger.warn('Invalid choice.')
+ return
+
+
+def on_init(api, data = None):
+ '''
+ This event is called after Onionr is initialized, but before the command
+ inputted is executed. Could be called when daemon is starting or when
+ just the client is running.
+ '''
+
+ pluginapi = api
+ mail = OnionrMail(pluginapi)
+ api.commands.register(['mail'], mail.menu)
+ api.commands.register_help('mail', 'Interact with OnionrMail')
+ return
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 21542d07..926d3276 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,5 @@
![Onionr logo](./docs/onionr-logo.png)
-[![Build Status](https://travis-ci.org/beardog108/onionr.svg?branch=master)](https://travis-ci.org/beardog108/onionr)
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
@@ -10,10 +9,11 @@ Major work in progress.
***THIS SOFTWARE IS NOT USABLE OR SECURE YET.***
+**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
**Roadmap/features:**
-Check the [GitHub Project](https://github.com/beardog108/onionr/projects/1) to see progress towards the alpha release.
+Check the [Gitlab Project](https://gitlab.com/beardog/Onionr/milestones/1) to see progress towards the alpha release.
* [X] Fully p2p/decentralized, no trackers or other single points of failure
* [X] High level of anonymity