Add plugin support
parent
5641651c85
commit
ab17e0d198
|
@ -181,9 +181,13 @@ class API:
|
|||
return resp
|
||||
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||
logger.info('Starting client on ' + self.host + ':' + str(bindPort) + '...')
|
||||
#logger.debug('Client token: ' + logger.colors.underline + self.clientToken)
|
||||
|
||||
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
|
||||
try:
|
||||
app.run(host=self.host, port=bindPort, debug=True, threaded=True)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
logger.fatal('Failed to start client on ' + self.host + ':' + str(bindPort) + ', exiting...')
|
||||
exit(1)
|
||||
|
||||
def validateHost(self, hostType):
|
||||
'''
|
||||
|
|
|
@ -29,12 +29,19 @@ class OnionrCommunicate:
|
|||
|
||||
This class handles communication with nodes in the Onionr network.
|
||||
'''
|
||||
|
||||
self._core = core.Core()
|
||||
self._utils = onionrutils.OnionrUtils(self._core)
|
||||
self._crypto = onionrcrypto.OnionrCrypto(self._core)
|
||||
logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2]))
|
||||
self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2]))
|
||||
logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight()))
|
||||
|
||||
try:
|
||||
logger.info('Starting Bitcoin Node... with Tor socks port:' + str(sys.argv[2]))
|
||||
self.bitcoin = btc.OnionrBTC(torP=int(sys.argv[2]))
|
||||
logger.info('Bitcoin Node started, on block: ' + self.bitcoin.node.getBlockHash(self.bitcoin.node.getLastBlockHeight()))
|
||||
except:
|
||||
logger.fatal('Failed to start Bitcoin Node, exiting...')
|
||||
exit(1)
|
||||
|
||||
blockProcessTimer = 0
|
||||
blockProcessAmount = 5
|
||||
heartBeatTimer = 0
|
||||
|
@ -48,6 +55,10 @@ class OnionrCommunicate:
|
|||
|
||||
if os.path.exists(self._core.queueDB):
|
||||
self._core.clearDaemonQueue()
|
||||
|
||||
# Loads in and starts the enabled plugins
|
||||
plugins.reload()
|
||||
|
||||
while True:
|
||||
command = self._core.daemonQueue()
|
||||
# Process blocks based on a timer
|
||||
|
@ -63,7 +74,6 @@ class OnionrCommunicate:
|
|||
self.lookupBlocks()
|
||||
self.processBlocks()
|
||||
blockProcessTimer = 0
|
||||
#logger.debug('Communicator daemon heartbeat')
|
||||
if command != False:
|
||||
if command[0] == 'shutdown':
|
||||
logger.warn('Daemon recieved exit command.')
|
||||
|
@ -71,17 +81,19 @@ class OnionrCommunicate:
|
|||
time.sleep(1)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def getNewPeers(self):
|
||||
'''
|
||||
Get new peers
|
||||
'''
|
||||
|
||||
return
|
||||
|
||||
def lookupBlocks(self):
|
||||
'''
|
||||
Lookup blocks and merge new ones
|
||||
'''
|
||||
|
||||
peerList = self._core.listAdders()
|
||||
blocks = ''
|
||||
for i in peerList:
|
||||
|
@ -126,6 +138,7 @@ class OnionrCommunicate:
|
|||
|
||||
This is meant to be called from the communicator daemon on its timer.
|
||||
'''
|
||||
|
||||
for i in self._core.getBlockList(True).split("\n"):
|
||||
if i != "":
|
||||
logger.warn('UNSAVED BLOCK: ' + i)
|
||||
|
@ -137,6 +150,7 @@ class OnionrCommunicate:
|
|||
'''
|
||||
Download a block from random order of peers
|
||||
'''
|
||||
|
||||
peerList = self._core.listAdders()
|
||||
blocks = ''
|
||||
for i in peerList:
|
||||
|
@ -164,12 +178,14 @@ class OnionrCommunicate:
|
|||
'''
|
||||
URL encodes the data
|
||||
'''
|
||||
|
||||
return urllib.parse.quote_plus(data)
|
||||
|
||||
def performGet(self, action, peer, data=None, peerType='tor'):
|
||||
'''
|
||||
Performs a request to a peer through Tor or i2p (currently only Tor)
|
||||
'''
|
||||
|
||||
if not peer.endswith('.onion') and not peer.endswith('.onion/'):
|
||||
raise PeerError('Currently only Tor .onion peers are supported. You must manually specify .onion')
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ def get(key, default = None):
|
|||
'''
|
||||
Gets the key from configuration, or returns `default`
|
||||
'''
|
||||
|
||||
if is_set(key):
|
||||
return get_config()[key]
|
||||
return default
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
import sys, os, base64, random, getpass, shutil, subprocess, requests, time, platform
|
||||
import api, core, gui, config, logger
|
||||
import api, core, gui, config, logger, onionrplugins as plugins
|
||||
from onionrutils import OnionrUtils
|
||||
from netcontroller import NetController
|
||||
|
||||
|
@ -130,26 +130,44 @@ class Onionr:
|
|||
|
||||
def getCommands(self):
|
||||
return {
|
||||
'': self.showHelpSuggestion,
|
||||
'help': self.showHelp,
|
||||
'version': self.version,
|
||||
'config': self.configure,
|
||||
'start': self.start,
|
||||
'stop': self.killDaemon,
|
||||
'stats': self.showStats,
|
||||
|
||||
'enable-plugin': self.enablePlugin,
|
||||
'enplugin': self.enablePlugin,
|
||||
'enableplugin': self.enablePlugin,
|
||||
'enmod': self.enablePlugin,
|
||||
'disable-plugin': self.disablePlugin,
|
||||
'displugin': self.disablePlugin,
|
||||
'disableplugin': self.disablePlugin,
|
||||
'dismod': self.disablePlugin,
|
||||
'reload-plugin': self.reloadPlugin,
|
||||
'reloadplugin': self.reloadPlugin,
|
||||
'reload-plugins': self.reloadPlugin,
|
||||
'reloadplugins': self.reloadPlugin,
|
||||
|
||||
'listpeers': self.listPeers,
|
||||
'list-peers': self.listPeers,
|
||||
'': self.showHelpSuggestion,
|
||||
|
||||
'addmsg': self.addMessage,
|
||||
'addmessage': self.addMessage,
|
||||
'add-msg': self.addMessage,
|
||||
'add-message': self.addMessage,
|
||||
'pm': self.sendEncrypt,
|
||||
|
||||
'gui': self.openGUI,
|
||||
|
||||
'addpeer': self.addPeer,
|
||||
'add-peer': self.addPeer,
|
||||
'add-address': self.addAddress,
|
||||
'connect': self.addAddress,
|
||||
'addaddress': self.addAddress
|
||||
'addaddress': self.addAddress,
|
||||
|
||||
'connect': self.addAddress
|
||||
}
|
||||
|
||||
def getHelp(self):
|
||||
|
@ -160,10 +178,13 @@ class Onionr:
|
|||
'start': 'Starts the Onionr daemon',
|
||||
'stop': 'Stops the Onionr daemon',
|
||||
'stats': 'Displays node statistics',
|
||||
'list-peers': 'Displays a list of peers (?)',
|
||||
'enable-plugin': 'Enables and starts a plugin',
|
||||
'disable-plugin': 'Disables and stops a plugin',
|
||||
'reload-plugin': 'Reloads a plugin',
|
||||
'list-peers': 'Displays a list of peers',
|
||||
'add-peer': 'Adds a peer (?)',
|
||||
'add-msg': 'Broadcasts a message to the Onionr network',
|
||||
'pm': 'Adds a private message (?)',
|
||||
'pm': 'Adds a private message to block',
|
||||
'gui': 'Opens a graphical interface for Onionr'
|
||||
}
|
||||
|
||||
|
@ -263,7 +284,9 @@ class Onionr:
|
|||
else:
|
||||
logger.info("Adding peer: " + logger.colors.underline + newPeer)
|
||||
self.onionrCore.addPeer(newPeer)
|
||||
|
||||
|
||||
return
|
||||
|
||||
def addAddress(self):
|
||||
'''Adds a Onionr node address'''
|
||||
try:
|
||||
|
@ -277,6 +300,8 @@ class Onionr:
|
|||
else:
|
||||
logger.warn("Unable to add address")
|
||||
|
||||
return
|
||||
|
||||
def addMessage(self):
|
||||
'''
|
||||
Broadcasts a message to the Onionr network
|
||||
|
@ -291,6 +316,52 @@ class Onionr:
|
|||
self.onionrCore.addToBlockDB(addedHash, selfInsert=True)
|
||||
self.onionrCore.setBlockType(addedHash, 'txt')
|
||||
|
||||
return
|
||||
|
||||
def enablePlugin(self):
|
||||
'''
|
||||
Enables and starts the given plugin
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Enabling plugin \"' + plugin_name + '\"...')
|
||||
plugins.enable(plugin_name)
|
||||
else:
|
||||
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
||||
|
||||
return
|
||||
|
||||
def disablePlugin(self):
|
||||
'''
|
||||
Disables and stops the given plugin
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Disabling plugin \"' + plugin_name + '\"...')
|
||||
plugins.disable(plugin_name)
|
||||
else:
|
||||
logger.info(sys.argv[0] + ' ' + sys.argv[1] + ' <plugin>')
|
||||
|
||||
return
|
||||
|
||||
def reloadPlugin(self):
|
||||
'''
|
||||
Reloads (stops and starts) all plugins, or the given plugin
|
||||
'''
|
||||
|
||||
if len(sys.argv) >= 3:
|
||||
plugin_name = sys.argv[2]
|
||||
logger.info('Reloading plugin \"' + plugin_name + '\"...')
|
||||
plugins.stop(plugin_name)
|
||||
plugins.start(plugin_name)
|
||||
else:
|
||||
logger.info('Reloading all plugins...')
|
||||
plugins.reload()
|
||||
|
||||
return
|
||||
|
||||
def notFound(self):
|
||||
'''
|
||||
Displays a "command not found" message
|
||||
|
@ -325,6 +396,7 @@ class Onionr:
|
|||
'''
|
||||
Starts the Onionr communication daemon
|
||||
'''
|
||||
|
||||
if not os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||
if self._developmentMode:
|
||||
logger.warn('DEVELOPMENT MODE ENABLED (THIS IS LESS SECURE!)')
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
|
||||
This file deals with configuration management.
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import config, logger, onionrplugins as plugins
|
||||
|
||||
def event(event_name, data = None, onionr = None):
|
||||
'''
|
||||
Calls an event on all plugins (if defined)
|
||||
'''
|
||||
|
||||
for plugin in plugins.get_enabled_plugins():
|
||||
try:
|
||||
call(plugins.get_plugin(plugin), event_name, data, onionr)
|
||||
except:
|
||||
logger.warn('Event \"' + event_name + '\" failed for plugin \"' + plugin + '\".')
|
||||
|
||||
def call(plugin, event_name, data = None, onionr = None):
|
||||
'''
|
||||
Calls an event on a plugin if one is defined
|
||||
'''
|
||||
|
||||
if not plugin is None:
|
||||
try:
|
||||
attribute = 'on_' + str(event_name).lower()
|
||||
|
||||
# TODO: Use multithreading perhaps?
|
||||
if hasattr(plugin, attribute):
|
||||
logger.debug('Calling event ' + str(event_name))
|
||||
getattr(plugin, attribute)(onionr, data)
|
||||
|
||||
return True
|
||||
except:
|
||||
logger.warn('Failed to call event ' + str(event_name) + ' on module.')
|
||||
return False
|
||||
else:
|
||||
return True
|
|
@ -0,0 +1,231 @@
|
|||
'''
|
||||
Onionr - P2P Microblogging Platform & Social network
|
||||
|
||||
This file deals with management of modules/plugins.
|
||||
'''
|
||||
'''
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import os, re, importlib, config, logger
|
||||
import onionrevents as events
|
||||
|
||||
_pluginsfolder = 'data/plugins/'
|
||||
_instances = dict()
|
||||
|
||||
def reload(stop_event = True):
|
||||
'''
|
||||
Reloads all the plugins
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
try:
|
||||
enabled_plugins = get_enabled_plugins()
|
||||
|
||||
if stop_event is True:
|
||||
logger.debug('Reloading all plugins...')
|
||||
else:
|
||||
logger.debug('Loading all plugins...')
|
||||
|
||||
if stop_event is True:
|
||||
for plugin in enabled_plugins:
|
||||
stop(plugin)
|
||||
|
||||
for plugin in enabled_plugins:
|
||||
start(plugin)
|
||||
|
||||
return True
|
||||
except:
|
||||
logger.error('Failed to reload plugins.')
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def enable(name, start_event = True):
|
||||
'''
|
||||
Enables a plugin
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
if exists(name):
|
||||
enabled_plugins = get_enabled_plugins()
|
||||
enabled_plugins.append(name)
|
||||
config_plugins = config.get('plugins')
|
||||
config_plugins['enabled'] = enabled_plugins
|
||||
config.set('plugins', config_plugins, True)
|
||||
|
||||
events.call(get_plugin(name), 'enable')
|
||||
|
||||
if start_event is True:
|
||||
start(name)
|
||||
|
||||
return True
|
||||
else:
|
||||
logger.error('Failed to enable plugin \"' + name + '\", disabling plugin.')
|
||||
disable(name)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def disable(name, stop_event = True):
|
||||
'''
|
||||
Disables a plugin
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
if is_enabled(name):
|
||||
enabled_plugins = get_enabled_plugins()
|
||||
enabled_plugins.remove(name)
|
||||
config_plugins = config.get('plugins')
|
||||
config_plugins['enabled'] = enabled_plugins
|
||||
config.set('plugins', config_plugins, True)
|
||||
|
||||
if exists(name):
|
||||
events.call(get_plugin(name), 'disable')
|
||||
|
||||
if stop_event is True:
|
||||
stop(name)
|
||||
|
||||
def start(name):
|
||||
'''
|
||||
Starts the plugin
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
if exists(name):
|
||||
try:
|
||||
plugin = get_plugin(name)
|
||||
|
||||
if plugin is None:
|
||||
raise Exception('Failed to import module.')
|
||||
else:
|
||||
events.call(plugin, 'start')
|
||||
|
||||
return plugin
|
||||
except:
|
||||
logger.error('Failed to start module \"' + name + '\".')
|
||||
else:
|
||||
logger.error('Failed to start nonexistant module \"' + name + '\".')
|
||||
|
||||
return None
|
||||
|
||||
def stop(name):
|
||||
'''
|
||||
Stops the plugin
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
if exists(name):
|
||||
try:
|
||||
plugin = get_plugin(name)
|
||||
|
||||
if plugin is None:
|
||||
raise Exception('Failed to import module.')
|
||||
else:
|
||||
events.call(plugin, 'stop')
|
||||
|
||||
return plugin
|
||||
except:
|
||||
logger.error('Failed to stop module \"' + name + '\".')
|
||||
else:
|
||||
logger.error('Failed to stop nonexistant module \"' + name + '\".')
|
||||
|
||||
return None
|
||||
|
||||
def get_plugin(name):
|
||||
'''
|
||||
Returns the instance of a module
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
if str(name).lower() in _instances:
|
||||
return _instances[str(name).lower()]
|
||||
else:
|
||||
_instances[str(name).lower()] = importlib.import_module(get_plugins_folder(name, False).replace('/', '.') + 'main')
|
||||
return get_plugin(name)
|
||||
|
||||
def get_plugins():
|
||||
'''
|
||||
Returns a list of plugins (deprecated)
|
||||
'''
|
||||
|
||||
return _instances
|
||||
|
||||
def exists(name):
|
||||
'''
|
||||
Return value indicates whether or not the plugin exists
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
return os.path.isdir(get_plugins_folder(str(name).lower()))
|
||||
|
||||
def get_enabled_plugins():
|
||||
'''
|
||||
Returns a list of the enabled plugins
|
||||
'''
|
||||
|
||||
check()
|
||||
|
||||
config.reload()
|
||||
|
||||
return config.get('plugins')['enabled']
|
||||
|
||||
def is_enabled(name):
|
||||
'''
|
||||
Return value indicates whether or not the plugin is enabled
|
||||
'''
|
||||
|
||||
return name in get_enabled_plugins()
|
||||
|
||||
def get_plugins_folder(name = None, absolute = True):
|
||||
'''
|
||||
Returns the path to the plugins folder
|
||||
'''
|
||||
|
||||
path = ''
|
||||
|
||||
if name is None:
|
||||
path = _pluginsfolder
|
||||
else:
|
||||
# only allow alphanumeric characters
|
||||
path = _pluginsfolder + re.sub('[^0-9a-zA-Z]+', '', str(name).lower()) + '/'
|
||||
|
||||
if absolute is True:
|
||||
path = os.path.abspath(path)
|
||||
|
||||
return path
|
||||
|
||||
def check():
|
||||
'''
|
||||
Checks to make sure files exist
|
||||
'''
|
||||
|
||||
config.reload()
|
||||
|
||||
if not config.is_set('plugins'):
|
||||
logger.debug('Generating plugin config data...')
|
||||
config.set('plugins', {'enabled': []}, True)
|
||||
|
||||
if not os.path.exists(os.path.dirname(get_plugins_folder())):
|
||||
logger.debug('Generating plugin data folder...')
|
||||
os.path.mkdirs(os.path.dirname(get_plugins_folder()))
|
||||
|
||||
return
|
Loading…
Reference in New Issue