def build(files): config = files.get_config() # создание микротрубочки mt = Microtubule(columns=int(config['mt']['protofilaments']), timer=float(config['time']['timer']), fps=float(config['time']['fps']), events=Events(config), mode='window', window=5) mt.build()
## 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, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################### import unittest from core.events import Events from core.recaptcha import Recaptcha import core.cons as cons import core.shared as shared shared.events = Events() NAME = "Recaptcha" #LINK = "http://api.recaptcha.net/challenge?k=6LfRJwkAAAAAAGmA3mAiAcAsRsWvfkBijaZWEvkD" LINK = "http://www.google.com/recaptcha/api/challenge?k=6LfRJwkAAAAAAGmA3mAiAcAsRsWvfkBijaZWEvkD" class DialogMockup: """""" def __init__(self, name, get_captcha, return_solution, timeout=False): """""" image_type, image_data = get_captcha() if not timeout: return_solution(image_type)
def __init__(self, secret_passphrase): # setup log and config # needs a false setting on first in case config does not # load (like after changes). self.storage = False self.log = logging.getLogger('Wrapper.py') self.configManager = Config() self.configManager.loadconfig() self.config = self.configManager.config # Read Config items # hard coded cursor for non-readline mode self.cursor = ">" # This was to allow alternate encodings self.encoding = self.config["General"]["encoding"] self.serverpath = self.config["General"]["server-directory"] self.proxymode = self.config["Proxy"]["proxy-enabled"] self.wrapper_onlinemode = self.config["Proxy"]["online-mode"] self.halt_message = self.config["Misc"]["halt-message"] # encryption items (for passwords and sensitive user data) # salt is generated and stored in wrapper.properties.json config_changes = False salt = self.config["General"]["salt"] if not salt: salt = gensalt(self.encoding) self.config["General"]["salt"] = salt config_changes = True # passphrase is provided at startup by the wrapper operator or script (not stored) passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding, salt) self.cipher = Crypt(passphrase, self.encoding) # Update passwords (hash any plaintext passwords) for groups in self.config: for cfg_items in self.config[groups]: if cfg_items[-10:] == "-plaintext": # i.e., cfg_items ===> like ["web-password-plaintext"] hash_item = cfg_items[:-10] # hash_item ===> i.e., ["web-password"] if hash_item in self.config[groups] and self.config[ groups][cfg_items]: # encrypt contents of (i.e.) ["web-password-plaintext"] hashed_item = self.cipher.encrypt( self.config[groups][cfg_items]) # store in "" ["Web"]["web-password"] self.config[groups][hash_item] = hashed_item # set plaintext item to false (successful digest) self.config[groups][cfg_items] = False config_changes = True # Patch any old update paths "..wrapper/development/build/version.json" # new paths are: "..wrapper/development" for entries in self.config["Updates"]: if "/build/version.json" in str(self.config["Updates"][entries]): oldentry = copy.copy(self.config["Updates"][entries]) self.config["Updates"][entries] = oldentry.split( "/build/version.json")[0] config_changes = True # save changes made to config file if config_changes: self.configManager.save() # reload branch update info. self.auto_update_wrapper = self.config["Updates"][ "auto-update-wrapper"] self.auto_update_branch = self.config["Updates"]["auto-update-branch"] if not self.auto_update_branch: self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development" else: self.update_url = self.config["Updates"][self.auto_update_branch] self.use_timer_tick_event = self.config["Gameplay"][ "use-timer-tick-event"] self.use_readline = not (self.config["Misc"]["use-betterconsole"]) # Storages self.wrapper_storage = Storage("wrapper", encoding=self.encoding) self.wrapper_permissions = Storage("permissions", encoding=self.encoding, pickle=False) self.wrapper_usercache = Storage("usercache", encoding=self.encoding, pickle=False) # storage Data objects self.storage = self.wrapper_storage.Data self.usercache = self.wrapper_usercache.Data # self.wrapper_permissions accessed only by permissions module # core functions and datasets self.perms = Permissions(self) self.uuids = UUIDS(self.log, self.usercache) self.plugins = Plugins(self) self.commands = Commands(self) self.events = Events(self) self.players = {} self.registered_permissions = {} self.help = {} self.input_buff = "" self.sig_int = False self.command_hist = ['/help', 'help'] self.command_index = 1 # init items that are set up later (or opted out of/ not set up.) self.javaserver = None self.api = None self.irc = None self.scripts = None self.web = None self.proxy = None self.backups = None # HaltSig - Why? ... because if self.halt was just `False`, passing # a self.halt would simply be passing `False` (immutable). Changing # the value of self.halt would not necessarily change the value of the # passed parameter (unless it was specifically referenced back as # `wrapper.halt`). Since the halt signal needs to be passed, possibly # several layers deep, and into modules that it may be desireable to # not have direct access to wrapper, using a HaltSig object is # more desireable and reliable in behavior. self.halt = HaltSig() self.updated = False # future plan to expose this to api self.xplayer = ConsolePlayer(self) # Error messages for non-standard import failures. if not readline and self.use_readline: self.log.warning( "'readline' not imported. This is needed for proper" " console functioning. Press <Enter> to acknowledge...") sys.stdin.readline() # requests is just being used in too many places to try # and track its usages piece-meal. if not requests: self.log.error( "You must have the requests module installed to use wrapper!" " console functioning. Press <Enter> to Exit...") sys.stdin.readline() self._halt() # create server/proxy vitals and config objects self.servervitals = ServerVitals(self.players) # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS: # The ServerVitals class gets passed the player object list now, but # player objects are now housed in wrapper. This is how we are # passing information between proxy and wrapper. self.servervitals.serverpath = self.config["General"][ "server-directory"] self.servervitals.state = OFF self.servervitals.command_prefix = self.config["Misc"][ "command-prefix"] self.proxyconfig = ProxyConfig() self.proxyconfig.proxy = self.config["Proxy"] self.proxyconfig.entity = self.config["Entities"]
class Wrapper(object): def __init__(self, secret_passphrase): # setup log and config # needs a false setting on first in case config does not # load (like after changes). self.storage = False self.log = logging.getLogger('Wrapper.py') self.configManager = Config() self.configManager.loadconfig() self.config = self.configManager.config # Read Config items # hard coded cursor for non-readline mode self.cursor = ">" # This was to allow alternate encodings self.encoding = self.config["General"]["encoding"] self.serverpath = self.config["General"]["server-directory"] self.proxymode = self.config["Proxy"]["proxy-enabled"] self.wrapper_onlinemode = self.config["Proxy"]["online-mode"] self.halt_message = self.config["Misc"]["halt-message"] # encryption items (for passwords and sensitive user data) # salt is generated and stored in wrapper.properties.json config_changes = False salt = self.config["General"]["salt"] if not salt: salt = gensalt(self.encoding) self.config["General"]["salt"] = salt config_changes = True # passphrase is provided at startup by the wrapper operator or script (not stored) passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding, salt) self.cipher = Crypt(passphrase, self.encoding) # Update passwords (hash any plaintext passwords) for groups in self.config: for cfg_items in self.config[groups]: if cfg_items[-10:] == "-plaintext": # i.e., cfg_items ===> like ["web-password-plaintext"] hash_item = cfg_items[:-10] # hash_item ===> i.e., ["web-password"] if hash_item in self.config[groups] and self.config[ groups][cfg_items]: # encrypt contents of (i.e.) ["web-password-plaintext"] hashed_item = self.cipher.encrypt( self.config[groups][cfg_items]) # store in "" ["Web"]["web-password"] self.config[groups][hash_item] = hashed_item # set plaintext item to false (successful digest) self.config[groups][cfg_items] = False config_changes = True # Patch any old update paths "..wrapper/development/build/version.json" # new paths are: "..wrapper/development" for entries in self.config["Updates"]: if "/build/version.json" in str(self.config["Updates"][entries]): oldentry = copy.copy(self.config["Updates"][entries]) self.config["Updates"][entries] = oldentry.split( "/build/version.json")[0] config_changes = True # save changes made to config file if config_changes: self.configManager.save() # reload branch update info. self.auto_update_wrapper = self.config["Updates"][ "auto-update-wrapper"] self.auto_update_branch = self.config["Updates"]["auto-update-branch"] if not self.auto_update_branch: self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development" else: self.update_url = self.config["Updates"][self.auto_update_branch] self.use_timer_tick_event = self.config["Gameplay"][ "use-timer-tick-event"] self.use_readline = not (self.config["Misc"]["use-betterconsole"]) # Storages self.wrapper_storage = Storage("wrapper", encoding=self.encoding) self.wrapper_permissions = Storage("permissions", encoding=self.encoding, pickle=False) self.wrapper_usercache = Storage("usercache", encoding=self.encoding, pickle=False) # storage Data objects self.storage = self.wrapper_storage.Data self.usercache = self.wrapper_usercache.Data # self.wrapper_permissions accessed only by permissions module # core functions and datasets self.perms = Permissions(self) self.uuids = UUIDS(self.log, self.usercache) self.plugins = Plugins(self) self.commands = Commands(self) self.events = Events(self) self.players = {} self.registered_permissions = {} self.help = {} self.input_buff = "" self.sig_int = False self.command_hist = ['/help', 'help'] self.command_index = 1 # init items that are set up later (or opted out of/ not set up.) self.javaserver = None self.api = None self.irc = None self.scripts = None self.web = None self.proxy = None self.backups = None # HaltSig - Why? ... because if self.halt was just `False`, passing # a self.halt would simply be passing `False` (immutable). Changing # the value of self.halt would not necessarily change the value of the # passed parameter (unless it was specifically referenced back as # `wrapper.halt`). Since the halt signal needs to be passed, possibly # several layers deep, and into modules that it may be desireable to # not have direct access to wrapper, using a HaltSig object is # more desireable and reliable in behavior. self.halt = HaltSig() self.updated = False # future plan to expose this to api self.xplayer = ConsolePlayer(self) # Error messages for non-standard import failures. if not readline and self.use_readline: self.log.warning( "'readline' not imported. This is needed for proper" " console functioning. Press <Enter> to acknowledge...") sys.stdin.readline() # requests is just being used in too many places to try # and track its usages piece-meal. if not requests: self.log.error( "You must have the requests module installed to use wrapper!" " console functioning. Press <Enter> to Exit...") sys.stdin.readline() self._halt() # create server/proxy vitals and config objects self.servervitals = ServerVitals(self.players) # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS: # The ServerVitals class gets passed the player object list now, but # player objects are now housed in wrapper. This is how we are # passing information between proxy and wrapper. self.servervitals.serverpath = self.config["General"][ "server-directory"] self.servervitals.state = OFF self.servervitals.command_prefix = self.config["Misc"][ "command-prefix"] self.proxyconfig = ProxyConfig() self.proxyconfig.proxy = self.config["Proxy"] self.proxyconfig.entity = self.config["Entities"] def __del__(self): """prevent error message on very first wrapper starts when wrapper exits after creating new wrapper.properties file. """ if self.storage: self.wrapper_storage.close() self.wrapper_permissions.close() self.wrapper_usercache.close() def start(self): """wrapper execution starts here""" self.signals() self.backups = Backups(self) self.api = API(self, "Wrapper.py") self._registerwrappershelp() # This is not the actual server... the MCServer # class is a console wherein the server is started self.javaserver = MCServer(self, self.servervitals) self.javaserver.init() # load plugins self.plugins.loadplugins() if self.config["IRC"]["irc-enabled"]: # this should be a plugin self.irc = IRC(self.javaserver, self.log, self) t = threading.Thread(target=self.irc.init, args=()) t.daemon = True t.start() if self.config["Web"]["web-enabled"]: # this should be a plugin if manageweb.pkg_resources and manageweb.requests: self.log.warning( "Our apologies! Web mode is currently broken. Wrapper" " will start web mode anyway, but it will not likely " "function well (or at all). For now, you should turn " "off web mode in wrapper.properties.json.") self.web = manageweb.Web(self) t = threading.Thread(target=self.web.wrap, args=()) t.daemon = True t.start() else: self.log.error( "Web remote could not be started because you do not have" " the required modules installed: pkg_resources\n" "Hint: http://stackoverflow.com/questions/7446187") # Console Daemon runs while not wrapper.halt.halt consoledaemon = threading.Thread(target=self.parseconsoleinput, args=()) consoledaemon.daemon = True consoledaemon.start() # Timer runs while not wrapper.halt.halt ts = threading.Thread(target=self.event_timer_second, args=()) ts.daemon = True ts.start() if self.use_timer_tick_event: # Timer runs while not wrapper.halt.halt tt = threading.Thread(target=self.event_timer_tick, args=()) tt.daemon = True tt.start() if self.config["General"]["shell-scripts"]: if os.name in ("posix", "mac"): self.scripts = Scripts(self) else: self.log.error( "Sorry, but shell scripts only work on *NIX-based systems!" " If you are using a *NIX-based system, please file a " "bug report.") if self.proxymode: t = threading.Thread(target=self._startproxy, args=()) t.daemon = True t.start() if self.auto_update_wrapper: t = threading.Thread(target=self._auto_update_process, args=()) t.daemon = True t.start() self.javaserver.handle_server() # handle_server always runs, even if the actual server is not started self.plugins.disableplugins() self.log.info("Plugins disabled") self.wrapper_storage.close() self.wrapper_permissions.close() self.wrapper_usercache.close() self.log.info("Wrapper Storages closed and saved.") # wrapper execution ends here. handle_server ends when # wrapper.halt.halt is True. if self.sig_int: self.log.info("Ending threads, please wait...") time.sleep(5) os.system("reset") def signals(self): signal.signal(signal.SIGINT, self.sigint) signal.signal(signal.SIGTERM, self.sigterm) # noinspection PyBroadException try: # lacking in Windows signal.signal(signal.SIGTSTP, self.sigtstp) except: pass def sigint(*args): self = args[0] # We are only interested in the self component self.log.info("Wrapper.py received SIGINT; halting...\n") self.sig_int = True self._halt() def sigterm(*args): self = args[0] # We are only interested in the self component self.log.info("Wrapper.py received SIGTERM; halting...\n") self._halt() def sigtstp(*args): # We are only interested in the 'self' component self = args[0] self.log.info("Wrapper.py received SIGTSTP; NO sleep support!" " Wrapper halting...\n") os.system("kill -CONT %d" % self.javaserver.proc.pid) self._halt() def _halt(self): self.javaserver.stop(self.halt_message, restart_the_server=False) self.halt.halt = True def shutdown(self): self._halt() def write_stdout(self, message="", source="print"): """ :param message: desired output line. Default is wrapper. :param source: "server", "wrapper", "print" or "log". Default is print. """ cursor = self.cursor if self.use_readline: print(message) return def _wrapper(msg): """_wrapper is normally displaying a live typing buffer. Therefore, there is no cr/lf at end because it is constantly being re-printed in the same spot as the user types.""" if msg != "": # re-print what the console user was typing right below that. # /wrapper commands receive special magenta coloring if msg[0:1] == '/': print("{0}{1}{2}{3}{4}{5}".format(UP_LINE, cursor, FG_YELLOW, msg, RESET, CLEAR_EOL)) else: print("{0}{1}{2}{3}".format(BACKSPACE, cursor, msg, CLEAR_EOL)) def _server(msg): # print server lines print("{0}{1}{2}\r\n".format(UP_LINE, CLEAR_LINE, msg, CLEAR_EOL)) def _print(msg): _server(msg) parse = { "server": _server, "wrapper": _wrapper, "print": _print, } # if this fails due to key error, we WANT that raised, as it is # a program code error, not a run-time error. parse[source](message) def getconsoleinput(self): """If wrapper is NOT using readline (self.use_readline == False), then getconsoleinput manually implements our own character reading, parsing, arrow keys, command history, etc. This is desireable because it allows the user to continue to see their input and modify it, even if the server is producing console line messages that would normally "carry away" the user's typing. Implemented in response to issue 326: 'Command being typed gets carried off by console every time server generates output #326' by @Darkness3840: https://github.com/benbaptist/minecraft-wrapper/issues/326 """ if self.use_readline: # Obtain a line of console input try: consoleinput = sys.stdin.readline().strip() except Exception as e: self.log.error( "[continue] variable 'consoleinput' in 'console()' did" " not evaluate \n%s" % e) consoleinput = "" else: arrow_index = 0 # working buffer allows arrow use to restore what they # were typing but did not enter as a command yet working_buff = '' while not self.halt.halt: keypress = readkey.getcharacter() keycode = readkey.convertchar(keypress) length = len(self.input_buff) if keycode == "right": arrow_index += 1 if arrow_index > length: arrow_index = length if keycode == "left": arrow_index -= 1 if arrow_index < 1: arrow_index = 0 if keycode == "up": # goes 'back' in command history time self.command_index -= 1 if self.command_index < 1: self.command_index = 0 self.input_buff = self.command_hist[self.command_index] arrow_index = len(self.input_buff) if keycode == "down": # goes forward in command history time self.command_index += 1 if self.command_index + 1 > len(self.command_hist): # These actions happen when at most recent typing self.command_index = len(self.command_hist) self.input_buff = '%s' % working_buff self.write_stdout("%s " % self.input_buff, source="wrapper") arrow_index = len(self.input_buff) continue self.input_buff = self.command_hist[self.command_index] arrow_index = len(self.input_buff) buff_left = "%s" % self.input_buff[:arrow_index] buff_right = "%s" % self.input_buff[arrow_index:] if keycode == "backspace": if len(buff_left) > 0: buff_left = buff_left[:-1] self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff arrow_index -= 1 if keycode == "delete": if len(buff_right) > 0: buff_right = buff_right[1:] self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff if keycode in ("enter", "cr", "lf"): # scroll up (because cr is not added to buffer) # print("") break if keycode in ("ctrl-c", "ctrl-x"): self.sigterm() break # hide special key codes like PAGE_UP, etc if not used if not keycode: buff_left = "%s%s" % (buff_left, keypress) self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff arrow_index += 1 # with open('readout.txt', "w") as f: # f.write("left: '%s'\nright: '%s'\nbuff: '%s'" % ( # buff_left, buff_right, self.input_buff)) if len(buff_right) > 0: self.write_stdout( "{0}{1}{2}{3}".format(REVERSED, buff_left, RESET, buff_right), "wrapper") else: self.write_stdout("%s " % self.input_buff, source="wrapper") consoleinput = "%s" % self.input_buff self.input_buff = "" if consoleinput in self.command_hist: # if the command is already in the history somewhere, # remove it and re-append to the end (most recent) self.command_hist.remove(consoleinput) self.command_hist.append(consoleinput) else: # or just add it. self.command_hist.append(consoleinput) self.command_index = len(self.command_hist) # print the finished command to console self.write_stdout("%s\r\n" % self.input_buff, source="wrapper") return consoleinput def parseconsoleinput(self): while not self.halt.halt: consoleinput = self.getconsoleinput() # No command (perhaps just a line feed or spaces?) if len(consoleinput) < 1: continue # for use with runwrapperconsolecommand() command wholecommandline = consoleinput[0:].split(" ") command = str(getargs(wholecommandline, 0)).lower() # this can be passed to runwrapperconsolecommand() command for args allargs = wholecommandline[1:] # Console only commands (not accessible in-game) if command in ("/halt", "halt"): self._halt() elif command in ("/stop", "stop"): self.javaserver.stop_server_command() # "kill" (with no slash) is a server command. elif command == "/kill": self.javaserver.kill("Server killed at Console...") elif command in ("/start", "start"): self.javaserver.start() elif command in ("/restart", "restart"): self.javaserver.restart() elif command in ("/update-wrapper", "update-wrapper"): self._checkforupdate(True) # "plugins" command (with no slash) reserved for server commands elif command == "/plugins": self.listplugins() elif command in ("/mem", "/memory", "mem", "memory"): self._memory() elif command in ("/raw", "raw"): self._raw(consoleinput) elif command in ("/freeze", "freeze"): self._freeze() elif command in ("/unfreeze", "unfreeze"): self._unfreeze() elif command == "/version": readout("/version", self.getbuildstring(), usereadline=self.use_readline) elif command in ("/mute", "/pause", "/cm", "/m", "/p"): self._mute_console(allargs) # Commands that share the commands.py in-game interface # "reload" (with no slash) may be used by bukkit servers elif command == "/reload": self.runwrapperconsolecommand("reload", []) # proxy mode ban system elif self.proxymode and command == "/ban": self.runwrapperconsolecommand("ban", allargs) elif self.proxymode and command == "/ban-ip": self.runwrapperconsolecommand("ban-ip", allargs) elif self.proxymode and command == "/pardon-ip": self.runwrapperconsolecommand("pardon-ip", allargs) elif self.proxymode and command == "/pardon": self.runwrapperconsolecommand("pardon", allargs) elif command in ("/perm", "/perms", "/super", "/permissions", "perm", "perms", "super", "permissions"): self.runwrapperconsolecommand("perms", allargs) elif command in ("/playerstats", "/stats", "playerstats", "stats"): self.runwrapperconsolecommand("playerstats", allargs) elif command in ("/ent", "/entity", "/entities", "ent", "entity", "entities"): self.runwrapperconsolecommand("ent", allargs) elif command in ("/config", "/con", "/prop", "/property", "/properties"): self.runwrapperconsolecommand("config", allargs) elif command in ("op", "/op"): self.runwrapperconsolecommand("op", allargs) elif command in ("deop", "/deop"): self.runwrapperconsolecommand("deop", allargs) elif command in ("pass", "/pass", "pw", "/pw", "password", "/password"): self.runwrapperconsolecommand("password", allargs) # TODO Add more commands below here, below the original items: # TODO __________________ # more commands here... # TODO __________________ # TODO add more commands above here, above the help-related items: # minecraft help command elif command == "help": readout("/help", "Get wrapper.py help.", separator=" (with a slash) - ", usereadline=self.use_readline) self.javaserver.console(consoleinput) # wrapper's help (console version) elif command == "/help": self._show_help_console() # wrapper ban help elif command == "/bans": self._show_help_bans() # Commmand not recognized by wrapper else: try: self.javaserver.console(consoleinput) except Exception as e: self.log.error("[BREAK] Console input exception" " (nothing passed to server) \n%s" % e) break continue def _registerwrappershelp(self): # All commands listed herein are accessible in-game # Also require player.isOp() new_usage = "<player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]" self.api.registerHelp( "Wrapper", "Internal Wrapper.py commands ", [ ("/wrapper [update/memory/halt]", "If no subcommand is provided, it will" " show the Wrapper version.", None), ("/playerstats [all]", "Show the most active players. If no subcommand" " is provided, it'll show the top 10 players.", None), ("/plugins", "Show a list of the installed plugins", None), ("/reload", "Reload all plugins.", None), ("/op %s" % new_usage, "This and deop are Wrapper commands.", None), ("/permissions <groups/users/RESET>", "Command used to manage permission groups and" " users, add permission nodes, etc.", None), ("/entity <count/kill> [eid] [count]", "/entity help/? for more help.. ", None), ("/config", "Change wrapper.properties (type" " /config help for more..)", None), ("/password", "Sample usage: /pw IRC control-irc-pass <new" "password>", None), # Minimum server version for commands to appear is # 1.7.6 (registers perm later in serverconnection.py) # These won't appear if proxy mode is not on (since # serverconnection is part of proxy). ("/ban <name> [reason..] [d:<days>/h:<hours>]", "Ban a player. Specifying h:<hours> or d:<days>" " creates a temp ban.", "mc1.7.6"), ("/ban-ip <ip> [<reason..> <d:<number of days>]", "- Ban an IP address. Reason and days" " (d:) are optional.", "mc1.7.6"), ("/pardon <player> [False]", " - pardon a player. Default is byuuidonly." " To unban a specific " "name (without checking uuid), use `pardon" " <player> False`", "mc1.7.6"), ("/pardon-ip <address>", "Pardon an IP address.", "mc1.7.6"), ("/banlist [players|ips] [searchtext]", "search and display the banlist (warning -" " displays on single page!)", "mc1.7.6") ]) def runwrapperconsolecommand(self, wrappercommand, argslist): xpayload = { 'player': self.xplayer, 'command': wrappercommand, 'args': argslist } self.commands.playercommand(xpayload) def isonlinemode(self): """ :returns: Whether the server OR (for proxy mode) wrapper is in online mode. This should normally 'always' render True. Under rare circumstances it could be false, such as when this wrapper and its server are the target for a wrapper lobby with player.connect(). """ if self.proxymode: # if wrapper is using proxy mode (which should be set to online) return self.config["Proxy"]["online-mode"] if self.javaserver is not None: if self.servervitals.onlineMode: # if local server is online-mode return True return False def listplugins(self): readout("", "List of Wrapper.py plugins installed:", separator="", pad=4, usereadline=self.use_readline) for plid in self.plugins: plugin = self.plugins[plid] if plugin["good"]: name = plugin["name"] summary = plugin["summary"] if summary is None: summary = "No description available for this plugin" version = plugin["version"] readout(name, summary, separator=(" - v%s - " % ".".join([str(_) for _ in version])), usereadline=self.use_readline) else: readout("failed to load plugin", plugin, pad=25, usereadline=self.use_readline) def _start_emailer(self): alerts = self.config["Alerts"]["enabled"] if alerts: self.config["Alerts"] = "alerts true" def _startproxy(self): # error will raise if requests or cryptography is missing. self.proxy = Proxy(self.halt, self.proxyconfig, self.servervitals, self.log, self.usercache, self.events) # wait for server to start timer = 0 while self.servervitals.state < STARTED: timer += 1 time.sleep(.1) if timer > 1200: self.log.warning( "Proxy mode did not detect a started server within 2" " minutes. Disabling proxy mode because something is" " wrong.") self.disable_proxymode() return if self.proxy.proxy_port == self.servervitals.server_port: self.log.warning("Proxy mode cannot start because the wrapper" " port is identical to the server port.") self.disable_proxymode() return proxythread = threading.Thread(target=self.proxy.host, args=()) proxythread.daemon = True proxythread.start() def disable_proxymode(self): self.proxymode = False self.config["Proxy"]["proxy-enabled"] = False self.configManager.save() self.log.warning( "\nProxy mode is now turned off in wrapper.properties.json.\n") @staticmethod def getbuildstring(): if core_buildinfo_version.__branch__ == "dev": return "%s (development build #%d)" % ( core_buildinfo_version.__version__, core_buildinfo_version.__build__) elif core_buildinfo_version.__branch__ == "stable": return "%s (stable)" % core_buildinfo_version.__version__ else: return "Version: %s (%s build #%d)" % ( core_buildinfo_version.__version__, core_buildinfo_version.__branch__, core_buildinfo_version.__build__) def _auto_update_process(self): while not self.halt.halt: time.sleep(3600) if self.updated: self.log.info("An update for wrapper has been loaded," " Please restart wrapper.") else: self._checkforupdate() def _checkforupdate(self, update_now=False): """ checks for update """ self.log.info("Checking for new builds...") update = self.get_wrapper_update_info() if update: version, build, repotype, reponame = update self.log.info( "New Wrapper.py %s build #%d is available!" " (current build is #%d)", repotype, build, core_buildinfo_version.__build__) if self.auto_update_wrapper or update_now: self.log.info("Updating...") self.performupdate(version, build) else: self.log.info( "Because you have 'auto-update-wrapper' set to False," " you must manually update Wrapper.py. To update" " Wrapper.py manually, please type /update-wrapper.") else: self.log.info("No new versions available.") def get_wrapper_update_info(self, repotype=None): """get the applicable branch wrapper update""" # read the installed branch info if repotype is None: repotype = core_buildinfo_version.__branch__ if self.auto_update_branch: branch_key = self.auto_update_branch else: branch_key = "%s-branch" % repotype r = requests.get("%s/build/version.json" % self.config["Updates"][branch_key]) if r.status_code == 200: data = r.json() if data["__build__"] > core_buildinfo_version.__build__: if repotype == "dev": reponame = "development" elif repotype == "stable": reponame = "master" else: reponame = data["__branch__"] if "__version__" not in data: data["__version__"] = data["version"] return data["__version__"], data["__build__"], data[ "__branch__"], reponame else: self.log.warning( "Failed to check for updates - are you connected to the" " internet? (Status Code %d)", r.status_code) return False def performupdate(self, version, build): """ Perform update; returns True if update succeeds. User must still restart wrapper manually. :param version: first argument from get_wrapper_update_info() :param build: second argument from get_wrapper_update_info() :return: True if update succeeds """ wraphash = requests.get("%s/build/Wrapper.py.md5" % self.update_url) wrapperfile = requests.get("%s/Wrapper.py" % self.update_url) if wraphash.status_code == 200 and wrapperfile.status_code == 200: self.log.info("Verifying Wrapper.py...") if hashlib.md5(wrapperfile.content).hexdigest() == wraphash.text: self.log.info( "Update file successfully verified. Installing...") with open(sys.argv[0], "wb") as f: f.write(wrapperfile.content) self.log.info( "Wrapper.py %s (#%d) installed. Please reboot Wrapper.py.", ".".join([str(_) for _ in version]), build) self.updated = True return True else: return False else: self.log.error( "Failed to update due to an internal error (%d, %d)", wraphash.status_code, wrapperfile.status_code, exc_info=True) return False def event_timer_second(self): while not self.halt.halt: time.sleep(1) self.events.callevent("timer.second", None) """ eventdoc <group> wrapper <group> <description> a timer that is called each second. <description> <abortable> No <abortable> """ def event_timer_tick(self): while not self.halt.halt: self.events.callevent("timer.tick", None) time.sleep(0.05) """ eventdoc <group> wrapper <group> <description> a timer that is called each 1/20th <sp> of a second, like a minecraft tick. <description> <abortable> No <abortable> <comments> Use of this timer is not suggested and is turned off <sp> by default in the wrapper.config.json file <comments> """ def _pause_console(self, pause_time): if not self.javaserver: readout("ERROR - ", "There is no running server instance to mute.", separator="", pad=10, usereadline=self.use_readline) return self.javaserver.server_muted = True readout("Server is now muted for %d seconds." % pause_time, "", separator="", command_text_fg="yellow", usereadline=self.use_readline) time.sleep(pause_time) readout("Server now unmuted.", "", separator="", usereadline=self.use_readline) self.javaserver.server_muted = False for lines in self.javaserver.queued_lines: readout("Q\\", "", lines, pad=3, usereadline=self.use_readline) time.sleep(.1) self.javaserver.queued_lines = [] def _mute_console(self, all_args): pausetime = 30 if len(all_args) > 0: pausetime = get_int(all_args[0]) # spur off a pause thread cm = threading.Thread(target=self._pause_console, args=(pausetime, )) cm.daemon = True cm.start() def _freeze(self): try: self.javaserver.freeze() except OSError as ex: self.log.error(ex) except EnvironmentError as e: self.log.warning(e) except Exception as exc: self.log.exception( "Something went wrong when trying to freeze the" " server! (%s)", exc) def _memory(self): try: get_bytes = self.javaserver.getmemoryusage() except OSError as e: self.log.error(e) except Exception as ex: self.log.exception( "Something went wrong when trying to fetch" " memory usage! (%s)", ex) else: amount, units = format_bytes(get_bytes) self.log.info("Server Memory Usage: %s %s (%s bytes)" % (amount, units, get_bytes)) def _raw(self, console_input): try: if len(getargsafter(console_input[1:].split(" "), 1)) > 0: self.javaserver.console( getargsafter(console_input[1:].split(" "), 1)) else: self.log.info("Usage: /raw [command]") except EnvironmentError as e: self.log.warning(e) def _unfreeze(self): try: self.javaserver.unfreeze() except OSError as ex: self.log.error(ex) except EnvironmentError as e: self.log.warning(e) except Exception as exc: self.log.exception( "Something went wrong when trying to unfreeze" " the server! (%s)", exc) def _show_help_console(self): # This is the console help command display. readout("", "Get Minecraft help.", separator="help (no slash) - ", pad=0, usereadline=self.use_readline) readout("/reload", "Reload Wrapper.py plugins.", usereadline=self.use_readline) readout("/plugins", "Lists Wrapper.py plugins.", usereadline=self.use_readline) readout("/update-wrapper", "Checks for new Wrapper.py updates, and will install\n" "them automatically if one is available.", usereadline=self.use_readline) readout("/stop", "Stop the minecraft server without" " auto-restarting and without\n" " shuttingdown Wrapper.py.", usereadline=self.use_readline) readout("/start", "Start the minecraft server.", usereadline=self.use_readline) readout("/restart", "Restarts the minecraft server.", usereadline=self.use_readline) readout("/halt", "Shutdown Wrapper.py completely.", usereadline=self.use_readline) readout("/cm [seconds]", "Mute server output (Wrapper console" " logging still happens)", usereadline=self.use_readline) readout("/kill", "Force kill the server without saving.", usereadline=self.use_readline) readout("/freeze", "Temporarily locks the server up" " until /unfreeze is executed\n" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/unfreeze", "Unlocks a frozen state server" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/mem", "Get memory usage of the server" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/raw [command]", "Send command to the Minecraft" " Server. Useful for Forge\n" " commands like '/fml confirm'.", usereadline=self.use_readline) readout("/password", "run `/password help` for more...)", usereadline=self.use_readline) readout("/perms", "/perms for more...)", usereadline=self.use_readline) readout("/config", "Change wrapper.properties (type" " /config help for more..)", usereadline=self.use_readline) readout("/version", self.getbuildstring(), usereadline=self.use_readline) readout("/entity", "Work with entities (run /entity for more...)", usereadline=self.use_readline) readout("/bans", "Display the ban help page.", usereadline=self.use_readline) def _show_help_bans(self): # ban commands help. if not self.proxymode: readout("ERROR - ", "Wrapper proxy-mode bans are not enabled " "(proxy mode is not on).", separator="", pad=10, usereadline=self.use_readline) return readout("", "Bans - To use the server's versions, do not type a slash.", separator="", pad=5, usereadline=self.use_readline) readout("", "", separator="-----1.7.6 and later ban commands-----", pad=10, usereadline=self.use_readline) readout("/ban", " - Ban a player. Specifying h:<hours> or d:<days>" " creates a temp ban.", separator="<name> [reason..] [d:<days>/h:<hours>] ", pad=12, usereadline=self.use_readline) readout("/ban-ip", " - Ban an IP address. Reason and days (d:) are optional.", separator="<ip> [<reason..> <d:<number of days>] ", pad=12, usereadline=self.use_readline) readout("/pardon", " - pardon a player. Default is byuuidonly. To unban a" "specific name (without checking uuid), use" " `pardon <player> False`", separator="<player> [byuuidonly(true/false)]", pad=12, usereadline=self.use_readline) readout("/pardon-ip", " - Pardon an IP address.", separator="<address> ", pad=12, usereadline=self.use_readline) readout("/banlist", " - search and display the banlist (warning -" " displays on single page!)", separator="[players|ips] [searchtext] ", pad=12, usereadline=self.use_readline)
def __init__(self, secret_passphrase): # setup log and config # needs a false setting on first in case config does not # load (like after changes). self.storage = False self.log = logging.getLogger('Wrapper.py') self.configManager = Config() self.configManager.loadconfig() self.config = self.configManager.config # Read Config items # hard coded cursor for non-readline mode self.cursor = ">" # This was to allow alternate encodings self.encoding = self.config["General"]["encoding"] self.serverpath = self.config["General"]["server-directory"] self.proxymode = self.config["Proxy"]["proxy-enabled"] self.wrapper_onlinemode = self.config["Proxy"]["online-mode"] self.halt_message = self.config["Misc"]["halt-message"] # encryption items (for passwords and sensitive user data) # salt is generated and stored in wrapper.properties.json config_changes = False salt = self.config["General"]["salt"] if not salt: salt = gensalt(self.encoding) self.config["General"]["salt"] = salt config_changes = True # passphrase is provided at startup by the wrapper operator or script (not stored) passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding, salt) self.cipher = Crypt(passphrase, self.encoding) # Update passwords (hash any plaintext passwords) for groups in self.config: for cfg_items in self.config[groups]: if cfg_items[-10:] == "-plaintext": # i.e., cfg_items ===> like ["web-password-plaintext"] hash_item = cfg_items[:-10] # hash_item ===> i.e., ["web-password"] if hash_item in self.config[groups] and self.config[groups][cfg_items]: # encrypt contents of (i.e.) ["web-password-plaintext"] hashed_item = self.cipher.encrypt( self.config[groups][cfg_items]) # store in "" ["Web"]["web-password"] self.config[groups][hash_item] = hashed_item # set plaintext item to false (successful digest) self.config[groups][cfg_items] = False config_changes = True # Patch any old update paths "..wrapper/development/build/version.json" # new paths are: "..wrapper/development" for entries in self.config["Updates"]: if "/build/version.json" in str(self.config["Updates"][entries]): oldentry = copy.copy(self.config["Updates"][entries]) self.config["Updates"][entries] = oldentry.split("/build/version.json")[0] config_changes = True # save changes made to config file if config_changes: self.configManager.save() # reload branch update info. self.auto_update_wrapper = self.config["Updates"]["auto-update-wrapper"] self.auto_update_branch = self.config["Updates"]["auto-update-branch"] if not self.auto_update_branch: self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development" else: self.update_url = self.config["Updates"][self.auto_update_branch] self.use_timer_tick_event = self.config[ "Gameplay"]["use-timer-tick-event"] self.use_readline = not(self.config["Misc"]["use-betterconsole"]) # Storages self.wrapper_storage = Storage( "wrapper", encoding=self.encoding) self.wrapper_permissions = Storage( "permissions", encoding=self.encoding, pickle=False) self.wrapper_usercache = Storage( "usercache", encoding=self.encoding, pickle=False) # storage Data objects self.storage = self.wrapper_storage.Data self.usercache = self.wrapper_usercache.Data # self.wrapper_permissions accessed only by permissions module # core functions and datasets self.perms = Permissions(self) self.uuids = UUIDS(self.log, self.usercache) self.plugins = Plugins(self) self.commands = Commands(self) self.events = Events(self) self.players = {} self.registered_permissions = {} self.help = {} self.input_buff = "" self.sig_int = False self.command_hist = ['/help', 'help'] self.command_index = 1 # init items that are set up later (or opted out of/ not set up.) self.javaserver = None self.api = None self.irc = None self.scripts = None self.web = None self.proxy = None self.backups = None # HaltSig - Why? ... because if self.halt was just `False`, passing # a self.halt would simply be passing `False` (immutable). Changing # the value of self.halt would not necessarily change the value of the # passed parameter (unless it was specifically referenced back as # `wrapper.halt`). Since the halt signal needs to be passed, possibly # several layers deep, and into modules that it may be desireable to # not have direct access to wrapper, using a HaltSig object is # more desireable and reliable in behavior. self.halt = HaltSig() self.updated = False # future plan to expose this to api self.xplayer = ConsolePlayer(self) # Error messages for non-standard import failures. if not readline and self.use_readline: self.log.warning( "'readline' not imported. This is needed for proper" " console functioning. Press <Enter> to acknowledge...") sys.stdin.readline() # requests is just being used in too many places to try # and track its usages piece-meal. if not requests: self.log.error( "You must have the requests module installed to use wrapper!" " console functioning. Press <Enter> to Exit...") sys.stdin.readline() self._halt() # create server/proxy vitals and config objects self.servervitals = ServerVitals(self.players) # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS: # The ServerVitals class gets passed the player object list now, but # player objects are now housed in wrapper. This is how we are # passing information between proxy and wrapper. self.servervitals.serverpath = self.config[ "General"]["server-directory"] self.servervitals.state = OFF self.servervitals.command_prefix = self.config[ "Misc"]["command-prefix"] self.proxyconfig = ProxyConfig() self.proxyconfig.proxy = self.config["Proxy"] self.proxyconfig.entity = self.config["Entities"]
class Wrapper(object): def __init__(self, secret_passphrase): # setup log and config # needs a false setting on first in case config does not # load (like after changes). self.storage = False self.log = logging.getLogger('Wrapper.py') self.configManager = Config() self.configManager.loadconfig() self.config = self.configManager.config # Read Config items # hard coded cursor for non-readline mode self.cursor = ">" # This was to allow alternate encodings self.encoding = self.config["General"]["encoding"] self.serverpath = self.config["General"]["server-directory"] self.proxymode = self.config["Proxy"]["proxy-enabled"] self.wrapper_onlinemode = self.config["Proxy"]["online-mode"] self.halt_message = self.config["Misc"]["halt-message"] # encryption items (for passwords and sensitive user data) # salt is generated and stored in wrapper.properties.json config_changes = False salt = self.config["General"]["salt"] if not salt: salt = gensalt(self.encoding) self.config["General"]["salt"] = salt config_changes = True # passphrase is provided at startup by the wrapper operator or script (not stored) passphrase = phrase_to_url_safebytes(secret_passphrase, self.encoding, salt) self.cipher = Crypt(passphrase, self.encoding) # Update passwords (hash any plaintext passwords) for groups in self.config: for cfg_items in self.config[groups]: if cfg_items[-10:] == "-plaintext": # i.e., cfg_items ===> like ["web-password-plaintext"] hash_item = cfg_items[:-10] # hash_item ===> i.e., ["web-password"] if hash_item in self.config[groups] and self.config[groups][cfg_items]: # encrypt contents of (i.e.) ["web-password-plaintext"] hashed_item = self.cipher.encrypt( self.config[groups][cfg_items]) # store in "" ["Web"]["web-password"] self.config[groups][hash_item] = hashed_item # set plaintext item to false (successful digest) self.config[groups][cfg_items] = False config_changes = True # Patch any old update paths "..wrapper/development/build/version.json" # new paths are: "..wrapper/development" for entries in self.config["Updates"]: if "/build/version.json" in str(self.config["Updates"][entries]): oldentry = copy.copy(self.config["Updates"][entries]) self.config["Updates"][entries] = oldentry.split("/build/version.json")[0] config_changes = True # save changes made to config file if config_changes: self.configManager.save() # reload branch update info. self.auto_update_wrapper = self.config["Updates"]["auto-update-wrapper"] self.auto_update_branch = self.config["Updates"]["auto-update-branch"] if not self.auto_update_branch: self.update_url = "https://raw.githubusercontent.com/benbaptist/minecraft-wrapper/development" else: self.update_url = self.config["Updates"][self.auto_update_branch] self.use_timer_tick_event = self.config[ "Gameplay"]["use-timer-tick-event"] self.use_readline = not(self.config["Misc"]["use-betterconsole"]) # Storages self.wrapper_storage = Storage( "wrapper", encoding=self.encoding) self.wrapper_permissions = Storage( "permissions", encoding=self.encoding, pickle=False) self.wrapper_usercache = Storage( "usercache", encoding=self.encoding, pickle=False) # storage Data objects self.storage = self.wrapper_storage.Data self.usercache = self.wrapper_usercache.Data # self.wrapper_permissions accessed only by permissions module # core functions and datasets self.perms = Permissions(self) self.uuids = UUIDS(self.log, self.usercache) self.plugins = Plugins(self) self.commands = Commands(self) self.events = Events(self) self.players = {} self.registered_permissions = {} self.help = {} self.input_buff = "" self.sig_int = False self.command_hist = ['/help', 'help'] self.command_index = 1 # init items that are set up later (or opted out of/ not set up.) self.javaserver = None self.api = None self.irc = None self.scripts = None self.web = None self.proxy = None self.backups = None # HaltSig - Why? ... because if self.halt was just `False`, passing # a self.halt would simply be passing `False` (immutable). Changing # the value of self.halt would not necessarily change the value of the # passed parameter (unless it was specifically referenced back as # `wrapper.halt`). Since the halt signal needs to be passed, possibly # several layers deep, and into modules that it may be desireable to # not have direct access to wrapper, using a HaltSig object is # more desireable and reliable in behavior. self.halt = HaltSig() self.updated = False # future plan to expose this to api self.xplayer = ConsolePlayer(self) # Error messages for non-standard import failures. if not readline and self.use_readline: self.log.warning( "'readline' not imported. This is needed for proper" " console functioning. Press <Enter> to acknowledge...") sys.stdin.readline() # requests is just being used in too many places to try # and track its usages piece-meal. if not requests: self.log.error( "You must have the requests module installed to use wrapper!" " console functioning. Press <Enter> to Exit...") sys.stdin.readline() self._halt() # create server/proxy vitals and config objects self.servervitals = ServerVitals(self.players) # LETS TAKE A SECOND TO DISCUSS PLAYER OBJECTS: # The ServerVitals class gets passed the player object list now, but # player objects are now housed in wrapper. This is how we are # passing information between proxy and wrapper. self.servervitals.serverpath = self.config[ "General"]["server-directory"] self.servervitals.state = OFF self.servervitals.command_prefix = self.config[ "Misc"]["command-prefix"] self.proxyconfig = ProxyConfig() self.proxyconfig.proxy = self.config["Proxy"] self.proxyconfig.entity = self.config["Entities"] def __del__(self): """prevent error message on very first wrapper starts when wrapper exits after creating new wrapper.properties file. """ if self.storage: self.wrapper_storage.close() self.wrapper_permissions.close() self.wrapper_usercache.close() def start(self): """wrapper execution starts here""" self.signals() self.backups = Backups(self) self.api = API(self, "Wrapper.py") self._registerwrappershelp() # This is not the actual server... the MCServer # class is a console wherein the server is started self.javaserver = MCServer(self, self.servervitals) self.javaserver.init() # load plugins self.plugins.loadplugins() if self.config["IRC"]["irc-enabled"]: # this should be a plugin self.irc = IRC(self.javaserver, self.log, self) t = threading.Thread(target=self.irc.init, args=()) t.daemon = True t.start() if self.config["Web"]["web-enabled"]: # this should be a plugin if manageweb.pkg_resources and manageweb.requests: self.log.warning( "Our apologies! Web mode is currently broken. Wrapper" " will start web mode anyway, but it will not likely " "function well (or at all). For now, you should turn " "off web mode in wrapper.properties.json.") self.web = manageweb.Web(self) t = threading.Thread(target=self.web.wrap, args=()) t.daemon = True t.start() else: self.log.error( "Web remote could not be started because you do not have" " the required modules installed: pkg_resources\n" "Hint: http://stackoverflow.com/questions/7446187") # Console Daemon runs while not wrapper.halt.halt consoledaemon = threading.Thread( target=self.parseconsoleinput, args=()) consoledaemon.daemon = True consoledaemon.start() # Timer runs while not wrapper.halt.halt ts = threading.Thread(target=self.event_timer_second, args=()) ts.daemon = True ts.start() if self.use_timer_tick_event: # Timer runs while not wrapper.halt.halt tt = threading.Thread(target=self.event_timer_tick, args=()) tt.daemon = True tt.start() if self.config["General"]["shell-scripts"]: if os.name in ("posix", "mac"): self.scripts = Scripts(self) else: self.log.error( "Sorry, but shell scripts only work on *NIX-based systems!" " If you are using a *NIX-based system, please file a " "bug report.") if self.proxymode: t = threading.Thread(target=self._startproxy, args=()) t.daemon = True t.start() if self.auto_update_wrapper: t = threading.Thread(target=self._auto_update_process, args=()) t.daemon = True t.start() self.javaserver.handle_server() # handle_server always runs, even if the actual server is not started self.plugins.disableplugins() self.log.info("Plugins disabled") self.wrapper_storage.close() self.wrapper_permissions.close() self.wrapper_usercache.close() self.log.info("Wrapper Storages closed and saved.") # wrapper execution ends here. handle_server ends when # wrapper.halt.halt is True. if self.sig_int: self.log.info("Ending threads, please wait...") time.sleep(5) os.system("reset") def signals(self): signal.signal(signal.SIGINT, self.sigint) signal.signal(signal.SIGTERM, self.sigterm) # noinspection PyBroadException try: # lacking in Windows signal.signal(signal.SIGTSTP, self.sigtstp) except: pass def sigint(*args): self = args[0] # We are only interested in the self component self.log.info("Wrapper.py received SIGINT; halting...\n") self.sig_int = True self._halt() def sigterm(*args): self = args[0] # We are only interested in the self component self.log.info("Wrapper.py received SIGTERM; halting...\n") self._halt() def sigtstp(*args): # We are only interested in the 'self' component self = args[0] self.log.info("Wrapper.py received SIGTSTP; NO sleep support!" " Wrapper halting...\n") os.system("kill -CONT %d" % self.javaserver.proc.pid) self._halt() def _halt(self): self.javaserver.stop(self.halt_message, restart_the_server=False) self.halt.halt = True def shutdown(self): self._halt() def write_stdout(self, message="", source="print"): """ :param message: desired output line. Default is wrapper. :param source: "server", "wrapper", "print" or "log". Default is print. """ cursor = self.cursor if self.use_readline: print(message) return def _wrapper(msg): """_wrapper is normally displaying a live typing buffer. Therefore, there is no cr/lf at end because it is constantly being re-printed in the same spot as the user types.""" if msg != "": # re-print what the console user was typing right below that. # /wrapper commands receive special magenta coloring if msg[0:1] == '/': print("{0}{1}{2}{3}{4}{5}".format( UP_LINE, cursor, FG_YELLOW, msg, RESET, CLEAR_EOL)) else: print("{0}{1}{2}{3}".format( BACKSPACE, cursor, msg, CLEAR_EOL)) def _server(msg): # print server lines print("{0}{1}{2}\r\n".format(UP_LINE, CLEAR_LINE, msg, CLEAR_EOL)) def _print(msg): _server(msg) parse = { "server": _server, "wrapper": _wrapper, "print": _print, } # if this fails due to key error, we WANT that raised, as it is # a program code error, not a run-time error. parse[source](message) def getconsoleinput(self): """If wrapper is NOT using readline (self.use_readline == False), then getconsoleinput manually implements our own character reading, parsing, arrow keys, command history, etc. This is desireable because it allows the user to continue to see their input and modify it, even if the server is producing console line messages that would normally "carry away" the user's typing. Implemented in response to issue 326: 'Command being typed gets carried off by console every time server generates output #326' by @Darkness3840: https://github.com/benbaptist/minecraft-wrapper/issues/326 """ if self.use_readline: # Obtain a line of console input try: consoleinput = sys.stdin.readline().strip() except Exception as e: self.log.error( "[continue] variable 'consoleinput' in 'console()' did" " not evaluate \n%s" % e) consoleinput = "" else: arrow_index = 0 # working buffer allows arrow use to restore what they # were typing but did not enter as a command yet working_buff = '' while not self.halt.halt: keypress = readkey.getcharacter() keycode = readkey.convertchar(keypress) length = len(self.input_buff) if keycode == "right": arrow_index += 1 if arrow_index > length: arrow_index = length if keycode == "left": arrow_index -= 1 if arrow_index < 1: arrow_index = 0 if keycode == "up": # goes 'back' in command history time self.command_index -= 1 if self.command_index < 1: self.command_index = 0 self.input_buff = self.command_hist[self.command_index] arrow_index = len(self.input_buff) if keycode == "down": # goes forward in command history time self.command_index += 1 if self.command_index + 1 > len(self.command_hist): # These actions happen when at most recent typing self.command_index = len(self.command_hist) self.input_buff = '%s' % working_buff self.write_stdout( "%s " % self.input_buff, source="wrapper") arrow_index = len(self.input_buff) continue self.input_buff = self.command_hist[self.command_index] arrow_index = len(self.input_buff) buff_left = "%s" % self.input_buff[:arrow_index] buff_right = "%s" % self.input_buff[arrow_index:] if keycode == "backspace": if len(buff_left) > 0: buff_left = buff_left[:-1] self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff arrow_index -= 1 if keycode == "delete": if len(buff_right) > 0: buff_right = buff_right[1:] self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff if keycode in ("enter", "cr", "lf"): # scroll up (because cr is not added to buffer) # print("") break if keycode in ("ctrl-c", "ctrl-x"): self.sigterm() break # hide special key codes like PAGE_UP, etc if not used if not keycode: buff_left = "%s%s" % (buff_left, keypress) self.input_buff = "%s%s" % (buff_left, buff_right) working_buff = "%s" % self.input_buff arrow_index += 1 # with open('readout.txt', "w") as f: # f.write("left: '%s'\nright: '%s'\nbuff: '%s'" % ( # buff_left, buff_right, self.input_buff)) if len(buff_right) > 0: self.write_stdout("{0}{1}{2}{3}".format( REVERSED, buff_left, RESET, buff_right), "wrapper") else: self.write_stdout( "%s " % self.input_buff, source="wrapper") consoleinput = "%s" % self.input_buff self.input_buff = "" if consoleinput in self.command_hist: # if the command is already in the history somewhere, # remove it and re-append to the end (most recent) self.command_hist.remove(consoleinput) self.command_hist.append(consoleinput) else: # or just add it. self.command_hist.append(consoleinput) self.command_index = len(self.command_hist) # print the finished command to console self.write_stdout( "%s\r\n" % self.input_buff, source="wrapper") return consoleinput def parseconsoleinput(self): while not self.halt.halt: consoleinput = self.getconsoleinput() # No command (perhaps just a line feed or spaces?) if len(consoleinput) < 1: continue # for use with runwrapperconsolecommand() command wholecommandline = consoleinput[0:].split(" ") command = str(getargs(wholecommandline, 0)).lower() # this can be passed to runwrapperconsolecommand() command for args allargs = wholecommandline[1:] # Console only commands (not accessible in-game) if command in ("/halt", "halt"): self._halt() elif command in ("/stop", "stop"): self.javaserver.stop_server_command() # "kill" (with no slash) is a server command. elif command == "/kill": self.javaserver.kill("Server killed at Console...") elif command in ("/start", "start"): self.javaserver.start() elif command in ("/restart", "restart"): self.javaserver.restart() elif command in ("/update-wrapper", "update-wrapper"): self._checkforupdate(True) # "plugins" command (with no slash) reserved for server commands elif command == "/plugins": self.listplugins() elif command in ("/mem", "/memory", "mem", "memory"): self._memory() elif command in ("/raw", "raw"): self._raw(consoleinput) elif command in ("/freeze", "freeze"): self._freeze() elif command in ("/unfreeze", "unfreeze"): self._unfreeze() elif command == "/version": readout("/version", self.getbuildstring(), usereadline=self.use_readline) elif command in ("/mute", "/pause", "/cm", "/m", "/p"): self._mute_console(allargs) # Commands that share the commands.py in-game interface # "reload" (with no slash) may be used by bukkit servers elif command == "/reload": self.runwrapperconsolecommand("reload", []) # proxy mode ban system elif self.proxymode and command == "/ban": self.runwrapperconsolecommand("ban", allargs) elif self.proxymode and command == "/ban-ip": self.runwrapperconsolecommand("ban-ip", allargs) elif self.proxymode and command == "/pardon-ip": self.runwrapperconsolecommand("pardon-ip", allargs) elif self.proxymode and command == "/pardon": self.runwrapperconsolecommand("pardon", allargs) elif command in ("/perm", "/perms", "/super", "/permissions", "perm", "perms", "super", "permissions"): self.runwrapperconsolecommand("perms", allargs) elif command in ("/playerstats", "/stats", "playerstats", "stats"): self.runwrapperconsolecommand("playerstats", allargs) elif command in ("/ent", "/entity", "/entities", "ent", "entity", "entities"): self.runwrapperconsolecommand("ent", allargs) elif command in ("/config", "/con", "/prop", "/property", "/properties"): self.runwrapperconsolecommand("config", allargs) elif command in ("op", "/op"): self.runwrapperconsolecommand("op", allargs) elif command in ("deop", "/deop"): self.runwrapperconsolecommand("deop", allargs) elif command in ("pass", "/pass", "pw", "/pw", "password", "/password"): self.runwrapperconsolecommand("password", allargs) # TODO Add more commands below here, below the original items: # TODO __________________ # more commands here... # TODO __________________ # TODO add more commands above here, above the help-related items: # minecraft help command elif command == "help": readout("/help", "Get wrapper.py help.", separator=" (with a slash) - ", usereadline=self.use_readline) self.javaserver.console(consoleinput) # wrapper's help (console version) elif command == "/help": self._show_help_console() # wrapper ban help elif command == "/bans": self._show_help_bans() # Commmand not recognized by wrapper else: try: self.javaserver.console(consoleinput) except Exception as e: self.log.error("[BREAK] Console input exception" " (nothing passed to server) \n%s" % e) break continue def _registerwrappershelp(self): # All commands listed herein are accessible in-game # Also require player.isOp() new_usage = "<player> [-s SUPER-OP] [-o OFFLINE] [-l <level>]" self.api.registerHelp( "Wrapper", "Internal Wrapper.py commands ", [ ("/wrapper [update/memory/halt]", "If no subcommand is provided, it will" " show the Wrapper version.", None), ("/playerstats [all]", "Show the most active players. If no subcommand" " is provided, it'll show the top 10 players.", None), ("/plugins", "Show a list of the installed plugins", None), ("/reload", "Reload all plugins.", None), ("/op %s" % new_usage, "This and deop are Wrapper commands.", None), ("/permissions <groups/users/RESET>", "Command used to manage permission groups and" " users, add permission nodes, etc.", None), ("/entity <count/kill> [eid] [count]", "/entity help/? for more help.. ", None), ("/config", "Change wrapper.properties (type" " /config help for more..)", None), ("/password", "Sample usage: /pw IRC control-irc-pass <new" "password>", None), # Minimum server version for commands to appear is # 1.7.6 (registers perm later in serverconnection.py) # These won't appear if proxy mode is not on (since # serverconnection is part of proxy). ("/ban <name> [reason..] [d:<days>/h:<hours>]", "Ban a player. Specifying h:<hours> or d:<days>" " creates a temp ban.", "mc1.7.6"), ("/ban-ip <ip> [<reason..> <d:<number of days>]", "- Ban an IP address. Reason and days" " (d:) are optional.", "mc1.7.6"), ("/pardon <player> [False]", " - pardon a player. Default is byuuidonly." " To unban a specific " "name (without checking uuid), use `pardon" " <player> False`", "mc1.7.6"), ("/pardon-ip <address>", "Pardon an IP address.", "mc1.7.6"), ("/banlist [players|ips] [searchtext]", "search and display the banlist (warning -" " displays on single page!)", "mc1.7.6") ]) def runwrapperconsolecommand(self, wrappercommand, argslist): xpayload = { 'player': self.xplayer, 'command': wrappercommand, 'args': argslist } self.commands.playercommand(xpayload) def isonlinemode(self): """ :returns: Whether the server OR (for proxy mode) wrapper is in online mode. This should normally 'always' render True. Under rare circumstances it could be false, such as when this wrapper and its server are the target for a wrapper lobby with player.connect(). """ if self.proxymode: # if wrapper is using proxy mode (which should be set to online) return self.config["Proxy"]["online-mode"] if self.javaserver is not None: if self.servervitals.onlineMode: # if local server is online-mode return True return False def listplugins(self): readout("", "List of Wrapper.py plugins installed:", separator="", pad=4, usereadline=self.use_readline) for plid in self.plugins: plugin = self.plugins[plid] if plugin["good"]: name = plugin["name"] summary = plugin["summary"] if summary is None: summary = "No description available for this plugin" version = plugin["version"] readout(name, summary, separator=( " - v%s - " % ".".join([str(_) for _ in version])), usereadline=self.use_readline) else: readout("failed to load plugin", plugin, pad=25, usereadline=self.use_readline) def _start_emailer(self): alerts = self.config["Alerts"]["enabled"] if alerts: self.config["Alerts"] = "alerts true" def _startproxy(self): # error will raise if requests or cryptography is missing. self.proxy = Proxy(self.halt, self.proxyconfig, self.servervitals, self.log, self.usercache, self.events) # wait for server to start timer = 0 while self.servervitals.state < STARTED: timer += 1 time.sleep(.1) if timer > 1200: self.log.warning( "Proxy mode did not detect a started server within 2" " minutes. Disabling proxy mode because something is" " wrong.") self.disable_proxymode() return if self.proxy.proxy_port == self.servervitals.server_port: self.log.warning("Proxy mode cannot start because the wrapper" " port is identical to the server port.") self.disable_proxymode() return proxythread = threading.Thread(target=self.proxy.host, args=()) proxythread.daemon = True proxythread.start() def disable_proxymode(self): self.proxymode = False self.config["Proxy"]["proxy-enabled"] = False self.configManager.save() self.log.warning( "\nProxy mode is now turned off in wrapper.properties.json.\n") @staticmethod def getbuildstring(): if core_buildinfo_version.__branch__ == "dev": return "%s (development build #%d)" % ( core_buildinfo_version.__version__, core_buildinfo_version.__build__) elif core_buildinfo_version.__branch__ == "stable": return "%s (stable)" % core_buildinfo_version.__version__ else: return "Version: %s (%s build #%d)" % ( core_buildinfo_version.__version__, core_buildinfo_version.__branch__, core_buildinfo_version.__build__) def _auto_update_process(self): while not self.halt.halt: time.sleep(3600) if self.updated: self.log.info( "An update for wrapper has been loaded," " Please restart wrapper.") else: self._checkforupdate() def _checkforupdate(self, update_now=False): """ checks for update """ self.log.info("Checking for new builds...") update = self.get_wrapper_update_info() if update: version, build, repotype, reponame = update self.log.info( "New Wrapper.py %s build #%d is available!" " (current build is #%d)", repotype, build, core_buildinfo_version.__build__) if self.auto_update_wrapper or update_now: self.log.info("Updating...") self.performupdate(version, build) else: self.log.info( "Because you have 'auto-update-wrapper' set to False," " you must manually update Wrapper.py. To update" " Wrapper.py manually, please type /update-wrapper.") else: self.log.info("No new versions available.") def get_wrapper_update_info(self, repotype=None): """get the applicable branch wrapper update""" # read the installed branch info if repotype is None: repotype = core_buildinfo_version.__branch__ if self.auto_update_branch: branch_key = self.auto_update_branch else: branch_key = "%s-branch" % repotype r = requests.get("%s/build/version.json" % self.config["Updates"][branch_key]) if r.status_code == 200: data = r.json() if data["__build__"] > core_buildinfo_version.__build__: if repotype == "dev": reponame = "development" elif repotype == "stable": reponame = "master" else: reponame = data["__branch__"] if "__version__" not in data: data["__version__"] = data["version"] return data["__version__"], data["__build__"], data["__branch__"], reponame else: self.log.warning( "Failed to check for updates - are you connected to the" " internet? (Status Code %d)", r.status_code) return False def performupdate(self, version, build): """ Perform update; returns True if update succeeds. User must still restart wrapper manually. :param version: first argument from get_wrapper_update_info() :param build: second argument from get_wrapper_update_info() :return: True if update succeeds """ wraphash = requests.get("%s/build/Wrapper.py.md5" % self.update_url) wrapperfile = requests.get("%s/Wrapper.py" % self.update_url) if wraphash.status_code == 200 and wrapperfile.status_code == 200: self.log.info("Verifying Wrapper.py...") if hashlib.md5(wrapperfile.content).hexdigest() == wraphash.text: self.log.info( "Update file successfully verified. Installing...") with open(sys.argv[0], "wb") as f: f.write(wrapperfile.content) self.log.info( "Wrapper.py %s (#%d) installed. Please reboot Wrapper.py.", ".".join([str(_) for _ in version]), build) self.updated = True return True else: return False else: self.log.error( "Failed to update due to an internal error (%d, %d)", wraphash.status_code, wrapperfile.status_code, exc_info=True) return False def event_timer_second(self): while not self.halt.halt: time.sleep(1) self.events.callevent("timer.second", None) """ eventdoc <group> wrapper <group> <description> a timer that is called each second. <description> <abortable> No <abortable> """ def event_timer_tick(self): while not self.halt.halt: self.events.callevent("timer.tick", None) time.sleep(0.05) """ eventdoc <group> wrapper <group> <description> a timer that is called each 1/20th <sp> of a second, like a minecraft tick. <description> <abortable> No <abortable> <comments> Use of this timer is not suggested and is turned off <sp> by default in the wrapper.config.json file <comments> """ def _pause_console(self, pause_time): if not self.javaserver: readout("ERROR - ", "There is no running server instance to mute.", separator="", pad=10, usereadline=self.use_readline) return self.javaserver.server_muted = True readout("Server is now muted for %d seconds." % pause_time, "", separator="", command_text_fg="yellow", usereadline=self.use_readline) time.sleep(pause_time) readout("Server now unmuted.", "", separator="", usereadline=self.use_readline) self.javaserver.server_muted = False for lines in self.javaserver.queued_lines: readout("Q\\", "", lines, pad=3, usereadline=self.use_readline) time.sleep(.1) self.javaserver.queued_lines = [] def _mute_console(self, all_args): pausetime = 30 if len(all_args) > 0: pausetime = get_int(all_args[0]) # spur off a pause thread cm = threading.Thread(target=self._pause_console, args=(pausetime,)) cm.daemon = True cm.start() def _freeze(self): try: self.javaserver.freeze() except OSError as ex: self.log.error(ex) except EnvironmentError as e: self.log.warning(e) except Exception as exc: self.log.exception( "Something went wrong when trying to freeze the" " server! (%s)", exc) def _memory(self): try: get_bytes = self.javaserver.getmemoryusage() except OSError as e: self.log.error(e) except Exception as ex: self.log.exception( "Something went wrong when trying to fetch" " memory usage! (%s)", ex) else: amount, units = format_bytes(get_bytes) self.log.info( "Server Memory Usage: %s %s (%s bytes)" % ( amount, units, get_bytes)) def _raw(self, console_input): try: if len(getargsafter(console_input[1:].split(" "), 1)) > 0: self.javaserver.console( getargsafter(console_input[1:].split(" "), 1)) else: self.log.info("Usage: /raw [command]") except EnvironmentError as e: self.log.warning(e) def _unfreeze(self): try: self.javaserver.unfreeze() except OSError as ex: self.log.error(ex) except EnvironmentError as e: self.log.warning(e) except Exception as exc: self.log.exception( "Something went wrong when trying to unfreeze" " the server! (%s)", exc) def _show_help_console(self): # This is the console help command display. readout("", "Get Minecraft help.", separator="help (no slash) - ", pad=0, usereadline=self.use_readline) readout("/reload", "Reload Wrapper.py plugins.", usereadline=self.use_readline) readout("/plugins", "Lists Wrapper.py plugins.", usereadline=self.use_readline) readout("/update-wrapper", "Checks for new Wrapper.py updates, and will install\n" "them automatically if one is available.", usereadline=self.use_readline) readout("/stop", "Stop the minecraft server without" " auto-restarting and without\n" " shuttingdown Wrapper.py.", usereadline=self.use_readline) readout("/start", "Start the minecraft server.", usereadline=self.use_readline) readout("/restart", "Restarts the minecraft server.", usereadline=self.use_readline) readout("/halt", "Shutdown Wrapper.py completely.", usereadline=self.use_readline) readout("/cm [seconds]", "Mute server output (Wrapper console" " logging still happens)", usereadline=self.use_readline) readout("/kill", "Force kill the server without saving.", usereadline=self.use_readline) readout("/freeze", "Temporarily locks the server up" " until /unfreeze is executed\n" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/unfreeze", "Unlocks a frozen state server" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/mem", "Get memory usage of the server" " (Only works on *NIX servers).", usereadline=self.use_readline) readout("/raw [command]", "Send command to the Minecraft" " Server. Useful for Forge\n" " commands like '/fml confirm'.", usereadline=self.use_readline) readout("/password", "run `/password help` for more...)", usereadline=self.use_readline) readout("/perms", "/perms for more...)", usereadline=self.use_readline) readout("/config", "Change wrapper.properties (type" " /config help for more..)", usereadline=self.use_readline) readout("/version", self.getbuildstring(), usereadline=self.use_readline) readout("/entity", "Work with entities (run /entity for more...)", usereadline=self.use_readline) readout("/bans", "Display the ban help page.", usereadline=self.use_readline) def _show_help_bans(self): # ban commands help. if not self.proxymode: readout( "ERROR - ", "Wrapper proxy-mode bans are not enabled " "(proxy mode is not on).", separator="", pad=10, usereadline=self.use_readline) return readout( "", "Bans - To use the server's versions, do not type a slash.", separator="", pad=5, usereadline=self.use_readline) readout( "", "", separator="-----1.7.6 and later ban commands-----", pad=10, usereadline=self.use_readline) readout( "/ban", " - Ban a player. Specifying h:<hours> or d:<days>" " creates a temp ban.", separator="<name> [reason..] [d:<days>/h:<hours>] ", pad=12, usereadline=self.use_readline) readout( "/ban-ip", " - Ban an IP address. Reason and days (d:) are optional.", separator="<ip> [<reason..> <d:<number of days>] ", pad=12, usereadline=self.use_readline) readout( "/pardon", " - pardon a player. Default is byuuidonly. To unban a" "specific name (without checking uuid), use" " `pardon <player> False`", separator="<player> [byuuidonly(true/false)]", pad=12, usereadline=self.use_readline) readout( "/pardon-ip", " - Pardon an IP address.", separator="<address> ", pad=12, usereadline=self.use_readline) readout( "/banlist", " - search and display the banlist (warning -" " displays on single page!)", separator="[players|ips] [searchtext] ", pad=12, usereadline=self.use_readline)
def __init__(self): # setup log and config # needs a false setting on first in case config does not # load (like after changes). self.storage = False self.log = logging.getLogger('Wrapper.py') self.configManager = Config() self.configManager.loadconfig() # set up config self.config = self.configManager.config # Read Config items # hard coded cursor for non-readline mode self.cursor = ">" self.wrapper_ban_system = False # This was to allow alternate encodings self.encoding = self.config["General"]["encoding"] self.proxymode = self.config["Proxy"]["proxy-enabled"] self.wrapper_onlinemode = self.config["Proxy"]["online-mode"] self.wrapper_ban_system = self.proxymode and self.wrapper_ban_system self.auto_update_wrapper = self.config["Updates"][ "auto-update-wrapper"] self.auto_update_branch = self.config["Updates"]["auto-update-branch"] self.use_timer_tick_event = self.config["Gameplay"][ "use-timer-tick-event"] self.command_prefix = self.config["Misc"]["command-prefix"] self.use_readline = self.config["Misc"]["use-readline"] # Storages self.wrapper_storage = Storage("wrapper", encoding=self.encoding) self.wrapper_permissions = Storage("permissions", encoding=self.encoding, pickle=False) self.wrapper_usercache = Storage("usercache", encoding=self.encoding, pickle=False) # storage Data objects self.storage = self.wrapper_storage.Data self.permissions = self.wrapper_permissions.Data self.usercache = self.wrapper_usercache.Data # core functions and datasets self.perms = Permissions(self) self.uuids = UUIDS(self) self.plugins = Plugins(self) self.commands = Commands(self) self.events = Events(self) self.registered_permissions = {} self.help = {} self.input_buff = "" self.sig_int = False self.command_hist = ['/help', 'help'] self.command_index = 1 # init items that are set up later (or opted out of/ not set up.) self.javaserver = None self.api = None self.irc = None self.scripts = None self.web = None self.proxy = None self.backups = None self.halt = False self.updated = False # future plan to expose this to api self.xplayer = ConsolePlayer(self) # define the slot once here and not at each clients Instantiation: self.inv_slots = range(46) # Error messages for non-standard import failures. if not readline and self.use_readline: self.log.warning( "'readline' not imported. This is needed for proper" " console functioning. Press <Enter> to acknowledge...") sys.stdin.readline() # requests is just being used in too many places to try # and track its usages piece-meal. if not requests: self.log.error( "You must have the requests module installed to use wrapper!" " console functioning. Press <Enter> to Exit...") sys.stdin.readline() self._halt()