From 673e606940df115840223d72511f67c612eaec81 Mon Sep 17 00:00:00 2001 From: michael Date: Tue, 23 Jun 2026 10:29:36 -0700 Subject: [PATCH] added guards for session.puppet, is_typing module docs, timeout and regex bugs in the client plugin, added client_live_report_typing to Command, and moved class_from_module call to module level --- evennia/commands/command.py | 7 ++ evennia/server/inputfuncs.py | 1 - evennia/server/is_typing.py | 68 ++++++++++++------- .../static/webclient/js/plugins/is_typing.js | 11 +-- 4 files changed, 58 insertions(+), 29 deletions(-) diff --git a/evennia/commands/command.py b/evennia/commands/command.py index ba5c1270f9..da3d70d552 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -165,12 +165,16 @@ class Command(metaclass=CommandMeta): this is usually the same as caller. self.raw_string - the full raw string input, including the command name, any args and no parsing. + self.client_live_report_typing - whether the client should report on the typing + status of a user while using a command. + defaults to false. The following class properties can/should be defined on your child class: key - identifier for command (e.g. "look") aliases - (optional) list of aliases (e.g. ["l", "loo"]) locks - lock string (default is "cmd:all()") + help_category - how to organize this help entry in help system (default is "General") auto_help - defaults to True. Allows for turning off auto-help generation @@ -205,6 +209,9 @@ class Command(metaclass=CommandMeta): is_exit = False # define the command not only by key but by the regex form of its arguments arg_regex = settings.COMMAND_DEFAULT_ARG_REGEX + # whether this command and its aliases should report on the typing status of the + # user. + client_live_report_typing = False # whether self.msg sends to all sessions of a related account/object (default # is to only send to the session sending the command). msg_all_sessions = settings.COMMAND_DEFAULT_MSG_ALL_SESSIONS diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 298cc03b67..ffaf941ddd 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -26,7 +26,6 @@ from codecs import lookup as codecs_lookup from django.conf import settings from evennia.commands.cmdhandler import cmdhandler -from evennia.commands.default.general import CmdSay from evennia.utils.logger import log_err from evennia.utils.utils import to_str diff --git a/evennia/server/is_typing.py b/evennia/server/is_typing.py index 7aa9de1fc4..5723b4299b 100644 --- a/evennia/server/is_typing.py +++ b/evennia/server/is_typing.py @@ -1,6 +1,19 @@ """ This module allows users based on a given condition (defaults to same location) to see whether applicable users are typing or not. Currently, only the webclient is supported. + +Relevant Settings: + WEBCLIENT_TYPING_TIMEOUT - the timeout in seconds between polling intervals. + WEBCLIENT_TYPING_AUDIENCE_GETTER - the path to the method that returns the sessions + that should receive typing updates. Must return + a list of session objects. + +Upon the webclient loading the is_typing plugin, it will request setup from the server +(is_typing_setup) which will return the timeout and what words to watch for. Anytime +the client uses a relevant word, it will notify the server of the relevant session and +its state (typing or not typing). The server then fetches that session's relevant (if +any) other sessions based on the criteria of the audience_getter and passes on the state +update to the fetched audience. """ from django.conf import settings @@ -18,7 +31,7 @@ def is_typing_get_audience_common_location(session, *args, **kwargs): """ if session.puppet is None: - return [] + return [] audience_including_typer = session.puppet.location.contents_get(content_type="character") @@ -27,6 +40,15 @@ def is_typing_get_audience_common_location(session, *args, **kwargs): return audience +# A utility to fetch the method used to get the relevant audience for client live +# reporting commands. The retrieved method should return a list of session objects. +# Sessions without a puppet will be ignored. +audience_getter = class_from_module( + settings.WEBCLIENT_TYPING_AUDIENCE_GETTER + or "evennia.server.is_typing.is_typing_get_audience_common_location" +) + + def is_typing_setup(session, *args, **kwargs): """ This fetches any commands/aliases/nicks that we want to monitor and the @@ -39,7 +61,7 @@ def is_typing_setup(session, *args, **kwargs): options = session.protocol_flags is_typing = options.get("ISTYPING", True) - if not is_typing: + if not is_typing or session.puppet is None: return live_report_commands = [ @@ -67,7 +89,7 @@ def is_typing_setup(session, *args, **kwargs): ) -def is_typing_state(session, *args, **kwargs): +def is_typing_state(user_session, *args, **kwargs): """ Broadcasts a typing state update from the session's puppet to all other characters meeting the configured conditions @@ -78,31 +100,31 @@ def is_typing_state(session, *args, **kwargs): **kwargs: - state (bool): The typing state to broadcast. """ - options = session.protocol_flags + global audience_getter + + options = user_session.protocol_flags is_typing = options.get("ISTYPING", True) - if not is_typing: + if not is_typing or user_session.puppet is None: return - audience_getter = class_from_module( - settings.WEBCLIENT_TYPING_AUDIENCE_GETTER - or "evennia.server.is_typing.is_typing_get_audience_common_location" - ) - state = kwargs.get("state") - audience = audience_getter(session=session, args=args, kwargs=kwargs) + audience = audience_getter(session=user_session, args=args, kwargs=kwargs) - for puppet in audience: + # Filter out clients not interested in updates + relevant_sessions = [ + puppet_session + for puppet in audience + for puppet_session in puppet.sessions.all() + if puppet_session.protocol_flags.get("ISTYPING", True) + ] # Potential timeout adjustment based on audience size - for puppet_session in puppet.sessions.all(): - puppet_session_options = puppet_session.protocol_flags - puppet_session_is_typing = puppet_session_options.get("ISTYPING", True) - - if puppet_session_is_typing: - puppet_session.msg( - is_typing={ - "type": "typing", - "payload": {"name": session.puppet.name, "state": state}, - } - ) + # Update relevant clients + for puppet_session in relevant_sessions: + puppet_session.msg( + is_typing={ + "type": "typing", + "payload": {"name": user_session.puppet.name, "state": state}, + } + ) diff --git a/evennia/web/static/webclient/js/plugins/is_typing.js b/evennia/web/static/webclient/js/plugins/is_typing.js index 8a5821baa1..57d4f0e3f3 100644 --- a/evennia/web/static/webclient/js/plugins/is_typing.js +++ b/evennia/web/static/webclient/js/plugins/is_typing.js @@ -119,7 +119,7 @@ let is_typing = (function (){ // A live report command is being used. if (Evennia.isConnected() && inputfield.length === 1 && - event.key.length === 1 && + // event.key.length === 1 && inputfield.val().match(regex)) { // Enter. Message sent. Reset. if (event.which === 13) { @@ -129,10 +129,9 @@ let is_typing = (function (){ } else if (!state.is_typing) { startedTyping(); - // Expiration is nearing. Update timeout. - } else if (Date.now() + timeout > state.timeout) { + // Expiration is nearing. Update timeout. Default is 5 seconds. + } else if (Date.now() > state.timeout - timeout * .2) { stillTyping(); - } // Not talking anymore but state hasn't been updated yet. } else if (state.is_typing) { @@ -198,7 +197,9 @@ let is_typing = (function (){ timeout = typing_timeout setLiveReportKeywords(live_report_keywords) - regex = new RegExp(`^\W*(${liveReportKeywords.reduce((acc, cur)=> acc + "|" + cur, "").substring(1)})`) + const wordCmds = liveReportKeywords.filter(kw => kw.length > 1).join("|"); + const charCmds = liveReportKeywords.filter(kw => kw.length === 1).join("|"); + regex = new RegExp(`^\\W*((${wordCmds})(\\s|$)|(${charCmds}))`) break; case 'typing':