From f556c681a5afae36a7d7c11ade5854a6de4a0896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Sun, 23 Oct 2016 12:53:45 +0200 Subject: Add message of the day feature --- README.md | 30 ++++++++++++++--------- mcwrapper/__init__.py | 40 ++++++++++++++++++++++++------- mcwrapper/alarm.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ mcwrapper/mod.py | 24 +++++++++++++++++++ mcwrapper/wrapper.py | 15 ++++-------- 5 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 mcwrapper/alarm.py create mode 100644 mcwrapper/mod.py diff --git a/README.md b/README.md index 7d3ee28..52ea73e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ MCSERVER-WRAPPER ================ Minecraft server wrapper written in Python3 that extracts server status and list -of online players. +of online players and more. Requires: ----------------- @@ -34,11 +34,16 @@ positional arguments: command Command to be executed to start Minecraft server. optional arguments: - -h, --help show this help message and exit - --verbose, -v Increase verbose level of output - --quiet, -q Decrease verbose level of output - --status-file, -s Outputs server status to file "status" - --players-file, -p Outputs list of online players to file "players" + -h, --help show this help message and exit + --verbose, -v Increase verbose level of output + --quiet, -q Decrease verbose level of output + --status-file, -s Outputs server status to file "status" + --players-file, -p Outputs list of online players to file "players" + --mod-file MOD_FILE, -m MOD_FILE + Prints periodically random line from given file as + message of the day. + --mod-time MOD_TIME Period used for message of the day in seconds. In + default 900 (15 minutes). ``` ### How it works @@ -74,8 +79,11 @@ This file in in status directory and is named as `players`. If server is running it contains online players. Player name per line. If server isn't running, it content don't have to be valid. -MCWRAPPER-TERMINAL ------------------- -This application is going to be used for interactive terminal access to minecraft -server console. It should use latest minecraft server log as input and output will -be pushed to input pipe of mcwrapper. This app is currently in development. +### Message of the day +This prints to players various short messages in given interval. Message is from +file passed as --mod-file and it's randomly selected line. This file is read on +wrapper start, so if you edit it while it's running, no change will happen unless +you send USR1 signal to mcwrapper server. You can use this simple script: +``` +[ -f server.pid ] && kill -USR1 $(cat server.pid) +``` diff --git a/mcwrapper/__init__.py b/mcwrapper/__init__.py index 2ae3c2a..dec7194 100755 --- a/mcwrapper/__init__.py +++ b/mcwrapper/__init__.py @@ -1,28 +1,27 @@ # vim: expandtab ft=python ts=4 sw=4 sts=4: -import os -import sys -import subprocess import signal -import time import atexit import argparse -from threading import Thread from . import prints -from . import wrapper +from . import alarm from .wrapper import MCWrapper +from .mod import MoD mcserver_wrapper = None +mcserver_mod = None def __wrapper_atexit__(): "This is called when wrapper is exiting" - mcserver_wrapper.clean() + if mcserver_wrapper is not None: + mcserver_wrapper.clean() def __wrapper_toexit__(): "This function is called when system signalizes that mcwrapper should exit" - mcserver_wrapper.stop() + if mcserver_wrapper is not None: + mcserver_wrapper.stop() def __signal_term__(_signo, _stack_frame): @@ -36,6 +35,12 @@ __HELP_DESC__ = """ """ +def reload(): + "Reloads input files. Currently applicable only on mod." + if mcserver_mod is not None: + mcserver_mod.load_mods() + + def main(): "Main function" global verbose_level @@ -49,6 +54,12 @@ def main(): parser.add_argument('--players-file', '-p', action='store_true', help="""Outputs list of online players to file \"players\" """) + parser.add_argument('--mod-file', '-m', type=str, + help="""Prints periodically random line from + given file as message of the day.""") + parser.add_argument('--mod-time', type=int, + help="""Period used for message of the day in + seconds. In default 900 (15 minutes).""") parser.add_argument('command', nargs=argparse.REMAINDER, help="""Command to be executed to start Minecraft server.""") @@ -58,20 +69,31 @@ def main(): command = args.command sfile = args.status_file pfile = args.players_file + mod_file = args.mod_file + mod_time = args.mod_time if not command: parser.print_help() return + # Just small hack to not open minecraft server gui if 'nogui' not in command: command.append('nogui') + alarm.init() + signal.signal(signal.SIGUSR1, reload) + signal.signal(signal.SIGUSR2, reload) # probably can be used for something else in future + global mcserver_wrapper mcserver_wrapper = MCWrapper(command, pfile, sfile) signal.signal(signal.SIGTERM, __signal_term__) signal.signal(signal.SIGINT, __signal_term__) atexit.register(__wrapper_atexit__) - mcserver_wrapper.execstart() + mcserver_wrapper.start() + if mod_file is not None: + global mcserver_mod + mcserver_mod = MoD(mcserver_wrapper, mod_file, mod_time or 900) + mcserver_wrapper.process.wait() if __name__ == '__main__': main() diff --git a/mcwrapper/alarm.py b/mcwrapper/alarm.py new file mode 100644 index 0000000..de61741 --- /dev/null +++ b/mcwrapper/alarm.py @@ -0,0 +1,66 @@ +# vim: expandtab ft=python ts=4 sw=4 sts=4: +import signal +import time + +# Dict with alarms. Alarm is dictionary with initial time, alarm time and +# handler +__alarms__ = dict() +__alarm_wait__ = None + + +def __handler__(signum, frame): + if __alarm_wait__["arg"] is not None: + __alarm_wait__["handler"](__alarm_wait__["arg"]) + else: + __alarm_wait__["handler"]() + if not __alarm_wait__["repeat"]: + __alarms__.pop(__alarm_wait__["name"]) + else: + __alarm_wait__["time"] = __alarm_wait__["time"] + \ + __alarm_wait__["timeout"] + __update__() + + +def __update__(): + lowest = None + lowest_time = None + now = time.time() + for name, al in __alarms__.items(): + t = al["time"] + al["timeout"] - now + if lowest_time is None or lowest_time > t: + lowest_time = t + lowest = al + global __alarm_wait__ + if lowest is not None: + if lowest_time < 1: + # Less then second is missed alarm. Fire handler. + __alarm_wait__ = lowest + __handler__(None, None) + return + __alarm_wait__ = lowest + signal.alarm(int(lowest_time)) + elif __alarm_wait__ is not None: + signal.alarm(0) # close any alarm + __alarm_wait__ = None + + +def init(): + signal.signal(signal.SIGALRM, __handler__) # prepare alarm + + +def set(name, t, handler, repeat=False, arg=None): + al = dict() + al["time"] = time.time() + al["handler"] = handler + al["timeout"] = t + al["repeat"] = repeat + al["arg"] = arg + al["name"] = name + __alarms__[name] = al + __update__() + + +def unset(name): + if name in __update__: + __alarms__.pop(name) + __update__() diff --git a/mcwrapper/mod.py b/mcwrapper/mod.py new file mode 100644 index 0000000..78ffb8e --- /dev/null +++ b/mcwrapper/mod.py @@ -0,0 +1,24 @@ +# vim: expandtab ft=python ts=4 sw=4 sts=4: +import random + +from . import alarm + + +class MoD: + "Message of the day handler" + def __init__(self, mcwrapper, file, period=900): + self.mcwrapper = mcwrapper + self.file = file + self.load_mods() + alarm.set("mod-time", period, self.__handler__, repeat=True) + + def load_mods(self): + "Loads messages from self.file" + with open(self.file, "r") as f: + self.lines = f.readlines() + + def __handler__(self): + if len(self.lines) > 0: + i = random.randint(0, len(self.lines) - 1) + self.mcwrapper.write_to_terminal("/say " + self.lines[i].rstrip() + + "\n") diff --git a/mcwrapper/wrapper.py b/mcwrapper/wrapper.py index 1896266..3e9a0d7 100644 --- a/mcwrapper/wrapper.py +++ b/mcwrapper/wrapper.py @@ -36,11 +36,11 @@ class MCWrapper: try: os.kill(lpid, 0) except OSError: - prints.warning("Detected forced termination of previous server " - "wrapper instance.") + prints.warning("Detected forced termination of previous server" + " wrapper instance.") else: - prints.error("Another wrapper is running with given identifier.", - -1, 1) + prints.error("Another wrapper is running with given " + "identifier.", -1, 1) try: os.mkfifo(__INPUTPIPE__, 0o640) except FileExistsError: @@ -75,11 +75,6 @@ class MCWrapper: except FileNotFoundError: pass - def execstart(self): - "Start execution of Minecraft server and hold until its exits" - self.start() - self.process.wait() - def start(self): "Start Minecraft server" self.process = subprocess.Popen( @@ -111,7 +106,7 @@ class MCWrapper: def write_to_terminal(self, text): "Write to server terminal. If server not running it does nothing" if self.status == 2: - prints.info("Input: " + text, 1) + prints.info("Input: " + text.rstrip(), 1) self.process.stdin.write(bytes(text, sys.getdefaultencoding())) self.process.stdin.flush() return True -- cgit v1.2.3