Merge branch 'onionrui' into 'master'
onionrui -> master See merge request beardog/Onionr!10
This commit is contained in:
commit
1939dd4427
1
Makefile
1
Makefile
@ -34,6 +34,7 @@ soft-reset:
|
|||||||
reset:
|
reset:
|
||||||
@echo "Hard-resetting Onionr..."
|
@echo "Hard-resetting Onionr..."
|
||||||
rm -rf onionr/data/ | true > /dev/null 2>&1
|
rm -rf onionr/data/ | true > /dev/null 2>&1
|
||||||
|
cd onionr/static-data/www/ui/; rm -rf ./dist; python compile.py
|
||||||
#@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
#@./RUN-LINUX.sh version | grep -v "Failed" --color=always
|
||||||
|
|
||||||
plugins-reset:
|
plugins-reset:
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
![Onionr logo](./docs/onionr-logo.png)
|
![Onionr logo](./docs/onionr-logo.png)
|
||||||
|
|
||||||
|
v0.3.0 (***experimental, not safe or easy to use yet***)
|
||||||
|
|
||||||
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
||||||
|
|
||||||
|
|
||||||
Anonymous P2P platform, using Tor & I2P.
|
Anonymous P2P platform, using Tor & I2P.
|
||||||
|
|
||||||
***Experimental, not safe or easy to use yet***
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
|
**The main repo for this software is at https://gitlab.com/beardog/Onionr/**
|
||||||
@ -45,4 +45,4 @@ Bitcoin/Bitcoin Cash: 1onion55FXzm6h8KQw3zFw2igpHcV7LPq
|
|||||||
|
|
||||||
The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved.
|
The Tor Project, I2P developers, and anyone else do not own, create, or endorse this project, and are not otherwise involved.
|
||||||
|
|
||||||
The badges (besides travis-ci build) are by Maik Ellerbrock is licensed under a Creative Commons Attribution 4.0 International License.
|
The badges (besides travis-ci build) are by Maik Ellerbrock is licensed under a Creative Commons Attribution 4.0 International License.
|
@ -24,7 +24,7 @@ from gevent.pywsgi import WSGIServer
|
|||||||
import sys, random, threading, hmac, hashlib, base64, time, math, os, json
|
import sys, random, threading, hmac, hashlib, base64, time, math, os, json
|
||||||
import core
|
import core
|
||||||
from onionrblockapi import Block
|
from onionrblockapi import Block
|
||||||
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config
|
import onionrutils, onionrexceptions, onionrcrypto, blockimporter, onionrevents as events, logger, config, onionr
|
||||||
|
|
||||||
class API:
|
class API:
|
||||||
'''
|
'''
|
||||||
@ -62,14 +62,9 @@ class API:
|
|||||||
}
|
}
|
||||||
|
|
||||||
for mimetype in mimetypes:
|
for mimetype in mimetypes:
|
||||||
logger.debug(path + ' endswith .' + mimetype + '?')
|
|
||||||
if path.endswith('.%s' % mimetype):
|
if path.endswith('.%s' % mimetype):
|
||||||
logger.debug('- True!')
|
|
||||||
return mimetypes[mimetype]
|
return mimetypes[mimetype]
|
||||||
else:
|
|
||||||
logger.debug('- no')
|
|
||||||
|
|
||||||
logger.debug('%s not in %s' % (path, mimetypes))
|
|
||||||
return 'text/plain'
|
return 'text/plain'
|
||||||
|
|
||||||
def __init__(self, debug, API_VERSION):
|
def __init__(self, debug, API_VERSION):
|
||||||
@ -80,14 +75,8 @@ class API:
|
|||||||
This also saves the used host (random localhost IP address) to the data folder in host.txt
|
This also saves the used host (random localhost IP address) to the data folder in host.txt
|
||||||
'''
|
'''
|
||||||
|
|
||||||
config.reload()
|
# configure logger and stuff
|
||||||
|
onionr.Onionr.setupConfig('data/', self = self)
|
||||||
if config.get('dev_mode', True):
|
|
||||||
self._developmentMode = True
|
|
||||||
logger.set_level(logger.LEVEL_DEBUG)
|
|
||||||
else:
|
|
||||||
self._developmentMode = False
|
|
||||||
logger.set_level(logger.LEVEL_INFO)
|
|
||||||
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self._privateDelayTime = 3
|
self._privateDelayTime = 3
|
||||||
@ -138,14 +127,14 @@ class API:
|
|||||||
resp.headers["Content-Security-Policy"] = "default-src 'none'; script-src 'none'; object-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'"
|
resp.headers["Content-Security-Policy"] = "default-src 'none'; script-src 'none'; object-src 'none'; style-src data: 'unsafe-inline'; img-src data:; media-src 'none'; frame-src 'none'; font-src 'none'; connect-src 'none'"
|
||||||
resp.headers['X-Frame-Options'] = 'deny'
|
resp.headers['X-Frame-Options'] = 'deny'
|
||||||
resp.headers['X-Content-Type-Options'] = "nosniff"
|
resp.headers['X-Content-Type-Options'] = "nosniff"
|
||||||
resp.headers['api'] = API_VERSION
|
resp.headers['X-API'] = API_VERSION
|
||||||
|
|
||||||
# reset to text/plain to help prevent browser attacks
|
# reset to text/plain to help prevent browser attacks
|
||||||
self.mimeType = 'text/plain'
|
self.mimeType = 'text/plain'
|
||||||
self.overrideCSP = False
|
self.overrideCSP = False
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@app.route('/www/private/<path:path>')
|
@app.route('/www/private/<path:path>')
|
||||||
def www_private(path):
|
def www_private(path):
|
||||||
startTime = math.floor(time.time())
|
startTime = math.floor(time.time())
|
||||||
@ -160,14 +149,17 @@ class API:
|
|||||||
|
|
||||||
self.validateHost('private')
|
self.validateHost('private')
|
||||||
|
|
||||||
|
if config.get('www.public.guess_mime', True):
|
||||||
|
self.mimeType = API.guessMime(path)
|
||||||
|
|
||||||
endTime = math.floor(time.time())
|
endTime = math.floor(time.time())
|
||||||
elapsed = endTime - startTime
|
elapsed = endTime - startTime
|
||||||
|
|
||||||
if not hmac.compare_digest(timingToken, self.timeBypassToken):
|
if not hmac.compare_digest(timingToken, self.timeBypassToken):
|
||||||
if elapsed < self._privateDelayTime:
|
if (elapsed < self._privateDelayTime) and config.get('www.private.timing_protection', True):
|
||||||
time.sleep(self._privateDelayTime - elapsed)
|
time.sleep(self._privateDelayTime - elapsed)
|
||||||
|
|
||||||
return send_from_directory('static-data/www/private/', path)
|
return send_from_directory(config.get('www.private.path', 'static-data/www/private/'), path)
|
||||||
|
|
||||||
@app.route('/www/public/<path:path>')
|
@app.route('/www/public/<path:path>')
|
||||||
def www_public(path):
|
def www_public(path):
|
||||||
@ -176,7 +168,10 @@ class API:
|
|||||||
|
|
||||||
self.validateHost('public')
|
self.validateHost('public')
|
||||||
|
|
||||||
return send_from_directory('static-data/www/public/', path)
|
if config.get('www.public.guess_mime', True):
|
||||||
|
self.mimeType = API.guessMime(path)
|
||||||
|
|
||||||
|
return send_from_directory(config.get('www.public.path', 'static-data/www/public/'), path)
|
||||||
|
|
||||||
@app.route('/ui/<path:path>')
|
@app.route('/ui/<path:path>')
|
||||||
def ui_private(path):
|
def ui_private(path):
|
||||||
@ -206,11 +201,11 @@ class API:
|
|||||||
time.sleep(self._privateDelayTime - elapsed)
|
time.sleep(self._privateDelayTime - elapsed)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logger.debug('Serving %s' % path)
|
|
||||||
|
|
||||||
self.mimeType = API.guessMime(path)
|
self.mimeType = API.guessMime(path)
|
||||||
self.overrideCSP = True
|
self.overrideCSP = True
|
||||||
|
|
||||||
|
logger.debug('Serving %s (mime: %s)' % (path, self.mimeType))
|
||||||
|
|
||||||
return send_from_directory('static-data/www/ui/dist/', path, mimetype = API.guessMime(path))
|
return send_from_directory('static-data/www/ui/dist/', path, mimetype = API.guessMime(path))
|
||||||
|
|
||||||
@app.route('/client/')
|
@app.route('/client/')
|
||||||
@ -253,6 +248,16 @@ class API:
|
|||||||
resp = Response('Goodbye')
|
resp = Response('Goodbye')
|
||||||
elif action == 'ping':
|
elif action == 'ping':
|
||||||
resp = Response('pong')
|
resp = Response('pong')
|
||||||
|
elif action == 'site':
|
||||||
|
block = data
|
||||||
|
siteData = self._core.getData(data)
|
||||||
|
response = 'not found'
|
||||||
|
if siteData != '' and siteData != False:
|
||||||
|
self.mimeType = 'text/html'
|
||||||
|
response = siteData.split(b'-', 2)[-1]
|
||||||
|
resp = Response(response)
|
||||||
|
elif action == 'info':
|
||||||
|
resp = Response(json.dumps({'pubkey' : self._core._crypto.pubKey, 'host' : self._core.hsAddress}))
|
||||||
elif action == "insertBlock":
|
elif action == "insertBlock":
|
||||||
response = {'success' : False, 'reason' : 'An unknown error occurred'}
|
response = {'success' : False, 'reason' : 'An unknown error occurred'}
|
||||||
|
|
||||||
@ -450,7 +455,7 @@ class API:
|
|||||||
else:
|
else:
|
||||||
logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
|
logger.warn(newNode.decode() + ' failed to meet POW: ' + powHash)
|
||||||
resp = Response(resp)
|
resp = Response(resp)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@app.route('/public/')
|
@app.route('/public/')
|
||||||
def public_handler():
|
def public_handler():
|
||||||
|
@ -21,12 +21,14 @@
|
|||||||
'''
|
'''
|
||||||
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid
|
import sys, os, core, config, json, requests, time, logger, threading, base64, onionr, uuid
|
||||||
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
import onionrexceptions, onionrpeers, onionrevents as events, onionrplugins as plugins, onionrblockapi as block
|
||||||
import onionrdaemontools, onionrsockets, onionrchat
|
import onionrdaemontools, onionrsockets, onionrchat, onionr
|
||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
from defusedxml import minidom
|
from defusedxml import minidom
|
||||||
|
|
||||||
class OnionrCommunicatorDaemon:
|
class OnionrCommunicatorDaemon:
|
||||||
def __init__(self, debug, developmentMode):
|
def __init__(self, debug, developmentMode):
|
||||||
|
# configure logger and stuff
|
||||||
|
onionr.Onionr.setupConfig('data/', self = self)
|
||||||
|
|
||||||
self.isOnline = True # Assume we're connected to the internet
|
self.isOnline = True # Assume we're connected to the internet
|
||||||
|
|
||||||
@ -87,9 +89,8 @@ class OnionrCommunicatorDaemon:
|
|||||||
|
|
||||||
# Set timers, function reference, seconds
|
# Set timers, function reference, seconds
|
||||||
# requiresPeer True means the timer function won't fire if we have no connected peers
|
# requiresPeer True means the timer function won't fire if we have no connected peers
|
||||||
OnionrCommunicatorTimers(self, self.daemonCommands, 5)
|
|
||||||
OnionrCommunicatorTimers(self, self.detectAPICrash, 5)
|
|
||||||
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60, maxThreads=1)
|
peerPoolTimer = OnionrCommunicatorTimers(self, self.getOnlinePeers, 60, maxThreads=1)
|
||||||
|
OnionrCommunicatorTimers(self, self.runCheck, 1)
|
||||||
OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookupBlocks'), requiresPeer=True, maxThreads=1)
|
OnionrCommunicatorTimers(self, self.lookupBlocks, self._core.config.get('timers.lookupBlocks'), requiresPeer=True, maxThreads=1)
|
||||||
OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True)
|
OnionrCommunicatorTimers(self, self.getBlocks, self._core.config.get('timers.getBlocks'), requiresPeer=True)
|
||||||
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
|
OnionrCommunicatorTimers(self, self.clearOfflinePeer, 58)
|
||||||
@ -134,7 +135,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
|
|
||||||
def lookupAdders(self):
|
def lookupAdders(self):
|
||||||
'''Lookup new peer addresses'''
|
'''Lookup new peer addresses'''
|
||||||
logger.info('LOOKING UP NEW ADDRESSES')
|
logger.info('Looking up new addresses...')
|
||||||
tryAmount = 1
|
tryAmount = 1
|
||||||
for i in range(tryAmount):
|
for i in range(tryAmount):
|
||||||
# Download new peer address list from random online peers
|
# Download new peer address list from random online peers
|
||||||
@ -145,7 +146,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
|
|
||||||
def lookupBlocks(self):
|
def lookupBlocks(self):
|
||||||
'''Lookup new blocks & add them to download queue'''
|
'''Lookup new blocks & add them to download queue'''
|
||||||
logger.info('LOOKING UP NEW BLOCKS')
|
logger.info('Looking up new blocks...')
|
||||||
tryAmount = 2
|
tryAmount = 2
|
||||||
newBlocks = ''
|
newBlocks = ''
|
||||||
existingBlocks = self._core.getBlockList()
|
existingBlocks = self._core.getBlockList()
|
||||||
@ -176,7 +177,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
try:
|
try:
|
||||||
newBlocks = self.peerAction(peer, 'getBlockHashes') # get list of new block hashes
|
newBlocks = self.peerAction(peer, 'getBlockHashes') # get list of new block hashes
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.warn("could not get new blocks with " + peer, error=error)
|
logger.warn('Could not get new blocks from %s.' % peer, error = error)
|
||||||
newBlocks = False
|
newBlocks = False
|
||||||
if newBlocks != False:
|
if newBlocks != False:
|
||||||
# if request was a success
|
# if request was a success
|
||||||
@ -200,10 +201,10 @@ class OnionrCommunicatorDaemon:
|
|||||||
break
|
break
|
||||||
# Do not download blocks being downloaded or that are already saved (edge cases)
|
# Do not download blocks being downloaded or that are already saved (edge cases)
|
||||||
if blockHash in self.currentDownloading:
|
if blockHash in self.currentDownloading:
|
||||||
logger.debug('ALREADY DOWNLOADING ' + blockHash)
|
logger.debug('Already downloading block %s...' % blockHash)
|
||||||
continue
|
continue
|
||||||
if blockHash in self._core.getBlockList():
|
if blockHash in self._core.getBlockList():
|
||||||
logger.debug('%s is already saved' % (blockHash,))
|
logger.debug('Block %s is already saved.' % (blockHash,))
|
||||||
self.blockQueue.remove(blockHash)
|
self.blockQueue.remove(blockHash)
|
||||||
continue
|
continue
|
||||||
if self._core._blacklist.inBlacklist(blockHash):
|
if self._core._blacklist.inBlacklist(blockHash):
|
||||||
@ -232,22 +233,22 @@ class OnionrCommunicatorDaemon:
|
|||||||
#meta = metas[1]
|
#meta = metas[1]
|
||||||
if self._core._utils.validateMetadata(metadata, metas[2]): # check if metadata is valid, and verify nonce
|
if self._core._utils.validateMetadata(metadata, metas[2]): # check if metadata is valid, and verify nonce
|
||||||
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
if self._core._crypto.verifyPow(content): # check if POW is enough/correct
|
||||||
logger.info('Block passed proof, attempting save.')
|
logger.info('Attempting to save block %s...' % blockHash)
|
||||||
try:
|
try:
|
||||||
self._core.setData(content)
|
self._core.setData(content)
|
||||||
except onionrexceptions.DiskAllocationReached:
|
except onionrexceptions.DiskAllocationReached:
|
||||||
logger.error("Reached disk allocation allowance, cannot save this block.")
|
logger.error('Reached disk allocation allowance, cannot save block %s.' % blockHash)
|
||||||
removeFromQueue = False
|
removeFromQueue = False
|
||||||
else:
|
else:
|
||||||
self._core.addToBlockDB(blockHash, dataSaved=True)
|
self._core.addToBlockDB(blockHash, dataSaved=True)
|
||||||
self._core._utils.processBlockMetadata(blockHash) # caches block metadata values to block database
|
self._core._utils.processBlockMetadata(blockHash) # caches block metadata values to block database
|
||||||
else:
|
else:
|
||||||
logger.warn('POW failed for block ' + blockHash)
|
logger.warn('POW failed for block %s.' % blockHash)
|
||||||
else:
|
else:
|
||||||
if self._core._blacklist.inBlacklist(realHash):
|
if self._core._blacklist.inBlacklist(realHash):
|
||||||
logger.warn('%s is blacklisted' % (realHash,))
|
logger.warn('Block %s is blacklisted.' % (realHash,))
|
||||||
else:
|
else:
|
||||||
logger.warn('Metadata for ' + blockHash + ' is invalid.')
|
logger.warn('Metadata for block %s is invalid.' % blockHash)
|
||||||
self._core._blacklist.addToDB(blockHash)
|
self._core._blacklist.addToDB(blockHash)
|
||||||
else:
|
else:
|
||||||
# if block didn't meet expected hash
|
# if block didn't meet expected hash
|
||||||
@ -257,7 +258,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# Punish peer for sharing invalid block (not always malicious, but is bad regardless)
|
# Punish peer for sharing invalid block (not always malicious, but is bad regardless)
|
||||||
onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50)
|
onionrpeers.PeerProfiles(peerUsed, self._core).addScore(-50)
|
||||||
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
|
logger.warn('Block hash validation failed for ' + blockHash + ' got ' + tempHash)
|
||||||
if removeFromQueue:
|
if removeFromQueue:
|
||||||
try:
|
try:
|
||||||
@ -303,10 +304,12 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.decrementThreadCount('clearOfflinePeer')
|
self.decrementThreadCount('clearOfflinePeer')
|
||||||
|
|
||||||
def getOnlinePeers(self):
|
def getOnlinePeers(self):
|
||||||
'''Manages the self.onlinePeers attribute list, connects to more peers if we have none connected'''
|
'''
|
||||||
|
Manages the self.onlinePeers attribute list, connects to more peers if we have none connected
|
||||||
|
'''
|
||||||
|
|
||||||
logger.info('Refreshing peer pool.')
|
logger.debug('Refreshing peer pool...')
|
||||||
maxPeers = int(config.get('peers.maxConnect'))
|
maxPeers = int(config.get('peers.max_connect', 10))
|
||||||
needed = maxPeers - len(self.onlinePeers)
|
needed = maxPeers - len(self.onlinePeers)
|
||||||
|
|
||||||
for i in range(needed):
|
for i in range(needed):
|
||||||
@ -318,13 +321,15 @@ class OnionrCommunicatorDaemon:
|
|||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
if len(self.onlinePeers) == 0:
|
if len(self.onlinePeers) == 0:
|
||||||
logger.warn('Could not connect to any peer.')
|
logger.debug('Couldn\'t connect to any peers.')
|
||||||
self.decrementThreadCount('getOnlinePeers')
|
self.decrementThreadCount('getOnlinePeers')
|
||||||
|
|
||||||
def addBootstrapListToPeerList(self, peerList):
|
def addBootstrapListToPeerList(self, peerList):
|
||||||
'''Add the bootstrap list to the peer list (no duplicates)'''
|
'''
|
||||||
|
Add the bootstrap list to the peer list (no duplicates)
|
||||||
|
'''
|
||||||
for i in self._core.bootstrapList:
|
for i in self._core.bootstrapList:
|
||||||
if i not in peerList and i not in self.offlinePeers and i != self._core.hsAddress:
|
if i not in peerList and i not in self.offlinePeers and i != self._core.hsAddress and len(str(i).strip()) > 0:
|
||||||
peerList.append(i)
|
peerList.append(i)
|
||||||
self._core.addAddress(i)
|
self._core.addAddress(i)
|
||||||
|
|
||||||
@ -339,7 +344,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address')
|
raise onionrexceptions.InvalidAddress('Will not attempt connection test to invalid address')
|
||||||
else:
|
else:
|
||||||
peerList = self._core.listAdders()
|
peerList = self._core.listAdders()
|
||||||
|
|
||||||
peerList = onionrpeers.getScoreSortedPeerList(self._core)
|
peerList = onionrpeers.getScoreSortedPeerList(self._core)
|
||||||
|
|
||||||
if len(peerList) == 0 or useBootstrap:
|
if len(peerList) == 0 or useBootstrap:
|
||||||
@ -347,7 +352,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.addBootstrapListToPeerList(peerList)
|
self.addBootstrapListToPeerList(peerList)
|
||||||
|
|
||||||
for address in peerList:
|
for address in peerList:
|
||||||
if not config.get('tor.v3onions') and len(address) == 62:
|
if not config.get('tor.v3_onions') and len(address) == 62:
|
||||||
continue
|
continue
|
||||||
if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer:
|
if len(address) == 0 or address in tried or address in self.onlinePeers or address in self.cooldownPeer:
|
||||||
continue
|
continue
|
||||||
@ -360,7 +365,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.onlinePeers.append(address)
|
self.onlinePeers.append(address)
|
||||||
self.connectTimes[address] = self._core._utils.getEpoch()
|
self.connectTimes[address] = self._core._utils.getEpoch()
|
||||||
retData = address
|
retData = address
|
||||||
|
|
||||||
# add peer to profile list if they're not in it
|
# add peer to profile list if they're not in it
|
||||||
for profile in self.peerProfiles:
|
for profile in self.peerProfiles:
|
||||||
if profile.address == address:
|
if profile.address == address:
|
||||||
@ -424,7 +429,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
self._core.setAddressInfo(peer, 'lastConnect', self._core._utils.getEpoch())
|
self._core.setAddressInfo(peer, 'lastConnect', self._core._utils.getEpoch())
|
||||||
self.getPeerProfileInstance(peer).addScore(1)
|
self.getPeerProfileInstance(peer).addScore(1)
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def getPeerProfileInstance(self, peer):
|
def getPeerProfileInstance(self, peer):
|
||||||
'''Gets a peer profile instance from the list of profiles, by address name'''
|
'''Gets a peer profile instance from the list of profiles, by address name'''
|
||||||
for i in self.peerProfiles:
|
for i in self.peerProfiles:
|
||||||
@ -440,11 +445,13 @@ class OnionrCommunicatorDaemon:
|
|||||||
def heartbeat(self):
|
def heartbeat(self):
|
||||||
'''Show a heartbeat debug message'''
|
'''Show a heartbeat debug message'''
|
||||||
currentTime = self._core._utils.getEpoch() - self.startTime
|
currentTime = self._core._utils.getEpoch() - self.startTime
|
||||||
logger.debug('heartbeat, running seconds: ' + str(currentTime))
|
logger.debug('Heartbeat. Node online for %s.' % self.daemonTools.humanReadableTime(currentTime))
|
||||||
self.decrementThreadCount('heartbeat')
|
self.decrementThreadCount('heartbeat')
|
||||||
|
|
||||||
def daemonCommands(self):
|
def daemonCommands(self):
|
||||||
'''process daemon commands from daemonQueue'''
|
'''
|
||||||
|
Process daemon commands from daemonQueue
|
||||||
|
'''
|
||||||
cmd = self._core.daemonQueue()
|
cmd = self._core.daemonQueue()
|
||||||
|
|
||||||
if cmd is not False:
|
if cmd is not False:
|
||||||
@ -457,7 +464,7 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.announce(cmd[1])
|
self.announce(cmd[1])
|
||||||
else:
|
else:
|
||||||
logger.warn("Not introducing, since I have no connected nodes.")
|
logger.warn("Not introducing, since I have no connected nodes.")
|
||||||
elif cmd[0] == 'runCheck':
|
elif cmd[0] == 'runCheck': # deprecated
|
||||||
logger.debug('Status check; looks good.')
|
logger.debug('Status check; looks good.')
|
||||||
open(self._core.dataDir + '.runcheck', 'w+').close()
|
open(self._core.dataDir + '.runcheck', 'w+').close()
|
||||||
elif cmd[0] == 'connectedPeers':
|
elif cmd[0] == 'connectedPeers':
|
||||||
@ -538,6 +545,12 @@ class OnionrCommunicatorDaemon:
|
|||||||
self.shutdown = True
|
self.shutdown = True
|
||||||
self.decrementThreadCount('detectAPICrash')
|
self.decrementThreadCount('detectAPICrash')
|
||||||
|
|
||||||
|
def runCheck(self):
|
||||||
|
if self.daemonTools.runCheck():
|
||||||
|
logger.debug('Status check; looks good.')
|
||||||
|
|
||||||
|
self.decrementThreadCount('runCheck')
|
||||||
|
|
||||||
class OnionrCommunicatorTimers:
|
class OnionrCommunicatorTimers:
|
||||||
def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5, requiresPeer=False):
|
def __init__(self, daemonInstance, timerFunction, frequency, makeThread=True, threadAmount=1, maxThreads=5, requiresPeer=False):
|
||||||
self.timerFunction = timerFunction
|
self.timerFunction = timerFunction
|
||||||
@ -571,7 +584,7 @@ class OnionrCommunicatorTimers:
|
|||||||
if self.makeThread:
|
if self.makeThread:
|
||||||
for i in range(self.threadAmount):
|
for i in range(self.threadAmount):
|
||||||
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
if self.daemonInstance.threadCounts[self.timerFunction.__name__] >= self.maxThreads:
|
||||||
logger.warn(self.timerFunction.__name__ + ' has too many current threads to start anymore.')
|
logger.warn('%s is currently using the maximum number of threads, not starting another.' % self.timerFunction.__name__)
|
||||||
else:
|
else:
|
||||||
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
self.daemonInstance.threadCounts[self.timerFunction.__name__] += 1
|
||||||
newThread = threading.Thread(target=self.timerFunction)
|
newThread = threading.Thread(target=self.timerFunction)
|
||||||
|
100
onionr/core.py
100
onionr/core.py
@ -23,6 +23,7 @@ from onionrblockapi import Block
|
|||||||
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues
|
import onionrutils, onionrcrypto, onionrproofs, onionrevents as events, onionrexceptions, onionrvalues
|
||||||
import onionrblacklist, onionrchat, onionrusers
|
import onionrblacklist, onionrchat, onionrusers
|
||||||
import dbcreator
|
import dbcreator
|
||||||
|
|
||||||
if sys.version_info < (3, 6):
|
if sys.version_info < (3, 6):
|
||||||
try:
|
try:
|
||||||
import sha3
|
import sha3
|
||||||
@ -42,7 +43,7 @@ class Core:
|
|||||||
self.dataDir += '/'
|
self.dataDir += '/'
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.dataDir = 'data/'
|
self.dataDir = 'data/'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.queueDB = self.dataDir + 'queue.db'
|
self.queueDB = self.dataDir + 'queue.db'
|
||||||
self.peerDB = self.dataDir + 'peers.db'
|
self.peerDB = self.dataDir + 'peers.db'
|
||||||
@ -104,7 +105,10 @@ class Core:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def refreshFirstStartVars(self):
|
def refreshFirstStartVars(self):
|
||||||
'''Hack to refresh some vars which may not be set on first start'''
|
'''
|
||||||
|
Hack to refresh some vars which may not be set on first start
|
||||||
|
'''
|
||||||
|
|
||||||
if os.path.exists(self.dataDir + '/hs/hostname'):
|
if os.path.exists(self.dataDir + '/hs/hostname'):
|
||||||
with open(self.dataDir + '/hs/hostname', 'r') as hs:
|
with open(self.dataDir + '/hs/hostname', 'r') as hs:
|
||||||
self.hsAddress = hs.read().strip()
|
self.hsAddress = hs.read().strip()
|
||||||
@ -113,6 +117,7 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Adds a public key to the key database (misleading function name)
|
Adds a public key to the key database (misleading function name)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# This function simply adds a peer to the DB
|
# This function simply adds a peer to the DB
|
||||||
if not self._utils.validatePubKey(peerID):
|
if not self._utils.validatePubKey(peerID):
|
||||||
return False
|
return False
|
||||||
@ -121,13 +126,13 @@ class Core:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
events.event('pubkey_add', data = {'key': peerID}, onionr = None)
|
events.event('pubkey_add', data = {'key': peerID}, onionr = None)
|
||||||
|
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||||
hashID = self._crypto.pubKeyHashID(peerID)
|
hashID = self._crypto.pubKeyHashID(peerID)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
t = (peerID, name, 'unknown', hashID, powID, 0)
|
t = (peerID, name, 'unknown', hashID, powID, 0)
|
||||||
|
|
||||||
for i in c.execute("SELECT * FROM PEERS where id = '" + peerID + "';"):
|
for i in c.execute("SELECT * FROM peers WHERE id = ?;", (peerID,)):
|
||||||
try:
|
try:
|
||||||
if i[0] == peerID:
|
if i[0] == peerID:
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -146,8 +151,8 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Add an address to the address database (only tor currently)
|
Add an address to the address database (only tor currently)
|
||||||
'''
|
'''
|
||||||
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
|
|
||||||
|
|
||||||
|
if address == config.get('i2p.ownAddr', None) or address == self.hsAddress:
|
||||||
return False
|
return False
|
||||||
if self._utils.validateID(address):
|
if self._utils.validateID(address):
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||||
@ -155,7 +160,7 @@ class Core:
|
|||||||
# check if address is in database
|
# check if address is in database
|
||||||
# this is safe to do because the address is validated above, but we strip some chars here too just in case
|
# this is safe to do because the address is validated above, but we strip some chars here too just in case
|
||||||
address = address.replace('\'', '').replace(';', '').replace('"', '').replace('\\', '')
|
address = address.replace('\'', '').replace(';', '').replace('"', '').replace('\\', '')
|
||||||
for i in c.execute("SELECT * FROM adders where address = '" + address + "';"):
|
for i in c.execute("SELECT * FROM adders WHERE address = ?;", (address,)):
|
||||||
try:
|
try:
|
||||||
if i[0] == address:
|
if i[0] == address:
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -174,13 +179,14 @@ class Core:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.debug('Invalid ID')
|
logger.debug('Invalid ID: %s' % address)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def removeAddress(self, address):
|
def removeAddress(self, address):
|
||||||
'''
|
'''
|
||||||
Remove an address from the address database
|
Remove an address from the address database
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if self._utils.validateID(address):
|
if self._utils.validateID(address):
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@ -200,6 +206,7 @@ class Core:
|
|||||||
|
|
||||||
**You may want blacklist.addToDB(blockHash)
|
**You may want blacklist.addToDB(blockHash)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if self._utils.validateHash(block):
|
if self._utils.validateHash(block):
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
@ -207,10 +214,10 @@ class Core:
|
|||||||
c.execute('Delete from hashes where hash=?;', t)
|
c.execute('Delete from hashes where hash=?;', t)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
blockFile = self.dataDir + '/blocks/' + block + '.dat'
|
blockFile = self.dataDir + '/blocks/%s.dat' % block
|
||||||
dataSize = 0
|
dataSize = 0
|
||||||
try:
|
try:
|
||||||
''' Get size of data when loaded as an object/var, rather than on disk,
|
''' Get size of data when loaded as an object/var, rather than on disk,
|
||||||
to avoid conflict with getsizeof when saving blocks
|
to avoid conflict with getsizeof when saving blocks
|
||||||
'''
|
'''
|
||||||
with open(blockFile, 'r') as data:
|
with open(blockFile, 'r') as data:
|
||||||
@ -224,18 +231,21 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Generate the address database
|
Generate the address database
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createAddressDB()
|
self.dbCreate.createAddressDB()
|
||||||
|
|
||||||
def createPeerDB(self):
|
def createPeerDB(self):
|
||||||
'''
|
'''
|
||||||
Generate the peer sqlite3 database and populate it with the peers table.
|
Generate the peer sqlite3 database and populate it with the peers table.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createPeerDB()
|
self.dbCreate.createPeerDB()
|
||||||
|
|
||||||
def createBlockDB(self):
|
def createBlockDB(self):
|
||||||
'''
|
'''
|
||||||
Create a database for blocks
|
Create a database for blocks
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self.dbCreate.createBlockDB()
|
self.dbCreate.createBlockDB()
|
||||||
|
|
||||||
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
|
def addToBlockDB(self, newHash, selfInsert=False, dataSaved=False):
|
||||||
@ -244,6 +254,7 @@ class Core:
|
|||||||
|
|
||||||
Should be in hex format!
|
Should be in hex format!
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if not os.path.exists(self.blockDB):
|
if not os.path.exists(self.blockDB):
|
||||||
raise Exception('Block db does not exist')
|
raise Exception('Block db does not exist')
|
||||||
if self._utils.hasBlock(newHash):
|
if self._utils.hasBlock(newHash):
|
||||||
@ -266,6 +277,7 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Simply return the data associated to a hash
|
Simply return the data associated to a hash
|
||||||
'''
|
'''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
|
# logger.debug('Opening %s' % (str(self.blockDataLocation) + str(hash) + '.dat'))
|
||||||
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
|
dataFile = open(self.blockDataLocation + hash + '.dat', 'rb')
|
||||||
@ -280,12 +292,13 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Set the data assciated with a hash
|
Set the data assciated with a hash
|
||||||
'''
|
'''
|
||||||
|
|
||||||
data = data
|
data = data
|
||||||
dataSize = sys.getsizeof(data)
|
dataSize = sys.getsizeof(data)
|
||||||
|
|
||||||
if not type(data) is bytes:
|
if not type(data) is bytes:
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
|
|
||||||
dataHash = self._crypto.sha3Hash(data)
|
dataHash = self._crypto.sha3Hash(data)
|
||||||
|
|
||||||
if type(dataHash) is bytes:
|
if type(dataHash) is bytes:
|
||||||
@ -301,7 +314,7 @@ class Core:
|
|||||||
blockFile.close()
|
blockFile.close()
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = '" + dataHash + "';")
|
c.execute("UPDATE hashes SET dataSaved=1 WHERE hash = ?;", (dataHash,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
with open(self.dataNonceFile, 'a') as nonceFile:
|
with open(self.dataNonceFile, 'a') as nonceFile:
|
||||||
@ -317,6 +330,7 @@ class Core:
|
|||||||
|
|
||||||
This function intended to be used by the client. Queue to exchange data between "client" and server.
|
This function intended to be used by the client. Queue to exchange data between "client" and server.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
retData = False
|
retData = False
|
||||||
if not os.path.exists(self.queueDB):
|
if not os.path.exists(self.queueDB):
|
||||||
self.dbCreate.createDaemonDB()
|
self.dbCreate.createDaemonDB()
|
||||||
@ -343,12 +357,15 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
Add a command to the daemon queue, used by the communication daemon (communicator.py)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
retData = True
|
retData = True
|
||||||
# Intended to be used by the web server
|
# Intended to be used by the web server
|
||||||
|
|
||||||
date = self._utils.getEpoch()
|
date = self._utils.getEpoch()
|
||||||
conn = sqlite3.connect(self.queueDB, timeout=10)
|
conn = sqlite3.connect(self.queueDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
t = (command, data, date)
|
t = (command, data, date)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
|
c.execute('INSERT INTO commands (command, data, date) VALUES(?, ?, ?)', t)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -366,11 +383,13 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.queueDB, timeout=10)
|
conn = sqlite3.connect(self.queueDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c.execute('DELETE FROM commands;')
|
c.execute('DELETE FROM commands;')
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
events.event('queue_clear', onionr = None)
|
events.event('queue_clear', onionr = None)
|
||||||
|
|
||||||
@ -401,16 +420,21 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
payload = ""
|
|
||||||
|
payload = ''
|
||||||
|
|
||||||
if trust not in (0, 1, 2):
|
if trust not in (0, 1, 2):
|
||||||
logger.error('Tried to select invalid trust.')
|
logger.error('Tried to select invalid trust.')
|
||||||
return
|
return
|
||||||
|
|
||||||
if randomOrder:
|
if randomOrder:
|
||||||
payload = 'SELECT * FROM peers where trust >= %s ORDER BY RANDOM();' % (trust,)
|
payload = 'SELECT * FROM peers WHERE trust >= ? ORDER BY RANDOM();'
|
||||||
else:
|
else:
|
||||||
payload = 'SELECT * FROM peers where trust >= %s;' % (trust,)
|
payload = 'SELECT * FROM peers WHERE trust >= ?;'
|
||||||
|
|
||||||
peerList = []
|
peerList = []
|
||||||
for i in c.execute(payload):
|
|
||||||
|
for i in c.execute(payload, (trust,)):
|
||||||
try:
|
try:
|
||||||
if len(i[0]) != 0:
|
if len(i[0]) != 0:
|
||||||
if getPow:
|
if getPow:
|
||||||
@ -419,6 +443,7 @@ class Core:
|
|||||||
peerList.append(i[0])
|
peerList.append(i[0])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if getPow:
|
if getPow:
|
||||||
try:
|
try:
|
||||||
peerList.append(self._crypto.pubKey + '-' + self._crypto.pubKeyPowToken)
|
peerList.append(self._crypto.pubKey + '-' + self._crypto.pubKeyPowToken)
|
||||||
@ -426,7 +451,9 @@ class Core:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
peerList.append(self._crypto.pubKey)
|
peerList.append(self._crypto.pubKey)
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return peerList
|
return peerList
|
||||||
|
|
||||||
def getPeerInfo(self, peer, info):
|
def getPeerInfo(self, peer, info):
|
||||||
@ -445,18 +472,22 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (peer,)
|
command = (peer,)
|
||||||
|
|
||||||
infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'bytesStored': 4, 'trust': 5, 'pubkeyExchanged': 6, 'hashID': 7}
|
infoNumbers = {'id': 0, 'name': 1, 'adders': 2, 'dateSeen': 3, 'bytesStored': 4, 'trust': 5, 'pubkeyExchanged': 6, 'hashID': 7}
|
||||||
info = infoNumbers[info]
|
info = infoNumbers[info]
|
||||||
iterCount = 0
|
iterCount = 0
|
||||||
retVal = ''
|
retVal = ''
|
||||||
for row in c.execute('SELECT * from peers where id=?;', command):
|
|
||||||
|
for row in c.execute('SELECT * FROM peers WHERE id=?;', command):
|
||||||
for i in row:
|
for i in row:
|
||||||
if iterCount == info:
|
if iterCount == info:
|
||||||
retVal = i
|
retVal = i
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
iterCount += 1
|
iterCount += 1
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
@ -465,15 +496,20 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Update a peer for a key
|
Update a peer for a key
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.peerDB, timeout=10)
|
conn = sqlite3.connect(self.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (data, peer)
|
command = (data, peer)
|
||||||
|
|
||||||
# TODO: validate key on whitelist
|
# TODO: validate key on whitelist
|
||||||
if key not in ('id', 'name', 'pubkey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'):
|
if key not in ('id', 'name', 'pubkey', 'blockDBHash', 'forwardKey', 'dateSeen', 'bytesStored', 'trust'):
|
||||||
raise Exception("Got invalid database key when setting peer info")
|
raise Exception("Got invalid database key when setting peer info")
|
||||||
|
|
||||||
c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
|
c.execute('UPDATE peers SET ' + key + ' = ? WHERE id=?', command)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getAddressInfo(self, address, info):
|
def getAddressInfo(self, address, info):
|
||||||
@ -489,14 +525,17 @@ class Core:
|
|||||||
failure int 6
|
failure int 6
|
||||||
lastConnect 7
|
lastConnect 7
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (address,)
|
command = (address,)
|
||||||
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7}
|
infoNumbers = {'address': 0, 'type': 1, 'knownPeer': 2, 'speed': 3, 'success': 4, 'DBHash': 5, 'failure': 6, 'lastConnect': 7}
|
||||||
info = infoNumbers[info]
|
info = infoNumbers[info]
|
||||||
iterCount = 0
|
iterCount = 0
|
||||||
retVal = ''
|
retVal = ''
|
||||||
for row in c.execute('SELECT * from adders where address=?;', command):
|
|
||||||
|
for row in c.execute('SELECT * FROM adders WHERE address=?;', command):
|
||||||
for i in row:
|
for i in row:
|
||||||
if iterCount == info:
|
if iterCount == info:
|
||||||
retVal = i
|
retVal = i
|
||||||
@ -504,15 +543,19 @@ class Core:
|
|||||||
else:
|
else:
|
||||||
iterCount += 1
|
iterCount += 1
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return retVal
|
return retVal
|
||||||
|
|
||||||
def setAddressInfo(self, address, key, data):
|
def setAddressInfo(self, address, key, data):
|
||||||
'''
|
'''
|
||||||
Update an address for a key
|
Update an address for a key
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.addressDB, timeout=10)
|
conn = sqlite3.connect(self.addressDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
command = (data, address)
|
command = (data, address)
|
||||||
|
|
||||||
# TODO: validate key on whitelist
|
# TODO: validate key on whitelist
|
||||||
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt'):
|
if key not in ('address', 'type', 'knownPeer', 'speed', 'success', 'DBHash', 'failure', 'lastConnect', 'lastConnectAttempt'):
|
||||||
raise Exception("Got invalid database key when setting address info")
|
raise Exception("Got invalid database key when setting address info")
|
||||||
@ -520,18 +563,22 @@ class Core:
|
|||||||
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
c.execute('UPDATE adders SET ' + key + ' = ? WHERE address=?', command)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getBlockList(self, unsaved = False): # TODO: Use unsaved??
|
def getBlockList(self, unsaved = False): # TODO: Use unsaved??
|
||||||
'''
|
'''
|
||||||
Get list of our blocks
|
Get list of our blocks
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
if unsaved:
|
if unsaved:
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
execute = 'SELECT hash FROM hashes WHERE dataSaved != 1 ORDER BY RANDOM();'
|
||||||
else:
|
else:
|
||||||
execute = 'SELECT hash FROM hashes ORDER BY dateReceived ASC;'
|
execute = 'SELECT hash FROM hashes ORDER BY dateReceived ASC;'
|
||||||
|
|
||||||
rows = list()
|
rows = list()
|
||||||
for row in c.execute(execute):
|
for row in c.execute(execute):
|
||||||
for i in row:
|
for i in row:
|
||||||
@ -543,8 +590,10 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Returns the date a block was received
|
Returns the date a block was received
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
execute = 'SELECT dateReceived FROM hashes WHERE hash=?;'
|
||||||
args = (blockHash,)
|
args = (blockHash,)
|
||||||
for row in c.execute(execute, args):
|
for row in c.execute(execute, args):
|
||||||
@ -557,17 +606,22 @@ class Core:
|
|||||||
'''
|
'''
|
||||||
Returns a list of blocks by the type
|
Returns a list of blocks by the type
|
||||||
'''
|
'''
|
||||||
|
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
if orderDate:
|
if orderDate:
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
execute = 'SELECT hash FROM hashes WHERE dataType=? ORDER BY dateReceived;'
|
||||||
else:
|
else:
|
||||||
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
execute = 'SELECT hash FROM hashes WHERE dataType=?;'
|
||||||
|
|
||||||
args = (blockType,)
|
args = (blockType,)
|
||||||
rows = list()
|
rows = list()
|
||||||
|
|
||||||
for row in c.execute(execute, args):
|
for row in c.execute(execute, args):
|
||||||
for i in row:
|
for i in row:
|
||||||
rows.append(i)
|
rows.append(i)
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def getExpiredBlocks(self):
|
def getExpiredBlocks(self):
|
||||||
@ -582,7 +636,7 @@ class Core:
|
|||||||
for row in c.execute(execute):
|
for row in c.execute(execute):
|
||||||
for i in row:
|
for i in row:
|
||||||
rows.append(i)
|
rows.append(i)
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
def setBlockType(self, hash, blockType):
|
def setBlockType(self, hash, blockType):
|
||||||
'''
|
'''
|
||||||
@ -591,9 +645,10 @@ class Core:
|
|||||||
|
|
||||||
conn = sqlite3.connect(self.blockDB, timeout=10)
|
conn = sqlite3.connect(self.blockDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute("UPDATE hashes SET dataType='" + blockType + "' WHERE hash = '" + hash + "';")
|
c.execute("UPDATE hashes SET dataType = ? WHERE hash = ?;", (blockType, hash))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateBlockInfo(self, hash, key, data):
|
def updateBlockInfo(self, hash, key, data):
|
||||||
@ -621,6 +676,7 @@ class Core:
|
|||||||
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
|
c.execute("UPDATE hashes SET " + key + " = ? where hash = ?;", args)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None, expire=None):
|
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta = None, expire=None):
|
||||||
@ -628,6 +684,7 @@ class Core:
|
|||||||
Inserts a block into the network
|
Inserts a block into the network
|
||||||
encryptType must be specified to encrypt a block
|
encryptType must be specified to encrypt a block
|
||||||
'''
|
'''
|
||||||
|
|
||||||
retData = False
|
retData = False
|
||||||
|
|
||||||
# check nonce
|
# check nonce
|
||||||
@ -642,9 +699,6 @@ class Core:
|
|||||||
with open(self.dataNonceFile, 'a') as nonceFile:
|
with open(self.dataNonceFile, 'a') as nonceFile:
|
||||||
nonceFile.write(dataNonce + '\n')
|
nonceFile.write(dataNonce + '\n')
|
||||||
|
|
||||||
if meta is None:
|
|
||||||
meta = dict()
|
|
||||||
|
|
||||||
if type(data) is bytes:
|
if type(data) is bytes:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
data = str(data)
|
data = str(data)
|
||||||
@ -722,7 +776,7 @@ class Core:
|
|||||||
if type(expire) is not type(None):
|
if type(expire) is not type(None):
|
||||||
assert len(str(int(expire))) < 14
|
assert len(str(int(expire))) < 14
|
||||||
metadata['expire'] = expire
|
metadata['expire'] = expire
|
||||||
|
|
||||||
# send block data (and metadata) to POW module to get tokenized block data
|
# send block data (and metadata) to POW module to get tokenized block data
|
||||||
proof = onionrproofs.POW(metadata, data)
|
proof = onionrproofs.POW(metadata, data)
|
||||||
payload = proof.waitForResult()
|
payload = proof.waitForResult()
|
||||||
|
@ -73,6 +73,7 @@ LEVEL_INFO = 2
|
|||||||
LEVEL_WARN = 3
|
LEVEL_WARN = 3
|
||||||
LEVEL_ERROR = 4
|
LEVEL_ERROR = 4
|
||||||
LEVEL_FATAL = 5
|
LEVEL_FATAL = 5
|
||||||
|
LEVEL_IMPORTANT = 6
|
||||||
|
|
||||||
_type = OUTPUT_TO_CONSOLE | USE_ANSI # the default settings for logging
|
_type = OUTPUT_TO_CONSOLE | USE_ANSI # the default settings for logging
|
||||||
_level = LEVEL_DEBUG # the lowest level to log
|
_level = LEVEL_DEBUG # the lowest level to log
|
||||||
@ -123,18 +124,18 @@ def get_file():
|
|||||||
|
|
||||||
return _outputfile
|
return _outputfile
|
||||||
|
|
||||||
def raw(data, fd = sys.stdout):
|
def raw(data, fd = sys.stdout, sensitive = False):
|
||||||
'''
|
'''
|
||||||
Outputs raw data to console without formatting
|
Outputs raw data to console without formatting
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if get_settings() & OUTPUT_TO_CONSOLE:
|
if get_settings() & OUTPUT_TO_CONSOLE:
|
||||||
ts = fd.write('%s\n' % data)
|
ts = fd.write('%s\n' % data)
|
||||||
if get_settings() & OUTPUT_TO_FILE:
|
if get_settings() & OUTPUT_TO_FILE and not sensitive:
|
||||||
with open(_outputfile, "a+") as f:
|
with open(_outputfile, "a+") as f:
|
||||||
f.write(colors.filter(data) + '\n')
|
f.write(colors.filter(data) + '\n')
|
||||||
|
|
||||||
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True):
|
def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True, sensitive = False):
|
||||||
'''
|
'''
|
||||||
Logs the data
|
Logs the data
|
||||||
prefix : The prefix to the output
|
prefix : The prefix to the output
|
||||||
@ -149,7 +150,7 @@ def log(prefix, data, color = '', timestamp=True, fd = sys.stdout, prompt = True
|
|||||||
if not get_settings() & USE_ANSI:
|
if not get_settings() & USE_ANSI:
|
||||||
output = colors.filter(output)
|
output = colors.filter(output)
|
||||||
|
|
||||||
raw(output, fd = fd)
|
raw(output, fd = fd, sensitive = sensitive)
|
||||||
|
|
||||||
def readline(message = ''):
|
def readline(message = ''):
|
||||||
'''
|
'''
|
||||||
@ -201,37 +202,37 @@ def confirm(default = 'y', message = 'Are you sure %s? '):
|
|||||||
return default == 'y'
|
return default == 'y'
|
||||||
|
|
||||||
# debug: when there is info that could be useful for debugging purposes only
|
# debug: when there is info that could be useful for debugging purposes only
|
||||||
def debug(data, error = None, timestamp = True, prompt = True):
|
def debug(data, error = None, timestamp = True, prompt = True, sensitive = False, level = LEVEL_DEBUG):
|
||||||
if get_level() <= LEVEL_DEBUG:
|
if get_level() <= level:
|
||||||
log('/', data, timestamp=timestamp, prompt = prompt)
|
log('/', data, timestamp = timestamp, prompt = prompt, sensitive = sensitive)
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error())
|
||||||
|
|
||||||
# info: when there is something to notify the user of, such as the success of a process
|
# info: when there is something to notify the user of, such as the success of a process
|
||||||
def info(data, timestamp = False, prompt = True):
|
def info(data, timestamp = False, prompt = True, sensitive = False, level = LEVEL_INFO):
|
||||||
if get_level() <= LEVEL_INFO:
|
if get_level() <= level:
|
||||||
log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt)
|
log('+', data, colors.fg.green, timestamp = timestamp, prompt = prompt, sensitive = sensitive)
|
||||||
|
|
||||||
# warn: when there is a potential for something bad to happen
|
# warn: when there is a potential for something bad to happen
|
||||||
def warn(data, error = None, timestamp = True, prompt = True):
|
def warn(data, error = None, timestamp = True, prompt = True, sensitive = False, level = LEVEL_WARN):
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error())
|
||||||
if get_level() <= LEVEL_WARN:
|
if get_level() <= level:
|
||||||
log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt)
|
log('!', data, colors.fg.orange, timestamp = timestamp, prompt = prompt, sensitive = sensitive)
|
||||||
|
|
||||||
# error: when only one function, module, or process of the program encountered a problem and must stop
|
# error: when only one function, module, or process of the program encountered a problem and must stop
|
||||||
def error(data, error = None, timestamp = True, prompt = True):
|
def error(data, error = None, timestamp = True, prompt = True, sensitive = False, level = LEVEL_ERROR):
|
||||||
if get_level() <= LEVEL_ERROR:
|
if get_level() <= level:
|
||||||
log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt)
|
log('-', data, colors.fg.red, timestamp = timestamp, fd = sys.stderr, prompt = prompt, sensitive = sensitive)
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error())
|
||||||
|
|
||||||
# fatal: when the something so bad has happened that the program must stop
|
# fatal: when the something so bad has happened that the program must stop
|
||||||
def fatal(data, error = None, timestamp=True, prompt = True):
|
def fatal(data, error = None, timestamp=True, prompt = True, sensitive = False, level = LEVEL_FATAL):
|
||||||
if not error is None:
|
if not error is None:
|
||||||
debug('Error: ' + str(error) + parse_error())
|
debug('Error: ' + str(error) + parse_error(), sensitive = sensitive)
|
||||||
if get_level() <= LEVEL_FATAL:
|
if get_level() <= level:
|
||||||
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp=timestamp, fd = sys.stderr, prompt = prompt)
|
log('#', data, colors.bg.red + colors.fg.green + colors.bold, timestamp = timestamp, fd = sys.stderr, prompt = prompt, sensitive = sensitive)
|
||||||
|
|
||||||
# returns a formatted error message
|
# returns a formatted error message
|
||||||
def parse_error():
|
def parse_error():
|
||||||
|
@ -65,7 +65,7 @@ class NetController:
|
|||||||
Generate a torrc file for our tor instance
|
Generate a torrc file for our tor instance
|
||||||
'''
|
'''
|
||||||
hsVer = '# v2 onions'
|
hsVer = '# v2 onions'
|
||||||
if config.get('tor.v3onions'):
|
if config.get('tor.v3_onions'):
|
||||||
hsVer = 'HiddenServiceVersion 3'
|
hsVer = 'HiddenServiceVersion 3'
|
||||||
logger.info('Using v3 onions :)')
|
logger.info('Using v3 onions :)')
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ HashedControlPassword ''' + str(password) + '''
|
|||||||
logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running?')
|
logger.fatal('Failed to start Tor. Maybe a stray instance of Tor used by Onionr is still running?')
|
||||||
return False
|
return False
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.fatal("Got keyboard interrupt.")
|
logger.fatal('Got keyboard interrupt.', timestamp = false, level = logger.LEVEL_IMPORTANT)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug('Finished starting Tor.', timestamp=True)
|
logger.debug('Finished starting Tor.', timestamp=True)
|
||||||
|
238
onionr/onionr.py
238
onionr/onionr.py
@ -40,7 +40,7 @@ except ImportError:
|
|||||||
raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)")
|
raise Exception("You need the PySocks module (for use with socks5 proxy to use Tor)")
|
||||||
|
|
||||||
ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech'
|
ONIONR_TAGLINE = 'Anonymous P2P Platform - GPLv3 - https://Onionr.VoidNet.Tech'
|
||||||
ONIONR_VERSION = '0.3.1' # for debugging and stuff
|
ONIONR_VERSION = '0.3.2' # for debugging and stuff
|
||||||
ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION)
|
ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION)
|
||||||
API_VERSION = '5' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes know how to communicate without learning too much information about you.
|
API_VERSION = '5' # increments of 1; only change when something fundemental about how the API works changes. This way other nodes know how to communicate without learning too much information about you.
|
||||||
|
|
||||||
@ -64,37 +64,7 @@ class Onionr:
|
|||||||
self.dataDir = 'data/'
|
self.dataDir = 'data/'
|
||||||
|
|
||||||
# Load global configuration data
|
# Load global configuration data
|
||||||
|
data_exists = Onionr.setupConfig(self.dataDir, self = self)
|
||||||
data_exists = os.path.exists(self.dataDir)
|
|
||||||
|
|
||||||
if not data_exists:
|
|
||||||
os.mkdir(self.dataDir)
|
|
||||||
|
|
||||||
if os.path.exists('static-data/default_config.json'):
|
|
||||||
config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
|
|
||||||
else:
|
|
||||||
# the default config file doesn't exist, try hardcoded config
|
|
||||||
config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': self.dataDir + 'output.log'}, 'console': {'output': True, 'color': True}}})
|
|
||||||
if not data_exists:
|
|
||||||
config.save()
|
|
||||||
config.reload() # this will read the configuration file into memory
|
|
||||||
|
|
||||||
settings = 0b000
|
|
||||||
if config.get('log.console.color', True):
|
|
||||||
settings = settings | logger.USE_ANSI
|
|
||||||
if config.get('log.console.output', True):
|
|
||||||
settings = settings | logger.OUTPUT_TO_CONSOLE
|
|
||||||
if config.get('log.file.output', True):
|
|
||||||
settings = settings | logger.OUTPUT_TO_FILE
|
|
||||||
logger.set_file(config.get('log.file.path', '/tmp/onionr.log').replace('data/', self.dataDir))
|
|
||||||
logger.set_settings(settings)
|
|
||||||
|
|
||||||
if str(config.get('general.dev_mode', True)).lower() == 'true':
|
|
||||||
self._developmentMode = True
|
|
||||||
logger.set_level(logger.LEVEL_DEBUG)
|
|
||||||
else:
|
|
||||||
self._developmentMode = False
|
|
||||||
logger.set_level(logger.LEVEL_INFO)
|
|
||||||
|
|
||||||
self.onionrCore = core.Core()
|
self.onionrCore = core.Core()
|
||||||
self.onionrUtils = onionrutils.OnionrUtils(self.onionrCore)
|
self.onionrUtils = onionrutils.OnionrUtils(self.onionrCore)
|
||||||
@ -144,7 +114,7 @@ class Onionr:
|
|||||||
config.set('client.participate', True, savefile=True)
|
config.set('client.participate', True, savefile=True)
|
||||||
if type(config.get('client.api_version')) is type(None):
|
if type(config.get('client.api_version')) is type(None):
|
||||||
config.set('client.api_version', API_VERSION, savefile=True)
|
config.set('client.api_version', API_VERSION, savefile=True)
|
||||||
|
|
||||||
|
|
||||||
self.cmds = {
|
self.cmds = {
|
||||||
'': self.showHelpSuggestion,
|
'': self.showHelpSuggestion,
|
||||||
@ -156,6 +126,16 @@ class Onionr:
|
|||||||
'status': self.showStats,
|
'status': self.showStats,
|
||||||
'statistics': self.showStats,
|
'statistics': self.showStats,
|
||||||
'stats': self.showStats,
|
'stats': self.showStats,
|
||||||
|
'details' : self.showDetails,
|
||||||
|
'detail' : self.showDetails,
|
||||||
|
'show-details' : self.showDetails,
|
||||||
|
'show-detail' : self.showDetails,
|
||||||
|
'showdetails' : self.showDetails,
|
||||||
|
'showdetail' : self.showDetails,
|
||||||
|
'get-details' : self.showDetails,
|
||||||
|
'get-detail' : self.showDetails,
|
||||||
|
'getdetails' : self.showDetails,
|
||||||
|
'getdetail' : self.showDetails,
|
||||||
|
|
||||||
'enable-plugin': self.enablePlugin,
|
'enable-plugin': self.enablePlugin,
|
||||||
'enplugin': self.enablePlugin,
|
'enplugin': self.enablePlugin,
|
||||||
@ -204,6 +184,7 @@ class Onionr:
|
|||||||
|
|
||||||
'ui' : self.openUI,
|
'ui' : self.openUI,
|
||||||
'gui' : self.openUI,
|
'gui' : self.openUI,
|
||||||
|
'chat': self.startChat,
|
||||||
|
|
||||||
'getpassword': self.printWebPassword,
|
'getpassword': self.printWebPassword,
|
||||||
'get-password': self.printWebPassword,
|
'get-password': self.printWebPassword,
|
||||||
@ -223,14 +204,18 @@ class Onionr:
|
|||||||
'help': 'Displays this Onionr help menu',
|
'help': 'Displays this Onionr help menu',
|
||||||
'version': 'Displays the Onionr version',
|
'version': 'Displays the Onionr version',
|
||||||
'config': 'Configures something and adds it to the file',
|
'config': 'Configures something and adds it to the file',
|
||||||
|
|
||||||
'start': 'Starts the Onionr daemon',
|
'start': 'Starts the Onionr daemon',
|
||||||
'stop': 'Stops the Onionr daemon',
|
'stop': 'Stops the Onionr daemon',
|
||||||
|
|
||||||
'stats': 'Displays node statistics',
|
'stats': 'Displays node statistics',
|
||||||
'get-password': 'Displays the web password',
|
'details': 'Displays the web password, public key, and human readable public key',
|
||||||
|
|
||||||
'enable-plugin': 'Enables and starts a plugin',
|
'enable-plugin': 'Enables and starts a plugin',
|
||||||
'disable-plugin': 'Disables and stops a plugin',
|
'disable-plugin': 'Disables and stops a plugin',
|
||||||
'reload-plugin': 'Reloads a plugin',
|
'reload-plugin': 'Reloads a plugin',
|
||||||
'create-plugin': 'Creates directory structure for a plugin',
|
'create-plugin': 'Creates directory structure for a plugin',
|
||||||
|
|
||||||
'add-peer': 'Adds a peer to database',
|
'add-peer': 'Adds a peer to database',
|
||||||
'list-peers': 'Displays a list of peers',
|
'list-peers': 'Displays a list of peers',
|
||||||
'add-file': 'Create an Onionr block from a file',
|
'add-file': 'Create an Onionr block from a file',
|
||||||
@ -261,6 +246,17 @@ class Onionr:
|
|||||||
THIS SECTION HANDLES THE COMMANDS
|
THIS SECTION HANDLES THE COMMANDS
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def showDetails(self):
|
||||||
|
details = {
|
||||||
|
'Node Address' : self.get_hostname(),
|
||||||
|
'Web Password' : self.getWebPassword(),
|
||||||
|
'Public Key' : self.onionrCore._crypto.pubKey,
|
||||||
|
'Human-readable Public Key' : self.onionrCore._utils.getHumanReadableID()
|
||||||
|
}
|
||||||
|
|
||||||
|
for detail in details:
|
||||||
|
logger.info('%s%s: \n%s%s\n' % (logger.colors.fg.lightgreen, detail, logger.colors.fg.green, details[detail]), sensitive = True)
|
||||||
|
|
||||||
def startChat(self):
|
def startChat(self):
|
||||||
try:
|
try:
|
||||||
data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'})
|
data = json.dumps({'peer': sys.argv[2], 'reason': 'chat'})
|
||||||
@ -271,7 +267,49 @@ class Onionr:
|
|||||||
|
|
||||||
def getCommands(self):
|
def getCommands(self):
|
||||||
return self.cmds
|
return self.cmds
|
||||||
|
|
||||||
|
def friendCmd(self):
|
||||||
|
'''List, add, or remove friend(s)
|
||||||
|
Changes their peer DB entry.
|
||||||
|
'''
|
||||||
|
friend = ''
|
||||||
|
try:
|
||||||
|
# Get the friend command
|
||||||
|
action = sys.argv[2]
|
||||||
|
except IndexError:
|
||||||
|
logger.info('Syntax: friend add/remove/list [address]')
|
||||||
|
else:
|
||||||
|
action = action.lower()
|
||||||
|
if action == 'list':
|
||||||
|
# List out peers marked as our friend
|
||||||
|
for friend in self.onionrCore.listPeers(randomOrder=False, trust=1):
|
||||||
|
if friend == self.onionrCore._crypto.pubKey: # do not list our key
|
||||||
|
continue
|
||||||
|
friendProfile = onionrusers.OnionrUser(self.onionrCore, friend)
|
||||||
|
logger.info(friend + ' - ' + friendProfile.getName())
|
||||||
|
elif action in ('add', 'remove'):
|
||||||
|
try:
|
||||||
|
friend = sys.argv[3]
|
||||||
|
if not self.onionrUtils.validatePubKey(friend):
|
||||||
|
raise onionrexceptions.InvalidPubkey('Public key is invalid')
|
||||||
|
if friend not in self.onionrCore.listPeers():
|
||||||
|
raise onionrexceptions.KeyNotKnown
|
||||||
|
friend = onionrusers.OnionrUser(self.onionrCore, friend)
|
||||||
|
except IndexError:
|
||||||
|
logger.error('Friend ID is required.')
|
||||||
|
except onionrexceptions.KeyNotKnown:
|
||||||
|
logger.error('That peer is not in our database')
|
||||||
|
else:
|
||||||
|
if action == 'add':
|
||||||
|
friend.setTrust(1)
|
||||||
|
logger.info('Added %s as friend.' % (friend.publicKey,))
|
||||||
|
else:
|
||||||
|
friend.setTrust(0)
|
||||||
|
logger.info('Removed %s as friend.' % (friend.publicKey,))
|
||||||
|
else:
|
||||||
|
logger.info('Syntax: friend add/remove/list [address]')
|
||||||
|
|
||||||
|
|
||||||
def friendCmd(self):
|
def friendCmd(self):
|
||||||
'''List, add, or remove friend(s)
|
'''List, add, or remove friend(s)
|
||||||
Changes their peer DB entry.
|
Changes their peer DB entry.
|
||||||
@ -346,7 +384,7 @@ class Onionr:
|
|||||||
return config.get('client.hmac')
|
return config.get('client.hmac')
|
||||||
|
|
||||||
def printWebPassword(self):
|
def printWebPassword(self):
|
||||||
print(self.getWebPassword())
|
logger.info(self.getWebPassword(), sensitive = True)
|
||||||
|
|
||||||
def getHelp(self):
|
def getHelp(self):
|
||||||
return self.cmdhelp
|
return self.cmdhelp
|
||||||
@ -399,16 +437,16 @@ class Onionr:
|
|||||||
THIS SECTION DEFINES THE COMMANDS
|
THIS SECTION DEFINES THE COMMANDS
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def version(self, verbosity=5):
|
def version(self, verbosity = 5, function = logger.info):
|
||||||
'''
|
'''
|
||||||
Displays the Onionr version
|
Displays the Onionr version
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logger.info('Onionr %s (%s) - API v%s' % (ONIONR_VERSION, platform.machine(), API_VERSION))
|
function('Onionr v%s (%s) (API v%s)' % (ONIONR_VERSION, platform.machine(), API_VERSION))
|
||||||
if verbosity >= 1:
|
if verbosity >= 1:
|
||||||
logger.info(ONIONR_TAGLINE)
|
function(ONIONR_TAGLINE)
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
logger.info('Running on %s %s' % (platform.platform(), platform.release()))
|
function('Running on %s %s' % (platform.platform(), platform.release()))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -427,9 +465,7 @@ class Onionr:
|
|||||||
Displays a list of keys (used to be called peers) (?)
|
Displays a list of keys (used to be called peers) (?)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
logger.info('Public keys in database:\n')
|
logger.info('%sPublic keys in database: \n%s%s' % (logger.colors.fg.lightgreen, logger.colors.fg.green, '\n'.join(self.onionrCore.listPeers())))
|
||||||
for i in self.onionrCore.listPeers():
|
|
||||||
logger.info(i)
|
|
||||||
|
|
||||||
def addPeer(self):
|
def addPeer(self):
|
||||||
'''
|
'''
|
||||||
@ -618,37 +654,56 @@ class Onionr:
|
|||||||
'''
|
'''
|
||||||
Starts the Onionr communication daemon
|
Starts the Onionr communication daemon
|
||||||
'''
|
'''
|
||||||
|
|
||||||
communicatorDaemon = './communicator2.py'
|
communicatorDaemon = './communicator2.py'
|
||||||
|
|
||||||
apiThread = Thread(target=api.API, args=(self.debug,API_VERSION))
|
# remove runcheck if it exists
|
||||||
|
if os.path.isfile('data/.runcheck'):
|
||||||
|
logger.debug('Runcheck file found on daemon start, deleting in advance.')
|
||||||
|
os.remove('data/.runcheck')
|
||||||
|
|
||||||
|
apiThread = Thread(target = api.API, args = (self.debug, API_VERSION))
|
||||||
apiThread.start()
|
apiThread.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info('Got keyboard interrupt')
|
logger.debug('Got keyboard interrupt, shutting down...')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.onionrUtils.localCommand('shutdown')
|
self.onionrUtils.localCommand('shutdown')
|
||||||
else:
|
else:
|
||||||
if apiThread.isAlive():
|
if apiThread.isAlive():
|
||||||
|
# configure logger and stuff
|
||||||
|
Onionr.setupConfig('data/', self = self)
|
||||||
|
|
||||||
if self._developmentMode:
|
if self._developmentMode:
|
||||||
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)', timestamp = False)
|
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)', timestamp = False)
|
||||||
net = NetController(config.get('client.port', 59496))
|
net = NetController(config.get('client.port', 59496))
|
||||||
logger.info('Tor is starting...')
|
logger.debug('Tor is starting...')
|
||||||
if not net.startTor():
|
if not net.startTor():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
logger.info('Started .onion service: ' + logger.colors.underline + net.myID)
|
logger.debug('Started .onion service: %s' % (logger.colors.underline + net.myID))
|
||||||
logger.info('Our Public key: ' + self.onionrCore._crypto.pubKey)
|
logger.debug('Using public key: %s' % (logger.colors.underline + self.onionrCore._crypto.pubKey))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
#TODO make runable on windows
|
|
||||||
communicatorProc = subprocess.Popen([communicatorDaemon, "run", str(net.socksPort)])
|
# TODO: make runable on windows
|
||||||
# Print nice header thing :)
|
communicatorProc = subprocess.Popen([communicatorDaemon, 'run', str(net.socksPort)])
|
||||||
|
|
||||||
|
# print nice header thing :)
|
||||||
if config.get('general.display_header', True):
|
if config.get('general.display_header', True):
|
||||||
self.header()
|
self.header()
|
||||||
logger.debug('Started communicator')
|
|
||||||
|
# print out debug info
|
||||||
|
self.version(verbosity = 5, function = logger.debug)
|
||||||
|
logger.debug('Python version %s' % platform.python_version())
|
||||||
|
|
||||||
|
logger.debug('Started communicator.')
|
||||||
|
|
||||||
events.event('daemon_start', onionr = self)
|
events.event('daemon_start', onionr = self)
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
# Break if communicator process ends, so we don't have left over processes
|
# Break if communicator process ends, so we don't have left over processes
|
||||||
if communicatorProc.poll() is not None:
|
if communicatorProc.poll() is not None:
|
||||||
break
|
break
|
||||||
@ -689,9 +744,6 @@ class Onionr:
|
|||||||
messages = {
|
messages = {
|
||||||
# info about local client
|
# info about local client
|
||||||
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'),
|
'Onionr Daemon Status' : ((logger.colors.fg.green + 'Online') if self.onionrUtils.isCommunicatorRunning(timeout = 9) else logger.colors.fg.red + 'Offline'),
|
||||||
'Public Key' : self.onionrCore._crypto.pubKey,
|
|
||||||
'Human readable public key' : self.onionrCore._utils.getHumanReadableID(),
|
|
||||||
'Node Address' : self.get_hostname(),
|
|
||||||
|
|
||||||
# file and folder size stats
|
# file and folder size stats
|
||||||
'div1' : True, # this creates a solid line across the screen, a div
|
'div1' : True, # this creates a solid line across the screen, a div
|
||||||
@ -798,7 +850,8 @@ class Onionr:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
logger.error("Syntax %s %s" % (sys.argv[0], '/path/to/filename <blockhash>'))
|
logger.error("Syntax %s %s" % (sys.argv[0], '/path/to/filename <blockhash>'))
|
||||||
else:
|
else:
|
||||||
print(fileName)
|
logger.info(fileName)
|
||||||
|
|
||||||
contents = None
|
contents = None
|
||||||
if os.path.exists(fileName):
|
if os.path.exists(fileName):
|
||||||
logger.error("File already exists")
|
logger.error("File already exists")
|
||||||
@ -806,6 +859,7 @@ class Onionr:
|
|||||||
if not self.onionrUtils.validateHash(bHash):
|
if not self.onionrUtils.validateHash(bHash):
|
||||||
logger.error('Block hash is invalid')
|
logger.error('Block hash is invalid')
|
||||||
return
|
return
|
||||||
|
|
||||||
Block.mergeChain(bHash, fileName)
|
Block.mergeChain(bHash, fileName)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -830,17 +884,83 @@ class Onionr:
|
|||||||
else:
|
else:
|
||||||
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False)
|
logger.error('%s add-file <filename>' % sys.argv[0], timestamp = False)
|
||||||
|
|
||||||
|
def setupConfig(dataDir, self = None):
|
||||||
|
data_exists = os.path.exists(dataDir)
|
||||||
|
|
||||||
|
if not data_exists:
|
||||||
|
os.mkdir(dataDir)
|
||||||
|
|
||||||
|
if os.path.exists('static-data/default_config.json'):
|
||||||
|
config.set_config(json.loads(open('static-data/default_config.json').read())) # this is the default config, it will be overwritten if a config file already exists. Else, it saves it
|
||||||
|
else:
|
||||||
|
# the default config file doesn't exist, try hardcoded config
|
||||||
|
logger.warn('Default configuration file does not exist, switching to hardcoded fallback configuration!')
|
||||||
|
config.set_config({'dev_mode': True, 'log': {'file': {'output': True, 'path': dataDir + 'output.log'}, 'console': {'output': True, 'color': True}}})
|
||||||
|
if not data_exists:
|
||||||
|
config.save()
|
||||||
|
config.reload() # this will read the configuration file into memory
|
||||||
|
|
||||||
|
settings = 0b000
|
||||||
|
if config.get('log.console.color', True):
|
||||||
|
settings = settings | logger.USE_ANSI
|
||||||
|
if config.get('log.console.output', True):
|
||||||
|
settings = settings | logger.OUTPUT_TO_CONSOLE
|
||||||
|
if config.get('log.file.output', True):
|
||||||
|
settings = settings | logger.OUTPUT_TO_FILE
|
||||||
|
logger.set_file(config.get('log.file.path', '/tmp/onionr.log').replace('data/', dataDir))
|
||||||
|
logger.set_settings(settings)
|
||||||
|
|
||||||
|
if not self is None:
|
||||||
|
if str(config.get('general.dev_mode', True)).lower() == 'true':
|
||||||
|
self._developmentMode = True
|
||||||
|
logger.set_level(logger.LEVEL_DEBUG)
|
||||||
|
else:
|
||||||
|
self._developmentMode = False
|
||||||
|
logger.set_level(logger.LEVEL_INFO)
|
||||||
|
|
||||||
|
verbosity = str(config.get('log.verbosity', 'default')).lower().strip()
|
||||||
|
if not verbosity in ['default', 'null', 'none', 'nil']:
|
||||||
|
map = {
|
||||||
|
str(logger.LEVEL_DEBUG) : logger.LEVEL_DEBUG,
|
||||||
|
'verbose' : logger.LEVEL_DEBUG,
|
||||||
|
'debug' : logger.LEVEL_DEBUG,
|
||||||
|
str(logger.LEVEL_INFO) : logger.LEVEL_INFO,
|
||||||
|
'info' : logger.LEVEL_INFO,
|
||||||
|
'information' : logger.LEVEL_INFO,
|
||||||
|
str(logger.LEVEL_WARN) : logger.LEVEL_WARN,
|
||||||
|
'warn' : logger.LEVEL_WARN,
|
||||||
|
'warning' : logger.LEVEL_WARN,
|
||||||
|
'warnings' : logger.LEVEL_WARN,
|
||||||
|
str(logger.LEVEL_ERROR) : logger.LEVEL_ERROR,
|
||||||
|
'err' : logger.LEVEL_ERROR,
|
||||||
|
'error' : logger.LEVEL_ERROR,
|
||||||
|
'errors' : logger.LEVEL_ERROR,
|
||||||
|
str(logger.LEVEL_FATAL) : logger.LEVEL_FATAL,
|
||||||
|
'fatal' : logger.LEVEL_FATAL,
|
||||||
|
str(logger.LEVEL_IMPORTANT) : logger.LEVEL_IMPORTANT,
|
||||||
|
'silent' : logger.LEVEL_IMPORTANT,
|
||||||
|
'quiet' : logger.LEVEL_IMPORTANT,
|
||||||
|
'important' : logger.LEVEL_IMPORTANT
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbosity in map:
|
||||||
|
logger.set_level(map[verbosity])
|
||||||
|
else:
|
||||||
|
logger.warn('Verbosity level %s is not valid, using default verbosity.' % verbosity)
|
||||||
|
|
||||||
|
return data_exists
|
||||||
|
|
||||||
def openUI(self):
|
def openUI(self):
|
||||||
url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken())
|
url = 'http://127.0.0.1:%s/ui/index.html?timingToken=%s' % (config.get('client.port', 59496), self.onionrUtils.getTimeBypassToken())
|
||||||
|
|
||||||
print('Opening %s ...' % url)
|
logger.info('Opening %s ...' % url)
|
||||||
webbrowser.open(url, new = 1, autoraise = True)
|
webbrowser.open(url, new = 1, autoraise = True)
|
||||||
|
|
||||||
def header(self, message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'):
|
def header(self, message = logger.colors.fg.pink + logger.colors.bold + 'Onionr' + logger.colors.reset + logger.colors.fg.pink + ' has started.'):
|
||||||
if os.path.exists('static-data/header.txt'):
|
if os.path.exists('static-data/header.txt') and logger.get_level() <= logger.LEVEL_INFO:
|
||||||
with open('static-data/header.txt', 'rb') as file:
|
with open('static-data/header.txt', 'rb') as file:
|
||||||
# only to stdout, not file or log or anything
|
# only to stdout, not file or log or anything
|
||||||
sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold).replace('V', ONIONR_VERSION))
|
sys.stderr.write(file.read().decode().replace('P', logger.colors.fg.pink).replace('W', logger.colors.reset + logger.colors.bold).replace('G', logger.colors.fg.green).replace('\n', logger.colors.reset + '\n').replace('B', logger.colors.bold).replace('A', '%s' % API_VERSION).replace('V', ONIONR_VERSION))
|
||||||
logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n')
|
logger.info(logger.colors.fg.lightgreen + '-> ' + str(message) + logger.colors.reset + logger.colors.fg.lightgreen + ' <-\n')
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -22,34 +22,37 @@ class OnionrBlackList:
|
|||||||
def __init__(self, coreInst):
|
def __init__(self, coreInst):
|
||||||
self.blacklistDB = coreInst.dataDir + 'blacklist.db'
|
self.blacklistDB = coreInst.dataDir + 'blacklist.db'
|
||||||
self._core = coreInst
|
self._core = coreInst
|
||||||
|
|
||||||
if not os.path.exists(self.blacklistDB):
|
if not os.path.exists(self.blacklistDB):
|
||||||
self.generateDB()
|
self.generateDB()
|
||||||
return
|
return
|
||||||
|
|
||||||
def inBlacklist(self, data):
|
def inBlacklist(self, data):
|
||||||
hashed = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(data))
|
hashed = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(data))
|
||||||
retData = False
|
retData = False
|
||||||
|
|
||||||
if not hashed.isalnum():
|
if not hashed.isalnum():
|
||||||
raise Exception("Hashed data is not alpha numeric")
|
raise Exception("Hashed data is not alpha numeric")
|
||||||
if len(hashed) > 64:
|
if len(hashed) > 64:
|
||||||
raise Exception("Hashed data is too large")
|
raise Exception("Hashed data is too large")
|
||||||
for i in self._dbExecute("select * from blacklist where hash='%s'" % (hashed,)):
|
|
||||||
|
for i in self._dbExecute("SELECT * FROM blacklist WHERE hash = ?", (hashed,)):
|
||||||
retData = True # this only executes if an entry is present by that hash
|
retData = True # this only executes if an entry is present by that hash
|
||||||
break
|
break
|
||||||
|
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def _dbExecute(self, toExec):
|
def _dbExecute(self, toExec, params = ()):
|
||||||
conn = sqlite3.connect(self.blacklistDB)
|
conn = sqlite3.connect(self.blacklistDB)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
retData = c.execute(toExec)
|
retData = c.execute(toExec, params)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def deleteBeforeDate(self, date):
|
def deleteBeforeDate(self, date):
|
||||||
# TODO, delete blacklist entries before date
|
# TODO, delete blacklist entries before date
|
||||||
return
|
return
|
||||||
|
|
||||||
def deleteExpired(self, dataType=0):
|
def deleteExpired(self, dataType=0):
|
||||||
'''Delete expired entries'''
|
'''Delete expired entries'''
|
||||||
deleteList = []
|
deleteList = []
|
||||||
@ -60,13 +63,13 @@ class OnionrBlackList:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise TypeError("dataType must be int")
|
raise TypeError("dataType must be int")
|
||||||
|
|
||||||
for i in self._dbExecute('select * from blacklist where dataType=%s' % (dataType,)):
|
for i in self._dbExecute('SELECT * FROM blacklist WHERE dataType = ?', (dataType,)):
|
||||||
if i[1] == dataType:
|
if i[1] == dataType:
|
||||||
if (curTime - i[2]) >= i[3]:
|
if (curTime - i[2]) >= i[3]:
|
||||||
deleteList.append(i[0])
|
deleteList.append(i[0])
|
||||||
|
|
||||||
for thing in deleteList:
|
for thing in deleteList:
|
||||||
self._dbExecute("delete from blacklist where hash='%s'" % (thing,))
|
self._dbExecute("DELETE FROM blacklist WHERE hash = ?", (thing,))
|
||||||
|
|
||||||
def generateDB(self):
|
def generateDB(self):
|
||||||
self._dbExecute('''CREATE TABLE blacklist(
|
self._dbExecute('''CREATE TABLE blacklist(
|
||||||
@ -77,12 +80,12 @@ class OnionrBlackList:
|
|||||||
);
|
);
|
||||||
''')
|
''')
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearDB(self):
|
def clearDB(self):
|
||||||
self._dbExecute('''delete from blacklist;);''')
|
self._dbExecute('''DELETE FROM blacklist;);''')
|
||||||
|
|
||||||
def getList(self):
|
def getList(self):
|
||||||
data = self._dbExecute('select * from blacklist')
|
data = self._dbExecute('SELECT * FROM blacklist')
|
||||||
myList = []
|
myList = []
|
||||||
for i in data:
|
for i in data:
|
||||||
myList.append(i[0])
|
myList.append(i[0])
|
||||||
@ -113,4 +116,4 @@ class OnionrBlackList:
|
|||||||
return
|
return
|
||||||
insert = (hashed,)
|
insert = (hashed,)
|
||||||
blacklistDate = self._core._utils.getEpoch()
|
blacklistDate = self._core._utils.getEpoch()
|
||||||
self._dbExecute("insert into blacklist (hash, dataType, blacklistDate, expire) VALUES('%s', %s, %s, %s);" % (hashed, dataType, blacklistDate, expire))
|
self._dbExecute("INSERT INTO blacklist (hash, dataType, blacklistDate, expire) VALUES(?, ?, ?, ?);", (str(hashed), dataType, blacklistDate, expire))
|
||||||
|
@ -73,6 +73,7 @@ class Block:
|
|||||||
'''
|
'''
|
||||||
Decrypt a block, loading decrypted data into their vars
|
Decrypt a block, loading decrypted data into their vars
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if self.decrypted:
|
if self.decrypted:
|
||||||
return True
|
return True
|
||||||
retData = False
|
retData = False
|
||||||
@ -114,6 +115,7 @@ class Block:
|
|||||||
'''
|
'''
|
||||||
Verify if a block's signature is signed by its claimed signer
|
Verify if a block's signature is signed by its claimed signer
|
||||||
'''
|
'''
|
||||||
|
|
||||||
core = self.getCore()
|
core = self.getCore()
|
||||||
|
|
||||||
if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
|
if core._crypto.edVerify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
|
||||||
@ -173,7 +175,7 @@ class Block:
|
|||||||
self.raw = str(blockdata)
|
self.raw = str(blockdata)
|
||||||
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
self.bheader = json.loads(self.getRaw()[:self.getRaw().index('\n')])
|
||||||
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
self.bcontent = self.getRaw()[self.getRaw().index('\n') + 1:]
|
||||||
if self.bheader['encryptType'] in ('asym', 'sym'):
|
if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
|
||||||
self.bmetadata = self.getHeader('meta', None)
|
self.bmetadata = self.getHeader('meta', None)
|
||||||
self.isEncrypted = True
|
self.isEncrypted = True
|
||||||
else:
|
else:
|
||||||
@ -199,7 +201,13 @@ class Block:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error('Failed to update block data.', error = e, timestamp = False)
|
logger.error('Failed to parse block %s.' % self.getHash(), error = e, timestamp = False)
|
||||||
|
|
||||||
|
# if block can't be parsed, it's a waste of precious space. Throw it away.
|
||||||
|
if not self.delete():
|
||||||
|
logger.error('Failed to delete invalid block %s.' % self.getHash(), error = e)
|
||||||
|
else:
|
||||||
|
logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)
|
||||||
|
|
||||||
self.valid = False
|
self.valid = False
|
||||||
return False
|
return False
|
||||||
@ -214,7 +222,7 @@ class Block:
|
|||||||
|
|
||||||
if self.exists():
|
if self.exists():
|
||||||
os.remove(self.getBlockFile())
|
os.remove(self.getBlockFile())
|
||||||
removeBlock(self.getHash())
|
self.getCore().removeBlock(self.getHash())
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -235,10 +243,10 @@ class Block:
|
|||||||
if (not self.getBlockFile() is None) and (recreate is True):
|
if (not self.getBlockFile() is None) and (recreate is True):
|
||||||
with open(self.getBlockFile(), 'wb') as blockFile:
|
with open(self.getBlockFile(), 'wb') as blockFile:
|
||||||
blockFile.write(self.getRaw().encode())
|
blockFile.write(self.getRaw().encode())
|
||||||
self.update()
|
|
||||||
else:
|
else:
|
||||||
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign, expire=self.getExpire())
|
self.hash = self.getCore().insertBlock(self.getContent(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
|
||||||
self.update()
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
return self.getHash()
|
return self.getHash()
|
||||||
else:
|
else:
|
||||||
@ -782,7 +790,7 @@ class Block:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# dump old cached blocks if the size exeeds the maximum
|
# dump old cached blocks if the size exeeds the maximum
|
||||||
if sys.getsizeof(Block.blockCacheOrder) >= config.get('allocations.blockCacheTotal', 50000000): # 50MB default cache size
|
if sys.getsizeof(Block.blockCacheOrder) >= config.get('allocations.block_cache_total', 50000000): # 50MB default cache size
|
||||||
del Block.blockCache[blockCacheOrder.pop(0)]
|
del Block.blockCache[blockCacheOrder.pop(0)]
|
||||||
|
|
||||||
# cache block content
|
# cache block content
|
||||||
|
@ -46,4 +46,5 @@ class OnionrChat:
|
|||||||
self.communicator.socketClient.sendData(peer, "lol")
|
self.communicator.socketClient.sendData(peer, "lol")
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
time.sleep(2)
|
|
||||||
|
time.sleep(2)
|
||||||
|
@ -17,8 +17,11 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
import onionrexceptions, onionrpeers, onionrproofs, base64, logger, onionrusers, sqlite3
|
|
||||||
|
import onionrexceptions, onionrpeers, onionrproofs, logger, onionrusers
|
||||||
|
import base64, sqlite3, os
|
||||||
from dependencies import secrets
|
from dependencies import secrets
|
||||||
|
|
||||||
class DaemonTools:
|
class DaemonTools:
|
||||||
def __init__(self, daemon):
|
def __init__(self, daemon):
|
||||||
self.daemon = daemon
|
self.daemon = daemon
|
||||||
@ -64,7 +67,7 @@ class DaemonTools:
|
|||||||
logger.warn('Network check failed, are you connected to the internet?')
|
logger.warn('Network check failed, are you connected to the internet?')
|
||||||
self.daemon.isOnline = False
|
self.daemon.isOnline = False
|
||||||
self.daemon.decrementThreadCount('netCheck')
|
self.daemon.decrementThreadCount('netCheck')
|
||||||
|
|
||||||
def cleanOldBlocks(self):
|
def cleanOldBlocks(self):
|
||||||
'''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
|
'''Delete old blocks if our disk allocation is full/near full, and also expired blocks'''
|
||||||
|
|
||||||
@ -73,22 +76,25 @@ class DaemonTools:
|
|||||||
self.daemon._core._blacklist.addToDB(oldest)
|
self.daemon._core._blacklist.addToDB(oldest)
|
||||||
self.daemon._core.removeBlock(oldest)
|
self.daemon._core.removeBlock(oldest)
|
||||||
logger.info('Deleted block: %s' % (oldest,))
|
logger.info('Deleted block: %s' % (oldest,))
|
||||||
|
|
||||||
# Delete expired blocks
|
# Delete expired blocks
|
||||||
for bHash in self.daemon._core.getExpiredBlocks():
|
for bHash in self.daemon._core.getExpiredBlocks():
|
||||||
self.daemon._core._blacklist.addToDB(bHash)
|
self.daemon._core._blacklist.addToDB(bHash)
|
||||||
self.daemon._core.removeBlock(bHash)
|
self.daemon._core.removeBlock(bHash)
|
||||||
|
|
||||||
self.daemon.decrementThreadCount('cleanOldBlocks')
|
self.daemon.decrementThreadCount('cleanOldBlocks')
|
||||||
|
|
||||||
def cleanKeys(self):
|
def cleanKeys(self):
|
||||||
'''Delete expired forward secrecy keys'''
|
'''Delete expired forward secrecy keys'''
|
||||||
conn = sqlite3.connect(self.daemon._core.peerDB, timeout=10)
|
conn = sqlite3.connect(self.daemon._core.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
time = self.daemon._core._utils.getEpoch()
|
time = self.daemon._core._utils.getEpoch()
|
||||||
deleteKeys = []
|
deleteKeys = []
|
||||||
for entry in c.execute("SELECT * FROM forwardKeys where expire <= ?", (time,)):
|
|
||||||
|
for entry in c.execute("SELECT * FROM forwardKeys WHERE expire <= ?", (time,)):
|
||||||
logger.info(entry[1])
|
logger.info(entry[1])
|
||||||
deleteKeys.append(entry[1])
|
deleteKeys.append(entry[1])
|
||||||
|
|
||||||
for key in deleteKeys:
|
for key in deleteKeys:
|
||||||
logger.info('Deleting forward key '+ key)
|
logger.info('Deleting forward key '+ key)
|
||||||
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
c.execute("DELETE from forwardKeys where forwardKey = ?", (key,))
|
||||||
@ -114,8 +120,9 @@ class DaemonTools:
|
|||||||
del self.daemon.cooldownPeer[peer]
|
del self.daemon.cooldownPeer[peer]
|
||||||
|
|
||||||
# Cool down a peer, if we have max connections alive for long enough
|
# Cool down a peer, if we have max connections alive for long enough
|
||||||
if onlinePeerAmount >= self.daemon._core.config.get('peers.maxConnect'):
|
if onlinePeerAmount >= self.daemon._core.config.get('peers.max_connect', 10):
|
||||||
finding = True
|
finding = True
|
||||||
|
|
||||||
while finding:
|
while finding:
|
||||||
try:
|
try:
|
||||||
toCool = min(tempConnectTimes, key=tempConnectTimes.get)
|
toCool = min(tempConnectTimes, key=tempConnectTimes.get)
|
||||||
@ -128,4 +135,32 @@ class DaemonTools:
|
|||||||
else:
|
else:
|
||||||
self.daemon.removeOnlinePeer(toCool)
|
self.daemon.removeOnlinePeer(toCool)
|
||||||
self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch()
|
self.daemon.cooldownPeer[toCool] = self.daemon._core._utils.getEpoch()
|
||||||
self.daemon.decrementThreadCount('cooldownPeer')
|
|
||||||
|
self.daemon.decrementThreadCount('cooldownPeer')
|
||||||
|
|
||||||
|
def runCheck(self):
|
||||||
|
if os.path.isfile('data/.runcheck'):
|
||||||
|
os.remove('data/.runcheck')
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def humanReadableTime(self, seconds):
|
||||||
|
build = ''
|
||||||
|
|
||||||
|
units = {
|
||||||
|
'year' : 31557600,
|
||||||
|
'month' : (31557600 / 12),
|
||||||
|
'day' : 86400,
|
||||||
|
'hour' : 3600,
|
||||||
|
'minute' : 60,
|
||||||
|
'second' : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for unit in units:
|
||||||
|
amnt_unit = int(seconds / units[unit])
|
||||||
|
if amnt_unit >= 1:
|
||||||
|
seconds -= amnt_unit * units[unit]
|
||||||
|
build += '%s %s' % (amnt_unit, unit) + ('s' if amnt_unit != 1 else '') + ' '
|
||||||
|
|
||||||
|
return build.strip()
|
||||||
|
@ -44,7 +44,7 @@ class PeerProfiles:
|
|||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
self.success = 0
|
self.success = 0
|
||||||
self.score = self.success
|
self.score = self.success
|
||||||
|
|
||||||
def saveScore(self):
|
def saveScore(self):
|
||||||
'''Save the node's score to the database'''
|
'''Save the node's score to the database'''
|
||||||
self.coreInst.setAddressInfo(self.address, 'success', self.score)
|
self.coreInst.setAddressInfo(self.address, 'success', self.score)
|
||||||
@ -79,8 +79,8 @@ def peerCleanup(coreInst):
|
|||||||
logger.info('Cleaning peers...')
|
logger.info('Cleaning peers...')
|
||||||
config.reload()
|
config.reload()
|
||||||
|
|
||||||
minScore = int(config.get('peers.minimumScore'))
|
minScore = int(config.get('peers.minimum_score', -100))
|
||||||
maxPeers = int(config.get('peers.maxStoredPeers'))
|
maxPeers = int(config.get('peers.max_stored', 5000))
|
||||||
|
|
||||||
adders = getScoreSortedPeerList(coreInst)
|
adders = getScoreSortedPeerList(coreInst)
|
||||||
adders.reverse()
|
adders.reverse()
|
||||||
@ -102,4 +102,4 @@ def peerCleanup(coreInst):
|
|||||||
logger.warn('Removed address ' + address + '.')
|
logger.warn('Removed address ' + address + '.')
|
||||||
|
|
||||||
# Unban probably not malicious peers TODO improve
|
# Unban probably not malicious peers TODO improve
|
||||||
coreInst._blacklist.deleteExpired(dataType=1)
|
coreInst._blacklist.deleteExpired(dataType=1)
|
||||||
|
@ -77,7 +77,7 @@ def enable(name, onionr = None, start_event = True):
|
|||||||
else:
|
else:
|
||||||
enabled_plugins.append(name)
|
enabled_plugins.append(name)
|
||||||
config.set('plugins.enabled', enabled_plugins, True)
|
config.set('plugins.enabled', enabled_plugins, True)
|
||||||
|
|
||||||
if start_event is True:
|
if start_event is True:
|
||||||
start(name)
|
start(name)
|
||||||
return True
|
return True
|
||||||
@ -234,7 +234,7 @@ def check():
|
|||||||
config.reload()
|
config.reload()
|
||||||
|
|
||||||
if not config.is_set('plugins'):
|
if not config.is_set('plugins'):
|
||||||
logger.debug('Generating plugin config data...')
|
logger.debug('Generating plugin configuration data...')
|
||||||
config.set('plugins', {'enabled': []}, True)
|
config.set('plugins', {'enabled': []}, True)
|
||||||
|
|
||||||
if not os.path.exists(os.path.dirname(get_plugins_folder())):
|
if not os.path.exists(os.path.dirname(get_plugins_folder())):
|
||||||
|
@ -72,7 +72,7 @@ class OnionrSocketServer:
|
|||||||
self._core.socketServerResponseData[myPeer] = ''
|
self._core.socketServerResponseData[myPeer] = ''
|
||||||
|
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def socketStarter(self):
|
def socketStarter(self):
|
||||||
while not self._core.killSockets:
|
while not self._core.killSockets:
|
||||||
try:
|
try:
|
||||||
@ -87,14 +87,15 @@ class OnionrSocketServer:
|
|||||||
def detectShutdown(self):
|
def detectShutdown(self):
|
||||||
while not self._core.killSockets:
|
while not self._core.killSockets:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
logger.info('Killing socket server')
|
|
||||||
|
logger.debug('Killing socket server...')
|
||||||
self.http_server.stop()
|
self.http_server.stop()
|
||||||
|
|
||||||
def addSocket(self, peer, reason=''):
|
def addSocket(self, peer, reason=''):
|
||||||
bindPort = 1337
|
bindPort = 1337
|
||||||
|
|
||||||
assert len(reason) <= 12
|
assert len(reason) <= 12
|
||||||
|
|
||||||
with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller:
|
with stem.control.Controller.from_port(port=config.get('tor.controlPort')) as controller:
|
||||||
controller.authenticate(config.get('tor.controlpassword'))
|
controller.authenticate(config.get('tor.controlpassword'))
|
||||||
|
|
||||||
@ -106,7 +107,7 @@ class OnionrSocketServer:
|
|||||||
self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason, 'address': socket.service_id + '.onion'})
|
self._core.insertBlock(str(uuid.uuid4()), header='socket', sign=True, encryptType='asym', asymPeer=peer, meta={'reason': reason, 'address': socket.service_id + '.onion'})
|
||||||
self._core.socketReasons[peer] = reason
|
self._core.socketReasons[peer] = reason
|
||||||
return
|
return
|
||||||
|
|
||||||
class OnionrSocketClient:
|
class OnionrSocketClient:
|
||||||
def __init__(self, coreInst):
|
def __init__(self, coreInst):
|
||||||
self.sockets = {} # pubkey: tor address
|
self.sockets = {} # pubkey: tor address
|
||||||
@ -158,7 +159,7 @@ class OnionrSocketClient:
|
|||||||
postData = {'data': data}
|
postData = {'data': data}
|
||||||
self.connPool[peer] = {'date': self._core._utils.getEpoch(), 'data': self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData)}
|
self.connPool[peer] = {'date': self._core._utils.getEpoch(), 'data': self._core._utils.doPostRequest('http://' + address + '/dc/', data=postData)}
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
def getResponse(self, peer):
|
def getResponse(self, peer):
|
||||||
retData = ''
|
retData = ''
|
||||||
try:
|
try:
|
||||||
@ -166,6 +167,6 @@ class OnionrSocketClient:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
def sendData(self, peer, data):
|
def sendData(self, peer, data):
|
||||||
self.sendData[peer] = data
|
self.sendData[peer] = data
|
||||||
|
@ -40,7 +40,7 @@ class OnionrUser:
|
|||||||
|
|
||||||
self.trust = self._core.getPeerInfo(self.publicKey, 'trust')
|
self.trust = self._core.getPeerInfo(self.publicKey, 'trust')
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTrust(self, newTrust):
|
def setTrust(self, newTrust):
|
||||||
'''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
|
'''Set the peers trust. 0 = not trusted, 1 = friend, 2 = ultimate'''
|
||||||
self._core.setPeerInfo(self.publicKey, 'trust', newTrust)
|
self._core.setPeerInfo(self.publicKey, 'trust', newTrust)
|
||||||
@ -49,7 +49,7 @@ class OnionrUser:
|
|||||||
if self._core.getPeerInfo(self.publicKey, 'trust') == 1:
|
if self._core.getPeerInfo(self.publicKey, 'trust') == 1:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getName(self):
|
def getName(self):
|
||||||
retData = 'anonymous'
|
retData = 'anonymous'
|
||||||
name = self._core.getPeerInfo(self.publicKey, 'name')
|
name = self._core.getPeerInfo(self.publicKey, 'name')
|
||||||
@ -63,11 +63,11 @@ class OnionrUser:
|
|||||||
def encrypt(self, data):
|
def encrypt(self, data):
|
||||||
encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True)
|
encrypted = coreInst._crypto.pubKeyEncrypt(data, self.publicKey, encodedData=True)
|
||||||
return encrypted
|
return encrypted
|
||||||
|
|
||||||
def decrypt(self, data, anonymous=True):
|
def decrypt(self, data, anonymous=True):
|
||||||
decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True)
|
decrypted = coreInst._crypto.pubKeyDecrypt(data, self.publicKey, encodedData=True)
|
||||||
return decrypted
|
return decrypted
|
||||||
|
|
||||||
def forwardEncrypt(self, data):
|
def forwardEncrypt(self, data):
|
||||||
retData = ''
|
retData = ''
|
||||||
forwardKey = self._getLatestForwardKey()
|
forwardKey = self._getLatestForwardKey()
|
||||||
@ -78,7 +78,7 @@ class OnionrUser:
|
|||||||
raise onionrexceptions.InvalidPubkey("No valid forward key available for this user")
|
raise onionrexceptions.InvalidPubkey("No valid forward key available for this user")
|
||||||
#self.generateForwardKey()
|
#self.generateForwardKey()
|
||||||
return (retData, forwardKey)
|
return (retData, forwardKey)
|
||||||
|
|
||||||
def forwardDecrypt(self, encrypted):
|
def forwardDecrypt(self, encrypted):
|
||||||
retData = ""
|
retData = ""
|
||||||
#logger.error(self.publicKey)
|
#logger.error(self.publicKey)
|
||||||
@ -101,19 +101,21 @@ class OnionrUser:
|
|||||||
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
|
|
||||||
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? order by date desc", (self.publicKey,)):
|
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? ORDER BY date DESC", (self.publicKey,)):
|
||||||
key = row[0]
|
key = row[0]
|
||||||
break
|
break
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def _getForwardKeys(self):
|
def _getForwardKeys(self):
|
||||||
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
conn = sqlite3.connect(self._core.peerDB, timeout=10)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
keyList = []
|
keyList = []
|
||||||
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? order by date desc", (self.publicKey,)):
|
|
||||||
|
for row in c.execute("SELECT forwardKey FROM forwardKeys WHERE peerKey = ? ORDER BY date DESC", (self.publicKey,)):
|
||||||
key = row[0]
|
key = row[0]
|
||||||
keyList.append(key)
|
keyList.append(key)
|
||||||
|
|
||||||
@ -150,8 +152,10 @@ class OnionrUser:
|
|||||||
pubkey = self._core._utils.bytesToStr(pubkey)
|
pubkey = self._core._utils.bytesToStr(pubkey)
|
||||||
command = (pubkey,)
|
command = (pubkey,)
|
||||||
keyList = [] # list of tuples containing pub, private for peer
|
keyList = [] # list of tuples containing pub, private for peer
|
||||||
for result in c.execute("SELECT * FROM myForwardKeys where peer=?", command):
|
|
||||||
|
for result in c.execute("SELECT * FROM myForwardKeys WHERE peer = ?", command):
|
||||||
keyList.append((result[1], result[2]))
|
keyList.append((result[1], result[2]))
|
||||||
|
|
||||||
if len(keyList) == 0:
|
if len(keyList) == 0:
|
||||||
if genNew:
|
if genNew:
|
||||||
self.generateForwardKey()
|
self.generateForwardKey()
|
||||||
@ -173,7 +177,7 @@ class OnionrUser:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
def findAndSetID(self):
|
def findAndSetID(self):
|
||||||
'''Find any info about the user from existing blocks and cache it to their DB entry'''
|
'''Find any info about the user from existing blocks and cache it to their DB entry'''
|
||||||
infoBlocks = []
|
infoBlocks = []
|
||||||
@ -186,4 +190,4 @@ class OnionrUser:
|
|||||||
logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName)))
|
logger.info('%s is now using the name %s.' % (self.publicKey, self._core._utils.escapeAnsi(newName)))
|
||||||
self._core.setPeerInfo(self.publicKey, 'name', newName)
|
self._core.setPeerInfo(self.publicKey, 'name', newName)
|
||||||
else:
|
else:
|
||||||
raise onionrexceptions.InvalidPubkey
|
raise onionrexceptions.InvalidPubkey
|
||||||
|
@ -125,11 +125,11 @@ class OnionrUtils:
|
|||||||
for adder in newAdderList.split(','):
|
for adder in newAdderList.split(','):
|
||||||
adder = adder.strip()
|
adder = adder.strip()
|
||||||
if not adder in self._core.listAdders(randomOrder = False) and adder != self.getMyAddress() and not self._core._blacklist.inBlacklist(adder):
|
if not adder in self._core.listAdders(randomOrder = False) and adder != self.getMyAddress() and not self._core._blacklist.inBlacklist(adder):
|
||||||
if not config.get('tor.v3onions') and len(adder) == 62:
|
if not config.get('tor.v3_onions') and len(adder) == 62:
|
||||||
continue
|
continue
|
||||||
if self._core.addAddress(adder):
|
if self._core.addAddress(adder):
|
||||||
# Check if we have the maxmium amount of allowed stored peers
|
# Check if we have the maxmium amount of allowed stored peers
|
||||||
if config.get('peers.maxStoredPeers') > len(self._core.listAdders()):
|
if config.get('peers.max_stored') > len(self._core.listAdders()):
|
||||||
logger.info('Added %s to db.' % adder, timestamp = True)
|
logger.info('Added %s to db.' % adder, timestamp = True)
|
||||||
retVal = True
|
retVal = True
|
||||||
else:
|
else:
|
||||||
@ -276,9 +276,9 @@ class OnionrUtils:
|
|||||||
if myBlock.getMetadata('newFSKey') is not None:
|
if myBlock.getMetadata('newFSKey') is not None:
|
||||||
onionrusers.OnionrUser(self._core, signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
onionrusers.OnionrUser(self._core, signer).addForwardKey(myBlock.getMetadata('newFSKey'))
|
||||||
else:
|
else:
|
||||||
logger.debug('FS not used for this block')
|
logger.warn('FS not used for this encrypted block')
|
||||||
logger.info(myBlock.bmetadata)
|
logger.info(myBlock.bmetadata)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if len(blockType) <= 10:
|
if len(blockType) <= 10:
|
||||||
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
|
self._core.updateBlockInfo(blockHash, 'dataType', blockType)
|
||||||
@ -330,7 +330,7 @@ class OnionrUtils:
|
|||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
if not self.validateHash(hash):
|
if not self.validateHash(hash):
|
||||||
raise Exception("Invalid hash")
|
raise Exception("Invalid hash")
|
||||||
for result in c.execute("SELECT COUNT() FROM hashes where hash='" + hash + "'"):
|
for result in c.execute("SELECT COUNT() FROM hashes WHERE hash = ?", (hash,)):
|
||||||
if result[0] >= 1:
|
if result[0] >= 1:
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@ -404,7 +404,7 @@ class OnionrUtils:
|
|||||||
logger.warn('Block is expired')
|
logger.warn('Block is expired')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# if metadata loop gets no errors, it does not break, therefore metadata is valid
|
# if metadata loop gets no errors, it does not break, therefore metadata is valid
|
||||||
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
|
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
|
||||||
nonce = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(blockData))
|
nonce = self._core._utils.bytesToStr(self._core._crypto.sha3Hash(blockData))
|
||||||
try:
|
try:
|
||||||
@ -490,7 +490,13 @@ class OnionrUtils:
|
|||||||
retVal = False
|
retVal = False
|
||||||
if not idNoDomain.isalnum():
|
if not idNoDomain.isalnum():
|
||||||
retVal = False
|
retVal = False
|
||||||
|
|
||||||
|
# Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
|
||||||
|
try:
|
||||||
|
base64.b32decode(idNoDomain.upper().encode())
|
||||||
|
except binascii.Error:
|
||||||
|
retVal = False
|
||||||
|
|
||||||
# Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
|
# Validate address is valid base32 (when capitalized and minus extension); v2/v3 onions and .b32.i2p use base32
|
||||||
try:
|
try:
|
||||||
base64.b32decode(idNoDomain.upper().encode())
|
base64.b32decode(idNoDomain.upper().encode())
|
||||||
@ -512,7 +518,7 @@ class OnionrUtils:
|
|||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
command = (hash,)
|
command = (hash,)
|
||||||
retData = ''
|
retData = ''
|
||||||
for row in c.execute('SELECT ID FROM peers where hashID=?', command):
|
for row in c.execute('SELECT id FROM peers WHERE hashID = ?', command):
|
||||||
if row[0] != '':
|
if row[0] != '':
|
||||||
retData = row[0]
|
retData = row[0]
|
||||||
return retData
|
return retData
|
||||||
@ -521,18 +527,16 @@ class OnionrUtils:
|
|||||||
try:
|
try:
|
||||||
runcheck_file = self._core.dataDir + '.runcheck'
|
runcheck_file = self._core.dataDir + '.runcheck'
|
||||||
|
|
||||||
if os.path.isfile(runcheck_file):
|
if not os.path.isfile(runcheck_file):
|
||||||
os.remove(runcheck_file)
|
open(runcheck_file, 'w+').close()
|
||||||
logger.debug('%s file appears to have existed before the run check.' % runcheck_file, timestamp = False)
|
|
||||||
|
|
||||||
self._core.daemonQueueAdd('runCheck')
|
# self._core.daemonQueueAdd('runCheck') # deprecated
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
if os.path.isfile(runcheck_file):
|
|
||||||
os.remove(runcheck_file)
|
|
||||||
|
|
||||||
|
if not os.path.isfile(runcheck_file):
|
||||||
return True
|
return True
|
||||||
elif time.time() - starttime >= timeout:
|
elif time.time() - starttime >= timeout:
|
||||||
return False
|
return False
|
||||||
@ -543,6 +547,7 @@ class OnionrUtils:
|
|||||||
'''
|
'''
|
||||||
Generates a secure random hex encoded token
|
Generates a secure random hex encoded token
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return binascii.hexlify(os.urandom(size))
|
return binascii.hexlify(os.urandom(size))
|
||||||
|
|
||||||
def importNewBlocks(self, scanDir=''):
|
def importNewBlocks(self, scanDir=''):
|
||||||
@ -625,12 +630,14 @@ class OnionrUtils:
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
headers = {'user-agent': 'PyOnionr'}
|
headers = {'user-agent': 'PyOnionr'}
|
||||||
|
response_headers = dict()
|
||||||
try:
|
try:
|
||||||
proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
|
proxies = {'http': 'socks4a://127.0.0.1:' + str(port), 'https': 'socks4a://127.0.0.1:' + str(port)}
|
||||||
r = requests.get(url, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30))
|
r = requests.get(url, headers=headers, proxies=proxies, allow_redirects=False, timeout=(15, 30))
|
||||||
# Check server is using same API version as us
|
# Check server is using same API version as us
|
||||||
try:
|
try:
|
||||||
if r.headers['api'] != str(API_VERSION):
|
response_headers = r.headers
|
||||||
|
if r.headers['X-API'] != str(API_VERSION):
|
||||||
raise onionrexceptions.InvalidAPIVersion
|
raise onionrexceptions.InvalidAPIVersion
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise onionrexceptions.InvalidAPIVersion
|
raise onionrexceptions.InvalidAPIVersion
|
||||||
@ -638,9 +645,12 @@ class OnionrUtils:
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.debug('Failed to make request', error = e)
|
logger.debug('Failed to make GET request to %s' % url, error = e, sensitive = True)
|
||||||
except onionrexceptions.InvalidAPIVersion:
|
except onionrexceptions.InvalidAPIVersion:
|
||||||
logger.debug("Node is using different API version :(")
|
if 'X-API' in response_headers:
|
||||||
|
logger.debug('Using API version %s. Cannot communicate with node\'s API version of %s.' % (API_VERSION, response_headers['X-API']))
|
||||||
|
else:
|
||||||
|
logger.debug('Using API version %s. API version was not sent with the request.' % API_VERSION)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
|
if not 'ConnectTimeoutError' in str(e) and not 'Request rejected or failed' in str(e):
|
||||||
logger.debug('Error: %s' % str(e))
|
logger.debug('Error: %s' % str(e))
|
||||||
@ -659,16 +669,16 @@ class OnionrUtils:
|
|||||||
retData = ''
|
retData = ''
|
||||||
curTime = self.getRoundedEpoch(rounding)
|
curTime = self.getRoundedEpoch(rounding)
|
||||||
self.nistSaltTimestamp = curTime
|
self.nistSaltTimestamp = curTime
|
||||||
data = self.doGetRequest('https://beacon.nist.gov/rest/record/' + str(curTime), port=torPort)
|
data = self.doGetRequest('https://beacon.nist.gov/rest/record/' + str(curTime), port = torPort)
|
||||||
dataXML = minidom.parseString(data, forbid_dtd=True, forbid_entities=True, forbid_external=True)
|
dataXML = minidom.parseString(data, forbid_dtd = True, forbid_entities = True, forbid_external = True)
|
||||||
try:
|
try:
|
||||||
retData = dataXML.getElementsByTagName('outputValue')[0].childNodes[0].data
|
retData = dataXML.getElementsByTagName('outputValue')[0].childNodes[0].data
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.warn('Could not get NIST beacon value')
|
logger.warn('Failed to get the NIST beacon value.')
|
||||||
else:
|
else:
|
||||||
self.powSalt = retData
|
self.powSalt = retData
|
||||||
return retData
|
return retData
|
||||||
|
|
||||||
def strToBytes(self, data):
|
def strToBytes(self, data):
|
||||||
try:
|
try:
|
||||||
data = data.encode()
|
data = data.encode()
|
||||||
@ -681,7 +691,7 @@ class OnionrUtils:
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def checkNetwork(self, torPort=0):
|
def checkNetwork(self, torPort=0):
|
||||||
'''Check if we are connected to the internet (through Tor)'''
|
'''Check if we are connected to the internet (through Tor)'''
|
||||||
retData = False
|
retData = False
|
||||||
@ -689,7 +699,7 @@ class OnionrUtils:
|
|||||||
try:
|
try:
|
||||||
with open('static-data/connect-check.txt', 'r') as connectTest:
|
with open('static-data/connect-check.txt', 'r') as connectTest:
|
||||||
connectURLs = connectTest.read().split(',')
|
connectURLs = connectTest.read().split(',')
|
||||||
|
|
||||||
for url in connectURLs:
|
for url in connectURLs:
|
||||||
if self.doGetRequest(url, port=torPort) != False:
|
if self.doGetRequest(url, port=torPort) != False:
|
||||||
retData = True
|
retData = True
|
||||||
@ -722,4 +732,4 @@ def humanSize(num, suffix='B'):
|
|||||||
if abs(num) < 1024.0:
|
if abs(num) < 1024.0:
|
||||||
return "%.1f %s%s" % (num, unit, suffix)
|
return "%.1f %s%s" % (num, unit, suffix)
|
||||||
num /= 1024.0
|
num /= 1024.0
|
||||||
return "%.1f %s%s" % (num, 'Yi', suffix)
|
return "%.1f %s%s" % (num, 'Yi', suffix)
|
||||||
|
@ -38,13 +38,12 @@ class OnionrCLIUI:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
for i in range(100):
|
print('\n' * 80 + logger.colors.reset)
|
||||||
print('')
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
'''Main CLI UI interface menu'''
|
'''Main CLI UI interface menu'''
|
||||||
showMenu = True
|
showMenu = True
|
||||||
isOnline = "No"
|
isOnline = 'No'
|
||||||
firstRun = True
|
firstRun = True
|
||||||
choice = ''
|
choice = ''
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ class OnionrCLIUI:
|
|||||||
|
|
||||||
while showMenu:
|
while showMenu:
|
||||||
if firstRun:
|
if firstRun:
|
||||||
logger.info("please wait while Onionr starts...")
|
logger.info('Please wait while Onionr starts...')
|
||||||
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL)
|
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL)
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
firstRun = False
|
firstRun = False
|
||||||
@ -63,9 +62,7 @@ class OnionrCLIUI:
|
|||||||
else:
|
else:
|
||||||
isOnline = "No"
|
isOnline = "No"
|
||||||
|
|
||||||
print('''
|
logger.info('''Daemon Running: ''' + isOnline + '''
|
||||||
Daemon Running: ''' + isOnline + '''
|
|
||||||
|
|
||||||
1. Flow (Anonymous public chat, use at your own risk)
|
1. Flow (Anonymous public chat, use at your own risk)
|
||||||
2. Mail (Secure email-like service)
|
2. Mail (Secure email-like service)
|
||||||
3. File Sharing
|
3. File Sharing
|
||||||
@ -83,7 +80,7 @@ Daemon Running: ''' + isOnline + '''
|
|||||||
elif choice in ("2", "mail"):
|
elif choice in ("2", "mail"):
|
||||||
self.subCommand("mail")
|
self.subCommand("mail")
|
||||||
elif choice in ("3", "file sharing", "file"):
|
elif choice in ("3", "file sharing", "file"):
|
||||||
print("Not supported yet")
|
logger.warn("Not supported yet")
|
||||||
elif choice in ("4", "user settings", "settings"):
|
elif choice in ("4", "user settings", "settings"):
|
||||||
try:
|
try:
|
||||||
self.setName()
|
self.setName()
|
||||||
@ -91,21 +88,22 @@ Daemon Running: ''' + isOnline + '''
|
|||||||
pass
|
pass
|
||||||
elif choice in ("5", "daemon"):
|
elif choice in ("5", "daemon"):
|
||||||
if isOnline == "Yes":
|
if isOnline == "Yes":
|
||||||
print("Onionr daemon will shutdown...")
|
logger.info("Onionr daemon will shutdown...")
|
||||||
self.myCore.daemonQueueAdd('shutdown')
|
self.myCore.daemonQueueAdd('shutdown')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
daemon.kill()
|
daemon.kill()
|
||||||
except UnboundLocalError:
|
except UnboundLocalError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
print("Starting Daemon...")
|
logger.info("Starting Daemon...")
|
||||||
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
daemon = subprocess.Popen(["./onionr.py", "start"], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
||||||
elif choice in ("6", "quit"):
|
elif choice in ("6", "quit"):
|
||||||
showMenu = False
|
showMenu = False
|
||||||
elif choice == "":
|
elif choice == "":
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
print("Invalid choice")
|
logger.error("Invalid choice")
|
||||||
return
|
return
|
||||||
|
|
||||||
def setName(self):
|
def setName(self):
|
||||||
|
@ -71,7 +71,8 @@ class PlainEncryption:
|
|||||||
plaintext = data
|
plaintext = data
|
||||||
encrypted = self.api.get_core()._crypto.pubKeyEncrypt(plaintext, pubkey, anonymous=True, encodedData=True)
|
encrypted = self.api.get_core()._crypto.pubKeyEncrypt(plaintext, pubkey, anonymous=True, encodedData=True)
|
||||||
encrypted = self.api.get_core()._utils.bytesToStr(encrypted)
|
encrypted = self.api.get_core()._utils.bytesToStr(encrypted)
|
||||||
print('ONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted,))
|
logger.info('Encrypted Message: \n\nONIONR ENCRYPTED DATA %s END ENCRYPTED DATA' % (encrypted,))
|
||||||
|
|
||||||
def decrypt(self):
|
def decrypt(self):
|
||||||
plaintext = ""
|
plaintext = ""
|
||||||
data = ""
|
data = ""
|
||||||
@ -89,10 +90,10 @@ class PlainEncryption:
|
|||||||
myPub = self.api.get_core()._crypto.pubKey
|
myPub = self.api.get_core()._crypto.pubKey
|
||||||
decrypted = self.api.get_core()._crypto.pubKeyDecrypt(encrypted, privkey=self.api.get_core()._crypto.privKey, anonymous=True, encodedData=True)
|
decrypted = self.api.get_core()._crypto.pubKeyDecrypt(encrypted, privkey=self.api.get_core()._crypto.privKey, anonymous=True, encodedData=True)
|
||||||
if decrypted == False:
|
if decrypted == False:
|
||||||
print("Decryption failed")
|
logger.error("Decryption failed")
|
||||||
else:
|
else:
|
||||||
data = json.loads(decrypted)
|
data = json.loads(decrypted)
|
||||||
print(data['data'])
|
logger.info('Decrypted Message: \n\n%s' % data['data'])
|
||||||
try:
|
try:
|
||||||
logger.info("Signing public key: %s" % (data['signer'],))
|
logger.info("Signing public key: %s" % (data['signer'],))
|
||||||
assert self.api.get_core()._crypto.edVerify(data['data'], data['signer'], data['sig']) != False
|
assert self.api.get_core()._crypto.edVerify(data['data'], data['signer'], data['sig']) != False
|
||||||
@ -101,7 +102,6 @@ class PlainEncryption:
|
|||||||
else:
|
else:
|
||||||
logger.info("Message has good signature.")
|
logger.info("Message has good signature.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def on_init(api, data = None):
|
def on_init(api, data = None):
|
||||||
'''
|
'''
|
||||||
@ -114,4 +114,5 @@ def on_init(api, data = None):
|
|||||||
encrypt = PlainEncryption(pluginapi)
|
encrypt = PlainEncryption(pluginapi)
|
||||||
api.commands.register(['encrypt'], encrypt.encrypt)
|
api.commands.register(['encrypt'], encrypt.encrypt)
|
||||||
api.commands.register(['decrypt'], encrypt.decrypt)
|
api.commands.register(['decrypt'], encrypt.decrypt)
|
||||||
return
|
|
||||||
|
return
|
||||||
|
@ -132,10 +132,10 @@ def createRepository(plugins):
|
|||||||
contents = {'plugins' : plugins, 'author' : getpass.getuser(), 'compiled-by' : plugin_name}
|
contents = {'plugins' : plugins, 'author' : getpass.getuser(), 'compiled-by' : plugin_name}
|
||||||
|
|
||||||
block = Block(core = pluginapi.get_core())
|
block = Block(core = pluginapi.get_core())
|
||||||
|
|
||||||
block.setType('repository')
|
block.setType('repository')
|
||||||
block.setContent(json.dumps(contents))
|
block.setContent(json.dumps(contents))
|
||||||
|
|
||||||
return block.save(True)
|
return block.save(True)
|
||||||
|
|
||||||
def check():
|
def check():
|
||||||
@ -217,7 +217,7 @@ def pluginToBlock(plugin, import_block = True):
|
|||||||
info = ''
|
info = ''
|
||||||
with open(directory + 'info.json').read() as file:
|
with open(directory + 'info.json').read() as file:
|
||||||
info = json.loads(file.read())
|
info = json.loads(file.read())
|
||||||
|
|
||||||
if 'author' in info:
|
if 'author' in info:
|
||||||
author = info['author']
|
author = info['author']
|
||||||
if 'description' in info:
|
if 'description' in info:
|
||||||
@ -228,10 +228,10 @@ def pluginToBlock(plugin, import_block = True):
|
|||||||
metadata = {'author' : author, 'date' : str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')), 'name' : plugin, 'info' : info, 'compiled-by' : plugin_name, 'content' : data.decode('utf-8'), 'description' : description}
|
metadata = {'author' : author, 'date' : str(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')), 'name' : plugin, 'info' : info, 'compiled-by' : plugin_name, 'content' : data.decode('utf-8'), 'description' : description}
|
||||||
|
|
||||||
block = Block(core = pluginapi.get_core())
|
block = Block(core = pluginapi.get_core())
|
||||||
|
|
||||||
block.setType('plugin')
|
block.setType('plugin')
|
||||||
block.setContent(json.dumps(metadata))
|
block.setContent(json.dumps(metadata))
|
||||||
|
|
||||||
hash = block.save(True)
|
hash = block.save(True)
|
||||||
# hash = pluginapi.get_core().insertBlock(, header = 'plugin', sign = True)
|
# hash = pluginapi.get_core().insertBlock(, header = 'plugin', sign = True)
|
||||||
|
|
||||||
@ -390,12 +390,12 @@ def commandInstallPlugin():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn('Failed to lookup plugin in repositories.', timestamp = False)
|
logger.warn('Failed to lookup plugin in repositories.', timestamp = False)
|
||||||
logger.error('asdf', error = e, timestamp = False)
|
logger.error('asdf', error = e, timestamp = False)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if pkobh is None:
|
if pkobh is None:
|
||||||
logger.error('No key for this plugin found in keystore or repositories, please specify.', timestamp = False)
|
logger.error('No key for this plugin found in keystore or repositories, please specify.', timestamp = False)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
valid_hash = pluginapi.get_utils().validateHash(pkobh)
|
valid_hash = pluginapi.get_utils().validateHash(pkobh)
|
||||||
@ -552,49 +552,48 @@ def commandPublishPlugin():
|
|||||||
logger.error('Plugin %s does not exist.' % pluginname, timestamp = False)
|
logger.error('Plugin %s does not exist.' % pluginname, timestamp = False)
|
||||||
else:
|
else:
|
||||||
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
||||||
|
|
||||||
def commandCreateRepository():
|
def commandCreateRepository():
|
||||||
if len(sys.argv) >= 3:
|
if len(sys.argv) >= 3:
|
||||||
check()
|
check()
|
||||||
|
|
||||||
plugins = list()
|
plugins = list()
|
||||||
script = sys.argv[0]
|
script = sys.argv[0]
|
||||||
|
|
||||||
del sys.argv[:2]
|
del sys.argv[:2]
|
||||||
success = True
|
success = True
|
||||||
for pluginname in sys.argv:
|
for pluginname in sys.argv:
|
||||||
distributor = None
|
distributor = None
|
||||||
|
|
||||||
if ':' in pluginname:
|
if ':' in pluginname:
|
||||||
split = pluginname.split(':')
|
split = pluginname.split(':')
|
||||||
pluginname = split[0]
|
pluginname = split[0]
|
||||||
distributor = split[1]
|
distributor = split[1]
|
||||||
|
|
||||||
pluginname = sanitize(pluginname)
|
pluginname = sanitize(pluginname)
|
||||||
|
|
||||||
if distributor is None:
|
if distributor is None:
|
||||||
distributor = getKey(pluginname)
|
distributor = getKey(pluginname)
|
||||||
if distributor is None:
|
if distributor is None:
|
||||||
logger.error('No distributor key was found for the plugin %s.' % pluginname, timestamp = False)
|
logger.error('No distributor key was found for the plugin %s.' % pluginname, timestamp = False)
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
plugins.append([pluginname, distributor])
|
plugins.append([pluginname, distributor])
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.error('Please correct the above errors, then recreate the repository.')
|
logger.error('Please correct the above errors, then recreate the repository.')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
blockhash = createRepository(plugins)
|
blockhash = createRepository(plugins)
|
||||||
print(blockhash)
|
|
||||||
if not blockhash is None:
|
if not blockhash is None:
|
||||||
logger.info('Successfully created repository. Execute the following command to add the repository:\n ' + logger.colors.underline + '%s --add-repository %s' % (script, blockhash))
|
logger.info('Successfully created repository. Execute the following command to add the repository:\n ' + logger.colors.underline + '%s --add-repository %s' % (script, blockhash))
|
||||||
else:
|
else:
|
||||||
logger.error('Failed to create repository, an unknown error occurred.')
|
logger.error('Failed to create repository, an unknown error occurred.')
|
||||||
else:
|
else:
|
||||||
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' [plugins...]')
|
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' [plugins...]')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# event listeners
|
# event listeners
|
||||||
|
|
||||||
def on_init(api, data = None):
|
def on_init(api, data = None):
|
||||||
|
@ -66,7 +66,7 @@ class OnionrMail:
|
|||||||
self.sentboxList = []
|
self.sentboxList = []
|
||||||
self.sentMessages = {}
|
self.sentMessages = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def inbox(self):
|
def inbox(self):
|
||||||
blockCount = 0
|
blockCount = 0
|
||||||
pmBlockMap = {}
|
pmBlockMap = {}
|
||||||
@ -87,7 +87,7 @@ class OnionrMail:
|
|||||||
continue
|
continue
|
||||||
blockCount += 1
|
blockCount += 1
|
||||||
pmBlockMap[blockCount] = blockHash
|
pmBlockMap[blockCount] = blockHash
|
||||||
|
|
||||||
block = pmBlocks[blockHash]
|
block = pmBlocks[blockHash]
|
||||||
senderKey = block.signer
|
senderKey = block.signer
|
||||||
try:
|
try:
|
||||||
@ -102,7 +102,7 @@ class OnionrMail:
|
|||||||
displayList.append('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash))
|
displayList.append('%s. %s - %s: %s' % (blockCount, blockDate, senderDisplay[:12], blockHash))
|
||||||
#displayList.reverse()
|
#displayList.reverse()
|
||||||
for i in displayList:
|
for i in displayList:
|
||||||
print(i)
|
logger.info(i)
|
||||||
try:
|
try:
|
||||||
choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
|
choice = logger.readline('Enter a block number, -r to refresh, or -q to stop: ').strip().lower()
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
@ -129,16 +129,18 @@ class OnionrMail:
|
|||||||
else:
|
else:
|
||||||
cancel = ''
|
cancel = ''
|
||||||
readBlock.verifySig()
|
readBlock.verifySig()
|
||||||
print('Message recieved from %s' % (self.myCore._utils.bytesToStr(readBlock.signer,)))
|
|
||||||
print('Valid signature:', readBlock.validSig)
|
logger.info('Message recieved from %s' % (self.myCore._utils.bytesToStr(readBlock.signer,)))
|
||||||
|
logger.info('Valid signature: %s' % readBlock.validSig)
|
||||||
|
|
||||||
if not readBlock.validSig:
|
if not readBlock.validSig:
|
||||||
logger.warn('This message has an INVALID signature. ANYONE could have sent this message.')
|
logger.warn('This message has an INVALID signature. ANYONE could have sent this message.')
|
||||||
cancel = logger.readline('Press enter to continue to message, or -q to not open the message (recommended).')
|
cancel = logger.readline('Press enter to continue to message, or -q to not open the message (recommended).')
|
||||||
if cancel != '-q':
|
if cancel != '-q':
|
||||||
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
|
print(draw_border(self.myCore._utils.escapeAnsi(readBlock.bcontent.decode().strip())))
|
||||||
input("Press enter to continue")
|
logger.readline("Press enter to continue")
|
||||||
return
|
return
|
||||||
|
|
||||||
def sentbox(self):
|
def sentbox(self):
|
||||||
'''
|
'''
|
||||||
Display sent mail messages
|
Display sent mail messages
|
||||||
@ -146,7 +148,7 @@ class OnionrMail:
|
|||||||
entering = True
|
entering = True
|
||||||
while entering:
|
while entering:
|
||||||
self.getSentList()
|
self.getSentList()
|
||||||
print('Enter block number or -q to return')
|
logger.info('Enter block number or -q to return')
|
||||||
try:
|
try:
|
||||||
choice = input('>')
|
choice = input('>')
|
||||||
except (EOFError, KeyboardInterrupt) as e:
|
except (EOFError, KeyboardInterrupt) as e:
|
||||||
@ -158,21 +160,22 @@ class OnionrMail:
|
|||||||
try:
|
try:
|
||||||
self.sentboxList[int(choice) - 1]
|
self.sentboxList[int(choice) - 1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
print('Invalid block')
|
logger.warn('Invalid block.')
|
||||||
else:
|
else:
|
||||||
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice) - 1]][1])
|
logger.info('Sent to: ' + self.sentMessages[self.sentboxList[int(choice) - 1]][1])
|
||||||
# Print ansi escaped sent message
|
# Print ansi escaped sent message
|
||||||
print(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice) - 1]][0]))
|
logger.info(self.myCore._utils.escapeAnsi(self.sentMessages[self.sentboxList[int(choice) - 1]][0]))
|
||||||
input('Press enter to continue...')
|
input('Press enter to continue...')
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getSentList(self):
|
def getSentList(self):
|
||||||
count = 1
|
count = 1
|
||||||
for i in self.sentboxTools.listSent():
|
for i in self.sentboxTools.listSent():
|
||||||
self.sentboxList.append(i['hash'])
|
self.sentboxList.append(i['hash'])
|
||||||
self.sentMessages[i['hash']] = (i['message'], i['peer'])
|
self.sentMessages[i['hash']] = (i['message'], i['peer'])
|
||||||
print('%s. %s - %s - %s' % (count, i['hash'], i['peer'][:12], i['date']))
|
|
||||||
|
logger.info('%s. %s - %s - %s' % (count, i['hash'], i['peer'][:12], i['date']))
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
def draftMessage(self):
|
def draftMessage(self):
|
||||||
@ -198,7 +201,7 @@ class OnionrMail:
|
|||||||
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
# if -q or ctrl-c/d, exit function here, otherwise we successfully got the public key
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Enter your message, stop by entering -q on a new line.')
|
logger.info('Enter your message, stop by entering -q on a new line.')
|
||||||
while newLine != '-q':
|
while newLine != '-q':
|
||||||
try:
|
try:
|
||||||
newLine = input()
|
newLine = input()
|
||||||
@ -209,7 +212,7 @@ class OnionrMail:
|
|||||||
newLine += '\n'
|
newLine += '\n'
|
||||||
message += newLine
|
message += newLine
|
||||||
|
|
||||||
print('Inserting encrypted message as Onionr block....')
|
logger.info('Inserting encrypted message as Onionr block....')
|
||||||
|
|
||||||
blockID = self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
|
blockID = self.myCore.insertBlock(message, header='pm', encryptType='asym', asymPeer=recip, sign=True)
|
||||||
self.sentboxTools.addToSent(blockID, recip, message)
|
self.sentboxTools.addToSent(blockID, recip, message)
|
||||||
@ -217,7 +220,7 @@ class OnionrMail:
|
|||||||
choice = ''
|
choice = ''
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
print(self.strings.programTag + '\n\nOur ID: ' + self.myCore._crypto.pubKey + self.strings.mainMenu.title()) # print out main menu
|
logger.info(self.strings.programTag + '\n\nOur ID: ' + self.myCore._crypto.pubKey + self.strings.mainMenu.title()) # print out main menu
|
||||||
|
|
||||||
try:
|
try:
|
||||||
choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
|
choice = logger.readline('Enter 1-%s:\n' % (len(self.strings.mainMenuChoices))).lower().strip()
|
||||||
@ -251,4 +254,4 @@ def on_init(api, data = None):
|
|||||||
mail = OnionrMail(pluginapi)
|
mail = OnionrMail(pluginapi)
|
||||||
api.commands.register(['mail'], mail.menu)
|
api.commands.register(['mail'], mail.menu)
|
||||||
api.commands.register_help('mail', 'Interact with OnionrMail')
|
api.commands.register_help('mail', 'Interact with OnionrMail')
|
||||||
return
|
return
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
{
|
{
|
||||||
"general" : {
|
"general" : {
|
||||||
"dev_mode": true,
|
"dev_mode" : true,
|
||||||
"display_header" : true,
|
"display_header" : true,
|
||||||
"minimum_block_pow": 5,
|
"minimum_block_pow": 5,
|
||||||
"minimum_send_pow": 5,
|
"minimum_send_pow": 5,
|
||||||
|
|
||||||
|
"minimum_block_pow": 5,
|
||||||
|
"minimum_send_pow": 5,
|
||||||
|
|
||||||
"direct_connect" : {
|
"direct_connect" : {
|
||||||
"respond" : true,
|
"respond" : true,
|
||||||
"execute_callbacks" : true
|
"execute_callbacks" : true
|
||||||
@ -13,11 +16,16 @@
|
|||||||
|
|
||||||
"www" : {
|
"www" : {
|
||||||
"public" : {
|
"public" : {
|
||||||
"run" : true
|
"run" : true,
|
||||||
|
"path" : "static-data/www/public/",
|
||||||
|
"guess_mime" : true
|
||||||
},
|
},
|
||||||
|
|
||||||
"private" : {
|
"private" : {
|
||||||
"run" : true
|
"run" : true,
|
||||||
|
"path" : "static-data/www/private/",
|
||||||
|
"guess_mime" : true,
|
||||||
|
"timing_protection" : true
|
||||||
},
|
},
|
||||||
|
|
||||||
"ui" : {
|
"ui" : {
|
||||||
@ -30,41 +38,55 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"log": {
|
"plugins" : {
|
||||||
|
"enabled" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
"disabled" : {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"log" : {
|
||||||
|
"verbosity" : "default",
|
||||||
|
|
||||||
"file": {
|
"file": {
|
||||||
"output": false,
|
"output": false,
|
||||||
"path": "data/output.log"
|
"path": "data/output.log"
|
||||||
},
|
},
|
||||||
|
|
||||||
"console": {
|
"console" : {
|
||||||
"output": true,
|
"output" : true,
|
||||||
"color": true
|
"color" : true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"tor" : {
|
"tor" : {
|
||||||
"v3onions": false
|
"v3onions" : false
|
||||||
},
|
},
|
||||||
|
|
||||||
"i2p":{
|
"i2p" : {
|
||||||
"host": false,
|
"host" : false,
|
||||||
"connect": true,
|
"connect" : true,
|
||||||
"ownAddr": ""
|
"own_addr" : ""
|
||||||
},
|
},
|
||||||
|
|
||||||
"allocations":{
|
"allocations" : {
|
||||||
"disk": 10000000000,
|
"disk" : 10000000000,
|
||||||
"netTotal": 1000000000,
|
"net_total" : 1000000000,
|
||||||
"blockCache": 5000000,
|
"blockCache" : 5000000,
|
||||||
"blockCacheTotal": 50000000
|
"blockCacheTotal" : 50000000
|
||||||
},
|
},
|
||||||
"peers":{
|
|
||||||
"minimumScore": -100,
|
"peers" : {
|
||||||
"maxStoredPeers": 5000,
|
"minimum_score" : -100,
|
||||||
"maxConnect": 10
|
"max_stored_peers" : 5000,
|
||||||
|
"max_connect" : 10
|
||||||
},
|
},
|
||||||
"timers":{
|
|
||||||
"lookupBlocks": 25,
|
"timers" : {
|
||||||
"getBlocks": 30
|
"lookup_blocks" : 25,
|
||||||
|
"get_blocks" : 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ P G'
|
|||||||
P G''
|
P G''
|
||||||
P G'' '
|
P G'' '
|
||||||
P G''''''
|
P G''''''
|
||||||
P :G;'''''P:
|
P :G''''''P:
|
||||||
P ::G;'''P::
|
P ::G''''P::
|
||||||
P :::G;;P:::
|
P :::G''P:::
|
||||||
P ::::::::
|
P ::::::::
|
||||||
P ::::::::::::
|
P ::::::::::::
|
||||||
P :::::::::::::::
|
P :::::::::::::::
|
||||||
@ -20,6 +20,7 @@ P :::: ::::: ::::: ::: W :::: :: :: :: ::::: :: :: :: ::
|
|||||||
P :::: :::::: :::::: ::::
|
P :::: :::::: :::::: ::::
|
||||||
P :::: :::::::::::: :::: GvPBV
|
P :::: :::::::::::: :::: GvPBV
|
||||||
P ::::: :::::::: ::::
|
P ::::: :::::::: ::::
|
||||||
P ::::: ::::::
|
P ::::: :::::
|
||||||
P ::::::::::::::::
|
P ::::::::::::::::
|
||||||
P :::::::
|
P :::::::
|
||||||
|
|
||||||
|
1
onionr/static-data/www/ui/common/default-icon.html
Normal file
1
onionr/static-data/www/ui/common/default-icon.html
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,19 @@
|
|||||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modal-title"><$= LANG.MODAL_TITLE $></h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modal-content"><$= LANG.MODAL_MESSAGE $></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
||||||
<script src="js/main.js"></script>
|
<script src="js/main.js"></script>
|
||||||
|
31
onionr/static-data/www/ui/common/onionr-reply-creator.html
Normal file
31
onionr/static-data/www/ui/common/onionr-reply-creator.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!-- POST REPLIES -->
|
||||||
|
<div class="onionr-post-creator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="onionr-reply-creator container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-reply-creator-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id"><$= LANG.REPLY_CREATOR_YOU $></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-reply-creator-content" oninput="replyCreatorChange()"></textarea>
|
||||||
|
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-reply-creator-content-message"></div>
|
||||||
|
|
||||||
|
<input type="button" onclick="makeReply()" title="<$= LANG.REPLY_CREATOR_CREATE $>" value="<$= LANG.REPLY_CREATOR_CREATE $>" id="onionr-reply-creator-create" class="onionr-post-creator-create" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div id="onionr-replies"></div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST REPLIES -->
|
@ -1,6 +1,6 @@
|
|||||||
<!-- POST -->
|
<!-- POST -->
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="onionr-post">
|
<div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost('$post-hash', 'user-id-url', 'user-name-url', '')">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<img class="onionr-post-user-icon" src="$user-image">
|
<img class="onionr-post-user-icon" src="$user-image">
|
||||||
@ -8,8 +8,8 @@
|
|||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-auto">
|
<div class="col col-auto">
|
||||||
<a class="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
|
<a class="onionr-post-user-name" id="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
|
||||||
<a class="onionr-post-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
|
<a class="onionr-post-user-id" id="onionr-post-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col col-auto text-right ml-auto pl-0">
|
<div class="col col-auto text-right ml-auto pl-0">
|
||||||
@ -22,8 +22,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="onionr-post-controls pt-2">
|
<div class="onionr-post-controls pt-2">
|
||||||
<a href="#!" onclick="toggleLike('$post-id')" class="glyphicon glyphicon-heart mr-2"><$= LANG.POST_LIKE $></a>
|
<a href="#!" onclick="toggleLike('$post-hash')" class="glyphicon glyphicon-heart mr-2">$liked</a>
|
||||||
<a href="#!" onclick="reply('$post-id')" class="glyphicon glyphicon-comment mr-2"><$= LANG.POST_REPLY $></a>
|
<a href="#!" onclick="reply('$post-hash')" class="glyphicon glyphicon-comment mr-2"><$= LANG.POST_REPLY $></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<!-- POST FOCUS REPLIES -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row">
|
||||||
|
<div class="onionr-post-focus-reply-creator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1"></div>
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-post-focus-reply-creator-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-post-focus-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-post-focus-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id"><$= LANG.REPLY_CREATOR_YOU $></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-post-focus-reply-creator-content" oninput="focusReplyCreatorChange()"></textarea>
|
||||||
|
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-post-focus-reply-creator-content-message"></div>
|
||||||
|
|
||||||
|
<input type="button" onclick="makeFocusReply()" title="<$= LANG.REPLY_CREATOR_CREATE $>" value="<$= LANG.REPLY_CREATOR_CREATE $>" id="onionr-post-focus-reply-creator-create" class="onionr-post-creator-create" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-focus-replies"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST FOCUS REPLIES -->
|
31
onionr/static-data/www/ui/common/onionr-timeline-reply.html
Normal file
31
onionr/static-data/www/ui/common/onionr-timeline-reply.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<!-- POST -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-post" id="onionr-post-$post-hash" onclick="focusPost('$post-hash', 'user-id-url', 'user-name-url', '')">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-3">
|
||||||
|
<img class="onionr-post-user-icon" src="$user-image">
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-user-name" id="onionr-post-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')">$user-name</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-auto text-right ml-auto pl-0">
|
||||||
|
<div class="onionr-post-date text-right" data-placement="top" data-toggle="tooltip" title="$date">$date-relative-truncated</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-content">
|
||||||
|
$content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-controls pt-2">
|
||||||
|
<a href="#!" onclick="toggleLike('$post-hash')" class="glyphicon glyphicon-heart mr-2">$liked</a>
|
||||||
|
<a href="#!" onclick="reply('$post-hash')" class="glyphicon glyphicon-comment mr-2"><$= LANG.POST_REPLY $></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST -->
|
@ -41,16 +41,16 @@ LANG = type('LANG', (), langmap)
|
|||||||
|
|
||||||
# templating
|
# templating
|
||||||
class Template:
|
class Template:
|
||||||
def jsTemplate(template):
|
def jsTemplate(template, filename = ''):
|
||||||
with open('common/%s.html' % template, 'r') as file:
|
with open('common/%s.html' % template, 'r') as file:
|
||||||
return Template.parseTags(file.read().replace('\\', '\\\\').replace('\'', '\\\'').replace('\n', "\\\n"))
|
return Template.parseTags(file.read().replace('\\', '\\\\').replace('\'', '\\\'').replace('\n', "\\\n"), filename)
|
||||||
|
|
||||||
def htmlTemplate(template):
|
def htmlTemplate(template, filename = ''):
|
||||||
with open('common/%s.html' % template, 'r') as file:
|
with open('common/%s.html' % template, 'r') as file:
|
||||||
return Template.parseTags(file.read())
|
return Template.parseTags(file.read(), filename)
|
||||||
|
|
||||||
# tag parser
|
# tag parser
|
||||||
def parseTags(contents):
|
def parseTags(contents, filename = ''):
|
||||||
# <$ logic $>
|
# <$ logic $>
|
||||||
for match in re.findall(r'(<\$(?!=)(.*?)\$>)', contents):
|
for match in re.findall(r'(<\$(?!=)(.*?)\$>)', contents):
|
||||||
try:
|
try:
|
||||||
@ -66,7 +66,7 @@ class Template:
|
|||||||
try:
|
try:
|
||||||
out = eval(match[1].strip())
|
out = eval(match[1].strip())
|
||||||
contents = contents.replace(match[0], '' if out is None else str(out))
|
contents = contents.replace(match[0], '' if out is None else str(out))
|
||||||
except NameError as e:
|
except (NameError, AttributeError) as e:
|
||||||
name = match[1].strip()
|
name = match[1].strip()
|
||||||
print('Warning: %s does not exist, treating as an str' % name)
|
print('Warning: %s does not exist, treating as an str' % name)
|
||||||
contents = contents.replace(match[0], name)
|
contents = contents.replace(match[0], name)
|
||||||
@ -118,7 +118,7 @@ def iterate(directory):
|
|||||||
|
|
||||||
# do python tags
|
# do python tags
|
||||||
if settings['python_tags']:
|
if settings['python_tags']:
|
||||||
contents = Template.parseTags(contents)
|
contents = Template.parseTags(contents, filename)
|
||||||
|
|
||||||
# write file
|
# write file
|
||||||
file.write(contents)
|
file.write(contents)
|
||||||
|
43
onionr/static-data/www/ui/dist/css/main.css
vendored
43
onionr/static-data/www/ui/dist/css/main.css
vendored
@ -37,10 +37,20 @@ body {
|
|||||||
|
|
||||||
/* timeline */
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post-focus-separator {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.onionr-post {
|
.onionr-post {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +70,35 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-name {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-id:before { content: "("; }
|
||||||
|
.onionr-post-creator-user-id:after { content: ")"; }
|
||||||
|
|
||||||
|
.onionr-post-creator-content {
|
||||||
|
word-wrap: break-word;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-create {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.h-divider {
|
.h-divider {
|
||||||
margin: 5px 15px;
|
margin: 5px 15px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@ -77,3 +116,7 @@ body {
|
|||||||
.onionr-profile-username {
|
.onionr-profile-username {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-profile-save {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@ -5,6 +5,17 @@ body {
|
|||||||
|
|
||||||
/* timeline */
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post-focus-separator {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
.onionr-post {
|
.onionr-post {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@ -31,6 +42,35 @@ body {
|
|||||||
font-size: 15pt;
|
font-size: 15pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-name {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-id {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-date {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-content {
|
||||||
|
font-family: sans-serif, serif;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
font-size: 15pt;
|
||||||
|
background-color: lightgray;
|
||||||
|
color: black;
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.h-divider {
|
.h-divider {
|
||||||
border-top:1px solid gray;
|
border-top:1px solid gray;
|
||||||
}
|
}
|
||||||
|
144
onionr/static-data/www/ui/dist/index.html
vendored
144
onionr/static-data/www/ui/dist/index.html
vendored
@ -40,10 +40,24 @@
|
|||||||
<div class="onionr-profile">
|
<div class="onionr-profile">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4 col-lg-12">
|
<div class="col-4 col-lg-12">
|
||||||
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
|
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8 col-lg-12">
|
<div class="col-8 col-lg-12">
|
||||||
<h2 id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left">arinerron</h2>
|
<h2 maxlength="25" id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left" data-placement="top" data-toggle="tooltip" title="unknown" data-editable></h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p maxlength="128" id="onionr-profile-description" class="onionr-profile-description" data-editable></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 onionr-profile-edit" id="onionr-profile-edit" style="display: none">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 col-lg-12">
|
||||||
|
<input type="button" onclick="updateUser()" class="onionr-profile-save text-center" id="onionr-profile-save" value="Save" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-12">
|
||||||
|
<input type="button" onclick="cancelUpdate()" class="onionr-profile-save text-center" id="onionr-profile-cancel" value="Cancel" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,6 +66,40 @@
|
|||||||
<div class="h-divider pb-3 d-block d-lg-none"></div>
|
<div class="h-divider pb-3 d-block d-lg-none"></div>
|
||||||
|
|
||||||
<div class="col-sm-12 col-lg-6">
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
<div class="row" id="onionr-timeline-post-creator">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-timeline">
|
||||||
|
<h2>Timeline</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- POST CREATOR -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-post-creator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-post-creator-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-post-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-post-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">you</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-post-creator-content" oninput="postCreatorChange()"></textarea>
|
||||||
|
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-post-creator-content-message"></div>
|
||||||
|
|
||||||
|
<input type="button" onclick="makePost()" title="Create post" value="Create post" id="onionr-post-creator-create" class="onionr-post-creator-create" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST CREATOR -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" id="onionr-timeline-posts">
|
<div class="row" id="onionr-timeline-posts">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -60,11 +108,99 @@
|
|||||||
<div class="d-none d-lg-block col-lg-3">
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="onionr-trending">
|
<div class="onionr-replies">
|
||||||
<h2>Trending</h2>
|
<h2 id="onionr-replies-title"></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="onionr-reply-creator-panel">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- POST FOCUS DIALOG -->
|
||||||
|
<div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row p-3">
|
||||||
|
<div class="col-2">
|
||||||
|
<img src="" id="onionr-post-focus-user-icon" class="onionr-post-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-user-name" id="onionr-post-focus-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');">$user-name</a>
|
||||||
|
<a class="onionr-post-user-id" id="onionr-post-focus-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-auto text-right ml-auto pl-0">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="onionr-post-content" id="onionr-post-focus-content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="col-12 onionr-post-focus-separator" />
|
||||||
|
|
||||||
|
<!-- POST FOCUS REPLIES -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="row">
|
||||||
|
<div class="onionr-post-focus-reply-creator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-1"></div>
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-post-focus-reply-creator-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-post-focus-reply-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-post-focus-reply-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id">you</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-post-focus-reply-creator-content" oninput="focusReplyCreatorChange()"></textarea>
|
||||||
|
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-post-focus-reply-creator-content-message"></div>
|
||||||
|
|
||||||
|
<input type="button" onclick="makeFocusReply()" title="Reply" value="Reply" id="onionr-post-focus-reply-creator-create" class="onionr-post-creator-create" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="onionr-post-focus-replies"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST FOCUS REPLIES -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST FOCUS DIALOG -->
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="modal" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modal-title">Loading...</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modal-content">Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
354
onionr/static-data/www/ui/dist/js/main.js
vendored
354
onionr/static-data/www/ui/dist/js/main.js
vendored
File diff suppressed because one or more lines are too long
486
onionr/static-data/www/ui/dist/js/timeline.js
vendored
486
onionr/static-data/www/ui/dist/js/timeline.js
vendored
@ -4,24 +4,488 @@ Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, fun
|
|||||||
try {
|
try {
|
||||||
var block = data[i];
|
var block = data[i];
|
||||||
|
|
||||||
var post = new Post();
|
var finished = false;
|
||||||
var user = new User();
|
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
var blockContent = JSON.parse(block.getContent());
|
var blockContent = JSON.parse(block.getContent());
|
||||||
|
|
||||||
user.setName('unknown');
|
// just ignore anything shorter than 280 characters
|
||||||
user.setID(new String(block.getHeader('signer', 'unknown')));
|
if(String(blockContent['content']).length <= 280 && block.getParent() === null) {
|
||||||
post.setContent(blockContent['content']);
|
post.setContent(blockContent['content']);
|
||||||
post.setPostDate(block.getDate());
|
post.setPostDate(block.getDate());
|
||||||
post.setUser(user);
|
post.setUser(user);
|
||||||
|
|
||||||
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
post.setHash(block.getHash());
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while(!finished);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.log('Troublemaker block: ' + data[i].getHash());
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function viewProfile(id, name) {
|
function toggleLike(hash) {
|
||||||
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
|
var post = getPostMap(hash);
|
||||||
|
if(post === null || !getPostMap()[hash]['liked']) {
|
||||||
|
console.log('Liking ' + hash + '...');
|
||||||
|
|
||||||
|
if(post === null)
|
||||||
|
getPostMap()[hash] = {};
|
||||||
|
|
||||||
|
getPostMap()[hash]['liked'] = true;
|
||||||
|
|
||||||
|
set('postmap', JSON.stringify(getPostMap()));
|
||||||
|
|
||||||
|
var block = new Block();
|
||||||
|
|
||||||
|
block.setType('onionr-post-like');
|
||||||
|
block.setContent(JSON.stringify({'hash' : hash}));
|
||||||
|
block.save(true, function(hash) {});
|
||||||
|
} else {
|
||||||
|
console.log('Unliking ' + hash + '...');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function postCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-post-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = 'Please use less than 16 newlines';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '%s characters remaining'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-post-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-post-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replyCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-reply-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = 'Please use less than 16 newlines';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '%s characters remaining'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-reply-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-reply-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusReplyCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-post-focus-reply-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = 'Please use less than 16 newlines';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '%s characters remaining'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '%s characters over maximum'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-post-focus-reply-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-post-focus-reply-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewProfile(id, name) {
|
||||||
|
id = decodeURIComponent(id);
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
|
||||||
|
|
||||||
|
User.getUser(id, function(data) {
|
||||||
|
if(data !== null) {
|
||||||
|
document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon());
|
||||||
|
document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon());
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(Sanitize.username(data.getName()));
|
||||||
|
document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID());
|
||||||
|
document.getElementById("onionr-profile-description").innerHTML = Sanitize.html(Sanitize.description(data.getDescription()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser() {
|
||||||
|
toggleSaveButton(false);
|
||||||
|
|
||||||
|
// jQuery('#modal').modal('show');
|
||||||
|
|
||||||
|
var name = jQuery('#onionr-profile-username').text();
|
||||||
|
var id = document.getElementById("onionr-profile-username").title;
|
||||||
|
var icon = document.getElementById("onionr-profile-user-icon").b64;
|
||||||
|
var description = jQuery("#onionr-profile-description").text();
|
||||||
|
|
||||||
|
var user = new User();
|
||||||
|
|
||||||
|
user.setName(name);
|
||||||
|
user.setID(id);
|
||||||
|
user.setIcon(icon);
|
||||||
|
user.setDescription(Sanitize.description(description));
|
||||||
|
|
||||||
|
user.remember();
|
||||||
|
user.save(function() {
|
||||||
|
setCurrentUser(user);
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelUpdate() {
|
||||||
|
toggleSaveButton(false);
|
||||||
|
|
||||||
|
var name = jQuery('#onionr-profile-username').text();
|
||||||
|
var id = document.getElementById("onionr-profile-username").title;
|
||||||
|
|
||||||
|
viewProfile(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSaveButton(show) {
|
||||||
|
document.getElementById("onionr-profile-edit").style.display = (show ? 'block' : 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePost() {
|
||||||
|
var content = document.getElementById("onionr-post-creator-content").value;
|
||||||
|
|
||||||
|
if(content.trim() !== '') {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
post.setUser(getCurrentUser());
|
||||||
|
post.setContent(content);
|
||||||
|
post.setPostDate(new Date());
|
||||||
|
|
||||||
|
post.save(function(data) {}); // async, but no function
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML;
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-content").value = "";
|
||||||
|
document.getElementById("onionr-post-creator-content").focus();
|
||||||
|
postCreatorChange();
|
||||||
|
} else {
|
||||||
|
console.log('Not making empty post.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplies(id, callback) {
|
||||||
|
Block.getBlocks({'type' : 'onionr-post', 'parent' : id, 'signed' : true, 'reverse' : true}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusPost(id) {
|
||||||
|
viewReplies(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewRepliesMobile(id) {
|
||||||
|
var post = document.getElementById('onionr-post-' + id);
|
||||||
|
|
||||||
|
var user_name = '';
|
||||||
|
var user_id = '';
|
||||||
|
var user_id_trunc = '';
|
||||||
|
var user_icon = '';
|
||||||
|
var post_content = '';
|
||||||
|
|
||||||
|
if(post !== null && post !== undefined) {
|
||||||
|
// if the post is in the timeline, get the data from it
|
||||||
|
user_name = post.getElementsByClassName('onionr-post-user-name')[0].innerHTML;
|
||||||
|
user_id = post.getElementsByClassName('onionr-post-user-id')[0].title;
|
||||||
|
user_id_trunc = post.getElementsByClassName('onionr-post-user-id')[0].innerHTML;
|
||||||
|
user_icon = post.getElementsByClassName('onionr-post-user-icon')[0].src;
|
||||||
|
post_content = post.getElementsByClassName('onionr-post-content')[0].innerHTML;
|
||||||
|
} else {
|
||||||
|
// otherwise, fetch the data
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('onionr-post-focus-user-icon').src = user_icon;
|
||||||
|
document.getElementById('onionr-post-focus-user-name').innerHTML = user_name;
|
||||||
|
document.getElementById('onionr-post-focus-user-id').innerHTML = user_id_trunc;
|
||||||
|
document.getElementById('onionr-post-focus-user-id').title = user_id;
|
||||||
|
document.getElementById('onionr-post-focus-content').innerHTML = post_content;
|
||||||
|
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-content').value = '';
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-content-message').value = '';
|
||||||
|
|
||||||
|
jQuery('#onionr-post-focus').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewReplies(id) {
|
||||||
|
document.getElementById('onionr-replies-title').innerHTML = 'Replies';
|
||||||
|
document.getElementById('onionr-reply-creator-panel').originalPost = id;
|
||||||
|
document.getElementById('onionr-reply-creator-panel').innerHTML = '<!-- POST REPLIES -->\
|
||||||
|
<div class="onionr-post-creator">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="onionr-reply-creator container">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="col-3">\
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-reply-creator-user-icon">\
|
||||||
|
</div>\
|
||||||
|
<div class="col-9">\
|
||||||
|
<div class="row">\
|
||||||
|
<div class="col col-auto">\
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-reply-creator-user-name" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')"></a>\
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-reply-creator-user-id" href="#!" onclick="viewProfile(\'$user-id-url\', \'$user-name-url\')" data-placement="top" data-toggle="tooltip" title="$user-id">you</a>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
\
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-reply-creator-content" oninput="replyCreatorChange()"></textarea>\
|
||||||
|
\
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-reply-creator-content-message"></div>\
|
||||||
|
\
|
||||||
|
<input type="button" onclick="makeReply()" title="Reply" value="Reply" id="onionr-reply-creator-create" class="onionr-post-creator-create" />\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
\
|
||||||
|
<div class="row">\
|
||||||
|
<div id="onionr-replies"></div>\
|
||||||
|
</div>\
|
||||||
|
<!-- END POST REPLIES -->\
|
||||||
|
';
|
||||||
|
|
||||||
|
document.getElementById('onionr-reply-creator-content').innerHTML = '';
|
||||||
|
document.getElementById("onionr-reply-creator-content").placeholder = "Enter a message here...";
|
||||||
|
document.getElementById('onionr-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
|
||||||
|
document.getElementById('onionr-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
|
||||||
|
|
||||||
|
document.getElementById('onionr-replies').innerHTML = '';
|
||||||
|
getReplies(id, function(data) {
|
||||||
|
var replies = document.getElementById('onionr-replies');
|
||||||
|
|
||||||
|
replies.innerHTML = '';
|
||||||
|
|
||||||
|
for(var i = 0; i < data.length; i++) {
|
||||||
|
try {
|
||||||
|
var block = data[i];
|
||||||
|
|
||||||
|
var finished = false;
|
||||||
|
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
var blockContent = JSON.parse(block.getContent());
|
||||||
|
|
||||||
|
// just ignore anything shorter than 280 characters
|
||||||
|
if(String(blockContent['content']).length <= 280) {
|
||||||
|
post.setContent(blockContent['content']);
|
||||||
|
post.setPostDate(block.getDate());
|
||||||
|
post.setUser(user);
|
||||||
|
|
||||||
|
post.setHash(block.getHash());
|
||||||
|
|
||||||
|
replies.innerHTML += post.getHTML('reply');
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while(!finished);
|
||||||
|
} catch(e) {
|
||||||
|
console.log('Troublemaker block: ' + data[i].getHash());
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeReply() {
|
||||||
|
var content = document.getElementById("onionr-reply-creator-content").value;
|
||||||
|
|
||||||
|
if(content.trim() !== '') {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
var originalPost = document.getElementById('onionr-reply-creator-panel').originalPost;
|
||||||
|
|
||||||
|
console.log('Original post hash: ' + originalPost);
|
||||||
|
|
||||||
|
post.setUser(getCurrentUser());
|
||||||
|
post.setParent(originalPost);
|
||||||
|
post.setContent(content);
|
||||||
|
post.setPostDate(new Date());
|
||||||
|
|
||||||
|
post.save(function(data) {}); // async, but no function
|
||||||
|
|
||||||
|
document.getElementById('onionr-replies').innerHTML = post.getHTML('reply') + document.getElementById('onionr-replies').innerHTML;
|
||||||
|
|
||||||
|
document.getElementById("onionr-reply-creator-content").value = "";
|
||||||
|
document.getElementById("onionr-reply-creator-content").focus();
|
||||||
|
replyCreatorChange();
|
||||||
|
} else {
|
||||||
|
console.log('Not making empty reply.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery('body').on('click', '[data-editable]', function() {
|
||||||
|
var el = jQuery(this);
|
||||||
|
var txt = el.text();
|
||||||
|
var maxlength = el.attr("maxlength");
|
||||||
|
|
||||||
|
var input = jQuery('<input/>').val(txt);
|
||||||
|
input.attr('maxlength', maxlength);
|
||||||
|
el.replaceWith(input);
|
||||||
|
|
||||||
|
var save = function() {
|
||||||
|
var newTxt = input.val();
|
||||||
|
|
||||||
|
if(el.attr('id') === 'onionr-profile-username')
|
||||||
|
newTxt = Sanitize.username(newTxt);
|
||||||
|
if(el.attr('id') === 'onionr-profile-description')
|
||||||
|
newTxt = Sanitize.description(newTxt);
|
||||||
|
|
||||||
|
var p = el.text(newTxt);
|
||||||
|
|
||||||
|
input.replaceWith(p);
|
||||||
|
|
||||||
|
if(newTxt !== txt)
|
||||||
|
toggleSaveButton(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
var saveEnter = function(event) {
|
||||||
|
console.log(event);
|
||||||
|
console.log(event.keyCode);
|
||||||
|
if (event.keyCode === 13)
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
input.one('blur', save).bind('keyup', saveEnter).focus();
|
||||||
|
});
|
||||||
|
//viewProfile('$user-id-url', '$user-name-url')
|
||||||
|
// jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);});
|
||||||
|
//jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); });
|
||||||
|
// jQuery('#onionr-post').click(function(e) { alert(1); });
|
||||||
|
|
||||||
|
currentUser = getCurrentUser();
|
||||||
|
if(currentUser !== undefined && currentUser !== null) {
|
||||||
|
document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName());
|
||||||
|
document.getElementById("onionr-post-creator-user-id").innerHTML = "you";
|
||||||
|
document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon());
|
||||||
|
document.getElementById("onionr-post-creator-user-id").title = currentUser.getID();
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-content").placeholder = "Enter a message here...";
|
||||||
|
document.getElementById("onionr-post-focus-reply-creator-content").placeholder = "Enter a message here...";
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-focus-reply-creator-user-id").innerHTML = "you";
|
||||||
|
}
|
||||||
|
|
||||||
|
viewCurrentProfile = function() {
|
||||||
|
viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile;
|
||||||
|
document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile;
|
||||||
|
|
||||||
|
// on some browsers it saves the user input on reload. So, it should also recheck the input.
|
||||||
|
postCreatorChange();
|
||||||
|
@ -6,10 +6,35 @@
|
|||||||
"NOTIFICATIONS" : "Notifications",
|
"NOTIFICATIONS" : "Notifications",
|
||||||
"MESSAGES" : "Messages",
|
"MESSAGES" : "Messages",
|
||||||
|
|
||||||
|
"LATEST" : "Latest...",
|
||||||
"TRENDING" : "Trending",
|
"TRENDING" : "Trending",
|
||||||
|
"REPLIES" : "Replies",
|
||||||
|
|
||||||
|
"MODAL_TITLE" : "Loading...",
|
||||||
|
"MODAL_MESSAGE" : "Onionr has begun performing a CPU-intensive operation. If this operation does not complete in the next 10 seconds, try reloading the page.",
|
||||||
|
|
||||||
"POST_LIKE" : "like",
|
"POST_LIKE" : "like",
|
||||||
"POST_REPLY" : "reply"
|
"POST_UNLIKE" : "unlike",
|
||||||
|
"POST_REPLY" : "reply",
|
||||||
|
|
||||||
|
"POST_CREATOR_YOU" : "you",
|
||||||
|
"POST_CREATOR_PLACEHOLDER" : "Enter a message here...",
|
||||||
|
"POST_CREATOR_CREATE" : "Create post",
|
||||||
|
|
||||||
|
"REPLY_CREATOR_YOU" : "you",
|
||||||
|
"REPLY_CREATOR_PLACEHOLDER" : "Enter reply here...",
|
||||||
|
"REPLY_CREATOR_CREATE" : "Reply",
|
||||||
|
|
||||||
|
"POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines",
|
||||||
|
"POST_CREATOR_MESSAGE_REMAINING" : "%s characters remaining",
|
||||||
|
"POST_CREATOR_MESSAGE_OVER" : "%s characters over maximum",
|
||||||
|
|
||||||
|
"REPLY_CREATOR_MESSAGE_MAXIMUM_NEWLINES" : "Please use less than 16 newlines",
|
||||||
|
"REPLY_CREATOR_MESSAGE_REMAINING" : "%s characters remaining",
|
||||||
|
"REPLY_CREATOR_MESSAGE_OVER" : "%s characters over maximum",
|
||||||
|
|
||||||
|
"PROFILE_EDIT_SAVE" : "Save",
|
||||||
|
"PROFILE_EDIT_CANCEL" : "Cancel"
|
||||||
},
|
},
|
||||||
|
|
||||||
"spa" : {
|
"spa" : {
|
||||||
|
@ -37,10 +37,20 @@ body {
|
|||||||
|
|
||||||
/* timeline */
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post-focus-separator {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.onionr-post {
|
.onionr-post {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +70,35 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-name {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-id:before { content: "("; }
|
||||||
|
.onionr-post-creator-user-id:after { content: ")"; }
|
||||||
|
|
||||||
|
.onionr-post-creator-content {
|
||||||
|
word-wrap: break-word;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-icon {
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-create {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.h-divider {
|
.h-divider {
|
||||||
margin: 5px 15px;
|
margin: 5px 15px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@ -77,3 +116,7 @@ body {
|
|||||||
.onionr-profile-username {
|
.onionr-profile-username {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-profile-save {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
@ -5,6 +5,17 @@ body {
|
|||||||
|
|
||||||
/* timeline */
|
/* timeline */
|
||||||
|
|
||||||
|
.onionr-post-focus-separator {
|
||||||
|
border-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
.onionr-post {
|
.onionr-post {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@ -31,6 +42,35 @@ body {
|
|||||||
font-size: 15pt;
|
font-size: 15pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-name {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-user-id {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-date {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onionr-post-creator-content {
|
||||||
|
font-family: sans-serif, serif;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
font-size: 15pt;
|
||||||
|
background-color: lightgray;
|
||||||
|
color: black;
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.h-divider {
|
.h-divider {
|
||||||
border-top:1px solid gray;
|
border-top:1px solid gray;
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,24 @@
|
|||||||
<div class="onionr-profile">
|
<div class="onionr-profile">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-4 col-lg-12">
|
<div class="col-4 col-lg-12">
|
||||||
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="img/default.png">
|
<img id="onionr-profile-user-icon" class="onionr-profile-user-icon" src="">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8 col-lg-12">
|
<div class="col-8 col-lg-12">
|
||||||
<h2 id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left">arinerron</h2>
|
<h2 maxlength="25" id="onionr-profile-username" class="onionr-profile-username text-left text-lg-center text-sm-left" data-placement="top" data-toggle="tooltip" title="unknown" data-editable></h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<p maxlength="128" id="onionr-profile-description" class="onionr-profile-description" data-editable></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 onionr-profile-edit" id="onionr-profile-edit" style="display: none">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 col-lg-12">
|
||||||
|
<input type="button" onclick="updateUser()" class="onionr-profile-save text-center" id="onionr-profile-save" value="<$= LANG.PROFILE_EDIT_SAVE $>" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-lg-12">
|
||||||
|
<input type="button" onclick="cancelUpdate()" class="onionr-profile-save text-center" id="onionr-profile-cancel" value="<$= LANG.PROFILE_EDIT_CANCEL $>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -22,6 +36,40 @@
|
|||||||
<div class="h-divider pb-3 d-block d-lg-none"></div>
|
<div class="h-divider pb-3 d-block d-lg-none"></div>
|
||||||
|
|
||||||
<div class="col-sm-12 col-lg-6">
|
<div class="col-sm-12 col-lg-6">
|
||||||
|
<div class="row" id="onionr-timeline-post-creator">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-timeline">
|
||||||
|
<h2><$= LANG.TIMELINE $></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- POST CREATOR -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="onionr-post-creator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<img class="onionr-post-creator-user-icon" id="onionr-post-creator-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-creator-user-name" id="onionr-post-creator-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')"></a>
|
||||||
|
<a class="onionr-post-creator-user-id" id="onionr-post-creator-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url')" data-placement="top" data-toggle="tooltip" title="$user-id"><$= LANG.POST_CREATOR_YOU $></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea class="onionr-post-creator-content" id="onionr-post-creator-content" oninput="postCreatorChange()"></textarea>
|
||||||
|
|
||||||
|
<div class="onionr-post-creator-content-message" id="onionr-post-creator-content-message"></div>
|
||||||
|
|
||||||
|
<input type="button" onclick="makePost()" title="<$= LANG.POST_CREATOR_CREATE $>" value="<$= LANG.POST_CREATOR_CREATE $>" id="onionr-post-creator-create" class="onionr-post-creator-create" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST CREATOR -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" id="onionr-timeline-posts">
|
<div class="row" id="onionr-timeline-posts">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -30,15 +78,58 @@
|
|||||||
<div class="d-none d-lg-block col-lg-3">
|
<div class="d-none d-lg-block col-lg-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="onionr-trending">
|
<div class="onionr-replies">
|
||||||
<h2><$= LANG.TRENDING $></h2>
|
<h2 id="onionr-replies-title"></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="onionr-reply-creator-panel">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- POST FOCUS DIALOG -->
|
||||||
|
<div class="modal fade" id="onionr-post-focus" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row p-3">
|
||||||
|
<div class="col-2">
|
||||||
|
<img src="" id="onionr-post-focus-user-icon" class="onionr-post-user-icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-auto">
|
||||||
|
<a class="onionr-post-user-name" id="onionr-post-focus-user-name" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');">$user-name</a>
|
||||||
|
<a class="onionr-post-user-id" id="onionr-post-focus-user-id" href="#!" onclick="viewProfile('$user-id-url', '$user-name-url'); jQuery('#onionr-post-focus').modal('hide');" data-placement="top" data-toggle="tooltip" title="$user-id">$user-id-truncated</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col col-auto text-right ml-auto pl-0">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="onionr-post-content" id="onionr-post-focus-content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="col-12 onionr-post-focus-separator" />
|
||||||
|
|
||||||
|
<$= htmlTemplate('onionr-timeline-reply-creator') $>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- END POST FOCUS DIALOG -->
|
||||||
|
|
||||||
<footer />
|
<footer />
|
||||||
<script src="js/timeline.js"></script>
|
<script src="js/timeline.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -17,18 +17,56 @@ function remove(key) {
|
|||||||
return localStorage.removeItem(key);
|
return localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getParameter(name) {
|
||||||
|
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
|
||||||
|
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* usermap localStorage stuff */
|
||||||
|
|
||||||
var usermap = JSON.parse(get('usermap', '{}'));
|
var usermap = JSON.parse(get('usermap', '{}'));
|
||||||
|
var postmap = JSON.parse(get('postmap', '{}'))
|
||||||
|
|
||||||
function getUserMap() {
|
function getUserMap() {
|
||||||
return usermap;
|
return usermap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPostMap(hash) {
|
||||||
|
if(hash !== undefined) {
|
||||||
|
if(hash in postmap)
|
||||||
|
return postmap[hash];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return postmap;
|
||||||
|
}
|
||||||
|
|
||||||
function deserializeUser(id) {
|
function deserializeUser(id) {
|
||||||
|
if(!(id in getUserMap()))
|
||||||
|
return null;
|
||||||
|
|
||||||
var serialized = getUserMap()[id]
|
var serialized = getUserMap()[id]
|
||||||
var user = new User();
|
var user = new User();
|
||||||
|
|
||||||
user.setName(serialized['name']);
|
user.setName(serialized['name']);
|
||||||
user.setID(serialized['id']);
|
user.setID(serialized['id']);
|
||||||
user.setIcon(serialized['icon']);
|
user.setIcon(serialized['icon']);
|
||||||
|
user.setDescription(serialized['description']);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentUser() {
|
||||||
|
var user = get('currentUser', null);
|
||||||
|
|
||||||
|
if(user === null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return User.getUser(user, function() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCurrentUser(user) {
|
||||||
|
set('currentUser', user.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns a relative date format, e.g. "5 minutes" */
|
/* returns a relative date format, e.g. "5 minutes" */
|
||||||
@ -89,10 +127,10 @@ function timeSince(date, size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* replace all instances of string */
|
/* replace all instances of string */
|
||||||
String.prototype.replaceAll = function(search, replacement) {
|
String.prototype.replaceAll = function(search, replacement, limit) {
|
||||||
// taken from https://stackoverflow.com/a/17606289/3678023
|
// taken from https://stackoverflow.com/a/17606289/3678023
|
||||||
var target = this;
|
var target = this;
|
||||||
return target.split(search).join(replacement);
|
return target.split(search, limit).join(replacement);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* useful functions to sanitize data */
|
/* useful functions to sanitize data */
|
||||||
@ -106,6 +144,16 @@ class Sanitize {
|
|||||||
static url(url) {
|
static url(url) {
|
||||||
return encodeURIComponent(url);
|
return encodeURIComponent(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* usernames */
|
||||||
|
static username(username) {
|
||||||
|
return String(username).replace(/[\W_]+/g, " ").substring(0, 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* profile descriptions */
|
||||||
|
static description(description) {
|
||||||
|
return String(description).substring(0, 128);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* config stuff */
|
/* config stuff */
|
||||||
@ -157,42 +205,118 @@ class User {
|
|||||||
return this.image;
|
return this.image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDescription(description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
serialize() {
|
serialize() {
|
||||||
return {
|
return {
|
||||||
'name' : this.getName(),
|
'name' : this.getName(),
|
||||||
'id' : this.getID(),
|
'id' : this.getID(),
|
||||||
'icon' : this.getIcon()
|
'icon' : this.getIcon(),
|
||||||
|
'description' : this.getDescription()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* save in usermap */
|
||||||
remember() {
|
remember() {
|
||||||
usermap[this.getID()] = this.serialize();
|
usermap[this.getID()] = this.serialize();
|
||||||
set('usermap', JSON.stringify(usermap));
|
set('usermap', JSON.stringify(usermap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* save as a block */
|
||||||
|
save(callback) {
|
||||||
|
var block = new Block();
|
||||||
|
|
||||||
|
block.setType('onionr-user');
|
||||||
|
block.setContent(JSON.stringify(this.serialize()));
|
||||||
|
|
||||||
|
return block.save(true, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getUser(id, callback) {
|
||||||
|
// console.log(callback);
|
||||||
|
var user = deserializeUser(id);
|
||||||
|
if(user === null) {
|
||||||
|
Block.getBlocks({'type' : 'onionr-user-info', 'signed' : true, 'reverse' : true}, function(data) {
|
||||||
|
if(data.length !== 0) {
|
||||||
|
try {
|
||||||
|
user = new User();
|
||||||
|
|
||||||
|
var userInfo = JSON.parse(data[0].getContent());
|
||||||
|
|
||||||
|
if(userInfo['id'] === id) {
|
||||||
|
user.setName(userInfo['name']);
|
||||||
|
user.setIcon(userInfo['icon']);
|
||||||
|
user.setDescription(userInfo['description']);
|
||||||
|
user.setID(id);
|
||||||
|
|
||||||
|
user.remember();
|
||||||
|
// console.log(callback);
|
||||||
|
callback(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// console.log(callback);
|
||||||
|
callback(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* post class */
|
/* post class */
|
||||||
class Post {
|
class Post {
|
||||||
/* returns the html content of a post */
|
/* returns the html content of a post */
|
||||||
getHTML() {
|
getHTML(type) {
|
||||||
|
var replyTemplate = '<$= jsTemplate('onionr-timeline-reply') $>';
|
||||||
var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>';
|
var postTemplate = '<$= jsTemplate('onionr-timeline-post') $>';
|
||||||
|
|
||||||
|
var template = '';
|
||||||
|
|
||||||
|
if(type !== undefined && type !== null && type == 'reply')
|
||||||
|
template = replyTemplate;
|
||||||
|
else
|
||||||
|
template = postTemplate;
|
||||||
|
|
||||||
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
|
var device = (jQuery(document).width() < 768 ? 'mobile' : 'desktop');
|
||||||
|
|
||||||
postTemplate = postTemplate.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName())));
|
template = template.replaceAll('$user-name-url', Sanitize.html(Sanitize.url(this.getUser().getName())));
|
||||||
postTemplate = postTemplate.replaceAll('$user-name', Sanitize.html(this.getUser().getName()));
|
template = template.replaceAll('$user-name', Sanitize.html(this.getUser().getName()));
|
||||||
postTemplate = postTemplate.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID())));
|
template = template.replaceAll('$user-id-url', Sanitize.html(Sanitize.url(this.getUser().getID())));
|
||||||
|
|
||||||
postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...'));
|
template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().substring(0, 12) + '...'));
|
||||||
// postTemplate = postTemplate.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-')));
|
// template = template.replaceAll('$user-id-truncated', Sanitize.html(this.getUser().getID().split('-').slice(0, 4).join('-')));
|
||||||
|
|
||||||
postTemplate = postTemplate.replaceAll('$user-id', Sanitize.html(this.getUser().getID()));
|
template = template.replaceAll('$user-id', Sanitize.html(this.getUser().getID()));
|
||||||
postTemplate = postTemplate.replaceAll('$user-image', Sanitize.html(this.getUser().getIcon()));
|
template = template.replaceAll('$user-image', "data:image/jpeg;base64," + Sanitize.html(this.getUser().getIcon()));
|
||||||
postTemplate = postTemplate.replaceAll('$content', Sanitize.html(this.getContent()));
|
template = template.replaceAll('$content', Sanitize.html(this.getContent()).replaceAll('\n', '<br />', 16)); // Maximum of 16 lines
|
||||||
postTemplate = postTemplate.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
|
template = template.replaceAll('$post-hash', this.getHash());
|
||||||
postTemplate = postTemplate.replaceAll('$date', this.getPostDate().toLocaleString());
|
template = template.replaceAll('$date-relative-truncated', timeSince(this.getPostDate(), 'mobile'));
|
||||||
|
template = template.replaceAll('$date-relative', timeSince(this.getPostDate(), device) + (device === 'desktop' ? ' ago' : ''));
|
||||||
|
template = template.replaceAll('$date', this.getPostDate().toLocaleString());
|
||||||
|
|
||||||
return postTemplate;
|
if(this.getHash() in getPostMap() && getPostMap()[this.getHash()]['liked']) {
|
||||||
|
template = template.replaceAll('$liked', '<$= LANG.POST_UNLIKE $>');
|
||||||
|
} else {
|
||||||
|
template = template.replaceAll('$liked', '<$= LANG.POST_LIKE $>');
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(user) {
|
setUser(user) {
|
||||||
@ -211,6 +335,14 @@ class Post {
|
|||||||
return this.content;
|
return this.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setParent(parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
getParent() {
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
|
||||||
setPostDate(date) { // unix timestamp input
|
setPostDate(date) { // unix timestamp input
|
||||||
if(date instanceof Date)
|
if(date instanceof Date)
|
||||||
this.date = date;
|
this.date = date;
|
||||||
@ -221,6 +353,51 @@ class Post {
|
|||||||
getPostDate() {
|
getPostDate() {
|
||||||
return this.date;
|
return this.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setHash(hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHash() {
|
||||||
|
return this.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(callback) {
|
||||||
|
var args = {'type' : 'onionr-post', 'sign' : true, 'content' : JSON.stringify({'content' : this.getContent()})};
|
||||||
|
|
||||||
|
if(this.getParent() !== undefined && this.getParent() !== null)
|
||||||
|
args['parent'] = (this.getParent() instanceof Post ? this.getParent().getHash() : (this.getParent() instanceof Block ? this.getParent().getHash() : this.getParent()));
|
||||||
|
|
||||||
|
var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken());
|
||||||
|
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
var http = new XMLHttpRequest();
|
||||||
|
|
||||||
|
if(callback !== undefined) {
|
||||||
|
// async
|
||||||
|
|
||||||
|
var thisObject = this;
|
||||||
|
|
||||||
|
http.addEventListener('load', function() {
|
||||||
|
thisObject.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash']));
|
||||||
|
callback(thisObject.getHash());
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
http.open('GET', url, true);
|
||||||
|
http.timeout = 5000;
|
||||||
|
http.send(null);
|
||||||
|
} else {
|
||||||
|
// sync
|
||||||
|
|
||||||
|
http.open('GET', url, false);
|
||||||
|
http.send(null);
|
||||||
|
|
||||||
|
this.setHash(Block.parseBlockArray(JSON.parse(http.responseText)['hash']));
|
||||||
|
|
||||||
|
return this.getHash();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* block class */
|
/* block class */
|
||||||
@ -269,8 +446,12 @@ class Block {
|
|||||||
|
|
||||||
// returns the parent block's hash (not Block object, for performance)
|
// returns the parent block's hash (not Block object, for performance)
|
||||||
getParent() {
|
getParent() {
|
||||||
if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null)
|
// console.log(this.parent);
|
||||||
this.parent = Block.openBlock(this.parent); // convert hash to Block object
|
|
||||||
|
// TODO: Create a function to fetch the block contents and parse it from the server; right now it is only possible to search for types of blocks (see Block.getBlocks), so it is impossible to return a Block object here
|
||||||
|
|
||||||
|
// if(!(this.parent instanceof Block) && this.parent !== undefined && this.parent !== null)
|
||||||
|
// this.parent = Block.openBlock(this.parent); // convert hash to Block object
|
||||||
return this.parent;
|
return this.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,11 +504,57 @@ class Block {
|
|||||||
return !(this.hash === null || this.hash === undefined);
|
return !(this.hash === null || this.hash === undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saves the block, returns the hash
|
||||||
|
save(sign, callback) {
|
||||||
|
var type = this.getType();
|
||||||
|
var content = this.getContent();
|
||||||
|
var parent = this.getParent();
|
||||||
|
|
||||||
|
if(content !== undefined && content !== null && type !== '') {
|
||||||
|
var args = {'content' : content};
|
||||||
|
|
||||||
|
if(type !== undefined && type !== null && type !== '')
|
||||||
|
args['type'] = type;
|
||||||
|
if(parent !== undefined && parent !== null && parent.getHash() !== undefined && parent.getHash() !== null && parent.getHash() !== '')
|
||||||
|
args['parent'] = parent.getHash();
|
||||||
|
if(sign !== undefined && sign !== null)
|
||||||
|
args['sign'] = String(sign) !== 'false'
|
||||||
|
|
||||||
|
|
||||||
|
var url = '/client/?action=insertBlock&data=' + Sanitize.url(JSON.stringify(args)) + '&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken());
|
||||||
|
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
var http = new XMLHttpRequest();
|
||||||
|
|
||||||
|
if(callback !== undefined) {
|
||||||
|
// async
|
||||||
|
|
||||||
|
http.addEventListener('load', function() {
|
||||||
|
callback(Block.parseBlockArray(JSON.parse(http.responseText)['hash']));
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
http.open('GET', url, true);
|
||||||
|
http.timeout = 5000;
|
||||||
|
http.send(null);
|
||||||
|
} else {
|
||||||
|
// sync
|
||||||
|
|
||||||
|
http.open('GET', url, false);
|
||||||
|
http.send(null);
|
||||||
|
|
||||||
|
return Block.parseBlockArray(JSON.parse(http.responseText)['hash']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* static functions */
|
/* static functions */
|
||||||
|
|
||||||
// recreates a block by hash
|
// recreates a block by hash
|
||||||
static openBlock(hash) {
|
static openBlock(hash) {
|
||||||
return parseBlock(response);
|
return Block.parseBlock(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
// converts an associative array to a Block
|
// converts an associative array to a Block
|
||||||
@ -406,14 +633,57 @@ class Block {
|
|||||||
|
|
||||||
/* temporary code */
|
/* temporary code */
|
||||||
|
|
||||||
|
var tt = getParameter("timingToken");
|
||||||
|
if(tt !== null && tt !== undefined) {
|
||||||
|
setTimingToken(tt);
|
||||||
|
}
|
||||||
|
|
||||||
if(getWebPassword() === null) {
|
if(getWebPassword() === null) {
|
||||||
var password = "";
|
var password = "";
|
||||||
while(password.length != 64) {
|
while(password.length != 64) {
|
||||||
password = prompt("Please enter the web password (run `./RUN-LINUX.sh --get-password`)");
|
password = prompt("Please enter the web password (run `./RUN-LINUX.sh --details`)");
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimingToken(prompt("Please enter the timing token (optional)"));
|
|
||||||
|
|
||||||
setWebPassword(password);
|
setWebPassword(password);
|
||||||
window.location.reload(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(getCurrentUser() === null) {
|
||||||
|
jQuery('#modal').modal('show');
|
||||||
|
|
||||||
|
var url = '/client/?action=info&token=' + Sanitize.url(getWebPassword()) + '&timingToken=' + Sanitize.url(getTimingToken());
|
||||||
|
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
|
var http = new XMLHttpRequest();
|
||||||
|
|
||||||
|
// sync
|
||||||
|
|
||||||
|
http.addEventListener('load', function() {
|
||||||
|
var id = JSON.parse(http.responseText)['pubkey'];
|
||||||
|
|
||||||
|
User.getUser(id, function(data) {
|
||||||
|
if(data === null || data === undefined) {
|
||||||
|
var user = new User();
|
||||||
|
|
||||||
|
user.setName('New User');
|
||||||
|
user.setID(id);
|
||||||
|
user.setIcon('<$= Template.jsTemplate("default-icon") $>');
|
||||||
|
user.setDescription('A new OnionrUI user');
|
||||||
|
|
||||||
|
user.remember();
|
||||||
|
user.save();
|
||||||
|
|
||||||
|
setCurrentUser(user);
|
||||||
|
} else {
|
||||||
|
setCurrentUser(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
http.open('GET', url, true);
|
||||||
|
http.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser = getCurrentUser();
|
||||||
|
@ -1,28 +1,460 @@
|
|||||||
|
|
||||||
/* just for testing rn */
|
/* just for testing rn */
|
||||||
Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) {
|
Block.getBlocks({'type' : 'onionr-post', 'signed' : true, 'reverse' : true}, function(data) {
|
||||||
for(var i = 0; i < data.length; i++) {
|
for(var i = 0; i < data.length; i++) {
|
||||||
try {
|
try {
|
||||||
var block = data[i];
|
var block = data[i];
|
||||||
|
|
||||||
var post = new Post();
|
var finished = false;
|
||||||
var user = new User();
|
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
var blockContent = JSON.parse(block.getContent());
|
var blockContent = JSON.parse(block.getContent());
|
||||||
|
|
||||||
user.setName('unknown');
|
// just ignore anything shorter than 280 characters
|
||||||
user.setID(new String(block.getHeader('signer', 'unknown')));
|
if(String(blockContent['content']).length <= 280 && block.getParent() === null) {
|
||||||
post.setContent(blockContent['content']);
|
post.setContent(blockContent['content']);
|
||||||
post.setPostDate(block.getDate());
|
post.setPostDate(block.getDate());
|
||||||
post.setUser(user);
|
post.setUser(user);
|
||||||
|
|
||||||
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
post.setHash(block.getHash());
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML += post.getHTML();
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while(!finished);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
console.log('Troublemaker block: ' + data[i].getHash());
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function viewProfile(id, name) {
|
function toggleLike(hash) {
|
||||||
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
|
var post = getPostMap(hash);
|
||||||
|
if(post === null || !getPostMap()[hash]['liked']) {
|
||||||
|
console.log('Liking ' + hash + '...');
|
||||||
|
|
||||||
|
if(post === null)
|
||||||
|
getPostMap()[hash] = {};
|
||||||
|
|
||||||
|
getPostMap()[hash]['liked'] = true;
|
||||||
|
|
||||||
|
set('postmap', JSON.stringify(getPostMap()));
|
||||||
|
|
||||||
|
var block = new Block();
|
||||||
|
|
||||||
|
block.setType('onionr-post-like');
|
||||||
|
block.setContent(JSON.stringify({'hash' : hash}));
|
||||||
|
block.save(true, function(hash) {});
|
||||||
|
} else {
|
||||||
|
console.log('Unliking ' + hash + '...');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function postCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-post-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-post-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-post-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replyCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-reply-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-reply-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-reply-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusReplyCreatorChange() {
|
||||||
|
var content = document.getElementById('onionr-post-focus-reply-creator-content').value;
|
||||||
|
var message = '';
|
||||||
|
|
||||||
|
var maxlength = 280;
|
||||||
|
|
||||||
|
var disable = true;
|
||||||
|
var warn = false;
|
||||||
|
|
||||||
|
if(content.length !== 0) {
|
||||||
|
if(content.length - content.replaceAll('\n', '').length > 16) {
|
||||||
|
// 16 max newlines
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_MAXIMUM_NEWLINES $>';
|
||||||
|
} else if(content.length <= maxlength) {
|
||||||
|
// 280 max characters
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_REMAINING $>'.replaceAll('%s', (280 - content.length));
|
||||||
|
disable = false;
|
||||||
|
|
||||||
|
if(maxlength - content.length < maxlength / 4) {
|
||||||
|
warn = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message = '<$= LANG.POST_CREATOR_MESSAGE_OVER $>'.replaceAll('%s', (content.length - maxlength));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var element = document.getElementById('onionr-post-focus-reply-creator-content-message');
|
||||||
|
var button = document.getElementById("onionr-post-focus-reply-creator-create");
|
||||||
|
|
||||||
|
if(message === '')
|
||||||
|
element.style.visibility = 'hidden';
|
||||||
|
else {
|
||||||
|
element.style.visibility = 'visible';
|
||||||
|
|
||||||
|
element.innerHTML = message;
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
element.style.color = 'red';
|
||||||
|
else if(warn)
|
||||||
|
element.style.color = '#FF8C00';
|
||||||
|
else
|
||||||
|
element.style.color = 'gray';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(disable)
|
||||||
|
button.disabled = true;
|
||||||
|
else
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewProfile(id, name) {
|
||||||
|
id = decodeURIComponent(id);
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(decodeURIComponent(name));
|
||||||
|
|
||||||
|
User.getUser(id, function(data) {
|
||||||
|
if(data !== null) {
|
||||||
|
document.getElementById("onionr-profile-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(data.getIcon());
|
||||||
|
document.getElementById("onionr-profile-user-icon").b64 = Sanitize.html(data.getIcon());
|
||||||
|
document.getElementById("onionr-profile-username").innerHTML = Sanitize.html(Sanitize.username(data.getName()));
|
||||||
|
document.getElementById("onionr-profile-username").title = Sanitize.html(data.getID());
|
||||||
|
document.getElementById("onionr-profile-description").innerHTML = Sanitize.html(Sanitize.description(data.getDescription()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUser() {
|
||||||
|
toggleSaveButton(false);
|
||||||
|
|
||||||
|
// jQuery('#modal').modal('show');
|
||||||
|
|
||||||
|
var name = jQuery('#onionr-profile-username').text();
|
||||||
|
var id = document.getElementById("onionr-profile-username").title;
|
||||||
|
var icon = document.getElementById("onionr-profile-user-icon").b64;
|
||||||
|
var description = jQuery("#onionr-profile-description").text();
|
||||||
|
|
||||||
|
var user = new User();
|
||||||
|
|
||||||
|
user.setName(name);
|
||||||
|
user.setID(id);
|
||||||
|
user.setIcon(icon);
|
||||||
|
user.setDescription(Sanitize.description(description));
|
||||||
|
|
||||||
|
user.remember();
|
||||||
|
user.save(function() {
|
||||||
|
setCurrentUser(user);
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelUpdate() {
|
||||||
|
toggleSaveButton(false);
|
||||||
|
|
||||||
|
var name = jQuery('#onionr-profile-username').text();
|
||||||
|
var id = document.getElementById("onionr-profile-username").title;
|
||||||
|
|
||||||
|
viewProfile(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSaveButton(show) {
|
||||||
|
document.getElementById("onionr-profile-edit").style.display = (show ? 'block' : 'none');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePost() {
|
||||||
|
var content = document.getElementById("onionr-post-creator-content").value;
|
||||||
|
|
||||||
|
if(content.trim() !== '') {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
post.setUser(getCurrentUser());
|
||||||
|
post.setContent(content);
|
||||||
|
post.setPostDate(new Date());
|
||||||
|
|
||||||
|
post.save(function(data) {}); // async, but no function
|
||||||
|
|
||||||
|
document.getElementById('onionr-timeline-posts').innerHTML = post.getHTML() + document.getElementById('onionr-timeline-posts').innerHTML;
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-content").value = "";
|
||||||
|
document.getElementById("onionr-post-creator-content").focus();
|
||||||
|
postCreatorChange();
|
||||||
|
} else {
|
||||||
|
console.log('Not making empty post.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReplies(id, callback) {
|
||||||
|
Block.getBlocks({'type' : 'onionr-post', 'parent' : id, 'signed' : true, 'reverse' : true}, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusPost(id) {
|
||||||
|
viewReplies(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewRepliesMobile(id) {
|
||||||
|
var post = document.getElementById('onionr-post-' + id);
|
||||||
|
|
||||||
|
var user_name = '';
|
||||||
|
var user_id = '';
|
||||||
|
var user_id_trunc = '';
|
||||||
|
var user_icon = '';
|
||||||
|
var post_content = '';
|
||||||
|
|
||||||
|
if(post !== null && post !== undefined) {
|
||||||
|
// if the post is in the timeline, get the data from it
|
||||||
|
user_name = post.getElementsByClassName('onionr-post-user-name')[0].innerHTML;
|
||||||
|
user_id = post.getElementsByClassName('onionr-post-user-id')[0].title;
|
||||||
|
user_id_trunc = post.getElementsByClassName('onionr-post-user-id')[0].innerHTML;
|
||||||
|
user_icon = post.getElementsByClassName('onionr-post-user-icon')[0].src;
|
||||||
|
post_content = post.getElementsByClassName('onionr-post-content')[0].innerHTML;
|
||||||
|
} else {
|
||||||
|
// otherwise, fetch the data
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('onionr-post-focus-user-icon').src = user_icon;
|
||||||
|
document.getElementById('onionr-post-focus-user-name').innerHTML = user_name;
|
||||||
|
document.getElementById('onionr-post-focus-user-id').innerHTML = user_id_trunc;
|
||||||
|
document.getElementById('onionr-post-focus-user-id').title = user_id;
|
||||||
|
document.getElementById('onionr-post-focus-content').innerHTML = post_content;
|
||||||
|
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-content').value = '';
|
||||||
|
document.getElementById('onionr-post-focus-reply-creator-content-message').value = '';
|
||||||
|
|
||||||
|
jQuery('#onionr-post-focus').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewReplies(id) {
|
||||||
|
document.getElementById('onionr-replies-title').innerHTML = '<$= LANG.REPLIES $>';
|
||||||
|
document.getElementById('onionr-reply-creator-panel').originalPost = id;
|
||||||
|
document.getElementById('onionr-reply-creator-panel').innerHTML = '<$= jsTemplate('onionr-reply-creator') $>';
|
||||||
|
|
||||||
|
document.getElementById('onionr-reply-creator-content').innerHTML = '';
|
||||||
|
document.getElementById("onionr-reply-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
|
||||||
|
document.getElementById('onionr-reply-creator-user-name').innerHTML = Sanitize.html(Sanitize.username(getCurrentUser().getName()));
|
||||||
|
document.getElementById('onionr-reply-creator-user-icon').src = "data:image/jpeg;base64," + Sanitize.html(getCurrentUser().getIcon());
|
||||||
|
|
||||||
|
document.getElementById('onionr-replies').innerHTML = '';
|
||||||
|
getReplies(id, function(data) {
|
||||||
|
var replies = document.getElementById('onionr-replies');
|
||||||
|
|
||||||
|
replies.innerHTML = '';
|
||||||
|
|
||||||
|
for(var i = 0; i < data.length; i++) {
|
||||||
|
try {
|
||||||
|
var block = data[i];
|
||||||
|
|
||||||
|
var finished = false;
|
||||||
|
User.getUser(new String(block.getHeader('signer', 'unknown')), function(user) {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
var blockContent = JSON.parse(block.getContent());
|
||||||
|
|
||||||
|
// just ignore anything shorter than 280 characters
|
||||||
|
if(String(blockContent['content']).length <= 280) {
|
||||||
|
post.setContent(blockContent['content']);
|
||||||
|
post.setPostDate(block.getDate());
|
||||||
|
post.setUser(user);
|
||||||
|
|
||||||
|
post.setHash(block.getHash());
|
||||||
|
|
||||||
|
replies.innerHTML += post.getHTML('reply');
|
||||||
|
}
|
||||||
|
|
||||||
|
finished = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while(!finished);
|
||||||
|
} catch(e) {
|
||||||
|
console.log('Troublemaker block: ' + data[i].getHash());
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeReply() {
|
||||||
|
var content = document.getElementById("onionr-reply-creator-content").value;
|
||||||
|
|
||||||
|
if(content.trim() !== '') {
|
||||||
|
var post = new Post();
|
||||||
|
|
||||||
|
var originalPost = document.getElementById('onionr-reply-creator-panel').originalPost;
|
||||||
|
|
||||||
|
console.log('Original post hash: ' + originalPost);
|
||||||
|
|
||||||
|
post.setUser(getCurrentUser());
|
||||||
|
post.setParent(originalPost);
|
||||||
|
post.setContent(content);
|
||||||
|
post.setPostDate(new Date());
|
||||||
|
|
||||||
|
post.save(function(data) {}); // async, but no function
|
||||||
|
|
||||||
|
document.getElementById('onionr-replies').innerHTML = post.getHTML('reply') + document.getElementById('onionr-replies').innerHTML;
|
||||||
|
|
||||||
|
document.getElementById("onionr-reply-creator-content").value = "";
|
||||||
|
document.getElementById("onionr-reply-creator-content").focus();
|
||||||
|
replyCreatorChange();
|
||||||
|
} else {
|
||||||
|
console.log('Not making empty reply.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery('body').on('click', '[data-editable]', function() {
|
||||||
|
var el = jQuery(this);
|
||||||
|
var txt = el.text();
|
||||||
|
var maxlength = el.attr("maxlength");
|
||||||
|
|
||||||
|
var input = jQuery('<input/>').val(txt);
|
||||||
|
input.attr('maxlength', maxlength);
|
||||||
|
el.replaceWith(input);
|
||||||
|
|
||||||
|
var save = function() {
|
||||||
|
var newTxt = input.val();
|
||||||
|
|
||||||
|
if(el.attr('id') === 'onionr-profile-username')
|
||||||
|
newTxt = Sanitize.username(newTxt);
|
||||||
|
if(el.attr('id') === 'onionr-profile-description')
|
||||||
|
newTxt = Sanitize.description(newTxt);
|
||||||
|
|
||||||
|
var p = el.text(newTxt);
|
||||||
|
|
||||||
|
input.replaceWith(p);
|
||||||
|
|
||||||
|
if(newTxt !== txt)
|
||||||
|
toggleSaveButton(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
var saveEnter = function(event) {
|
||||||
|
console.log(event);
|
||||||
|
console.log(event.keyCode);
|
||||||
|
if (event.keyCode === 13)
|
||||||
|
save();
|
||||||
|
};
|
||||||
|
|
||||||
|
input.one('blur', save).bind('keyup', saveEnter).focus();
|
||||||
|
});
|
||||||
|
//viewProfile('$user-id-url', '$user-name-url')
|
||||||
|
// jQuery('#onionr-post-user-id').on('click', function(e) { alert(3);});
|
||||||
|
//jQuery('#onionr-post *').on('click', function(e) { e.stopPropagation(); });
|
||||||
|
// jQuery('#onionr-post').click(function(e) { alert(1); });
|
||||||
|
|
||||||
|
currentUser = getCurrentUser();
|
||||||
|
if(currentUser !== undefined && currentUser !== null) {
|
||||||
|
document.getElementById("onionr-post-creator-user-name").innerHTML = Sanitize.html(currentUser.getName());
|
||||||
|
document.getElementById("onionr-post-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>";
|
||||||
|
document.getElementById("onionr-post-creator-user-icon").src = "data:image/jpeg;base64," + Sanitize.html(currentUser.getIcon());
|
||||||
|
document.getElementById("onionr-post-creator-user-id").title = currentUser.getID();
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
|
||||||
|
document.getElementById("onionr-post-focus-reply-creator-content").placeholder = "<$= LANG.POST_CREATOR_PLACEHOLDER $>";
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-focus-reply-creator-user-id").innerHTML = "<$= LANG.POST_CREATOR_YOU $>";
|
||||||
|
}
|
||||||
|
|
||||||
|
viewCurrentProfile = function() {
|
||||||
|
viewProfile(encodeURIComponent(currentUser.getID()), encodeURIComponent(currentUser.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("onionr-post-creator-user-id").onclick = viewCurrentProfile;
|
||||||
|
document.getElementById("onionr-post-creator-user-name").onclick = viewCurrentProfile;
|
||||||
|
|
||||||
|
// on some browsers it saves the user input on reload. So, it should also recheck the input.
|
||||||
|
postCreatorChange();
|
||||||
|
Loading…
Reference in New Issue
Block a user