diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 4c2750efeb..02611530e0 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -25,11 +25,13 @@ from codecs import lookup as codecs_lookup from django.conf import settings -from evennia import ObjectDB +from evennia import DefaultCharacter, TASK_HANDLER from evennia.commands.cmdhandler import cmdhandler from evennia.commands.default.general import CmdSay +from evennia.utils.ansi import strip_mxp from evennia.utils.logger import log_err -from evennia.utils.utils import to_str +from evennia.utils.utils import to_str, delay +from evennia.scripts.tickerhandler import TICKER_HANDLER BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore @@ -44,6 +46,8 @@ _SA = object.__setattr__ _STRIP_INCOMING_MXP = settings.MXP_ENABLED and settings.MXP_OUTGOING_ONLY _STRIP_MXP = None +_IS_TYPING_PARTICIPANTS = {} + def _NA(o): return "N/A" @@ -142,8 +146,10 @@ def echo(session, *args, **kwargs): """ Echo test function """ - if _STRIP_INCOMING_MXP: - txt = strip_mxp(txt) + # txt = kwargs.get("txt") + # + # if _STRIP_INCOMING_MXP and txt: + # txt = strip_mxp(txt) session.data_out(text="Echo returns: %s" % args) @@ -385,8 +391,6 @@ def repeat(session, *args, **kwargs): the above settings. """ - from evennia.scripts.tickerhandler import TICKER_HANDLER - name = kwargs.get("callback", "") interval = max(5, int(kwargs.get("interval", 60))) @@ -655,24 +659,104 @@ def msdp_send(session, *args, **kwargs): session.msg(send=((), out)) +def is_typing_send_update(): + """ + Send relevant updates to participants + + """ + participants = list(_IS_TYPING_PARTICIPANTS.keys()) + + for participant in participants: + if _IS_TYPING_PARTICIPANTS[participant]["session"].puppet is not None: + + payload = [] + # Get potentials + # TODO: exclude the speaking character + potentials = DefaultCharacter.objects.filter_family(db_location=_IS_TYPING_PARTICIPANTS[participant]["session"].puppet.location) + + # if len(potentials) > 0: + for puppet in potentials: + + # We're only interested in sending updates if they're capable of receiving them + if str(puppet.sessid) in participants: + payload.append({ + "name": puppet.name, + "state": _IS_TYPING_PARTICIPANTS[str(puppet.sessid)]['state'], + }) + + _IS_TYPING_PARTICIPANTS[participant]['session'].msg(is_typing={ + 'type': 'typing', + 'payload': payload + }) + delay(5, is_typing_send_update) + else: + del _IS_TYPING_PARTICIPANTS[str(participant)] + + if len(_IS_TYPING_PARTICIPANTS.keys()) > 0: + delay(5, is_typing_send_update) + + def is_typing_get_aliases(session, *args, **kwargs): + """ + Used in setting up clients. Fetch list of possible "talking" triggers + + Args: + session: + *args: + **kwargs: + + Returns: + + """ + # Add the participant to the list. + _IS_TYPING_PARTICIPANTS[str(session.sessid)] = { + "state": False, + "session": session, + } + session.msg(is_typing={'type': 'aliases', 'payload': CmdSay.aliases}) + if len(_IS_TYPING_PARTICIPANTS.keys()) == 1: + delay(5, is_typing_send_update) -def is_typing_state(session, *args, **kwargs): - # audience = ObjectDB.objects.filter(db_typeclass_path="typeclasses.characters.Character", - # db_location=session.puppet.location).exclude(db_key=session.puppet.key) - audience = ObjectDB.objects.filter(db_typeclass_path="typeclasses.characters.Character", - db_location=session.puppet.location) +def is_typing_update_participant(session, *args, **kwargs): + """ + Update a participant session's typing status - for puppet in audience: - for puppet_session in puppet.sessions.all(): - puppet_session.msg(is_typing={'type': 'typing', - 'payload': { - 'name': session.puppet.name, - 'state': args[0] - }}) + Args: + session: + *args: First argument is a boolean indicating their typing state + **kwargs: + + Returns: + + """ + # If the session isn't found then server restarted + if _IS_TYPING_PARTICIPANTS.get(session.sessid) is None: + _IS_TYPING_PARTICIPANTS[str(session.sessid)] = { + "session": session, + "state": args[0], + } + + if len(_IS_TYPING_PARTICIPANTS.keys()) == 1: + delay(5, is_typing_send_update) + else: + _IS_TYPING_PARTICIPANTS[str(session.sessid)]['state'] = args[0] + +def is_typing_remove_participant(session, *args, **kwargs): + """ + Handle logging out/ending a session + + Args: + session: + *args: + **kwargs: + + Returns: + + """ + del _IS_TYPING_PARTICIPANTS[str(session.sessid)] # client specific diff --git a/evennia/web/static/webclient/js/plugins/is_typing.js b/evennia/web/static/webclient/js/plugins/is_typing.js index 0bc857d879..d037546e30 100644 --- a/evennia/web/static/webclient/js/plugins/is_typing.js +++ b/evennia/web/static/webclient/js/plugins/is_typing.js @@ -1,209 +1,224 @@ -let is_typing = (function (){ - let Evennia; - // 10 second timeout - const timeout = 10 * 1000 - const state = { - timeout: null, - callback: null, - is_typing: false, - typing_players: [], - cleanup_callback: null, +let is_typing = (function () { + let Evennia; + const timeout = 2 * 1000; + const state = { + timeout: null, + callback: null, + is_typing: false, + typing_players: [], + cleanup_callback: null, + }; + + const sayCommands = ["say"]; + + const createDialog = function () { + const ele = [ + '
', + '
', + "
", + ].join("\n"); + + $("body").append(ele); + }; + + const playerElement = (name) => + `
${name} is typing...
`; + + const startedTyping = function () { + state.is_typing = true; + state.timeout = Date.now() + timeout; + state.callback = setTimeout(stoppedTyping, timeout); + + sendIsTyping(); + }; + + const stillTyping = function () { + state.timeout = Date.now() + timeout; + clearTimeout(state.callback); + state.callback = setTimeout(stoppedTyping, timeout); + }; + + const stoppedTyping = function () { + state.is_typing = false; + clearTimeout(state.callback); + state.callback = null; + state.timeout = null; + + sendIsTyping(); + }; + + // Make our commands array regex safe + const escapeRegExp = function (text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + }; + + // Get the say command's aliases + const requestSayAliases = function () { + Evennia.msg("is_typing_get_aliases"); + }; + + const setSayAliases = function (aliases) { + aliases.forEach((alias) => sayCommands.push(escapeRegExp(alias))); + }; + + // Update server + const sendIsTyping = function () { + Evennia.msg("is_typing_update_participant", [state.is_typing]); + }; + + const onLoggedIn = function () { + requestSayAliases(); + }; + + // Listen for talk commands + const onKeydown = function (event) { + const regex = new RegExp( + `^\W*(${sayCommands.reduce((acc, cur) => acc + "|" + cur, "").substring(1)})`, + ); + const inputfield = $(".inputfield:focus"); + + // A 'say' command is being used. + if ( + Evennia.isConnected() && + inputfield.length === 1 && + event.key.length === 1 && + inputfield.val().match(regex) + ) { + if (event.which === 13) { + // Enter. Message sent. Reset. + stoppedTyping(); + } else if (!state.is_typing) { + // Speaking just started. Set is_talking and timeout. + startedTyping(); + } else if (Date.now() > state.timeout) { + // Expiration is nearing. Update timeout. + stillTyping(); + } + // Not talking anymore but state hasn't been updated yet. + } else if (state.is_typing) { + stoppedTyping(); + } + }; + + // Reset everything + const onConnectionClose = function () { + state.is_typing = false; + state.timeout = null; + state.typing_players = []; + + if (state.callback) { + clearTimeout(state.callback); } - const sayCommands = ['say'] - - const createDialog = function() { - const ele =[ - '
', - '
', - '
' - ].join('\n') - - $('body').append(ele) + if (state.cleanup_callback) { + clearInterval(state.cleanup_callback); } - const playerElement =(name)=> `
${name} is typing...
` + Evennia.msg("is_typing_remove_participant"); + }; - const startedTyping = function () { - state.is_typing = true; - state.timeout = Date.now() + timeout; - state.callback = setTimeout(stoppedTyping, timeout); + const cleanupTimedOutPlayers = function () { + const now = Date.now(); + const timedOut = []; - sendIsTyping() + state.typing_players.forEach((player, index) => { + if (player.timeout < now) { + timedOut.push(index); + $(`#istyping-${player}`).remove(); + } + }); + + timedOut + .reverse() + .forEach((index) => state.typing_players.splice(index, 1)); + + if (state.typing_players.length === 0) { + clearInterval(state.cleanup_callback); + $("#istyping").hide(); } + }; - const stillTyping = function () { - state.timeout = Date.now() + timeout - clearTimeout(state.callback) - state.callback = setTimeout(stoppedTyping, timeout) + const is_typing = function (args, kwargs) { + if ("type" in kwargs) { + switch (kwargs.type) { + case "aliases": + setSayAliases(kwargs.payload); + break; - sendIsTyping() - } + case "typing": + const updated_players = kwargs.payload; + let player = null; - const stoppedTyping = function () { - state.is_typing = false; - clearTimeout(state.callback); - state.callback = null; - state.timeout = null; + updated_players.forEach((updated) => { + player = state.typing_players.filter( + (player) => player.name === updated.name, + ); - sendIsTyping() - } + // New talker + if (updated.state && player.length === 0) { + state.typing_players.push({ + name: updated.name, + timeout: Date.now() + timeout, + }); + $("#typingplayers").append(playerElement(updated.name)); - // Make our commands array regex safe - const escapeRegExp = function (text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - } - - // Get the say command's aliases - const requestSayAliases = function () { - Evennia.msg('is_typing_get_aliases') - } - - const setSayAliases = function (aliases) { - aliases.forEach(alias=>sayCommands.push(escapeRegExp(alias))) - } - - // Update server - const sendIsTyping = function () { - Evennia.msg('is_typing_state', [state.is_typing]) - } - - const onLoggedIn = function () { - requestSayAliases(); - } - - // Listen for talk commands - const onKeydown = function (event) { - const regex = new RegExp(`^\W*(${sayCommands.reduce((acc, cur)=> acc + "|" + cur, "").substring(1)})`) - const inputfield = $(".inputfield:focus"); - - // A 'say' command is being used. - if (Evennia.isConnected() && - inputfield.length === 1 && - event.key.length === 1 && - inputfield.val().match(regex)) { - // Enter. Message sent. Reset. - if (event.which === 13) { - stoppedTyping() - - // Speaking just started. Set is_talking and timeout. - } else if (!state.is_typing) { - startedTyping(); - - // Expiration is nearing. Update timeout. - } else if (Date.now() + 5 * 1000 > state.timeout) { - stillTyping(); + // Existing talker is still going + } else if (updated.state && player.length > 0) { + player[0].timeout = Date.now() + timeout; + // They're done talking + } else { + state.typing_players = state.typing_players.filter( + (player) => player.name !== updated.name, + ); + $(`#istyping-${updated.name}`).remove(); } - // Not talking anymore but state hasn't been updated yet. - } else if (state.is_typing) { - stoppedTyping(); - } + }); + + if (state.typing_players.length > 0 && !state.cleanup_callback) { + state.cleanup_callback = setInterval(cleanupTimedOutPlayers, 100); + $("#istyping").show(); + } else if ( + state.typing_players.length === 0 && + state.cleanup_callback + ) { + clearInterval(state.cleanup_callback); + state.cleanup_callback = null; + $("#istyping").hide(); + } + break; + + default: + console.log("Default case"); + console.log(args); + console.log(kwargs); + } } + }; - // Reset everything - const onConnectionClose = function () { - state.is_typing = false; - state.timeout = null; - state.typing_players = [] + const getState = () => state; - if (state.callback) { - clearTimeout(state.callback) - } + // + // Mandatory plugin init function + const init = function () { + let options = window.options; + options["is_typing"] = true; + Evennia = window.Evennia; - if (state.cleanup_callback) { - clearInterval(state.cleanup_callback) - } - } + Evennia.emitter.on("is_typing", is_typing); - const cleanupTimedOutPlayers = function () { - const now = Date.now(); - const timedOut = [] + createDialog(); - state.typing_players.forEach((player, index)=>{ - if (player.timeout < now) { - timedOut.push(index) - $(`#istyping-${player}`).remove() - } - }) + console.log("Is Typing plugin initialized"); + }; - timedOut.reverse().forEach(index=>state.typing_players.splice(index, 1)) + return { + init, + onLoggedIn, + onKeydown, + onConnectionClose, + getState, + }; +})(); - if (state.typing_players.length === 0) { - clearInterval(state.cleanup_callback) - $('#istyping').hide(); - } - } - - const is_typing = function (args, kwargs) { - if ('type' in kwargs) { - switch (kwargs.type) { - case 'aliases': - setSayAliases(kwargs.payload) - break; - - case 'typing': - const player = state.typing_players.filter(player=>player.name === kwargs.payload.name) - - // New talker - if (kwargs.payload.state && - player.length === 0) { - state.typing_players.push({name: kwargs.payload.name, timeout: Date.now() + timeout}) - $('#typingplayers').append(playerElement(kwargs.payload.name)) - - // Existing talker is still going - } else if (kwargs.payload.state && - player.length > 0) { - player[0].timeout = Date.now() + timeout; - - // They're done talking - } else { - state.typing_players = state.typing_players.filter(player=>player.name!== kwargs.payload.name) - $(`#istyping-${kwargs.payload.name}`).remove() - } - - if (state.typing_players.length > 0 && !state.cleanup_callback) { - state.cleanup_callback = setInterval(cleanupTimedOutPlayers, 100); - $('#istyping').show(); - - } else if (state.typing_players.length === 0 && state.cleanup_callback) { - clearInterval(state.cleanup_callback) - state.cleanup_callback = null; - $('#istyping').hide(); - } - break; - - default: - console.log("Default case") - console.log(args) - console.log(kwargs) - } - } - } - - const getState = () => state - - // - // Mandatory plugin init function - const init = function () { - let options = window.options; - options["is_typing"] = true; - Evennia = window.Evennia; - - Evennia.emitter.on("is_typing", is_typing); - - createDialog(); - - console.log('Is Typing plugin initialized'); - } - - return { - init, - onLoggedIn, - onKeydown, - onConnectionClose, - getState - } -})() - -window.plugin_handler.add("is_typing", is_typing) \ No newline at end of file +window.plugin_handler.add("is_typing", is_typing);