def __init__(self, wrapper): self.wrapper = wrapper self.api = API(wrapper, "Web", internal=True) self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.socket = False self.data = Storage("web") if "keys" not in self.data.Data: self.data.Data["keys"] = [] # if not self.config["Web"]["web-password"] == None: # self.log.info("Changing web-mode password because web-password was changed in wrapper.properties") # ***** change code to hashlib if this gets uncommented # self.data.Data["password"] = md5.md5(self.config["Web"]["web-password"]).hexdigest() # self.config["Web"]["web-password"] = None # self.wrapper.configManager.save() self.api.registerEvent("server.consoleMessage", self.onServerConsole) self.api.registerEvent("player.message", self.onPlayerMessage) self.api.registerEvent("player.login", self.onPlayerJoin) self.api.registerEvent("player.logout", self.onPlayerLeave) self.api.registerEvent("irc.message", self.onChannelMessage) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0
def __init__(self, wrapper): self.wrapper = wrapper self.config = wrapper.config # Remember if you need to save use 'wrapper.configManager.save()' not config.save self.log = logging.getLogger('Web') self.check_password = self.wrapper.cipher.check_pw() if not Flask: self.config["Web"]["web-enabled"] = False self.wrapper.configManager.save() self.log.critical( "You don't have the 'flask/flask_socketio' dashboard dependencies installed " "on your system. You can now restart, but Web mode is disabled." ) self.wrapper.haltsig.halt = True self.app = Flask(__name__) self.app.config['SECRET_KEY'] = "".join( [chr(random.randrange(48, 90)) for i in range(32)]) # LOL self.socketio = SocketIO(self.app) # Flask filters def strftime(f): return datetime.datetime.fromtimestamp( int(f)).strftime('%Y-%m-%d @ %I:%M%p') self.app.jinja_env.filters["strftime"] = strftime # Register handlers self.add_decorators() self.data_storage = Storage("dash") if "keys" not in self.data_storage.Data: self.data_storage.Data["keys"] = [] self.loginAttempts = 0 self.last_attempt = 0 self.disableLogins = 0 # Start thread for running server t = threading.Thread(target=self.run, args=()) t.daemon = True t.start()
def __init__(self, wrapper): self.wrapper = wrapper self.api = wrapper.api self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.pass_handler = self.wrapper.cipher self.socket = False self.storage = Storage("web", pickle=False) self.data = self.storage.Data self.xplayer = ConsolePlayer(self.wrapper, self.console_output) self.adminname = "Web Admin" self.xplayer.username = self.adminname self.onlyusesafe_ips = self.config["Web"]["safe-ips-use"] self.safe_ips = self.config["Web"]["safe-ips"] if "keys" not in self.data: self.data["keys"] = [] # Register events self.api.registerEvent("server.consoleMessage", self.on_server_console) self.api.registerEvent("player.message", self.on_player_message) self.api.registerEvent("player.login", self.on_player_join) self.api.registerEvent("player.logout", self.on_player_leave) self.api.registerEvent("irc.message", self.on_channel_message) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 self.props = "" self.propsCount = 0
def __init__(self, wrapper): self.wrapper = wrapper self.api = API(wrapper, "Web", internal=True) self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.socket = False self.data = Storage("web") self.pass_handler = self.wrapper.cipher if "keys" not in self.data.Data: self.data.Data["keys"] = [] self.api.registerEvent("server.consoleMessage", self.onServerConsole) self.api.registerEvent("player.message", self.onPlayerMessage) self.api.registerEvent("player.login", self.onPlayerJoin) self.api.registerEvent("player.logout", self.onPlayerLeave) self.api.registerEvent("irc.message", self.onChannelMessage) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0
def __init__(self, wrapper): self.wrapper = wrapper self.config = wrapper.config # Remember if you need to save use 'wrapper.configManager.save()' not config.save self.log = logging.getLogger('Web') self.check_password = self.wrapper.cipher.check_pw() if not Flask: self.config["Web"]["web-enabled"] = False self.wrapper.configManager.save() self.log.critical("You don't have the 'flask/flask_socketio' dashboard dependencies installed " "on your system. You can now restart, but Web mode is disabled.") self.wrapper.haltsig.halt = True self.app = Flask(__name__) self.app.config['SECRET_KEY'] = "".join([chr(random.randrange(48, 90)) for i in range(32)]) # LOL self.socketio = SocketIO(self.app) # Flask filters def strftime(f): return datetime.datetime.fromtimestamp(int(f)).strftime('%Y-%m-%d @ %I:%M%p') self.app.jinja_env.filters["strftime"] = strftime # Register handlers self.add_decorators() self.data_storage = Storage("dash") if "keys" not in self.data_storage.Data: self.data_storage.Data["keys"] = [] self.loginAttempts = 0 self.last_attempt = 0 self.disableLogins = 0 # Start thread for running server t = threading.Thread(target=self.run, args=()) t.daemon = True t.start()
def __init__(self, username, wrapper): self.wrapper = wrapper self.javaserver = wrapper.javaserver self.log = wrapper.log self.username = username self.loggedIn = time.time() # TODO - clean this out. let player objects GC with their client? # mcserver will set this to false later to close the thread. self.abort = False # meanwhile, it still needs to respect wrapper halts self.wrapper_signal = self.wrapper.halt # these are all MCUUID objects.. I have separated out various # uses of uuid to clarify for later refractoring # --------------- # Mojang uuid - the bought and paid Mojand UUID. Never # changes- our one constant point of reference per player. # offline uuid - created as a MD5 hash of "OfflinePlayer:%s" % username # client uuid - what the client stores as the uuid (should be # the same as Mojang?) The player.uuid used by old api (and # internally here). # server uuid = the local server uuid... used to reference # the player on the local server. Could be same as Mojang UUID # if server is in online mode or same as offline if server is # in offline mode (proxy mode). # ******************* # This can be False if cache (and requests) Fail... bad name or # bad Mojang service connection. self.mojangUuid = self.wrapper.uuids.getuuidbyusername(username) # IF False error carries forward, this is not a valid player, # for whatever reason... self.clientUuid = self.mojangUuid # These two are offline by default. self.offlineUuid = self.wrapper.uuids.getuuidfromname(self.username) # Start out as the Offline - # change it to Mojang if local server is Online self.serverUuid = self.offlineUuid self.ipaddress = "127.0.0.0" self.loginposition = [0, 0, 0] self._position = [0, 0, 0, 0, 0] # internally used for non-proxy mode self.client = None self.clientgameversion = self.wrapper.servervitals.protocolVersion self.clientboundPackets = Packets_cb(self.clientgameversion) self.serverboundPackets = Packets_sb(self.clientgameversion) self.playereid = None # some player properties associated with abilities # # default is 1. Should normally be congruent with speed. self.field_of_view = float(1) # Client set godmode is 0x01 self.godmode = 0x00 # Client set creative is 0x08 self.creative = 0x00 # default is 1 self.fly_speed = float(1) if self.wrapper.proxy: gotclient = False for client in self.wrapper.servervitals.clients: if client.username == self.username: self.client = client # Both MCUUID objects self.clientUuid = client.uuid self.serverUuid = client.serveruuid self.ipaddress = client.ip # pktSB already set to self.wrapper.servervitals.protocolVersion self.clientboundPackets = self.client.pktCB self.clientgameversion = self.client.clientversion gotclient = True break if not gotclient: self.log.error("Proxy is on, but this client is not " "listed in wrapper.proxy.clients!") self.log.error("The usual cause of this would be that" " someone is connecting directly to" " your server port and not the wrapper" " proxy port!") # Process login data self.data = Storage(self.clientUuid.string, root="wrapper-data/players") if "firstLoggedIn" not in self.data.Data: self.data.Data["firstLoggedIn"] = (time.time(), time.tzname) if "logins" not in self.data.Data: self.data.Data["logins"] = {} self.data.Data["lastLoggedIn"] = (self.loggedIn, time.tzname) self.data.save() # start player logged in time tracking thread t = threading.Thread(target=self._track, args=()) t.daemon = True t.start()
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.api = wrapper.api self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.pass_handler = self.wrapper.cipher self.socket = False self.storage = Storage("web", pickle=False) self.data = self.storage.Data self.xplayer = ConsolePlayer(self.wrapper, self.console_output) self.adminname = "Web Admin" self.xplayer.username = self.adminname self.onlyusesafe_ips = self.config["Web"]["safe-ips-use"] self.safe_ips = self.config["Web"]["safe-ips"] if "keys" not in self.data: self.data["keys"] = [] # Register events self.api.registerEvent("server.consoleMessage", self.on_server_console) self.api.registerEvent("player.message", self.on_player_message) self.api.registerEvent("player.login", self.on_player_join) self.api.registerEvent("player.logout", self.on_player_leave) self.api.registerEvent("irc.message", self.on_channel_message) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 self.props = "" self.propsCount = 0 # t = threading.Thread(target=self.update_graph, args=()) # t.daemon = True # t.start() # ================ Start and Run code section ================ # ordered by the time they are referenced in the code. # def update_graph(self): # while not self.wrapper.haltsig.halt: # while len(self.memoryGraph) > 200: # del self.memoryGraph[0] # if self.wrapper.javaserver.getmemoryusage(): # self.memoryGraph.append( # [time.time(), self.wrapper.javaserver.getmemoryusage()]) # time.sleep(1) def wrap(self): """ Wrapper starts excution here (via a thread). """ if not pkg_resources: self.log.error("`pkg_resources` is not installed. It is usually " "distributed with setuptools. Check https://stackov" "erflow.com/questions/7446187/no-module-named-pkg-r" "esources for possible solutions") return while not self.wrapper.haltsig.halt: try: if self.bind(): # cProfile.run("self.listen()", "cProfile-debug") self.listen() else: self.log.error( "Could not bind web to %s:%d - retrying in 5" " seconds" % ( self.config["Web"]["web-bind"], self.config["Web"]["web-port"] ) ) except: for line in traceback.format_exc().split("\n"): self.log.error(line) time.sleep(5) # closing also calls storage.save(). self.storage.close() def bind(self): """ Started by self.wrap() to bind socket. """ if self.socket is not False: self.socket.close() try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) self.socket.listen(5) return True except: return False def listen(self): """ Excuted by self.wrap() to listen for client(s). """ self.log.info("Web Interface bound to %s:%d" % ( self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) while not self.wrapper.haltsig.halt: # noinspection PyUnresolvedReferences sock, addr = self.socket.accept() if self.onlyusesafe_ips: if addr[0] not in self.safe_ips: sock.close() self.log.info( "Sorry charlie (an unathorized IP %s attempted " "connection)", addr[0] ) continue client = Client(self.wrapper, sock, addr, self) t = threading.Thread(target=client.wrap, args=()) t.daemon = True t.start() self.storage.save() def console_output(self, message): display = str(message) if type(message) is dict: if "text" in message: display = message["text"] self.on_server_console({"message": display}) # ========== EVENTS SECTION ========================== def on_server_console(self, payload): while len(self.consoleScrollback) > 1000: try: self.consoleScrollback.pop() except: break self.consoleScrollback.append((time.time(), payload["message"])) def on_player_message(self, payload): while len(self.chatScrollback) > 200: try: self.chatScrollback.pop() except: break self.chatScrollback.append( [time.time(), {"type": "player", "payload": {"player": payload["player"].username, "message": payload["message"]}}]) def on_player_join(self, payload): # abrupt disconnections can cause player on-join although player is # not on... if not payload["player"]: return while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append([ time.time(), {"type": "playerJoin", "payload": {"player": payload["player"].username}}]) def on_player_leave(self, payload): while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append([ time.time(), {"type": "playerLeave", "payload": {"player": payload["player"].username}}]) def on_channel_message(self, payload): while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append([ time.time(), {"type": "irc", "payload": payload}]) # ========== Externally-called Methods section ========================== def check_login(self, password): """ Returns True or False to indicate login success. - Called by client.run_action, action="login" """ # Threshold for logins if time.time() - self.disableLogins < 60: self.loginAttempts = 0 return None # check password validity if self.pass_handler.check_pw(password, self.config["Web"]["web-password"]): # noqa return True # unsuccessful password attempt self.loginAttempts += 1 if self.loginAttempts > 4 and time.time() - self.lastAttempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.lastAttempt = time.time() return False def make_key(self, remember_me, username): a = "" z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" for i in range(64): a += z[random.randrange(0, len(z))] # a += chr(random.randrange(97, 122)) self.data["keys"].append([a, time.time(), remember_me, username]) return a def validate_key(self, key): # day = 86400 week = 604800 curr_time = int(time.time()) old_keys = [] success = False for i in self.data["keys"]: if len(i) > 2: expire_time = int(i[1]) remembered = i[2] user = getargs(i, 3) if user != "": self.adminname = user # even "remembereds" should expire after a week after last use if remembered: expire_time += week if curr_time - expire_time > week: # or day or whatever # remove expired keys old_keys.append(i[0]) else: if i[0] == key: # Validate key if remembered: # remembereds are reset at each successful login: self.update_key(i, 1, curr_time) self.loginAttempts = 0 success = True else: # remove bad malformed keys old_keys.append(i[0]) for oldkey in old_keys: self.remove_key(oldkey) self.storage.save() return success def update_key(self, key, field_number, data): for i, v in enumerate(self.data["keys"]): if v[0] == key: self.data["keys"][i][field_number] = data def remove_key(self, key): for i, v in enumerate(self.data["keys"]): if v[0] == key: del self.data["keys"][i] def getdisk_usage(self): """only works on Python 3. returns 0 for Python 2""" if disk_usage: # noinspection PyCallingNonCallable spaces = disk_usage(self.serverpath) frsp = spaces.free return frsp return int(0)
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.api = API(wrapper, "Web", internal=True) self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.socket = False self.data = Storage("web") self.pass_handler = self.wrapper.cipher if "keys" not in self.data.Data: self.data.Data["keys"] = [] self.api.registerEvent("server.consoleMessage", self.onServerConsole) self.api.registerEvent("player.message", self.onPlayerMessage) self.api.registerEvent("player.login", self.onPlayerJoin) self.api.registerEvent("player.logout", self.onPlayerLeave) self.api.registerEvent("irc.message", self.onChannelMessage) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 # t = threading.Thread(target=self.updateGraph, args=()) # t.daemon = True # t.start() def __del__(self): self.data.close() def onServerConsole(self, payload): while len(self.consoleScrollback) > 1000: try: del self.consoleScrollback[0] except Exception as e: break self.consoleScrollback.append((time.time(), payload["message"])) def onPlayerMessage(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "player", "payload": { "player": payload["player"].username, "message": payload["message"] } })) def onPlayerJoin(self, payload): # print(payload) while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "playerJoin", "payload": { "player": payload["player"].username } })) def onPlayerLeave(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "playerLeave", "payload": { "player": payload["player"] } })) def onChannelMessage(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "irc", "payload": payload })) def updateGraph(self): while not self.wrapper.halt.halt: while len(self.memoryGraph) > 200: del self.memoryGraph[0] if self.wrapper.javaserver.getmemoryusage(): self.memoryGraph.append( [time.time(), self.wrapper.javaserver.getmemoryusage()]) time.sleep(1) def checkLogin(self, password): if time.time() - self.disableLogins < 60: return False # Threshold for logins if self.pass_handler.check_pw(password, self.config["Web"]["web-password"]): return True self.loginAttempts += 1 if self.loginAttempts > 10 and time.time() - self.lastAttempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.lastAttempt = time.time() def makeKey(self, rememberme): a = "" z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_" for i in range(64): # not enough performance issue to justify xrange a += z[random.randrange(0, len(z))] # a += chr(random.randrange(97, 122)) if rememberme: print("Will remember!") self.data.Data["keys"].append([a, time.time(), rememberme]) return a def validateKey(self, key): for i in self.data.Data["keys"]: expiretime = 2592000 if len(i) > 2: if not i[2]: expiretime = 21600 # Validate key and ensure it's under a week old if i[0] == key and time.time() - i[1] < expiretime: self.loginAttempts = 0 return True return False def removeKey(self, key): # we dont want to do things like this. Never delete or insert while iterating over a dictionary # because dictionaries change order as the hashtables are changed during insert and delete operations... for i, v in enumerate(self.data.Data["keys"]): if v[0] == key: del self.data.Data["keys"][i] def wrap(self): while not self.wrapper.halt.halt: try: if self.bind(): self.listen() else: self.log.error( "Could not bind web to %s:%d - retrying in 5 seconds", self.config["Web"]["web-bind"], self.config["Web"]["web-port"]) except Exception as e: self.log.exception(e) time.sleep(5) def bind(self): if self.socket is not False: self.socket.close() try: self.socket = socket.socket() self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) self.socket.listen(5) return True except Exception as e: return False def listen(self): self.log.info("Web Interface bound to %s:%d", self.config["Web"]["web-bind"], self.config["Web"]["web-port"]) while not self.wrapper.halt.halt: # noinspection PyUnresolvedReferences sock, addr = self.socket.accept() # self.log.debug("(WEB) Connection %s started", str(addr)) client = WebClient(sock, addr, self) t = threading.Thread(target=client.wrap, args=()) t.daemon = True t.start()
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.api = API(wrapper, "Web", internal=True) self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.socket = False self.data = Storage("web") self.pass_handler = self.wrapper.cipher if "keys" not in self.data.Data: self.data.Data["keys"] = [] self.api.registerEvent("server.consoleMessage", self.onServerConsole) self.api.registerEvent("player.message", self.onPlayerMessage) self.api.registerEvent("player.login", self.onPlayerJoin) self.api.registerEvent("player.logout", self.onPlayerLeave) self.api.registerEvent("irc.message", self.onChannelMessage) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 # t = threading.Thread(target=self.updateGraph, args=()) # t.daemon = True # t.start() def __del__(self): self.data.close() def onServerConsole(self, payload): while len(self.consoleScrollback) > 1000: try: del self.consoleScrollback[0] except Exception as e: break self.consoleScrollback.append((time.time(), payload["message"])) def onPlayerMessage(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "player", "payload": { "player": payload["player"].username, "message": payload["message"] } })) def onPlayerJoin(self, payload): # print(payload) while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "playerJoin", "payload": { "player": payload["player"].username } })) def onPlayerLeave(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), { "type": "playerLeave", "payload": { "player": payload["player"] } })) def onChannelMessage(self, payload): while len(self.chatScrollback) > 200: try: del self.chatScrollback[0] except Exception as e: break self.chatScrollback.append((time.time(), {"type": "irc", "payload": payload})) def updateGraph(self): while not self.wrapper.halt.halt: while len(self.memoryGraph) > 200: del self.memoryGraph[0] if self.wrapper.javaserver.getmemoryusage(): self.memoryGraph.append([time.time(), self.wrapper.javaserver.getmemoryusage()]) time.sleep(1) def checkLogin(self, password): if time.time() - self.disableLogins < 60: return False # Threshold for logins if self.pass_handler.check_pw(password, self.config["Web"]["web-password"]): return True self.loginAttempts += 1 if self.loginAttempts > 10 and time.time() - self.lastAttempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.lastAttempt = time.time() def makeKey(self, rememberme): a = "" z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_" for i in range(64): # not enough performance issue to justify xrange a += z[random.randrange(0, len(z))] # a += chr(random.randrange(97, 122)) if rememberme: print("Will remember!") self.data.Data["keys"].append([a, time.time(), rememberme]) return a def validateKey(self, key): for i in self.data.Data["keys"]: expiretime = 2592000 if len(i) > 2: if not i[2]: expiretime = 21600 # Validate key and ensure it's under a week old if i[0] == key and time.time() - i[1] < expiretime: self.loginAttempts = 0 return True return False def removeKey(self, key): # we dont want to do things like this. Never delete or insert while iterating over a dictionary # because dictionaries change order as the hashtables are changed during insert and delete operations... for i, v in enumerate(self.data.Data["keys"]): if v[0] == key: del self.data.Data["keys"][i] def wrap(self): while not self.wrapper.halt.halt: try: if self.bind(): self.listen() else: self.log.error("Could not bind web to %s:%d - retrying in 5 seconds", self.config["Web"]["web-bind"], self.config["Web"]["web-port"]) except Exception as e: self.log.exception(e) time.sleep(5) def bind(self): if self.socket is not False: self.socket.close() try: self.socket = socket.socket() self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) self.socket.listen(5) return True except Exception as e: return False def listen(self): self.log.info("Web Interface bound to %s:%d", self.config["Web"]["web-bind"], self.config["Web"]["web-port"]) while not self.wrapper.halt.halt: # noinspection PyUnresolvedReferences sock, addr = self.socket.accept() # self.log.debug("(WEB) Connection %s started", str(addr)) client = WebClient(sock, addr, self) t = threading.Thread(target=client.wrap, args=()) t.daemon = True t.start()
def on_start(self): import sys storage = Storage.load_molecules(sys.argv[1]) atoms = storage.get_atoms() self.layout.init_game(storage, atoms)
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 __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()
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.api = wrapper.api self.log = logging.getLogger('Web') self.config = wrapper.config self.serverpath = self.config["General"]["server-directory"] self.pass_handler = self.wrapper.cipher self.socket = False self.storage = Storage("web", pickle=False) self.data = self.storage.Data self.xplayer = ConsolePlayer(self.wrapper, self.console_output) self.adminname = "Web Admin" self.xplayer.username = self.adminname self.onlyusesafe_ips = self.config["Web"]["safe-ips-use"] self.safe_ips = self.config["Web"]["safe-ips"] if "keys" not in self.data: self.data["keys"] = [] # Register events self.api.registerEvent("server.consoleMessage", self.on_server_console) self.api.registerEvent("player.message", self.on_player_message) self.api.registerEvent("player.login", self.on_player_join) self.api.registerEvent("player.logout", self.on_player_leave) self.api.registerEvent("irc.message", self.on_channel_message) self.consoleScrollback = [] self.chatScrollback = [] self.memoryGraph = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 self.props = "" self.propsCount = 0 # t = threading.Thread(target=self.update_graph, args=()) # t.daemon = True # t.start() # ================ Start and Run code section ================ # ordered by the time they are referenced in the code. # def update_graph(self): # while not self.wrapper.haltsig.halt: # while len(self.memoryGraph) > 200: # del self.memoryGraph[0] # if self.wrapper.javaserver.getmemoryusage(): # self.memoryGraph.append( # [time.time(), self.wrapper.javaserver.getmemoryusage()]) # time.sleep(1) def wrap(self): """ Wrapper starts excution here (via a thread). """ if not pkg_resources: self.log.error("`pkg_resources` is not installed. It is usually " "distributed with setuptools. Check https://stackov" "erflow.com/questions/7446187/no-module-named-pkg-r" "esources for possible solutions") return while not self.wrapper.haltsig.halt: try: if self.bind(): # cProfile.run("self.listen()", "cProfile-debug") self.listen() else: self.log.error( "Could not bind web to %s:%d - retrying in 5" " seconds" % (self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) except: for line in traceback.format_exc().split("\n"): self.log.error(line) time.sleep(5) # closing also calls storage.save(). self.storage.close() def bind(self): """ Started by self.wrap() to bind socket. """ if self.socket is not False: self.socket.close() try: self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind((self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) self.socket.listen(5) return True except: return False def listen(self): """ Excuted by self.wrap() to listen for client(s). """ self.log.info( "Web Interface bound to %s:%d" % (self.config["Web"]["web-bind"], self.config["Web"]["web-port"])) while not self.wrapper.haltsig.halt: # noinspection PyUnresolvedReferences sock, addr = self.socket.accept() if self.onlyusesafe_ips: if addr[0] not in self.safe_ips: sock.close() self.log.info( "Sorry charlie (an unathorized IP %s attempted " "connection)", addr[0]) continue client = Client(self.wrapper, sock, addr, self) t = threading.Thread(target=client.wrap, args=()) t.daemon = True t.start() self.storage.save() def console_output(self, message): display = str(message) if type(message) is dict: if "text" in message: display = message["text"] self.on_server_console({"message": display}) # ========== EVENTS SECTION ========================== def on_server_console(self, payload): while len(self.consoleScrollback) > 1000: try: self.consoleScrollback.pop() except: break self.consoleScrollback.append((time.time(), payload["message"])) def on_player_message(self, payload): while len(self.chatScrollback) > 200: try: self.chatScrollback.pop() except: break self.chatScrollback.append([ time.time(), { "type": "player", "payload": { "player": payload["player"].username, "message": payload["message"] } } ]) def on_player_join(self, payload): # abrupt disconnections can cause player on-join although player is # not on... if not payload["player"]: return while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append([ time.time(), { "type": "playerJoin", "payload": { "player": payload["player"].username } } ]) def on_player_leave(self, payload): while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append([ time.time(), { "type": "playerLeave", "payload": { "player": payload["player"].username } } ]) def on_channel_message(self, payload): while len(self.chatScrollback) > 200: self.chatScrollback.pop() self.chatScrollback.append( [time.time(), { "type": "irc", "payload": payload }]) # ========== Externally-called Methods section ========================== def check_login(self, password): """ Returns True or False to indicate login success. - Called by client.run_action, action="login" """ # Threshold for logins if time.time() - self.disableLogins < 60: self.loginAttempts = 0 return None # check password validity if self.pass_handler.check_pw( password, self.config["Web"]["web-password"]): # noqa return True # unsuccessful password attempt self.loginAttempts += 1 if self.loginAttempts > 4 and time.time() - self.lastAttempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.lastAttempt = time.time() return False def make_key(self, remember_me, username): a = "" z = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" for i in range(64): a += z[random.randrange(0, len(z))] # a += chr(random.randrange(97, 122)) self.data["keys"].append([a, time.time(), remember_me, username]) return a def validate_key(self, key): # day = 86400 week = 604800 curr_time = int(time.time()) old_keys = [] success = False for i in self.data["keys"]: if len(i) > 2: expire_time = int(i[1]) remembered = i[2] user = getargs(i, 3) if user != "": self.adminname = user # even "remembereds" should expire after a week after last use if remembered: expire_time += week if curr_time - expire_time > week: # or day or whatever # remove expired keys old_keys.append(i[0]) else: if i[0] == key: # Validate key if remembered: # remembereds are reset at each successful login: self.update_key(i, 1, curr_time) self.loginAttempts = 0 success = True else: # remove bad malformed keys old_keys.append(i[0]) for oldkey in old_keys: self.remove_key(oldkey) self.storage.save() return success def update_key(self, key, field_number, data): for i, v in enumerate(self.data["keys"]): if v[0] == key: self.data["keys"][i][field_number] = data def remove_key(self, key): for i, v in enumerate(self.data["keys"]): if v[0] == key: del self.data["keys"][i] def getdisk_usage(self): """only works on Python 3. returns 0 for Python 2""" if disk_usage: # noinspection PyCallingNonCallable spaces = disk_usage(self.serverpath) frsp = spaces.free return frsp return int(0)
class Player(object): """ .. code:: python def __init__(self, username, wrapper) .. This class is normally passed as an argument to an event callback, but can be also be called using getPlayer(username): .. code:: python player = self.api.getPlayer(<username>) .. Player objects contains methods and data of a currently logged-in player. This object is destroyed upon logging off. Most features are tied heavily to proxy mode implementations and the proxy client instance. The player object has a self.__str___ representation that returns the player.username. Therefore, plugins do not need to attempt string conversion or do explicit references to player.username in their code (str(player) or player.username in plugin code). When using events, events in the "proxy" (Group 'Proxy') section are only available in proxy mode. "server" events (Group 'core/mcserver.py') are available even without proxy mode, as long as the server is running. Supported properties of the player: .. code:: python self.username self.loggedIn self.mojangUuid self.offlineUuid self.loginposition self.playereid self.ipaddress # proxy only self.serverUuid (proxy only) self.clientUuid (proxy only) self.clientgameversion self.clientboundPackets = Packets_cb(self.clientgameversion) self.serverboundPackets = Packets_sb(self.clientgameversion) # some player properties associated with abilities (proxy) # default is 1. Should normally be congruent with speed. self.field_of_view = float(1) # Client set godmode is 0x01 self.godmode = 0x00 # Client set creative is 0x08 self.creative = 0x00 # default is 1 self.fly_speed = float(1) .. """ def __init__(self, username, wrapper): self.wrapper = wrapper self.javaserver = wrapper.javaserver self.log = wrapper.log self.username = username self.loggedIn = time.time() # TODO - clean this out. let player objects GC with their client? # mcserver will set this to false later to close the thread. self.abort = False # meanwhile, it still needs to respect wrapper halts self.wrapper_signal = self.wrapper.halt # these are all MCUUID objects.. I have separated out various # uses of uuid to clarify for later refractoring # --------------- # Mojang uuid - the bought and paid Mojand UUID. Never # changes- our one constant point of reference per player. # offline uuid - created as a MD5 hash of "OfflinePlayer:%s" % username # client uuid - what the client stores as the uuid (should be # the same as Mojang?) The player.uuid used by old api (and # internally here). # server uuid = the local server uuid... used to reference # the player on the local server. Could be same as Mojang UUID # if server is in online mode or same as offline if server is # in offline mode (proxy mode). # ******************* # This can be False if cache (and requests) Fail... bad name or # bad Mojang service connection. self.mojangUuid = self.wrapper.uuids.getuuidbyusername(username) # IF False error carries forward, this is not a valid player, # for whatever reason... self.clientUuid = self.mojangUuid # These two are offline by default. self.offlineUuid = self.wrapper.uuids.getuuidfromname(self.username) # Start out as the Offline - # change it to Mojang if local server is Online self.serverUuid = self.offlineUuid self.ipaddress = "127.0.0.0" self.loginposition = [0, 0, 0] self._position = [0, 0, 0, 0, 0] # internally used for non-proxy mode self.client = None self.clientgameversion = self.wrapper.servervitals.protocolVersion self.clientboundPackets = Packets_cb(self.clientgameversion) self.serverboundPackets = Packets_sb(self.clientgameversion) self.playereid = None # some player properties associated with abilities # # default is 1. Should normally be congruent with speed. self.field_of_view = float(1) # Client set godmode is 0x01 self.godmode = 0x00 # Client set creative is 0x08 self.creative = 0x00 # default is 1 self.fly_speed = float(1) if self.wrapper.proxy: gotclient = False for client in self.wrapper.servervitals.clients: if client.username == self.username: self.client = client # Both MCUUID objects self.clientUuid = client.uuid self.serverUuid = client.serveruuid self.ipaddress = client.ip # pktSB already set to self.wrapper.servervitals.protocolVersion self.clientboundPackets = self.client.pktCB self.clientgameversion = self.client.clientversion gotclient = True break if not gotclient: self.log.error("Proxy is on, but this client is not " "listed in wrapper.proxy.clients!") self.log.error("The usual cause of this would be that" " someone is connecting directly to" " your server port and not the wrapper" " proxy port!") # Process login data self.data = Storage(self.clientUuid.string, root="wrapper-data/players") if "firstLoggedIn" not in self.data.Data: self.data.Data["firstLoggedIn"] = (time.time(), time.tzname) if "logins" not in self.data.Data: self.data.Data["logins"] = {} self.data.Data["lastLoggedIn"] = (self.loggedIn, time.tzname) self.data.save() # start player logged in time tracking thread t = threading.Thread(target=self._track, args=()) t.daemon = True t.start() def __str__(self): return self.username def __del__(self): self.data.close() @property def name(self): return self.username @property def uuid(self): return self.mojangUuid def _track(self): """ internal tracking that updates a player's server play time. Not a part of the public player object API. Sample ReST formattings - # emphasized notes Note: *You do not need to run this function unless you want* *certain permission nodes to be granted by default.* *i.e., 'essentials.list' should be on by default, so players* *can run /list without having any permissions* # code samples :sample usage: .. code:: python < code here > .. """ self.data.Data["logins"][int(self.loggedIn)] = time.time() while not (self.abort or self.wrapper_signal.halt): timeupdate = time.time() if timeupdate % 60: # Just update every 60 seconds self.data.Data["logins"][int(self.loggedIn)] = int(time.time()) # this needs a fast response to ensure the storage closes # immediately on player logoff time.sleep(.5) self.data.close() def execute(self, string): """ Run a command as this player. If proxy mode is not enabled, it simply falls back to using the 1.8 'execute' command. To be clear, this does NOT work with any Wrapper.py or plugin commands. The command does not pass through the wrapper. It is only sent to the server console (or the actual server in proxy mode). :arg string: full command string send on player's behalf to server. :returns: Nothing; passes the server or the console as an "execute" command. """ try: self.client.chat_to_server("/%s" % string) except AttributeError: if self.wrapper.servervitals.protocolVersion > PROTOCOL_1_7_9: self.wrapper.javaserver.console("execute %s ~ ~ ~ %s" % (self.username, string)) else: self.log.warning("could not run player.execute - wrapper not" " in proxy mode and minecraft version is less" " than 1.8 (when /execute was implemented).") def sendCommand(self, command, args): """ Sends a command to the wrapper interface as the player instance. This would find a nice application with a '\sudo' plugin command. :sample usage: .. code:: python player=getPlayer("username") player.sendCommand("perms", ("users", "SurestTexas00", "info")) .. :Args: :command: The wrapper (or plugin) command to execute; no slash prefix :args: list of arguments (I think it is a list, not a tuple or dict!) :returns: Nothing; passes command through commands.py function 'playercommand()' """ pay = {"player": self, "command": command, "args": args} self.wrapper.api.callEvent("player.runCommand", pay) def say(self, string): """ Send a message as a player. :arg string: message/command sent to the server as the player. Beware: *in proxy mode, the message string is sent directly to* *the server without wrapper filtering,so it could be used to* *execute minecraft commands as the player if the string is* *prefixed with a slash.* """ try: self.client.chat_to_server(string) except AttributeError: # pre-1.8 self.wrapper.javaserver.console("say @a <%s> %s" % (self.username, string)) def getClient(self): """ Returns the player client context. Use at your own risk - items in client are generally private or subject to change (you are working with an undefined API!)... what works in this wrapper version may not work in the next. :returns: player client object (and possibly sets self.client to the matching client). """ if self.client is None: for client in self.wrapper.servervitals.clients: if client.username == self.username: self.client = client return client self.log.warning( "getClient could not return a client for:%s" " \nThe usual cause of this condition" " is that no client instance exists because" " proxy is not enabled.", self.username) return None else: return self.client def getPosition(self): """ Get the players position :Note: The player's position is obtained by parsing client packets, which are not sent until the client logs in to the server. Allow some time after server login to verify the wrapper has had the oppportunity to parse a suitable packet to get the information! :returns: a tuple of the player's current position x, y, z, and yaw, pitch of head. """ if self.wrapper.proxy: return self.client.position + self.client.head else: # Non-proxy mode: return self._position def getGamemode(self): """ Get the player's current gamemode. :Note: The player's Gamemode is obtained by parsing client packets, which are not sent until the client logs in to the server. Allow some time after server login to verify the wrapper has had the oppportunity to parse a suitable packet to get the information! :returns: An Integer of the the player's current gamemode. """ try: return self.client.gamemode except AttributeError: # Non-proxy mode: return 0 def getDimension(self): """ Get the player's current dimension. :Note: The player's Dimension is obtained by parsing client packets, which are not sent until the client logs in to the server. Allow some time after server login to verify the wrapper has had the oppportunity to parse a suitable packet to get the information! :returns: the player's current dimension. :Nether: -1 :Overworld: 0 :End: 1 """ try: return self.client.dimension except AttributeError: # Non-proxy mode: return 0 def setGamemode(self, gamemode=0): """ Sets the user's gamemode. :arg gamemode: desired gamemode, as a value 0-3 """ if gamemode in (0, 1, 2, 3): try: self.client.gamemode = gamemode except AttributeError: # Non-proxy mode: pass self.wrapper.javaserver.console("gamemode %d %s" % (gamemode, self.username)) def setResourcePack(self, url, hashrp=""): """ Sets the player's resource pack to a different URL. If the user hasn't already allowed resource packs, the user will be prompted to change to the specified resource pack. Probably broken right now. :Args: :url: URL of resource pack :hashrp: resource pack hash :return: False if not in proxy mode. """ try: version = self.wrapper.proxy.srv_data.protocolVersion except AttributeError: # Non proxy mode return False if version < PROTOCOL_1_8START: self.client.packet.sendpkt(self.clientboundPackets.PLUGIN_MESSAGE, [_STRING, _BYTEARRAY], ("MC|RPack", url)) else: self.client.packet.sendpkt( self.clientboundPackets.RESOURCE_PACK_SEND, [_STRING, _STRING], (url, hashrp)) def isOp(self, strict=False): """ Check if player has Operator status. Accepts player as OP based on either the username OR server UUID (unless 'strict' is set). Note: *If a player has been opped since the last server start,* *make sure that you run refreshOpsList() to ensure that* *wrapper will acknowlege them as OP.* :arg strict: True - use ONLY the UUID as verification :returns: A 1-10 (or more?) op level if the player is currently a server operator. Can be treated, as before, like a boolean - 'if player.isOp():', but now also adds ability to granularize with the OP level. Levels above 4 are reserved for wrapper. 10 indicates owner. 5-9 are reserved for future minecraft or wrapper levels. pre-1.8 servers return 1. levels above 4 are based on name only from the file "superops.txt" in the wrapper folder. To assign levels, change the lines of <PlayerName>=<oplevel> to your desired names. Player must be an actual OP before the superops.txt will have any effect. Op level of 10 is be required to operate permissions commands. """ if self.wrapper.servervitals.operator_list in (False, None): return False # no ops in file # each op item is a dictionary for ops in self.wrapper.servervitals.operator_list: if ops["uuid"] == self.serverUuid.string: return ops["level"] if ops["name"] == self.username and not strict: return ops["level"] return False def message(self, message="", position=0): """ Sends a message to the player. :Args: :message: Can be text, colorcoded text, or json chat :position: an integer 0-2. 2 will place it above XP bar. 1 or 0 will place it in the chat. Using position 2 will only display any text component (or can be used to display standard minecraft translates, such as "{'translate': 'commands.generic.notFound', 'color': 'red'}" and "{'translate': 'tile.bed.noSleep'}" :returns: Nothing """ if self.wrapper.proxy: self.client.chat_to_client(message, position) else: self.javaserver.broadcast(message, who=self.username) def actionMessage(self, message=""): try: version = self.wrapper.proxy.srv_data.protocolVersion except AttributeError: # Non proxy mode return False if version < PROTOCOL_1_8START: parsing = [_STRING, _NULL] data = [message] else: parsing = [_STRING, _BYTE] data = (json.dumps({"text": processoldcolorcodes(message)}), 2) self.client.packet.sendpkt( self.clientboundPackets.CHAT_MESSAGE, parsing, # "string|byte" data) def setVisualXP(self, progress, level, total): """ Change the XP bar on the client's side only. Does not affect actual XP levels. :Args: :progress: Float between Between 0 and 1 :level: Integer (short in older versions) of EXP level :total: Total EXP. :returns: Nothing """ try: version = self.wrapper.proxy.srv_data.protocolVersion except AttributeError: # Non proxy mode return False if version > PROTOCOL_1_8START: parsing = [_FLOAT, _VARINT, _VARINT] else: parsing = [_FLOAT, _SHORT, _SHORT] self.client.packet.sendpkt(self.clientboundPackets.SET_EXPERIENCE, parsing, (progress, level, total)) def openWindow(self, windowtype, title, slots): """ Opens an inventory window on the client side. EntityHorse is not supported due to further EID requirement. *1.8* *experimental only.* :Args: :windowtype: Window Type (text string). See below or applicable wiki entry (for version specific info) :title: Window title - wiki says chat object (could be string too?) :slots: :returns: None (False if client is less than 1.8 version) Valid window names (1.9) :minecraft\:chest: Chest, large chest, or minecart with chest :minecraft\:crafting_table: Crafting table :minecraft\:furnace: Furnace :minecraft\:dispenser: Dispenser :minecraft\:enchanting_table: Enchantment table :minecraft\:brewing_stand: Brewing stand :minecraft\:villager: Villager :minecraft\:beacon: Beacon :minecraft\:anvil: Anvil :minecraft\:hopper: Hopper or minecart with hopper :minecraft\:dropper: Dropper :EntityHorse: Horse, donkey, or mule """ try: version = self.wrapper.proxy.srv_data.protocolVersion except AttributeError: # Non proxy mode return False client = self.client client.windowCounter += 1 if client.windowCounter > 200: client.windowCounter = 2 # TODO Test what kind of field title is (json or text) if not version > PROTOCOL_1_8START: return False client.packet.sendpkt(self.clientboundPackets.OPEN_WINDOW, [_UBYTE, _STRING, _JSON, _UBYTE], (client.windowCounter, windowtype, { "text": title }, slots)) return None # return a Window object soon def setPlayerAbilities(self, fly): """ *based on old playerSetFly (which was an unfinished function)* NOTE - You are implementing these abilities on the client side only.. if the player is in survival mode, the server may think the client is hacking! this will set 'is flying' and 'can fly' to true for the player. these flags/settings will be set according to the players properties, which you can set just prior to calling this method: :getPlayer().godmode: Hex or integer (see chart below) :getPlayer().creative: Hex or integer (see chart below) :getPlayer().field_of_view: Float - default is 1.0 :getPlayer().fly_speed: Float - default is 1.0 :arg fly: Boolean :True: set fly mode. :False: to unset fly mode :Bitflags used (for all versions): These can be added to produce combination effects. This function sets 0x02 and 0x04 together (0x06). :Invulnerable: 0x01 :Flying: 0x02 :Allow Flying: 0x04 :Creative Mode: 0x08 :returns: Nothing """ try: sendclient = self.client.packet.sendpkt sendserver = self.client.server.packet.sendpkt except AttributeError: # Non proxy mode return False # TODO later add and keep track of godmode and creative- code # will currently unset them. if fly: setfly = 0x06 # for set fly else: setfly = 0x00 bitfield = self.godmode | self.creative | setfly # Note in versions before 1.8, field of view is the # walking speed for client (still a float) Server # field of view is still walking speed sendclient(self.clientboundPackets.PLAYER_ABILITIES, [_BYTE, _FLOAT, _FLOAT], (bitfield, self.fly_speed, self.field_of_view)) sendserver(self.serverboundPackets.PLAYER_ABILITIES, [_BYTE, _FLOAT, _FLOAT], (bitfield, self.fly_speed, self.field_of_view)) def sendBlock(self, position, blockid, blockdata, sendblock=True, numparticles=1, partdata=1): """ Used to make phantom blocks visible ONLY to the client. Sends either a particle or a block to the minecraft player's client. For blocks iddata is just block id - No need to bitwise the blockdata; just pass the additional block data. The particle sender is only a basic version and is not intended to do anything more than send something like a barrier particle to temporarily highlight something for the player. Fancy particle operations should be custom done by the plugin or someone can write a nicer particle-renderer. :Args: :position: players position as tuple. The coordinates must be in the player's render distance or the block will appear at odd places. :blockid: usually block id, but could be particle id too. If sending pre-1.8 particles this is a string not a number... the valid values are found here :blockdata: additional block meta (a number specifying a subtype). :sendblock: True for sending a block. :numparticles: if particles, their numeric count. :partdata: if particles; particle data. Particles with additional ID cannot be used ("Ironcrack"). :Valid 'blockid' values: http://wayback.archive.org/web/20151023030926/https://gist.github.com/thinkofdeath/5110835 """ try: sendclient = self.client.packet.sendpkt except AttributeError: # Non proxy return False posx = position x = (position[0]) y = (position[1]) z = (position[2]) if self.clientgameversion > PROTOCOL_1_7_9: # 1.8 + iddata = blockid << 4 | blockdata # [1.8pos/1.7x | 1.7y | 1.7z | 1.7BlockID/1.8iddata | 1.7blockdata] # these are whitespaced this way to line up visually blockparser = [_POSITION, _NULL, _NULL, _VARINT, _NULL] particleparser = [ _INT, _BOOL, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _INT ] else: # 1.7 posx = x iddata = blockid blockparser = [_INT, _UBYTE, _INT, _VARINT, _UBYTE] particleparser = [ _STRING, _NULL, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _FLOAT, _INT ] if sendblock: sendclient(self.clientboundPackets.BLOCK_CHANGE, blockparser, (posx, y, x, iddata, blockdata)) else: sendclient(self.clientboundPackets.PARTICLE, particleparser, (blockid, True, x + .5, y + .5, z + .5, 0, 0, 0, partdata, numparticles)) # Inventory-related actions. def getItemInSlot(self, slot): """ Returns the item object of an item currently being held. """ try: return self.client.inventory[slot] except AttributeError: # Non proxy return False def getHeldItem(self): """ Returns the item object of an item currently being held. """ try: return self.client.inventory[36 + self.client.slot] except AttributeError: # Non proxy return False # Permissions-related def hasPermission(self, node, another_player=False, group_match=True, find_child_groups=True): """ If the player has the specified permission node (either directly, or inherited from a group that the player is in), it will return the value (usually True) of the node. Otherwise, it returns False. Using group_match and find_child_groups are enabled by default. Permissions can be sped up by disabling child inheritance or even group matching entirely (for high speed loops, for instance). Normally, permissions are related to commands the player typed, so the 'cost' of child inheritance is not a concern. :Args: :node: Permission node (string) :another_player: sending a string name of another player will check THAT PLAYER's permission instead! Useful for checking a player's permission for someone who is not logged in and has no player object. :group_match: return a permission for any group the player is a member of. If False, will only return permissions player has directly. :find_child_groups: If group matching, this will additionally locate matches when a group contains a permission that is another group's name. So if group 'admin' contains a permission called 'moderator', anyone with group admin will also have group moderator's permissions as well. :returns: Boolean indicating whether player has permission or not. """ uuid_to_check = self.mojangUuid.string if another_player: # get other player mojang uuid uuid_to_check = str( self.wrapper.uuids.getuuidbyusername(another_player)) if not uuid_to_check: # probably a bad name provided.. No further check needed. return False return self.wrapper.perms.has_permission(uuid_to_check, node, group_match, find_child_groups) def setPermission(self, node, value=True): """ Adds the specified permission node and optionally a value to the player. :Args: :node: Permission node (string) :value: defaults to True, but can be set to False to explicitly revoke a particular permission from the player, or to any arbitrary value. :returns: Nothing """ self.wrapper.perms.set_permission(self.mojangUuid.string, node, value) def removePermission(self, node): """ Completely removes a permission node from the player. They will inherit this permission from their groups or from plugin defaults. If the player does not have the specific permission, an IndexError is raised. Note that this method has no effect on nodes inherited from groups or plugin defaults. :arg node: Permission node (string) :returns: Boolean; True if operation succeeds, False if it fails (set debug mode to see/log error). """ return self.wrapper.perms.remove_permission(self.mojangUuid.string, node) def resetPerms(self, uuid): """ resets all user data (removes all permissions). :arg uuid: The online/mojang uuid (string) :returns: nothing """ return self.wrapper.perms.fill_user(uuid) def hasGroup(self, group): """ Returns a boolean of whether or not the player is in the specified permission group. :arg group: Group node (string) :returns: Boolean of whether player has permission or not. """ return self.wrapper.perms.has_group(self.mojangUuid.string, group) def getGroups(self): """ Returns a list of permission groups that the player is in. :returns: list of groups """ return self.wrapper.perms.get_groups(self.mojangUuid.string) def setGroup(self, group, creategroup=True): """ Adds the player to a specified group. Returns False if the command fails (set debiug to see error). Failure is only normally expected if the group does not exist and creategroup is False. :Args: :group: Group node (string) :creategroup: If True (by default), will create the group if it does not exist already. This WILL generate a warning log since it is not an expected condition. :returns: Boolean; True if operation succeeds, False if it fails (set debug mode to see/log error). """ return self.wrapper.perms.set_group(self.mojangUuid.string, group, creategroup) def removeGroup(self, group): """ Removes the player to a specified group. :arg group: Group node (string) :returns: (use debug logging to see any errors) :True: Group was found and .remove operation performed (assume success if no exception raised). :None: User not in group :False: player uuid not found! """ return self.wrapper.perms.remove_group(self.mojangUuid.string, group) # Player Information def getFirstLogin(self): """ Returns a tuple containing the timestamp of when the user first logged in for the first time, and the timezone (same as time.tzname). """ return self.data.Data["firstLoggedIn"] # Cross-server commands def connect(self, address, port): # TODO - WORK IN PROGRESS """ Upon calling, the player object will become defunct and the client will be transferred to another server or wrapper instance (provided it has online-mode turned off). :Args: :address: server address (local address) :port: server port (local port) :returns: Nothing """ self.client.change_servers(address, port)
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 getStorage(self, name, world=False, pickle=True): """ Returns a storage object manager for saving data between reboots. :Args: :name: The name of the storage (on disk). :world: THe location of the storage on disk - :False: '/wrapper-data/plugins'. :True: '<serverpath>/<worldname>/plugins'. :Pickle: Whether wrapper should pickle or save as json. Pickle formatting is the default. pickling is less strict than json formats and leverages binary storage. Use of json can result in errors if your keys or data do not conform to json standards (like use of string keys). However, pickle is not generally human-readable, whereas json is human readable. :Returns: A storage object manager. The manager contains a storage dictionary called 'Data'. 'Data' contains the data your plugin will remember across reboots. ___ :NOTE: This method is somewhat different from previous Wrapper versions prior to 0.10.1 (build 182). The storage object is no longer a data object itself; It is a manager used for controlling the saving of the object data. The actual data is contained in the property/dictionary variable 'Data' ___ :sample methods: The new method: .. code:: python # to start a storage: self.homes = self.api.getStorage("homes", True) # access the data: for player in self.homes.Data: # note upper case `D` print("player %s has a home at: %s" % ( player, self.homes.Data[player])) # to save (storages also do periodic saves every minute): self.homes.save() # to close (and save): def onDisable(self): self.homes.close() # to load a storage from disk: self.homes.load() .. the key difference is here (under the old Storage API): .. code:: python # This used to work under the former API # however, this will produce an exception # because "self.homes" is no longer an # iterable data set: for player in self.homes: <= Exception! print("player %s has a home at: %s" % ( player, self.homes[player])) .. **tip** *to make the transition easier for existing code, redefine your the storage statements above like this to re-write as few lines as possible (and avoid problems with other plugins that might link to your plugin's data)*: .. code:: python # change your storage setup from: self.homes = self.api.getStorage("homes", True) # to: self.homestorage = self.api.getStorage("homes", True) self.homes = homestorage.Data # Now the only other change you need to make is to any # .save() or .close() statements: def onDisable(self): # self.homes.close() # change to - self.homestorage.close() .. """ if world: return Storage( name, root="%s/%s/plugins/%s" % (self.serverpath, self.minecraft.getWorldName(), self.id), pickle=pickle) else: return Storage(name, root="wrapper-data/plugins/%s" % self.id, pickle=pickle)
def __init__(self, username, wrapper): """ :UUIDS: All uuids are wrapper's MCUUID objects. If being used in a string context, they must be used with the *.string property (or str() explicitly): player.mojangUuid.string player.mojangUuis.__str__ str(player.mojangUuid) The only exception to this is the `uuid` property, which is always a string. :uuid (property, string): This will pull the best uuid available in this order- :1) mojangUuid: The bought and paid Mojand UUID. Never changes and is the prefered way to ID player keys. :2) offlineUuid: A MD5 hash of "OfflinePlayer:%s" % username :3) clientUuid: What the client believes is the uuid. If Wrapper is online, this should be the same as mojangUuid. :4) serverUuid: The player's local uuid on the server, usually the same as offline uuid. :param username: :param wrapper: """ self.wrapper = wrapper self.javaserver = wrapper.javaserver self.log = wrapper.log self.username = username self.loggedIn = time.time() # mcserver will set this to false later to close the thread. self.abort = False self.data = None # meanwhile, it still needs to respect wrapper halts self.wrapper_signal = self.wrapper.haltsig self.kick_nonproxy_connects = self.wrapper.config["Proxy"][ "disconnect-nonproxy-connections"] self.mojangUuid = False self.clientUuid = False # These two are offline by default. self.offlineUuid = self.wrapper.uuids.getuuidfromname(self.username) self.serverUuid = self.offlineUuid self.ipaddress = "127.0.0.0" self.loginposition = [0, 0, 0] self._position = [0, 0, 0, 0, 0] # internally used for non-proxy mode self.client = None self.clientgameversion = self.wrapper.javaserver.protocolVersion self.cbpkt = Packets_cb(self.clientgameversion) self.sbpkt = Packets_sb(self.clientgameversion) self.playereid = None # some player properties associated with abilities # # default is 1. Should normally be congruent with speed. self.field_of_view = float(1) # Client set godmode is 0x01 self.godmode = 0x00 # Client set creative is 0x08 self.creative = 0x00 # default is 1 self.fly_speed = float(1) if self.wrapper.proxy: gotclient = False for client in self.wrapper.proxy.clients: if client.username == self.username: self.client = client self.clientUuid = client.wrapper_uuid self.serverUuid = client.local_uuid self.mojangUuid = client.mojanguuid self.ipaddress = client.ip # pktSB already set to self.wrapper.javaserver.protocolVersion # noqa self.clientboundPackets = self.client.pktCB self.clientgameversion = self.client.clientversion gotclient = True break if not gotclient: pprint.pprint(self.wrapper.proxy.clients) self.log.error("Proxy is on, but this client is not " "listed in proxy.clients!") self.log.error("The usual cause of this would be that" " someone attempted to connect directly to" " your server port and not the wrapper" " proxy port, but can also be the result of" " a player that has abruptly disconnected.") if self.kick_nonproxy_connects: port = self.wrapper.proxy.proxy_port self.log.info("API.player Kicked %s" % self.name) self.abort = True self.wrapper.javaserver.console( "kick %s %s" % ( self.name, "Access Denied! Use port %s instead!" % port ) ) return if not self.mojangUuid: # poll cache/mojang for proper uuid self.mojangUuid = self.wrapper.uuids.getuuidbyusername(username) # Process login data self.data = Storage( self.mojangUuid.string, root="wrapper-data/players") if "firstLoggedIn" not in self.data.Data: self.data.Data["firstLoggedIn"] = (time.time(), time.tzname) if "logins" not in self.data.Data: self.data.Data["logins"] = {} self.data.Data["lastLoggedIn"] = (self.loggedIn, time.tzname) self.data.save() # start player logged in time tracking thread t = threading.Thread(target=self._track, args=()) t.daemon = True t.start()
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.config = wrapper.config # Remember if you need to save use 'wrapper.configManager.save()' not config.save self.log = logging.getLogger('Web') self.check_password = self.wrapper.cipher.check_pw() if not Flask: self.config["Web"]["web-enabled"] = False self.wrapper.configManager.save() self.log.critical("You don't have the 'flask/flask_socketio' dashboard dependencies installed " "on your system. You can now restart, but Web mode is disabled.") self.wrapper.haltsig.halt = True self.app = Flask(__name__) self.app.config['SECRET_KEY'] = "".join([chr(random.randrange(48, 90)) for i in range(32)]) # LOL self.socketio = SocketIO(self.app) # Flask filters def strftime(f): return datetime.datetime.fromtimestamp(int(f)).strftime('%Y-%m-%d @ %I:%M%p') self.app.jinja_env.filters["strftime"] = strftime # Register handlers self.add_decorators() self.data_storage = Storage("dash") if "keys" not in self.data_storage.Data: self.data_storage.Data["keys"] = [] self.loginAttempts = 0 self.last_attempt = 0 self.disableLogins = 0 # Start thread for running server t = threading.Thread(target=self.run, args=()) t.daemon = True t.start() def __del__(self): self.data_storage.close() # Authorization methods def check_login(self, password): if time.time() - self.disableLogins < 60: return False # Threshold for logins if self.check_password(password, self.config["Web"]["web-password"]): return True self.loginAttempts += 1 if self.loginAttempts > 10 and time.time() - self.last_attempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.last_attempt = time.time() def make_key(self, rememberme): chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_" a = "".join([random.choice(chars) for _i in range(64)]) self.data_storage.Data["keys"].append([a, time.time(), rememberme]) return a def validate_key(self): if "__wrapper_cookie" not in request.cookie: return False key = request.cookie["__wrapper_cookie"] for i in self.data_storage.Data["keys"]: expiretime = 7884000 # Three weeks old if len(i) > 2: if not i[2]: expiretime = 21600 # Validate key and ensure it's under the expiretime if i[0] == key and time.time() - i[1] < expiretime: self.loginAttempts = 0 return True return False def remove_key(self, key): for i, v in enumerate(self.data_storage.Data["keys"]): if v[0] == key: del self.data_storage.Data["keys"][i] # Dectorators and misc. def add_decorators(self): @self.app.before_request def handle(): print("I'm a freakin' sandwich dude!") @self.app.route("/") def index(): return render_template("dashboard.html") @self.app.route("/login", methods=["GET", "POST"]) def login(): badpass = False if request.method == "POST": password = request.form["password"] rememberme = "remember" in request.form if self.check_login(password): key = self.make_key(rememberme) return redirect("/") # self.log.warning("%s logged in to web mode (remember me: %s)", request.addr, rememberme) else: badpass = True return render_template("login.html", badPass=badpass) @self.socketio.on('connect') def handle_connect(): pass @self.socketio.on('disconnect') def handle_disconnect(): pass def run(self, halt): while not self.wrapper.haltsig.halt: self.socketio.run(self.app, host=self.config["Web"]["web-bind"], port=self.config["Web"]["web-port"]) self.data_storage.close()
def _update_db(index, term, docid): if _db.get(term, False): if _db[term].get(docid, False): _db[term][docid].update([index]) else: _db[term][docid] = set([index]) else: _db[term][docid] = set([index]) if __name__ == '__main__': processing = Preprocessing() print('Hurray !!! Program Started') data = Doc()._registerInstances()._getInstance('XML').parse('data.xml') glData = Storage()._transformData(data)._get_transformed_version() glData['tokens'] = glData['TEXT'].apply( lambda row: processing._action(row)) print 'tokens', glData['tokens'] result = zip(glData['DOCID'], glData['tokens']) print 'result', result for docid, etW in result: map(lambda term: _update_db(term[0], term[1], docid), list(enumerate(etW))) p_in_idx = Storage()._set_data_positional_data_frame( _db.keys(), _db.values())._get_positional_data_frame()
class Web(object): def __init__(self, wrapper): self.wrapper = wrapper self.config = wrapper.config # Remember if you need to save use 'wrapper.configManager.save()' not config.save self.log = logging.getLogger('Web') if not Flask: self.config["Web"]["web-enabled"] = False self.wrapper.configManager.save() self.log.critical("You don't have the 'flask/flask_socketio' dashboard dependencies installed " "on your system. You can now restart, but Web mode is disabled.") self.wrapper.halt = True self.app = Flask(__name__) self.app.config['SECRET_KEY'] = "".join([chr(random.randrange(48, 90)) for i in range(32)]) # LOL self.socketio = SocketIO(self.app) # Flask filters def strftime(f): return datetime.datetime.fromtimestamp(int(f)).strftime('%Y-%m-%d @ %I:%M%p') self.app.jinja_env.filters["strftime"] = strftime # Register handlers self.add_decorators() self.data = Storage("dash") if "keys" not in self.data.Data: self.data.Data["keys"] = [] self.loginAttempts = 0 self.lastAttempt = 0 self.disableLogins = 0 # Start thread for running server t = threading.Thread(target=self.run, args=()) t.daemon = True t.start() def __del__(self): self.data.close() # Authorization methods def checkLogin(self, password): if time.time() - self.disableLogins < 60: return False # Threshold for logins if password == self.config["Web"]["web-password"]: return True self.loginAttempts += 1 if self.loginAttempts > 10 and time.time() - self.lastAttempt < 60: self.disableLogins = time.time() self.log.warning("Disabled login attempts for one minute") self.lastAttempt = time.time() def makeKey(self, rememberme): chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@-_" a = "".join([random.choice(chars) for i in range(64)]) self.data.Data["keys"].append([a, time.time(), rememberme]) return a def validateKey(self): if "__wrapper_cookie" not in request.cookie: return False key = request.cookie["__wrapper_cookie"] for i in self.data.Data["keys"]: expiretime = 7884000 # Three weeks old if len(i) > 2: if not i[2]: expiretime = 21600 # Validate key and ensure it's under the expiretime if i[0] == key and time.time() - i[1] < expiretime: self.loginAttempts = 0 return True return False def removeKey(self, key): for i, v in enumerate(self.data.Data["keys"]): if v[0] == key: del self.data.Data["keys"][i] # Dectorators and misc. def add_decorators(self): @self.app.before_request def handle(): print("I'm a freakin' sandwich dude!") @self.app.route("/") def index(): return render_template("dashboard.html") @self.app.route("/login", methods=["GET", "POST"]) def login(): badpass = False if request.method == "POST": password = request.form["password"] rememberme = "remember" in request.form if self.checkLogin(password): key = self.makeKey(rememberme) return redirect("/") # self.log.warning("%s logged in to web mode (remember me: %s)", request.addr, rememberme) else: badpass = True return render_template("login.html", badPass=badpass) @self.socketio.on('connect') def handle_connect(): pass @self.socketio.on('disconnect') def handle_disconnect(): pass def run(self): # Need a method to end this Thread! # the ending code needs a self.data.close() to close the storage object self.socketio.run(self.app, host=self.config["Web"]["web-bind"], port=self.config["Web"]["web-port"])
def getStorage(self, name, world=False, formatting="pickle"): """ Returns a storage object manager. The manager contains the storage object, 'Data' (a dictionary). 'Data' contains the data your plugin will remember across reboots. :NOTE: This method is somewhat different from previous Wrapper versions prior to 0.10.1 (build 182). The storage object is no longer a data object itself; It is a manager used for controlling the saving of the object data. The actual data is contained in Dictionary subitem 'Data' ___ :Args: :name: The name of the storage (on disk). :world: :False: set the storage's location to '/wrapper-data/plugins'. :True: set the storage path to '<serverpath>/<worldname>/plugins'. :formatting="pickle": Pickle formatting is the default. pickling is less strict than json formats and leverages binary storage. Use of json (or future implemented formats) can result in errors if your keys or data do not conform to json standards (like use of string keys). However, pickle is not generally human-readable, whereas json is human readable. If you need a human-readable copy (for debugging), consider using self.api.helpers.putjsonfile(<yourDictionary>) to write a copy to disk in Json. if you do so, check the return status of `putjsonfile` to make sure it was written. ___ :sample methods: The new method: .. code:: python # to start a storage: self.homes = self.api.getStorage("homes", True) # access the data: for player in self.homes.Data: # note upper case `D` print("player %s has a home at: %s" % ( player, self.homes.Data[player])) # to save (storages also do periodic saves every minute): self.homes.save() # to close (and save): def onDisable(self): self.homes.close() .. the key difference is here (under the old Storage API): .. code:: python # This used to work under the former API # however, this will produce an exception # because "self.homes" is no longer an # iterable data set: for player in self.homes: <= Exception! print("player %s has a home at: %s" % ( player, self.homes[player])) .. **tip** *to make the transition easier for existing code, redefine your the storage statements above like this to re-write as few lines as possible (and avoid problems with other plugins that might link to your plugin's data)*: .. code:: python # change your storage setup from: self.homes = self.api.getStorage("homes", True) # to: self.homestorage = self.api.getStorage("homes", True) self.homes = homestorage.Data # Now the only other change you need to make is to any # .save() or .close() statements: def onDisable(self): # self.homes.close() # change to - self.homestorage.close() .. """ pickle = False if formatting == "pickle": pickle = True if world: return Storage( name, root="%s/%s/plugins/%s" % (self.serverpath, self.minecraft.getWorldName(), self.id), pickle=pickle) else: return Storage(name, root="wrapper-data/plugins/%s" % self.id, pickle=pickle)
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, molecules_file, board_size): self.storage = Storage.load_molecules(molecules_file) atoms = self.storage.get_atoms() self.board = Board([], 0) self.board.generate(board_size, atoms)