diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/argmodules.py | 35 | ||||
-rw-r--r-- | modules/list-modules.py | 51 | ||||
-rw-r--r-- | modules/players.py | 52 | ||||
-rw-r--r-- | modules/say.py | 49 | ||||
-rw-r--r-- | modules/status.py | 71 | ||||
-rw-r--r-- | modules/utils.py | 196 |
6 files changed, 454 insertions, 0 deletions
diff --git a/modules/argmodules.py b/modules/argmodules.py new file mode 100644 index 0000000..1b004f1 --- /dev/null +++ b/modules/argmodules.py @@ -0,0 +1,35 @@ +import re +import utils +from utils import conf + +services = ( + utils.Service.argument, + ) + +__add_modules__ = set() + +def argument(args): + global __add_modules__ + if not re.search('^--modules=', args[0]): + return 0 + __add_modules__ = args[0][10:].split(',') + return 1 + +def argument_short(l, args): + global __add_modules__ + if l == 'm': + if len(args) < 1: + return 0 + __add_modules__ = args[0].split(',') + return 1 + return 0 + +def argument_exec(): + for mod in __add_modules__: + conf.modules.add(mod) + +def argument_help(): + if conf.action == 'start' or conf.action == 'list-modules': + print(' -m MODULE,... --module=MODULE,...') + print(' Load additional server modules. Multiple modules can be') + print(' specified. Separate them using commas.') diff --git a/modules/list-modules.py b/modules/list-modules.py new file mode 100644 index 0000000..ca93dc3 --- /dev/null +++ b/modules/list-modules.py @@ -0,0 +1,51 @@ +import sys +import re +import utils +from utils import conf +import importlib.machinery as imp + +services = ( + utils.Service.action, + ) + +def action(args): + if conf.action == None: + if args[0].lower() != 'list-modules': + return False + conf.action = 'list-modules' + conf.action_module = sys.modules[__name__] + return 1 + elif conf.identifier == None: + conf.identifier = args[0] + return 1 + return 0 + +def action_exec(): + if conf.verbose_level >= 1: + for mod in conf.modules: + try: + module = imp.SourceFileLoader(mod, + conf.modulesFolder + '/' + mod + '.py').load_module() + print(module) + for service in module.services: + print(' ' + utils.Service.toStr(service)) + except FileNotFoundError: + sys.exit('Unknown module: ' + mod) + else: + # TODO add check if module exists + for mod in conf.modules: + print(mod) + +def action_help(): + print(' list-modules') + print(' List all modules that will be used.') + +def action_full_help(): + print('mcwrapper [arguments...] list-modules [IDENTIFIER]') + print(' List all modules that will be used.') + print('') + print(' arguments') + utils.printArgumentsHelp() + print(' IDENTIFIER') + print(' Identifier of Minecraft server instance.') + print(' If specified, server modules are printed.') diff --git a/modules/players.py b/modules/players.py new file mode 100644 index 0000000..91e50b6 --- /dev/null +++ b/modules/players.py @@ -0,0 +1,52 @@ +import os +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.config, + utils.Service.init, + utils.Service.clean, + utils.Service.parse + ) + +players = set() + +def config(conf): + conf.playersFile = conf.folder + '/players' + +def init(): + with open(conf.playersFile, 'w') as f: + pass + +def clean(): + os.remove(conf.playersFile) + +def parse(line): + if 'logged in with entity id' in line: + name = line[len('[00:00:00] [Server thread/INFO]: '):] + name = name[:name.index('[')] + __user_join__(name) + elif 'left the game' in line: + name = line[len('[00:00:00] [Server thread/INFO]: '):] + name = name[:name.index(' ')] + __user_leave__(name) + else: + return False + return True + + +def __user_join__(username): + print("User '" + username + "' joined server.") + with open(conf.playersFile, 'a') as f: + players.add(username) + f.write(username + '\n') + +def __user_leave__(username): + print("User '" + username + "' left server.") + players.remove(username) + with open(conf.playersFile, 'w') as f: + f.writelines(players) + if players: + f.write('\n') diff --git a/modules/say.py b/modules/say.py new file mode 100644 index 0000000..64810e0 --- /dev/null +++ b/modules/say.py @@ -0,0 +1,49 @@ +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.action, + ) + +def action(args): + if conf.action == None: + if args[0].lower() != 'say': + return False + conf.action = 'say' + conf.action_module = sys.modules[__name__] + conf.sayMessage = [] + for arg in args[1:]: + if conf.identifier == None: + conf.identifier = arg + else: + conf.sayMessage.append(arg) + return len(args) + else: + return 0 + +def action_exec(): + if not conf.sayMessage or not conf.identifier: + action_full_help() + return + if not utils.isServerRunning(): + sys.exit("Server is not running or wrong identifier.") + with open(sconf.inputPipe, 'w') as f: + f.write("/say " + ' '.join(map(str, sconf.saymessage)) + '\n') + f.flush() + +def action_help(): + print(' say') + print(' Sends message to Minecraft server chat.') + +def action_full_help(): + print('mcwrapper [arguments...] say IDENTIFIER {message...}') + print(' Sends message to Minecraft server chat.') + print('') + print(' arguments') + utils.printArgumentsHelp() + print(' IDENTIFIER') + print(' Identifier of running server instance.') + print(' message') + print(' Message to be send to Minecraft server chat.') diff --git a/modules/status.py b/modules/status.py new file mode 100644 index 0000000..906c1ec --- /dev/null +++ b/modules/status.py @@ -0,0 +1,71 @@ +import os +import sys +import re +import utils +from utils import conf + +services = ( + utils.Service.config, + utils.Service.init, + utils.Service.clean, + utils.Service.parse, + ) + +__STATUSSTRINGS__ = { + 0: "Not running", + 1: "Starting", + 2: "Running", + 3: "Stopping", + } + +def config(conf): + conf.statusFile = conf.folder + '/status' + +def init(): + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[1]) + +def clean(): + os.remove(conf.statusFile) + +def parse(line): + if ': Done' in line: + __server_start__() + elif ': Stopping the server' in line: + __server_stop__() + else: + return False + return True + +def __server_start__(): + print("Server start.") + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[2] + '\n') + pass + +def __server_stop__(): + print("Server stop.") + with open(conf.statusFile, 'w') as f: + f.write(__STATUSSTRINGS__[3] + '\n') + pass + +#### For other modules #### +def get_status(conf): + """Returns server status as number. + Requires conf (server configuration) set with identifier using utils.confset(). + Returns: + 0 - Not running + 1 - Starting + 2 - Running + 3 - Stopping + -1 - Unknown status + """ + conf.statusFile = conf.folder + '/status' + if not os.path.exists(conf.statusFile): + return 0 + with open(conf.statusFile, 'r') as f: + status = f.readline().rstrip() + for i in range(len(__STATUSSTRINGS__)): + if __STATUSSTRINGS__[i] == status: + return i + return -1 diff --git a/modules/utils.py b/modules/utils.py new file mode 100644 index 0000000..c8406ce --- /dev/null +++ b/modules/utils.py @@ -0,0 +1,196 @@ +# This is python file with usable utilities for modules +# This can't be used as mcwrapper module. Although it's not a problem if loaded. +import os +import datetime +import re +import struct +import traceback +from enum import Enum + +# Dummy variable to be used before it is set by mcwrapper +conf = type('defconf', (object,), {}) + +class Service(Enum): + # Request service of conf function + # This function is called right after argument parsing. + # Configuration is loaded before almost anything is done. + # Prototype: config(conf) + # Where conf is class containing configuration variables. + config = 1 + # Request service of init function + # This function is called right before Minecraft server is started. + # Prototype: init() + init = 2 + # Request service of clean function + # This function is called before mcwrapper exits. + # Prototype: clean() + clean = 3 + # Request service of parse function + # Prototype: parse(line) + # Where line is line from Minecraft server standard and error output. + parse = 4 + # Signalize that exceptions shouldn't be ignored. + # Otherwise exception is printed and module is removed. + exceptionThrow = 101 + # Requests service of action and action_help function. + # This flag can't be denied by serviceServer or by configuration. + # Prototype: action(act, args) + # Where act is string specifying action and args are rest of command line + # arguments. + # Prototype: action_help() + action = 201 + # Requests service of argument function. + # This flag can't be denied by serviceServer or by configuration. + # Prototype: argument(arg, args) + # Where arg is parser argument and args are rest of command line arguments. + argument = 202 + def toStr(service): + if service == Service.config: + return 'S-Config' + elif service == Service.init: + return 'S-Init' + elif service == Service.clean: + return 'S-Clean' + elif service == Service.parse: + return 'S-Parse' + elif service == Service.exceptionThrow: + return 'F-ExceptionThrow' + elif service == Service.action: + return 'P-Action' + elif service == Service.argument: + return 'P-Argument' + +def __module_disable__(module): + """Disable specified module""" + if verbose_level >= 0: + print('Disabling module: ' + str(module)) + if Service.clean in module.services: + try: + module.clean() + except Exception: + traceback.print_exc() + for name, value in vars(conf).items(): + if re.search('^__modules', name): + try: + value.remove(module) + except KeyError: + pass + del module + +def printArgumentsHelp(): + """Prints help for all arguments from loaded modules""" + print(' -h, --help') + print(' Prints this help text.') + print(' -v, --verbose') + print(' Increase verbose level of output.') + print(' -q, --quiet') + print(' Decrease verbose level of output.') + for mod in conf.__modules_argument__: + mod.argument_help() + +def serviceCall(servicename, func, argv=[], mode=0): + """Calls func in all/n-th modules with specified service. + + servicename - String name of service + func - String name of functions to be called + argv - List of arguments passed to functions + mode - Mode of execution + 0 - called for every module without result returning + 1 - called for every module and return result + 2 - called until True is returned from function called + """ + def execmod(servicename, func, argv, mod): + cmd = 'mod.' + func + '( ' + for i in range(0, len(argv)): + cmd += 'argv[' + str(i) + '],' + cmd = cmd[0:len(cmd)-1] + ')' + try: + return eval(cmd) + except Exception as e: + if Service.exceptionThrow in mod.services: + raise e + else: + traceback.print_exc() + __module_disable__(mod) + return None + if mode == 0: + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + execmod(servicename, func, argv, mod) + return + elif mode == 1: + ret = dict() + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + ret[mod] = execmod(servicename, func, argv, mod) + return ret + elif mode == 2: + for mod in vars(conf)['__modules_' + servicename + '__'].copy(): + rtn = execmod(servicename, func, argv, mod) + if rtn: + return rtn, mod + return None, None + +def isServerRunning(): + """Check if server is running. It checks if input_pipe exists. + Returns: + True - Running in any state or residue pipe exists. + False - Not running + """ + return os.path.exists(conf.inputPipe) + +__default_config__ = { + "modules": {'say', 'argmodules', 'list-modules', 'printconf'}, + "identifier": None, + } +__default_server_config__ = { + "modules": {'status', 'players'}, + "folder": '/dev/shm/mcwrapper-exampleserver', + "logOutput": False, + "logFile": datetime.datetime.now().strftime('%y-%m-%d-%H-%M-%S') + '.log', + "command": [], + } + +def setServerConf(identifier): + """Sets server configuration.""" + conf.identifier = identifier + try: + conf.server[identifier] + vars(conf).update(conf.server[identifier]) + except AttributeError: + if conf.verbose_level >= 0: + print('W: No configuration associated with identifier: "' + conf.identifier) + configSet(__default_server_config__) + # Set additional runtime configuration variables + conf.inputPipe = conf.folder + '/input_pipe' + +def configSet(confs): + """This is for setting default configurations. If configuration for module is + not set in conf file, then it must be set while module initialization. + + confs - dictionary of configuration options and default values. + """ + for name, val in confs.items(): + try: + dir(conf).index(name) + except ValueError: + exec('conf.' + name + '=val') + +def varint_unpack(data): + "Returns varint value from beginning of data and number of bytes used." + i = 0 + nextbt = True + newdata = 0 + while nextbt: + bt = data[i] + if not bt & (1 << 7): + nextbt = False + bt = bt & ~(1 << 7) + newdata = newdata | (bt << (i * 7)) + print(newdata) + return newdata, i + +def varint_pack(integer): + pass + +################################################################################# +## dummy module +services = () |