def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session account = request.user website_uid = csession.get("website_authenticated_uid", None) webclient_uid = csession.get("webclient_authenticated_uid", None) if not csession.session_key: # this is necessary to build the sessid key csession.save() if account.is_authenticated(): # Logged into website if not website_uid: # fresh website login (just from login page) csession["website_authenticated_uid"] = account.id if webclient_uid is None: # auto-login web client csession["webclient_authenticated_uid"] = account.id elif webclient_uid: # Not logged into website, but logged into webclient if not website_uid: csession["website_authenticated_uid"] = account.id account = AccountDB.objects.get(id=webclient_uid) try: # calls our custom authenticate, in web/utils/backend.py authenticate(autologin=account) login(request, account) except AttributeError: logger.log_trace()
def stop(self, kill=False): """ Called to stop the script from running. This also deletes the script. Args: kill (bool, optional): - Stop the script without calling any relevant script hooks. Returns: result (int): 0 if the script failed to stop, 1 otherwise. Used in counting. """ if not kill: try: self.at_stop() except Exception: logger.log_trace() self._stop_task() try: self.delete() except AssertionError: logger.log_trace() return 0 return 1
def open_parent_menu(self): """Open the parent menu, using `self.parents`. Note: You probably don't need to call this method directly, since the caller can go back to the parent menu using the `keys_go_back` automatically. """ parents = list(self.parents) if parents: parent_class, parent_obj, parent_keys = parents[-1] del parents[-1] if self.caller.cmdset.has(BuildingMenuCmdSet): self.caller.cmdset.remove(BuildingMenuCmdSet) try: menu_class = class_from_module(parent_class) except Exception: log_trace("BuildingMenu: attempting to load class {} failed".format( repr(parent_class))) return # Create the parent menu try: building_menu = menu_class(self.caller, parent_obj, keys=parent_keys, parents=tuple(parents)) except Exception: log_trace("An error occurred while creating building menu {}".format( repr(parent_class))) return else: return building_menu.open()
def start(self, force_restart=False): """ Called every time the script is started (for persistent scripts, this is usually once every server start) Args: force_restart (bool, optional): Normally an already started script will not be started again. if `force_restart=True`, the script will always restart the script, regardless of if it has started before. Returns: result (int): 0 or 1 depending on if the script successfully started or not. Used in counting. """ if self.is_active and not force_restart: # The script is already running, but make sure we have a _task if this is after a cache flush if not self.ndb._task and self.db_interval >= 0: self.ndb._task = ExtendedLoopingCall(self._step_task) try: start_delay, callcount = SCRIPT_FLUSH_TIMERS[self.id] del SCRIPT_FLUSH_TIMERS[self.id] now = False except (KeyError, ValueError, TypeError): now = not self.db_start_delay start_delay = None callcount = 0 self.ndb._task.start(self.db_interval, now=now, start_delay=start_delay, count_start=callcount) return 0 obj = self.obj if obj: # check so the scripted object is valid and initalized try: obj.cmdset except AttributeError: # this means the object is not initialized. logger.log_trace() self.is_active = False return 0 # try to restart a paused script try: if self.unpause(manual_unpause=False): return 1 except RuntimeError: # manually paused. return 0 # start the script from scratch self.is_active = True try: self.at_start() except Exception: logger.log_trace() if self.db_interval > 0: self._start_task() return 1
def dataReceived(self, data): """ This method will split the incoming data depending on if it starts with IAC (a telnet command) or not. All other data will be handled in line mode. Some clients also sends an erroneous line break after IAC, which we must watch out for. OOB protocols (MSDP etc) already intercept subnegotiations on their own, never entering this method. They will relay their parsed data directly to self.data_in. """ if data and data[0] == IAC or self.iaw_mode: try: #print "IAC mode" super(TelnetProtocol, self).dataReceived(data) if len(data) == 1: self.iaw_mode = True else: self.iaw_mode = False return except Exception, err1: conv = "" try: for b in data: conv += " " + repr(ord(b)) except Exception, err2: conv = str(err2) + ":", str(data) out = "Telnet Error (%s): %s (%s)" % (err1, data, conv) logger.log_trace(out) return
def dataReceived(self, data): """ Handle incoming data over the wire. This method will split the incoming data depending on if it starts with IAC (a telnet command) or not. All other data will be handled in line mode. Some clients also sends an erroneous line break after IAC, which we must watch out for. Args: data (str): Incoming data. Notes: OOB protocols (MSDP etc) already intercept subnegotiations on their own, never entering this method. They will relay their parsed data directly to self.data_in. """ if data and data[0] == IAC or self.iaw_mode: try: super(TelnetProtocol, self).dataReceived(data) if len(data) == 1: self.iaw_mode = True else: self.iaw_mode = False return except Exception as err1: conv = "" try: for b in data: conv += " " + repr(ord(b)) except Exception as err2: conv = str(err2) + ":", str(data) out = "Telnet Error (%s): %s (%s)" % (err1, data, conv) logger.log_trace(out) return if data and data.strip() == NULL: # This is an ancient type of keepalive still used by some # legacy clients. There should never be a reason to send # a lone NULL character so this seems ok to support for # backwards compatibility. data = _IDLE_COMMAND if self.no_lb_mode and _RE_LEND.search(data): # we are in no_lb_mode and receive a line break; # this means we should empty the buffer and send # the command. data = "".join(self.line_buffer) + data data = data.rstrip("\r\n") + "\n" self.line_buffer = [] self.no_lb_mode = False elif not _RE_LEND.search(data): # no line break at the end of the command, buffer instead. self.line_buffer.append(data) self.no_lb_mode = True return # if we get to this point the command should end with a linebreak. StatefulTelnetProtocol.dataReceived(self, data)
def unpause(self, manual_unpause=True): """ Restart a paused script. This WILL call the `at_start()` hook. Args: manual_unpause (bool, optional): This is False if unpause is called by the server reload/reset mechanism. Returns: result (bool): True if unpause was triggered, False otherwise. Raises: RuntimeError: If trying to automatically resart this script (usually after a reset/reload), but it was manually paused, and so should not the auto-unpaused. """ if not manual_unpause and self.db._manual_pause: # if this script was paused manually (by a direct call of pause), # it cannot be automatically unpaused (e.g. by a @reload) raise RuntimeError if self.db._paused_time: # only unpause if previously paused self.is_active = True try: self.at_start() except Exception: logger.log_trace() self._start_task() return True
def _callback(self): """ This will be called repeatedly every `self.interval` seconds. `self.subscriptions` contain tuples of (obj, args, kwargs) for each subscribing object. If overloading, this callback is expected to handle all subscriptions when it is triggered. It should not return anything and should not traceback on poorly designed hooks. The callback should ideally work under @inlineCallbacks so it can yield appropriately. The _hook_key, which is passed down through the handler via kwargs is used here to identify which hook method to call. """ for store_key, (obj, args, kwargs) in self.subscriptions.items(): hook_key = yield kwargs.pop("_hook_key", "at_tick") if not obj or not obj.pk: # object was deleted between calls self.remove(store_key) continue try: yield _GA(obj, hook_key)(*args, **kwargs) except ObjectDoesNotExist: log_trace() self.remove(store_key) except Exception: log_trace() finally: # make sure to re-store kwargs["_hook_key"] = hook_key
def call_inputfuncs(self, session, **kwargs): """ Split incoming data into its inputfunc counterparts. This should be called by the serversession.data_in as sessionhandler.call_inputfunc(self, **kwargs). We also intercept OOB communication here. Args: sessions (Session): Session. Kwargs: kwargs (any): Incoming data from protocol on the form `{"commandname": ((args), {kwargs}),...}` """ # distribute incoming data to the correct receiving methods. if session: input_debug = session.protocol_flags.get("INPUTDEBUG", False) for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems(): cname = cmdname.strip().lower() try: cmdkwargs.pop("options", None) if cname in _INPUT_FUNCS: _INPUT_FUNCS[cname](session, *cmdargs, **cmdkwargs) else: _INPUT_FUNCS["default"](session, cname, *cmdargs, **cmdkwargs) except Exception as err: if input_debug: session.msg(err) log_trace()
def create_help_entry(key, entrytext, category="General", locks=None): """ Create a static help entry in the help database. Note that Command help entries are dynamic and directly taken from the __doc__ entries of the command. The database-stored help entries are intended for more general help on the game, more extensive info, in-game setting information and so on. """ global _HelpEntry if not _HelpEntry: from evennia.help.models import HelpEntry as _HelpEntry try: new_help = _HelpEntry() new_help.key = key new_help.entrytext = entrytext new_help.help_category = category if locks: new_help.locks.add(locks) new_help.save() return new_help except IntegrityError: string = "Could not add help entry: key '%s' already exists." % key logger.log_errmsg(string) return None except Exception: logger.log_trace() return None
def start_server(self, server_twistd_cmd): """ (Re-)Launch the Evennia server. Args: server_twisted_cmd (list): The server start instruction to pass to POpen to start the server. """ # start the Server process = None with open(settings.SERVER_LOG_FILE, 'a') as logfile: # we link stdout to a file in order to catch # eventual errors happening before the Server has # opened its logger. try: if _is_windows(): # Windows requires special care create_no_window = 0x08000000 process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT, creationflags=create_no_window) else: process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT) except Exception: logger.log_trace() self.factory.portal.server_twistd_cmd = server_twistd_cmd logfile.flush() if process and not _is_windows(): # avoid zombie-process on Unix/BSD process.wait() return
def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session account = request.user # these can have 3 values: # None - previously unused (auto-login) # False - actively logged out (don't auto-login) # <uid> - logged in User/Account id website_uid = csession.get("website_authenticated_uid", None) webclient_uid = csession.get("webclient_authenticated_uid", None) # check if user has authenticated to website if not csession.session_key: # this is necessary to build the sessid key csession.save() if webclient_uid: # The webclient has previously registered a login to this browser_session if not account.is_authenticated() and not website_uid: account = AccountDB.objects.get(id=webclient_uid) try: # calls our custom authenticate in web/utils/backends.py account = authenticate(autologin=account) login(request, account) csession["website_authenticated_uid"] = webclient_uid except AttributeError: logger.log_trace()
def distribute_message(self, msgobj, online=False): """ Method for grabbing all listeners that a message should be sent to on this channel, and sending them a message. Args: msgobj (Msg or TempMsg): Message to distribute. online (bool): Only send to receivers who are actually online (not currently used): Notes: This is also where logging happens, if enabled. """ # get all players or objects connected to this channel and send to them for entity in self.subscriptions.all(): # if the entity is muted, we don't send them a message if entity in self.mutelist: continue try: # note our addition of the from_channel keyword here. This could be checked # by a custom player.msg() to treat channel-receives differently. entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel":self.id}) except AttributeError as e: logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) if msgobj.keep_log: # log to file logger.log_file(msgobj.message, self.attributes.get("log_file") or "channel_%s.log" % self.key)
def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session player = request.user sesslogin = csession.get("logged_in", None) # check if user has authenticated to website if csession.session_key is None: # this is necessary to build the sessid key csession.save() elif player.is_authenticated(): if not sesslogin: # User has already authenticated to website csession["logged_in"] = player.id elif sesslogin: # The webclient has previously registered a login to this browser_session player = PlayerDB.objects.get(id=sesslogin) try: # calls our custom authenticate in web/utils/backends.py player = authenticate(autologin=player) login(request, player) except AttributeError: logger.log_trace()
def data_in(self, session, **kwargs): """ Data Portal -> Server. We also intercept OOB communication here. Args: sessions (Session): Session. Kwargs: kwargs (any): Other data from protocol. """ # distribute incoming data to the correct receiving methods. if session: input_debug = session.protocol_flags.get("INPUTDEBUG", False) for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems(): cname = cmdname.strip().lower() try: cmdkwargs.pop("options", None) if cname in _INPUT_FUNCS: _INPUT_FUNCS[cname](session, *cmdargs, **cmdkwargs) else: _INPUT_FUNCS["default"](session, cname, *cmdargs, **cmdkwargs) except Exception, err: if input_debug: session.msg(err) log_trace()
def func(self): """This is called when user enters anything.""" caller = self.caller try: getinput = caller.ndb._getinput if not getinput and hasattr(caller, "account"): getinput = caller.account.ndb._getinput caller = caller.account callback = getinput._callback caller.ndb._getinput._session = self.session prompt = caller.ndb._getinput._prompt args = caller.ndb._getinput._args kwargs = caller.ndb._getinput._kwargs result = self.raw_string.strip() # we strip the ending line break caused by sending ok = not callback(caller, prompt, result, *args, **kwargs) if ok: # only clear the state if the callback does not return # anything del caller.ndb._getinput caller.cmdset.remove(InputCmdSet) except Exception: # make sure to clean up cmdset if something goes wrong caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n") logger.log_trace("Error in get_input") caller.cmdset.remove(InputCmdSet)
def _create_character(character_key, level, session, new_player, typeclass, home, permissions, nickname): """ Helper function, creates a character based on a player's name. This is meant for Guest and MULTISESSION_MODE < 2 situations. """ try: new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions) # set character info new_character.set_data_info(character_key) new_character.set_level(level) # set playable character list new_player.db._playable_characters.append(new_character) # allow only the character itself and the player to puppet this character (and Immortals). new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % (new_character.id, new_player.id)) # If no description is set, set a default description if not new_character.db.desc: new_character.db.desc = "This is a Player." # Add nickname if not nickname: nickname = character_key new_character.set_nickname(nickname) # We need to set this to have @ic auto-connect to this character new_player.db._last_puppet = new_character except Exception, e: session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False
def restart(self, interval=None, repeats=None, start_delay=None): """ Restarts an already existing/running Script from the beginning, optionally using different settings. This will first call the stop hooks, and then the start hooks again. Args: interval (int, optional): Allows for changing the interval of the Script. Given in seconds. if `None`, will use the already stored interval. repeats (int, optional): The number of repeats. If unset, will use the previous setting. start_delay (bool, optional): If we should wait `interval` seconds before starting or not. If `None`, re-use the previous setting. """ try: self.at_stop() except Exception: logger.log_trace() self._stop_task() self.is_active = False if interval is not None: self.interval = interval if repeats is not None: self.repeats = repeats if start_delay is not None: self.start_delay = start_delay self.start()
def _step_task(self): """ Step task. This groups error handling. """ try: return maybeDeferred(self._step_callback).addErrback(self._step_errback) except Exception: logger.log_trace()
def server_receive_adminportal2server(self, packed_data): """ Receives admin data from the Portal (allows the portal to perform admin operations on the server). This is executed on the Server. Args: packed_data (str): Incoming, pickled data. """ sessid, kwargs = self.data_in(packed_data) operation = kwargs.pop("operation", "") server_sessionhandler = self.factory.server.sessions try: if operation == amp.PCONN: # portal_session_connect # create a new session and sync it server_sessionhandler.portal_connect(kwargs.get("sessiondata")) elif operation == amp.PCONNSYNC: # portal_session_sync server_sessionhandler.portal_session_sync(kwargs.get("sessiondata")) elif operation == amp.PDISCONN: # portal_session_disconnect # session closed from portal sid session = server_sessionhandler.get(sessid) if session: server_sessionhandler.portal_disconnect(session) elif operation == amp.PDISCONNALL: # portal_disconnect_all # portal orders all sessions to close server_sessionhandler.portal_disconnect_all() elif operation == amp.PSYNC: # portal_session_sync # force a resync of sessions from the portal side. This happens on # first server-connect. server_restart_mode = kwargs.get("server_restart_mode", "shutdown") self.factory.server.run_init_hooks(server_restart_mode) server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata")) elif operation == amp.SRELOAD: # server reload # shut down in reload mode server_sessionhandler.all_sessions_portal_sync() server_sessionhandler.server.shutdown(mode='reload') elif operation == amp.SRESET: # shut down in reset mode server_sessionhandler.all_sessions_portal_sync() server_sessionhandler.server.shutdown(mode='reset') elif operation == amp.SSHUTD: # server shutdown # shutdown in stop mode server_sessionhandler.server.shutdown(mode='shutdown') else: raise Exception("operation %(op)s not recognized." % {'op': operation}) except Exception: logger.log_trace() return {}
def create_guest_player(session): """ Creates a guest player/character for this session, if one is available. Args: session (Session): the session which will use the guest player/character. Returns: GUEST_ENABLED (boolean), player (Player): the boolean is whether guest accounts are enabled at all. the Player which was created from an available guest name. """ # check if guests are enabled. if not settings.GUEST_ENABLED: return False, None # Check IP bans. bans = ServerConfig.objects.conf("server_bans") if bans and any(tup[2].match(session.address) for tup in bans if tup[2]): # this is a banned IP! string = "{rYou have been banned and cannot continue from here." \ "\nIf you feel this ban is in error, please email an admin.{x" session.msg(string) session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") return True, None try: # Find an available guest name. for playername in settings.GUEST_LIST: if not PlayerDB.objects.filter(username__iexact=playername): break playername = None if playername == None: session.msg("All guest accounts are in use. Please try again later.") return True, None password = "******" % getrandbits(64) home = ObjectDB.objects.get_id(settings.GUEST_HOME) permissions = settings.PERMISSION_GUEST_DEFAULT typeclass = settings.BASE_CHARACTER_TYPECLASS ptypeclass = settings.BASE_GUEST_TYPECLASS new_player = _create_player(session, playername, password, permissions, ptypeclass) if new_player: _create_character(GAME_SETTINGS.get("default_player_character_key"), 1, session, new_player, typeclass, home, home, permissions, playername) except Exception: # We are in the middle between logged in and -not, so we have # to handle tracebacks ourselves at this point. If we don't, # we won't see any errors at all. session.msg("An error occurred. Please e-mail an admin if the problem persists.") logger.log_trace() finally: return True, new_player
def load_buffer(self): """ Load the buffer using the load function hook. """ try: self._buffer = self._loadfunc(self._caller) except Exception as e: from evennia.utils import logger logger.log_trace() self._caller.msg(_ERROR_LOADFUNC.format(error=e))
def decorator(*args, **kwargs): try: func(*args, **kwargs) except Exception as err: global _LOGGER if not _LOGGER: from evennia.utils import logger as _LOGGER _LOGGER.log_trace() raise # make sure the error is visible on the other side of the connection too print(err)
def __location_set(self, location): "Set location, checking for loops and allowing dbref" if isinstance(location, (basestring, int)): # allow setting of #dbref dbid = dbref(location, reqhash=False) if dbid: try: location = ObjectDB.objects.get(id=dbid) except ObjectDoesNotExist: # maybe it is just a name that happens to look like a dbid pass try: def is_loc_loop(loc, depth=0): "Recursively traverse target location, trying to catch a loop." if depth > 10: return elif loc == self: raise RuntimeError elif loc == None: raise RuntimeWarning return is_loc_loop(loc.db_location, depth + 1) try: is_loc_loop(location) except RuntimeWarning: pass # if we get to this point we are ready to change location old_location = self.db_location # this is checked in _db_db_location_post_save below self._safe_contents_update = True # actually set the field (this will error if location is invalid) self.db_location = location self.save(update_fields=["db_location"]) # remove the safe flag del self._safe_contents_update # update the contents cache if old_location: old_location.contents_cache.remove(self) if self.db_location: self.db_location.contents_cache.add(self) except RuntimeError: errmsg = "Error: %s.location = %s creates a location loop." % (self.key, location) logger.log_trace(errmsg) raise except Exception as e: errmsg = "Error (%s): %s is not a valid location." % (str(e), location) logger.log_trace(errmsg) raise
def msg(self, text=None, from_obj=None, session=None, **kwargs): """ Emits something to a session attached to the object. Args: text (str, optional): The message to send from_obj (obj, optional): object that is sending. If given, at_msg_send will be called session (Session or list, optional): Session or list of Sessions to relay data to, if any. If set, will force send to these sessions. If unset, who receives the message depends on the MULTISESSION_MODE. Notes: `at_msg_receive` will be called on this Object. All extra kwargs will be passed on to the protocol. """ # Send messages to the client. Messages are in format of JSON. """ raw = kwargs.get("raw", False) if not raw: try: text = json.dumps(text) except Exception, e: text = json.dumps({"err": "There is an error occurred while outputing messages."}) logger.log_errmsg("json.dumps failed: %s" % e) else: text = to_str(text, force_string=True) if text != None else "" # set raw=True if kwargs: kwargs["raw"] = True else: kwargs = {"raw": True} """ # try send hooks if from_obj: try: from_obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: logger.log_trace() try: if not self.at_msg_receive(text=text, **kwargs): # if at_msg_receive returns false, we abort message to this object return except Exception: logger.log_trace() # relay to session(s) sessions = make_iter(session) if session else self.sessions.all() for session in sessions: session.msg(text=text, **kwargs)
def _get_channel_cmdsets(player, player_cmdset): "Channel-cmdsets" # Create cmdset for all player's available channels try: channel_cmdset = None if not player_cmdset.no_channels: channel_cmdset = yield CHANNELHANDLER.get_cmdset(player) returnValue(channel_cmdset) except Exception: logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported
def restore(self, server_reload=True): """ Restore ticker_storage from database and re-initialize the handler from storage. This is triggered by the server at restart. Args: server_reload (bool, optional): If this is False, it means the server went through a cold reboot and all non-persistent tickers must be killed. """ # load stored command instructions and use them to re-initialize handler restored_tickers = ServerConfig.objects.conf(key=self.save_name) if restored_tickers: # the dbunserialize will convert all serialized dbobjs to real objects restored_tickers = dbunserialize(restored_tickers) self.ticker_storage = {} for store_key, (args, kwargs) in restored_tickers.iteritems(): try: # at this point obj is the actual object (or None) due to how # the dbunserialize works obj, callfunc, path, interval, idstring, persistent = store_key if not persistent and not server_reload: # this ticker will not be restarted continue if isinstance(callfunc, basestring) and not obj: # methods must have an existing object continue # we must rebuild the store_key here since obj must not be # stored as the object itself for the store_key to be hashable. store_key = self._store_key(obj, path, interval, callfunc, idstring, persistent) if obj and callfunc: kwargs["_callback"] = callfunc kwargs["_obj"] = obj elif path: modname, varname = path.rsplit(".", 1) callback = variable_from_module(modname, varname) kwargs["_callback"] = callback kwargs["_obj"] = None else: # Neither object nor path - discard this ticker log_err("Tickerhandler: Removing malformed ticker: %s" % str(store_key)) continue except Exception: # this suggests a malformed save or missing objects log_trace("Tickerhandler: Removing malformed ticker: %s" % str(store_key)) continue # if we get here we should create a new ticker self.ticker_storage[store_key] = (args, kwargs) self.ticker_pool.add(store_key, *args, **kwargs)
def _create_player(session, playername, password, permissions, typeclass=None): """ Helper function, creates a player of the specified typeclass. """ try: new_player = create.create_player(playername, None, password, permissions=permissions, typeclass=typeclass) except Exception, e: session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False
def mod_import(module): """ A generic Python module loader. Args: module (str, module): This can be either a Python path (dot-notation like `evennia.objects.models`), an absolute path (e.g. `/home/eve/evennia/evennia/objects.models.py`) or an already imported module object (e.g. `models`) Returns: module (module or None): An imported module. If the input argument was already a module, this is returned as-is, otherwise the path is parsed and imported. Returns `None` and logs error if import failed. """ if not module: return None if isinstance(module, types.ModuleType): # if this is already a module, we are done mod = module else: # first try to import as a python path try: mod = __import__(module, fromlist=["None"]) except ImportError as ex: # check just where the ImportError happened (it could have been # an erroneous import inside the module as well). This is the # trivial way to do it ... if str(ex) != "Import by filename is not supported.": raise # error in this module. Try absolute path import instead if not os.path.isabs(module): module = os.path.abspath(module) path, filename = module.rsplit(os.path.sep, 1) modname = re.sub(r"\.py$", "", filename) try: result = imp.find_module(modname, [path]) except ImportError: logger.log_trace("Could not find module '%s' (%s.py) at path '%s'" % (modname, modname, path)) return try: mod = imp.load_module(modname, *result) except ImportError: logger.log_trace("Could not find or import module %s at path '%s'" % (modname, path)) mod = None # we have to close the file handle manually result[0].close() return mod
def _get_cmdset(obj): "Get cmdset, triggering all hooks" try: yield obj.at_cmdset_get() except Exception: logger.log_trace() _msg_err(caller, _ERROR_CMDSETS) raise ErrorReported try: returnValue(obj.cmdset.current) except AttributeError: returnValue(None)
def at_start(self): """ This is called once every server restart. We reset the up time and load the relevant times. """ global SERVER_RUNTIME SERVER_RUNTIME = self.attributes.get("run_time") #In case of an error loading script from database, we'll check time #versus the last saved gametime in the logfile try: logfile = open(BACKUP_FILE) lines = [line.strip() for line in logfile if line[0].isdigit()] #get the last recorded time in file last_time = float(lines[-1]) if SERVER_RUNTIME < last_time: SERVER_RUNTIME = last_time except Exception: from evennia.utils import logger logger.log_trace()
def distribute_message(self, msg, online=False): """ Method for grabbing all listeners that a message should be sent to on this channel, and sending them a message. msg (str): Message to distribute. online (bool): Only send to receivers who are actually online (not currently used): """ # get all players connected to this channel and send to them for entity in self.subscriptions.all(): try: # note our addition of the from_channel keyword here. This could be checked # by a custom player.msg() to treat channel-receives differently. entity.msg(msg.message, from_obj=msg.senders, from_channel=self.id) except AttributeError, e: logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
def json_decode(self, data): """ Decodes incoming data from the client. Args: data (JSON): JSON object to unpack. Raises: Exception: If receiving a malform OOB request. Notes: [cmdname, [args],{kwargs}] -> cmdname *args **kwargs """ try: cmdname, args, kwargs = json.loads(data) except Exception: log_trace("Websocket malformed OOB request: %s" % data) raise self.sessionhandler.data_in(self, oob=(cmdname, args, kwargs))
def create_help_entry(key, entrytext, category="General", locks=None, aliases=None): """ Create a static help entry in the help database. Note that Command help entries are dynamic and directly taken from the __doc__ entries of the command. The database-stored help entries are intended for more general help on the game, more extensive info, in-game setting information and so on. Args: key (str): The name of the help entry. entrytext (str): The body of te help entry category (str, optional): The help category of the entry. locks (str, optional): A lockstring to restrict access. aliases (list of str): List of alternative (likely shorter) keynames. Returns: help (HelpEntry): A newly created help entry. """ global _HelpEntry if not _HelpEntry: from evennia.help.models import HelpEntry as _HelpEntry try: new_help = _HelpEntry() new_help.key = key new_help.entrytext = entrytext new_help.help_category = category if locks: new_help.locks.add(locks) if aliases: new_help.aliases.add(aliases) new_help.save() return new_help except IntegrityError: string = "Could not add help entry: key '%s' already exists." % key logger.log_err(string) return None except Exception: logger.log_trace() return None
def build_matches(raw_string, cmdset, include_prefixes=False): """ Build match tuples by matching raw_string against available commands. Args: raw_string (str): Input string that can look in any way; the only assumption is that the sought command's name/alias must be *first* in the string. cmdset (CmdSet): The current cmdset to pick Commands from. include_prefixes (bool): If set, include prefixes like @, ! etc (specified in settings) in the match, otherwise strip them before matching. Returns: matches (list) A list of match tuples created by `cmdparser.create_match`. """ matches = [] try: if include_prefixes: # use the cmdname as-is l_raw_string = raw_string.lower() for cmd in cmdset: matches.extend([create_match(cmdname, raw_string, cmd, cmdname) for cmdname in [cmd.key] + cmd.aliases if cmdname and l_raw_string.startswith(cmdname.lower()) and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):]))]) else: # strip prefixes set in settings raw_string = (raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string) l_raw_string = raw_string.lower() for cmd in cmdset: for raw_cmdname in [cmd.key] + cmd.aliases: cmdname = (raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_cmdname) > 1 else raw_cmdname) if cmdname and l_raw_string.startswith(cmdname.lower()) and \ (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])): matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname)) except Exception: log_trace("cmdhandler error. raw_input:%s" % raw_string) return matches
def _create_character(character_key, level, session, new_player, typeclass, home, permissions, nickname): """ Helper function, creates a character based on a player's name. This is meant for Guest and MULTISESSION_MODE < 2 situations. """ try: new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions) # set character info new_character.set_data_info(character_key) new_character.set_level(level) # set playable character list new_player.db._playable_characters.append(new_character) # allow only the character itself and the player to puppet this character (and Immortals). new_character.locks.add( "puppet:id(%i) or pid(%i) or perm(Immortals) or pperm(Immortals)" % (new_character.id, new_player.id)) # If no description is set, set a default description if not new_character.db.desc: new_character.db.desc = "This is a Player." # Add nickname if not nickname: nickname = character_key new_character.set_nickname(nickname) # We need to set this to have @ic auto-connect to this character new_player.db._last_puppet = new_character except Exception, e: session.msg( "There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Evennia -> User This is the main route for sending data back to the user from the server. Args: text (str, optional): text data to send from_obj (Object or Account or list, optional): Object sending. If given, its at_msg_send() hook will be called. If iterable, call on all entities. session (Session or list, optional): Session object or a list of Sessions to receive this send. If given, overrules the default send behavior for the current MULTISESSION_MODE. options (list): Protocol-specific options. Passed on to the protocol. Kwargs: any (dict): All other keywords are passed on to the protocol. """ if from_obj: # call hook for obj in make_iter(from_obj): try: obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: # this may not be assigned. logger.log_trace() try: if not self.at_msg_receive(text=text, **kwargs): # abort message to this account return except Exception: # this may not be assigned. pass kwargs["options"] = options # session relay sessions = make_iter(session) if session else self.sessions.all() for session in sessions: session.data_out(text=text, **kwargs)
def at_script_creation(self): """ Setup the script """ self.key = GAMETIME_SCRIPT_NAME self.desc = "Keeps track of the game time" self.interval = 60 self.persistent = True self.start_delay = True self.attributes.add("run_time", 0.0) # OOC time self.attributes.add("up_time", 0.0) # OOC time try: logfile = open(BACKUP_FILE) lines = [line.strip() for line in logfile if line[0].isdigit()] #get the last recorded time in file last_time = float(lines[-1]) if last_time: self.attributes.add("run_time", last_time) except Exception: from evennia.utils import logger logger.log_trace()
def execute_cmd(self, session, oobfuncname, *args, **kwargs): """ Execute an oob command Args: session (Session or int): Session or Session.sessid calling the oob command oobfuncname (str): The name of the oob command (case sensitive) Notes: If the oobfuncname is a valid oob function, `args` and `kwargs` are passed into the oob command. """ if not session: errmsg = "OOB Error: execute_cmd(%s,%s,%s,%s) - no valid session" % \ (session, oobfuncname, args, kwargs) raise RuntimeError(errmsg) try: oobfunc = _OOB_FUNCS[oobfuncname] except Exception: errmsg = "'%s' is not a valid OOB command. Commands available:\n %s" % ( oobfuncname, ", ".join(_OOB_FUNCS)) if _OOB_ERROR: _OOB_ERROR(session, errmsg, *args, **kwargs) errmsg = "OOB ERROR: %s" % errmsg logger.log_trace(errmsg) return # we found an oob command. Execute it. try: oobfunc(session, *args, **kwargs) except Exception as err: errmsg = "Exception in %s(*%s, **%s):\n%s" % (oobfuncname, args, kwargs, err) if _OOB_ERROR: _OOB_ERROR(session, errmsg, *args, **kwargs) errmsg = "OOB ERROR: %s" % errmsg logger.log_trace(errmsg)
def make_shared_login(cls, request): csession = request.session account = request.user website_uid = csession.get("website_authenticated_uid", None) webclient_uid = csession.get("webclient_authenticated_uid", None) if not csession.session_key: # this is necessary to build the sessid key csession.save() if account.is_authenticated: # Logged into website if website_uid is None: # fresh website login (just from login page) csession["website_authenticated_uid"] = account.id if webclient_uid is None: # auto-login web client csession["webclient_authenticated_uid"] = account.id elif webclient_uid: # Not logged into website, but logged into webclient if website_uid is None: csession["website_authenticated_uid"] = account.id account = AccountDB.objects.get(id=webclient_uid) try: # calls our custom authenticate, in web/utils/backend.py authenticate(autologin=account) login(request, account) except AttributeError: logger.log_trace() if csession.get("webclient_authenticated_uid", None): # set a nonce to prevent the webclient from erasing the webclient_authenticated_uid value csession["webclient_authenticated_nonce"] = ( csession.get("webclient_authenticated_nonce", 0) + 1 ) # wrap around to prevent integer overflows if csession["webclient_authenticated_nonce"] > 32: csession["webclient_authenticated_nonce"] = 0
def disableLocal(self, option): """ Disable a given option locally. Args: option (char): The telnet option to disable locally. """ if option == LINEMODE: return True if option == ECHO: return True if option == MCCP: self.mccp.no_mccp(option) return True else: try: return super().disableLocal(option) except Exception: from evennia.utils import logger logger.log_trace()
def _send_to_connect_channel(self, message): """ Helper method for loading and sending to the comm channel dedicated to connection messages. Args: message (str): A message to send to the connect channel. """ global _CONNECT_CHANNEL if not _CONNECT_CHANNEL: try: _CONNECT_CHANNEL = ChannelDB.objects.filter(db_key=settings.DEFAULT_CHANNELS[1]["key"])[0] except Exception: logger.log_trace() now = timezone.now() now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now.day, now.hour, now.minute) if _CONNECT_CHANNEL: _CONNECT_CHANNEL.tempmsg("[%s, %s]: %s" % (_CONNECT_CHANNEL.key, now, message)) else: logger.log_info("[%s]: %s" % (now, message))
def distribute_message(self, msgobj, online=False, **kwargs): """ Method for grabbing all listeners that a message should be sent to on this channel, and sending them a message. Args: msgobj (Msg or TempMsg): Message to distribute. online (bool): Only send to receivers who are actually online (not currently used): **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). Notes: This is also where logging happens, if enabled. """ # get all accounts or objects connected to this channel and send to them if online: subs = self.subscriptions.online() else: subs = self.subscriptions.all() for entity in subs: # if the entity is muted, we don't send them a message if entity in self.mutelist: continue try: # note our addition of the from_channel keyword here. This could be checked # by a custom account.msg() to treat channel-receives differently. entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id}) except AttributeError as e: logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) if msgobj.keep_log: # log to file logger.log_file( msgobj.message, self.attributes.get("log_file") or "channel_%s.log" % self.key)
def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session player = request.user sesslogin = csession.get("logged_in", None) if csession.session_key is None: # this is necessary to build the sessid key csession.save() elif player.is_authenticated(): if not sesslogin: csession["logged_in"] = player.id elif sesslogin: # The webclient has previously registered a login to this csession player = PlayerDB.objects.get(id=sesslogin) try: login(request, player) except AttributeError: logger.log_trace()
def data_out(self, text=None, **kwargs): """ Data Evennia -> Player access hook. webclient flags checked are raw=True - no parsing at all (leave ansi-to-html markers unparsed) nomarkup=True - clean out all ansi/html markers and tokens """ # string handling is similar to telnet try: text = utils.to_str(text if text else "", encoding=self.encoding) raw = kwargs.get("raw", False) nomarkup = kwargs.get("nomarkup", False) if raw: self.client.lineSend(self.suid, text) else: self.client.lineSend(self.suid, parse_html(text, strip_ansi=nomarkup)) return except Exception: logger.log_trace()
def __init__(self): self.storage = dict() pspace_dict = dict() def get_or_create_pspace(pspace): if pspace in pspace_dict: return pspace_dict[pspace] model, created = Pluginspace.objects.get_or_create(db_name=pspace) if created: model.save() pspace_dict[pspace] = model return model from django.conf import settings from evennia.utils.utils import class_from_module from evmush.models import Pluginspace, Namespace for plugin in settings.EVMUSH_PLUGINS.keys(): get_or_create_pspace(plugin) for namespaces in settings.IDENTITY_NAMESPACES: nspace, created = Namespace.objects.get_or_create(db_name=namespaces) if created: nspace.save() styler_class = class_from_module(settings.STYLER_CLASS) self.storage['styler'] = styler_class styler_class.load() try: manager = class_from_module(settings.CONTROLLER_MANAGER_CLASS)(self) self.storage['controller_manager'] = manager manager.load() except Exception as e: from evennia.utils import logger logger.log_trace(e) print(e)
def _create_character(session, new_account, typeclass, home, permissions): """ Helper function, creates a character based on an account's name. This is meant for Guest and MULTISESSION_MODE < 2 situations. """ try: new_character = create.create_object(typeclass, key=new_account.key, home=home, permissions=permissions) # set playable character list new_account.db._playable_characters.append(new_character) # allow only the character itself and the account to puppet this character (and Developers). new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % (new_character.id, new_account.id)) # If no description is set, set a default description if not new_character.db.desc: new_character.db.desc = "This is a character." # We need to set this to have ic auto-connect to this character new_account.db._last_puppet = new_character except Exception as e: session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace()
def dataReceived(self, data): """ Handle incoming data over the wire. This method will split the incoming data depending on if it starts with IAC (a telnet command) or not. All other data will be handled in line mode. Some clients also sends an erroneous line break after IAC, which we must watch out for. Args: data (str): Incoming data. Notes: OOB protocols (MSDP etc) already intercept subnegotiations on their own, never entering this method. They will relay their parsed data directly to self.data_in. """ if data and data[0] == IAC or self.iaw_mode: try: #print "IAC mode" super(TelnetProtocol, self).dataReceived(data) if len(data) == 1: self.iaw_mode = True else: self.iaw_mode = False return except Exception, err1: conv = "" try: for b in data: conv += " " + repr(ord(b)) except Exception, err2: conv = str(err2) + ":", str(data) out = "Telnet Error (%s): %s (%s)" % (err1, data, conv) logger.log_trace(out) return
def open_parent_menu(self): """Open the parent menu, using `self.parents`. Note: You probably don't need to call this method directly, since the caller can go back to the parent menu using the `keys_go_back` automatically. """ parents = list(self.parents) if parents: parent_class, parent_obj, parent_keys = parents[-1] del parents[-1] if self.caller.cmdset.has(BuildingMenuCmdSet): self.caller.cmdset.remove(BuildingMenuCmdSet) try: menu_class = class_from_module(parent_class) except Exception: log_trace( "BuildingMenu: attempting to load class {} failed".format( repr(parent_class))) return # Create the parent menu try: building_menu = menu_class(self.caller, parent_obj, keys=parent_keys, parents=tuple(parents)) except Exception: log_trace( "An error occurred while creating building menu {}".format( repr(parent_class))) return else: return building_menu.open()
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): """ Emits something to a session attached to the object. Args: text (str, optional): The message to send from_obj (obj, optional): object that is sending. If given, at_msg_send will be called session (Session or list, optional): Session or list of Sessions to relay data to, if any. If set, will force send to these sessions. If unset, who receives the message depends on the MULTISESSION_MODE. Notes: `at_msg_receive` will be called on this Object. All extra kwargs will be passed on to the protocol. """ # Send messages to the client. Messages are in format of JSON. # try send hooks if from_obj: try: from_obj.at_msg_send(text=text, to_obj=self, **kwargs) except Exception: logger.log_trace() try: if not self.at_msg_receive(text=text, **kwargs): # if at_msg_receive returns false, we abort message to this object return except Exception: logger.log_trace() kwargs["options"] = options # relay to session(s) sessions = make_iter(session) if session else self.sessions.all() for session in sessions: session.msg(text=text, **kwargs)
def get_client_session(self): """ Get the Client browser session (used for auto-login based on browser session) Returns: csession (ClientSession): This is a django-specific internal representation of the browser session. """ try: self.csessid = self.http_request_uri.split("?", 1)[1] except IndexError: # this may happen for custom webclients not caring for the # browser session. self.csessid = None return None except AttributeError: from evennia.utils import logger self.csessid = None logger.log_trace(str(self)) return None if self.csessid: return _CLIENT_SESSIONS(session_key=self.csessid)
def _msg_err(receiver, stringtuple): """ Helper function for returning an error to the caller. Args: receiver (Object): object to get the error message. stringtuple (tuple): tuple with two strings - one for the _IN_GAME_ERRORS mode (with the traceback) and one with the production string (with a timestamp) to be shown to the user. """ string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})." timestamp = logger.timeformat() tracestring = format_exc() logger.log_trace() if _IN_GAME_ERRORS: receiver.msg(string.format(traceback=tracestring, errmsg=stringtuple[0].strip(), timestamp=timestamp).strip()) else: receiver.msg(string.format(traceback=tracestring.splitlines()[-1], errmsg=stringtuple[1].strip(), timestamp=timestamp).strip())
def _create_account(session, accountname, password, permissions, typeclass=None, email=None): """ Helper function, creates an account of the specified typeclass. """ try: new_account = create.create_account(accountname, email, password, permissions=permissions, typeclass=typeclass) except Exception as e: session.msg("There was an error creating the Account:\n%s\n If this problem persists, contact an admin." % e) logger.log_trace() return False # This needs to be set so the engine knows this account is # logging in for the first time. (so it knows to call the right # hooks during login later) new_account.db.FIRST_LOGIN = True # join the new account to the public channel pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) if not pchannel or not pchannel.connect(new_account): string = "New account '%s' could not connect to public channel!" % new_account.key logger.log_err(string) return new_account
def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session account = request.user sesslogin = csession.get("logged_in", None) if csession.session_key is None: # this is necessary to build the sessid key csession.save() elif account.is_authenticated(): if not sesslogin: csession["logged_in"] = account.id elif sesslogin: # The webclient has previously registered a login to this session account = AccountDB.objects.get(id=sesslogin) try: authenticate(autologin=account ) # calls custom authenticate in web/utils/backend.py login(request, account) except AttributeError: logger.log_trace()
def msg(self, text=None, from_obj=None, session=None, **kwargs): """ Emits something to a session attached to the object. Overloads the default msg() implementation to include gender-aware markers in output. Args: text (str or tuple, optional): The message to send. This is treated internally like any send-command, so its value can be a tuple if sending multiple arguments to the `text` oob command. from_obj (obj, optional): object that is sending. If given, at_msg_send will be called session (Session or list, optional): session or list of sessions to relay to, if any. If set, will force send regardless of MULTISESSION_MODE. Notes: `at_msg_receive` will be called on this Object. All extra kwargs will be passed on to the protocol. """ if text is None: super().msg(from_obj=from_obj, session=session, **kwargs) return try: if text and isinstance(text, tuple): text = (_RE_GENDER_PRONOUN.sub(self._get_pronoun, text[0]), *text[1:]) else: text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text) except TypeError: pass except Exception as e: logger.log_trace(e) super().msg(text, from_obj=from_obj, session=session, **kwargs)
def _shared_login(request): """ Handle the shared login between website and webclient. """ csession = request.session account = request.user # these can have 3 values: # None - previously unused (auto-login) # False - actively logged out (don't auto-login) # <uid> - logged in User/Account id website_uid = csession.get("website_authenticated_uid", None) webclient_uid = csession.get("webclient_authenticated_uid", None) # check if user has authenticated to website if not csession.session_key: # this is necessary to build the sessid key csession.save() if webclient_uid: # The webclient has previously registered a login to this browser_session if not account.is_authenticated() and not website_uid: try: account = AccountDB.objects.get(id=webclient_uid) except AccountDB.DoesNotExist: # this can happen e.g. for guest accounts or deletions csession["website_authenticated_uid"] = False csession["webclient_authenticated_uid"] = False return try: # calls our custom authenticate in web/utils/backends.py account = authenticate(autologin=account) login(request, account) csession["website_authenticated_uid"] = webclient_uid except AttributeError: logger.log_trace()
def data_out(self, session, **kwargs): """ Called by server for having the portal relay messages and data to the correct session protocol. Args: session (Session): Session sending data. Kwargs: kwargs (any): Each key is a command instruction to the protocol on the form key = [[args],{kwargs}]. This will call a method send_<key> on the protocol. If no such method exixts, it sends the data to a method send_default. """ # from evennia.server.profiling.timetrace import timetrace # DEBUG # text = timetrace(text, "portalsessionhandler.data_out") # DEBUG # distribute outgoing data to the correct session methods. if session: for cmdname, (cmdargs, cmdkwargs) in kwargs.iteritems(): funcname = "send_%s" % cmdname.strip().lower() if hasattr(session, funcname): # better to use hassattr here over try..except # - avoids hiding AttributeErrors in the call. try: getattr(session, funcname)(*cmdargs, **cmdkwargs) except Exception: log_trace() else: try: # note that send_default always takes cmdname # as arg too. session.send_default(cmdname, *cmdargs, **cmdkwargs) except Exception: log_trace()
def unpause(self, manual_unpause=True): """ Restart a paused script. This WILL call the `at_start()` hook. Args: manual_unpause (bool, optional): This is False if unpause is called by the server reload/reset mechanism. Returns: result (bool): True if unpause was triggered, False otherwise. Raises: RuntimeError: If trying to automatically resart this script (usually after a reset/reload), but it was manually paused, and so should not the auto-unpaused. """ if not manual_unpause and self.db._manual_pause: # if this script was paused manually (by a direct call of pause), # it cannot be automatically unpaused (e.g. by a @reload) raise RuntimeError # Ensure that the script is fully unpaused, so that future calls # to unpause do not raise a RuntimeError self.db._manual_pause = False if self.db._paused_time: # only unpause if previously paused self.is_active = True try: self.at_start() except Exception: logger.log_trace() self._start_task() return True
def getChild(self, path, request): """ Create and return a proxy resource with the same proxy configuration as this one, except that its path also contains the segment given by path at the end. Args: path (str): Url path. request (Request object): Incoming request. Return: resource (EvenniaReverseProxyResource): A proxy resource. """ request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f)) return EvenniaReverseProxyResource( self.host, self.port, self.path + '/' + urlquote(path, safe=""), self.reactor)