Merge branch 'master' into develop
This commit is contained in:
commit
4376058ee9
11 changed files with 201 additions and 111 deletions
|
|
@ -457,7 +457,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
callertype="account", session=session, **kwargs)
|
callertype="account", session=session, **kwargs)
|
||||||
|
|
||||||
def search(self, searchdata, return_puppet=False, search_object=False,
|
def search(self, searchdata, return_puppet=False, search_object=False,
|
||||||
typeclass=None, nofound_string=None, multimatch_string=None, **kwargs):
|
typeclass=None, nofound_string=None, multimatch_string=None, use_nicks=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
This is similar to `DefaultObject.search` but defaults to searching
|
This is similar to `DefaultObject.search` but defaults to searching
|
||||||
for Accounts only.
|
for Accounts only.
|
||||||
|
|
@ -481,6 +481,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
multimatch_string (str, optional): A one-time error
|
multimatch_string (str, optional): A one-time error
|
||||||
message to echo if `searchdata` leads to multiple matches.
|
message to echo if `searchdata` leads to multiple matches.
|
||||||
If not given, will fall back to the default handler.
|
If not given, will fall back to the default handler.
|
||||||
|
use_nicks (bool, optional): Use account-level nick replacement.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
match (Account, Object or None): A single Account or Object match.
|
match (Account, Object or None): A single Account or Object match.
|
||||||
|
|
@ -496,8 +497,10 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
if searchdata.lower() in ("me", "*me", "self", "*self",):
|
if searchdata.lower() in ("me", "*me", "self", "*self",):
|
||||||
return self
|
return self
|
||||||
if search_object:
|
if search_object:
|
||||||
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass)
|
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass, use_nicks=use_nicks)
|
||||||
else:
|
else:
|
||||||
|
searchdata = self.nicks.nickreplace(searchdata, categories=("account", ), include_account=False)
|
||||||
|
|
||||||
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
|
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
|
||||||
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
|
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata,
|
||||||
nofound_string=nofound_string,
|
nofound_string=nofound_string,
|
||||||
|
|
|
||||||
|
|
@ -544,7 +544,8 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||||
Usage:
|
Usage:
|
||||||
@wall <message>
|
@wall <message>
|
||||||
|
|
||||||
Announces a message to all connected accounts.
|
Announces a message to all connected sessions
|
||||||
|
including all currently unlogged in.
|
||||||
"""
|
"""
|
||||||
key = "@wall"
|
key = "@wall"
|
||||||
locks = "cmd:perm(wall) or perm(Admin)"
|
locks = "cmd:perm(wall) or perm(Admin)"
|
||||||
|
|
@ -556,5 +557,5 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||||
self.caller.msg("Usage: @wall <message>")
|
self.caller.msg("Usage: @wall <message>")
|
||||||
return
|
return
|
||||||
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
|
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
|
||||||
self.msg("Announcing to all connected accounts ...")
|
self.msg("Announcing to all connected sessions ...")
|
||||||
SESSIONS.announce_all(message)
|
SESSIONS.announce_all(message)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
General Character commands usually available to all characters
|
General Character commands usually available to all characters
|
||||||
"""
|
"""
|
||||||
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import utils, evtable
|
from evennia.utils import utils, evtable
|
||||||
from evennia.typeclasses.attributes import NickTemplateInvalid
|
from evennia.typeclasses.attributes import NickTemplateInvalid
|
||||||
|
|
@ -75,37 +76,41 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdNick(COMMAND_DEFAULT_CLASS):
|
class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
define a personal alias/nick
|
define a personal alias/nick by defining a string to
|
||||||
|
match and replace it with another on the fly
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
nick[/switches] <string> [= [replacement_string]]
|
nick[/switches] <string> [= [replacement_string]]
|
||||||
nick[/switches] <template> = <replacement_template>
|
nick[/switches] <template> = <replacement_template>
|
||||||
nick/delete <string> or number
|
nick/delete <string> or number
|
||||||
nick/test <test string>
|
nicks
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
inputline - replace on the inputline (default)
|
inputline - replace on the inputline (default)
|
||||||
object - replace on object-lookup
|
object - replace on object-lookup
|
||||||
account - replace on account-lookup
|
account - replace on account-lookup
|
||||||
delete - remove nick by name or by index given by /list
|
|
||||||
clearall - clear all nicks
|
|
||||||
list - show all defined aliases (also "nicks" works)
|
list - show all defined aliases (also "nicks" works)
|
||||||
test - test input to see what it matches with
|
delete - remove nick by index in /list
|
||||||
|
clearall - clear all nicks
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
nick hi = say Hello, I'm Sarah!
|
nick hi = say Hello, I'm Sarah!
|
||||||
nick/object tom = the tall man
|
nick/object tom = the tall man
|
||||||
nick build $1 $2 = @create/drop $1;$2 - (template)
|
nick build $1 $2 = @create/drop $1;$2
|
||||||
nick tell $1 $2=@page $1=$2 - (template)
|
nick tell $1 $2=@page $1=$2
|
||||||
|
nick tm?$1=@page tallman=$1
|
||||||
|
nick tm\=$1=@page tallman=$1
|
||||||
|
|
||||||
A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
|
A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
|
||||||
Put the last $-marker without an ending space to catch all remaining text. You
|
Put the last $-marker without an ending space to catch all remaining text. You
|
||||||
can also use unix-glob matching:
|
can also use unix-glob matching for the left-hand side <string>:
|
||||||
|
|
||||||
* - matches everything
|
* - matches everything
|
||||||
? - matches a single character
|
? - matches 0 or 1 single characters
|
||||||
[seq] - matches all chars in sequence
|
[abcd] - matches these chars in any order
|
||||||
[!seq] - matches everything not in sequence
|
[!abcd] - matches everything not among these chars
|
||||||
|
\= - escape literal '=' you want in your <string>
|
||||||
|
|
||||||
Note that no objects are actually renamed or changed by this command - your nicks
|
Note that no objects are actually renamed or changed by this command - your nicks
|
||||||
are only available to you. If you want to permanently add keywords to an object
|
are only available to you. If you want to permanently add keywords to an object
|
||||||
|
|
@ -116,14 +121,36 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
aliases = ["nickname", "nicks"]
|
aliases = ["nickname", "nicks"]
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Support escaping of = with \=
|
||||||
|
"""
|
||||||
|
super(CmdNick, self).parse()
|
||||||
|
args = (self.lhs or "") + (" = %s" % self.rhs if self.rhs else "")
|
||||||
|
parts = re.split(r"(?<!\\)=", args, 1)
|
||||||
|
self.rhs = None
|
||||||
|
if len(parts) < 2:
|
||||||
|
self.lhs = parts[0].strip()
|
||||||
|
else:
|
||||||
|
self.lhs, self.rhs = [part.strip() for part in parts]
|
||||||
|
self.lhs = self.lhs.replace("\=", "=")
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Create the nickname"""
|
"""Create the nickname"""
|
||||||
|
|
||||||
caller = self.caller
|
def _cy(string):
|
||||||
switches = self.switches
|
"add color to the special markers"
|
||||||
nicktypes = [switch for switch in switches if switch in ("object", "account", "inputline")] or ["inputline"]
|
return re.sub(r"(\$[0-9]+|\*|\?|\[.+?\])", r"|Y\1|n", string)
|
||||||
|
|
||||||
nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or [])
|
caller = self.caller
|
||||||
|
account = self.caller.account or caller
|
||||||
|
switches = self.switches
|
||||||
|
nicktypes = [switch for switch in switches if switch in (
|
||||||
|
"object", "account", "inputline")] or ["inputline"]
|
||||||
|
|
||||||
|
nicklist = (utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or []) +
|
||||||
|
utils.make_iter(caller.nicks.get(category="object", return_obj=True) or []) +
|
||||||
|
utils.make_iter(account.nicks.get(category="account", return_obj=True) or []))
|
||||||
|
|
||||||
if 'list' in switches or self.cmdstring in ("nicks", "@nicks"):
|
if 'list' in switches or self.cmdstring in ("nicks", "@nicks"):
|
||||||
|
|
||||||
|
|
@ -133,24 +160,51 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
table = evtable.EvTable("#", "Type", "Nick match", "Replacement")
|
table = evtable.EvTable("#", "Type", "Nick match", "Replacement")
|
||||||
for inum, nickobj in enumerate(nicklist):
|
for inum, nickobj in enumerate(nicklist):
|
||||||
_, _, nickvalue, replacement = nickobj.value
|
_, _, nickvalue, replacement = nickobj.value
|
||||||
table.add_row(str(inum + 1), nickobj.db_category, nickvalue, replacement)
|
table.add_row(str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement))
|
||||||
string = "|wDefined Nicks:|n\n%s" % table
|
string = "|wDefined Nicks:|n\n%s" % table
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'clearall' in switches:
|
if 'clearall' in switches:
|
||||||
caller.nicks.clear()
|
caller.nicks.clear()
|
||||||
|
caller.account.nicks.clear()
|
||||||
caller.msg("Cleared all nicks.")
|
caller.msg("Cleared all nicks.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if 'delete' in switches or 'del' in switches:
|
||||||
|
if not self.args or not self.lhs:
|
||||||
|
caller.msg("usage nick/delete #num ('nicks' for list)")
|
||||||
|
return
|
||||||
|
# see if a number was given
|
||||||
|
arg = self.args.lstrip("#")
|
||||||
|
if arg.isdigit():
|
||||||
|
# we are given a index in nicklist
|
||||||
|
delindex = int(arg)
|
||||||
|
if 0 < delindex <= len(nicklist):
|
||||||
|
oldnick = nicklist[delindex - 1]
|
||||||
|
_, _, old_nickstring, old_replstring = oldnick.value
|
||||||
|
else:
|
||||||
|
caller.msg("Not a valid nick index. See 'nicks' for a list.")
|
||||||
|
return
|
||||||
|
nicktype = oldnick.category
|
||||||
|
nicktypestr = "%s-nick" % nicktype.capitalize()
|
||||||
|
|
||||||
|
if nicktype == "account":
|
||||||
|
account.nicks.remove(old_nickstring, category=nicktype)
|
||||||
|
else:
|
||||||
|
caller.nicks.remove(old_nickstring, category=nicktype)
|
||||||
|
caller.msg("%s removed: '|w%s|n' -> |w%s|n." % (
|
||||||
|
nicktypestr, old_nickstring, old_replstring))
|
||||||
|
return
|
||||||
|
|
||||||
if not self.args or not self.lhs:
|
if not self.args or not self.lhs:
|
||||||
caller.msg("Usage: nick[/switches] nickname = [realname]")
|
caller.msg("Usage: nick[/switches] nickname = [realname]")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# setting new nicks
|
||||||
|
|
||||||
nickstring = self.lhs
|
nickstring = self.lhs
|
||||||
replstring = self.rhs
|
replstring = self.rhs
|
||||||
old_nickstring = None
|
|
||||||
old_replstring = None
|
|
||||||
|
|
||||||
if replstring == nickstring:
|
if replstring == nickstring:
|
||||||
caller.msg("No point in setting nick same as the string to replace...")
|
caller.msg("No point in setting nick same as the string to replace...")
|
||||||
|
|
@ -160,47 +214,40 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = ""
|
string = ""
|
||||||
for nicktype in nicktypes:
|
for nicktype in nicktypes:
|
||||||
oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True)
|
if nicktype == "account":
|
||||||
|
obj = account
|
||||||
|
else:
|
||||||
|
obj = caller
|
||||||
|
|
||||||
|
nicktypestr = "%s-nick" % nicktype.capitalize()
|
||||||
|
old_nickstring = None
|
||||||
|
old_replstring = None
|
||||||
|
|
||||||
|
oldnick = obj.nicks.get(key=nickstring, category=nicktype, return_obj=True)
|
||||||
if oldnick:
|
if oldnick:
|
||||||
_, _, old_nickstring, old_replstring = oldnick.value
|
_, _, old_nickstring, old_replstring = oldnick.value
|
||||||
else:
|
if replstring:
|
||||||
# no old nick, see if a number was given
|
|
||||||
arg = self.args.lstrip("#")
|
|
||||||
if arg.isdigit():
|
|
||||||
# we are given a index in nicklist
|
|
||||||
delindex = int(arg)
|
|
||||||
if 0 < delindex <= len(nicklist):
|
|
||||||
oldnick = nicklist[delindex - 1]
|
|
||||||
_, _, old_nickstring, old_replstring = oldnick.value
|
|
||||||
else:
|
|
||||||
errstring += "Not a valid nick index."
|
|
||||||
else:
|
|
||||||
errstring += "Nick not found."
|
|
||||||
if "delete" in switches or "del" in switches:
|
|
||||||
# clear the nick
|
|
||||||
if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype):
|
|
||||||
caller.nicks.remove(old_nickstring, category=nicktype)
|
|
||||||
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring)
|
|
||||||
else:
|
|
||||||
errstring += "\nNick '|w%s|n' was not deleted." % old_nickstring
|
|
||||||
elif replstring:
|
|
||||||
# creating new nick
|
# creating new nick
|
||||||
errstring = ""
|
errstring = ""
|
||||||
if oldnick:
|
if oldnick:
|
||||||
string += "\nNick '|w%s|n' updated to map to '|w%s|n'." % (old_nickstring, replstring)
|
if replstring == old_replstring:
|
||||||
|
string += "\nIdentical %s already set." % nicktypestr.lower()
|
||||||
|
else:
|
||||||
|
string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % (
|
||||||
|
nicktypestr, old_nickstring, replstring)
|
||||||
else:
|
else:
|
||||||
string += "\nNick '|w%s|n' mapped to '|w%s|n'." % (nickstring, replstring)
|
string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (nicktypestr, nickstring, replstring)
|
||||||
try:
|
try:
|
||||||
caller.nicks.add(nickstring, replstring, category=nicktype)
|
obj.nicks.add(nickstring, replstring, category=nicktype)
|
||||||
except NickTemplateInvalid:
|
except NickTemplateInvalid:
|
||||||
caller.msg("You must use the same $-markers both in the nick and in the replacement.")
|
caller.msg("You must use the same $-markers both in the nick and in the replacement.")
|
||||||
return
|
return
|
||||||
elif old_nickstring and old_replstring:
|
elif old_nickstring and old_replstring:
|
||||||
# just looking at the nick
|
# just looking at the nick
|
||||||
string += "\nNick '|w%s|n' maps to '|w%s|n'." % (old_nickstring, old_replstring)
|
string += "\n%s '|w%s|n' maps to '|w%s|n'." % (nicktypestr, old_nickstring, old_replstring)
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = errstring if errstring else string
|
string = errstring if errstring else string
|
||||||
caller.msg(string)
|
caller.msg(_cy(string))
|
||||||
|
|
||||||
|
|
||||||
class CmdInventory(COMMAND_DEFAULT_CLASS):
|
class CmdInventory(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
|
||||||
|
|
@ -126,11 +126,12 @@ class TestGeneral(CommandTest):
|
||||||
self.call(general.CmdPose(), "looks around", "Char looks around")
|
self.call(general.CmdPose(), "looks around", "Char looks around")
|
||||||
|
|
||||||
def test_nick(self):
|
def test_nick(self):
|
||||||
self.call(general.CmdNick(), "testalias = testaliasedstring1", "Nick 'testalias' mapped to 'testaliasedstring1'.")
|
self.call(general.CmdNick(), "testalias = testaliasedstring1", "Inputlinenick 'testalias' mapped to 'testaliasedstring1'.")
|
||||||
self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Nick 'testalias' mapped to 'testaliasedstring2'.")
|
self.call(general.CmdNick(), "/account testalias = testaliasedstring2", "Accountnick 'testalias' mapped to 'testaliasedstring2'.")
|
||||||
self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Nick 'testalias' mapped to 'testaliasedstring3'.")
|
self.call(general.CmdNick(), "/object testalias = testaliasedstring3", "Objectnick 'testalias' mapped to 'testaliasedstring3'.")
|
||||||
self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias"))
|
self.assertEqual(u"testaliasedstring1", self.char1.nicks.get("testalias"))
|
||||||
self.assertEqual(u"testaliasedstring2", self.char1.nicks.get("testalias", category="account"))
|
self.assertEqual(None, self.char1.nicks.get("testalias", category="account"))
|
||||||
|
self.assertEqual(u"testaliasedstring2", self.char1.account.nicks.get("testalias", category="account"))
|
||||||
self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
|
self.assertEqual(u"testaliasedstring3", self.char1.nicks.get("testalias", category="object"))
|
||||||
|
|
||||||
def test_get_and_drop(self):
|
def test_get_and_drop(self):
|
||||||
|
|
@ -184,7 +185,7 @@ class TestAdmin(CommandTest):
|
||||||
self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).")
|
self.call(admin.CmdPerm(), "Char2 = Builder", "Permission 'Builder' given to Char2 (the Object/Character).")
|
||||||
|
|
||||||
def test_wall(self):
|
def test_wall(self):
|
||||||
self.call(admin.CmdWall(), "Test", "Announcing to all connected accounts ...")
|
self.call(admin.CmdWall(), "Test", "Announcing to all connected sessions ...")
|
||||||
|
|
||||||
def test_ban(self):
|
def test_ban(self):
|
||||||
self.call(admin.CmdBan(), "Char", "NameBan char was added.")
|
self.call(admin.CmdBan(), "Char", "NameBan char was added.")
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,15 @@ from evennia.utils.logger import log_trace
|
||||||
# module import
|
# module import
|
||||||
_MOD_IMPORT = None
|
_MOD_IMPORT = None
|
||||||
|
|
||||||
# throttles
|
# global throttles
|
||||||
_MAX_CONNECTION_RATE = float(settings.MAX_CONNECTION_RATE)
|
_MAX_CONNECTION_RATE = float(settings.MAX_CONNECTION_RATE)
|
||||||
|
# per-session throttles
|
||||||
_MAX_COMMAND_RATE = float(settings.MAX_COMMAND_RATE)
|
_MAX_COMMAND_RATE = float(settings.MAX_COMMAND_RATE)
|
||||||
_MAX_CHAR_LIMIT = int(settings.MAX_CHAR_LIMIT)
|
_MAX_CHAR_LIMIT = int(settings.MAX_CHAR_LIMIT)
|
||||||
|
|
||||||
_MIN_TIME_BETWEEN_CONNECTS = 1.0 / float(settings.MAX_CONNECTION_RATE)
|
_MIN_TIME_BETWEEN_CONNECTS = 1.0 / float(_MAX_CONNECTION_RATE)
|
||||||
|
_MIN_TIME_BETWEEN_COMMANDS = 1.0 / float(_MAX_COMMAND_RATE)
|
||||||
|
|
||||||
_ERROR_COMMAND_OVERFLOW = settings.COMMAND_RATE_WARNING
|
_ERROR_COMMAND_OVERFLOW = settings.COMMAND_RATE_WARNING
|
||||||
_ERROR_MAX_CHAR = settings.MAX_CHAR_LIMIT_WARNING
|
_ERROR_MAX_CHAR = settings.MAX_CHAR_LIMIT_WARNING
|
||||||
|
|
||||||
|
|
@ -58,9 +61,6 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
self.connection_last = self.uptime
|
self.connection_last = self.uptime
|
||||||
self.connection_task = None
|
self.connection_task = None
|
||||||
self.command_counter = 0
|
|
||||||
self.command_counter_reset = self.uptime
|
|
||||||
self.command_overflow = False
|
|
||||||
|
|
||||||
def at_server_connection(self):
|
def at_server_connection(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -354,8 +354,6 @@ class PortalSessionHandler(SessionHandler):
|
||||||
Data is serialized before passed on.
|
Data is serialized before passed on.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# from evennia.server.profiling.timetrace import timetrace # DEBUG
|
|
||||||
# text = timetrace(text, "portalsessionhandler.data_in") # DEBUG
|
|
||||||
try:
|
try:
|
||||||
text = kwargs['text']
|
text = kwargs['text']
|
||||||
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
|
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
|
||||||
|
|
@ -367,30 +365,38 @@ class PortalSessionHandler(SessionHandler):
|
||||||
pass
|
pass
|
||||||
if session:
|
if session:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if self.command_counter > _MAX_COMMAND_RATE > 0:
|
|
||||||
# data throttle (anti DoS measure)
|
try:
|
||||||
delta_time = now - self.command_counter_reset
|
command_counter_reset = session.command_counter_reset
|
||||||
self.command_counter = 0
|
except AttributeError:
|
||||||
self.command_counter_reset = now
|
command_counter_reset = session.command_counter_reset = now
|
||||||
self.command_overflow = delta_time < 1.0
|
session.command_counter = 0
|
||||||
if self.command_overflow:
|
|
||||||
reactor.callLater(1.0, self.data_in, None)
|
# global command-rate limit
|
||||||
if self.command_overflow:
|
if max(0, now - command_counter_reset) > 1.0:
|
||||||
|
# more than a second since resetting the counter. Refresh.
|
||||||
|
session.command_counter_reset = now
|
||||||
|
session.command_counter = 0
|
||||||
|
|
||||||
|
session.command_counter += 1
|
||||||
|
|
||||||
|
if session.command_counter * _MIN_TIME_BETWEEN_COMMANDS > 1.0:
|
||||||
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}])
|
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self.portal.amp_protocol:
|
||||||
|
# this can happen if someone connects before AMP connection
|
||||||
|
# was established (usually on first start)
|
||||||
|
reactor.callLater(1.0, self.data_in, session, **kwargs)
|
||||||
|
return
|
||||||
|
|
||||||
# scrub data
|
# scrub data
|
||||||
kwargs = self.clean_senddata(session, kwargs)
|
kwargs = self.clean_senddata(session, kwargs)
|
||||||
|
|
||||||
# relay data to Server
|
# relay data to Server
|
||||||
self.command_counter += 1
|
|
||||||
session.cmd_last = now
|
session.cmd_last = now
|
||||||
self.portal.amp_protocol.send_MsgPortal2Server(session,
|
self.portal.amp_protocol.send_MsgPortal2Server(session,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
else:
|
|
||||||
# called by the callLater callback
|
|
||||||
if self.command_overflow:
|
|
||||||
self.command_overflow = False
|
|
||||||
reactor.callLater(1.0, self.data_in, None)
|
|
||||||
|
|
||||||
def data_out(self, session, **kwargs):
|
def data_out(self, session, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -227,26 +227,45 @@ class TelnetOOB(object):
|
||||||
GMCP messages will be outgoing on the following
|
GMCP messages will be outgoing on the following
|
||||||
form (the non-JSON cmdname at the start is what
|
form (the non-JSON cmdname at the start is what
|
||||||
IRE games use, supposedly, and what clients appear
|
IRE games use, supposedly, and what clients appear
|
||||||
to have adopted):
|
to have adopted). A cmdname without Package will end
|
||||||
|
up in the Core package, while Core package names will
|
||||||
|
be stripped on the Evennia side.
|
||||||
|
|
||||||
[cmdname, [], {}] -> cmdname
|
[cmd.name, [], {}] -> Cmd.Name
|
||||||
[cmdname, [arg], {}] -> cmdname arg
|
[cmd.name, [arg], {}] -> Cmd.Name arg
|
||||||
[cmdname, [args],{}] -> cmdname [args]
|
[cmd.name, [args],{}] -> Cmd.Name [args]
|
||||||
[cmdname, [], {kwargs}] -> cmdname {kwargs}
|
[cmd.name, [], {kwargs}] -> Cmd.Name {kwargs}
|
||||||
[cmdname, [args, {kwargs}] -> cmdname [[args],{kwargs}]
|
[cmdname, [args, {kwargs}] -> Core.Cmdname [[args],{kwargs}]
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
There are also a few default mappings between evennia outputcmds and
|
||||||
|
GMCP:
|
||||||
|
client_options -> Core.Supports.Get
|
||||||
|
get_inputfuncs -> Core.Commands.Get
|
||||||
|
get_value -> Char.Value.Get
|
||||||
|
repeat -> Char.Repeat.Update
|
||||||
|
monitor -> Char.Monitor.Update
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if cmdname in EVENNIA_TO_GMCP:
|
||||||
|
gmcp_cmdname = EVENNIA_TO_GMCP[cmdname]
|
||||||
|
elif "_" in cmdname:
|
||||||
|
gmcp_cmdname = ".".join(word.capitalize() for word in cmdname.split("_"))
|
||||||
|
else:
|
||||||
|
gmcp_cmdname = "Core.%s" % cmdname.capitalize()
|
||||||
|
|
||||||
if not (args or kwargs):
|
if not (args or kwargs):
|
||||||
gmcp_string = cmdname
|
gmcp_string = gmcp_cmdname
|
||||||
elif args:
|
elif args:
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
args = args[0]
|
args = args[0]
|
||||||
if kwargs:
|
if kwargs:
|
||||||
gmcp_string = "%s %s" % (cmdname, json.dumps([args, kwargs]))
|
gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps([args, kwargs]))
|
||||||
else:
|
else:
|
||||||
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
|
gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps(args))
|
||||||
else: # only kwargs
|
else: # only kwargs
|
||||||
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
|
gmcp_string = "%s %s" % (gmcp_cmdname, json.dumps(kwargs))
|
||||||
|
|
||||||
# print("gmcp string", gmcp_string) # DEBUG
|
# print("gmcp string", gmcp_string) # DEBUG
|
||||||
return gmcp_string
|
return gmcp_string
|
||||||
|
|
@ -398,14 +417,9 @@ class TelnetOOB(object):
|
||||||
kwargs.pop("options", None)
|
kwargs.pop("options", None)
|
||||||
|
|
||||||
if self.MSDP:
|
if self.MSDP:
|
||||||
msdp_cmdname = cmdname
|
encoded_oob = self.encode_msdp(cmdname, *args, **kwargs)
|
||||||
encoded_oob = self.encode_msdp(msdp_cmdname, *args, **kwargs)
|
|
||||||
self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE)
|
self.protocol._write(IAC + SB + MSDP + encoded_oob + IAC + SE)
|
||||||
|
|
||||||
if self.GMCP:
|
if self.GMCP:
|
||||||
if cmdname in EVENNIA_TO_GMCP:
|
encoded_oob = self.encode_gmcp(cmdname, *args, **kwargs)
|
||||||
gmcp_cmdname = EVENNIA_TO_GMCP[cmdname]
|
|
||||||
else:
|
|
||||||
gmcp_cmdname = "Custom.Cmd"
|
|
||||||
encoded_oob = self.encode_gmcp(gmcp_cmdname, *args, **kwargs)
|
|
||||||
self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE)
|
self.protocol._write(IAC + SB + GMCP + encoded_oob + IAC + SE)
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,10 @@ def _server_maintenance():
|
||||||
if _MAINTENANCE_COUNT % 3700 == 0:
|
if _MAINTENANCE_COUNT % 3700 == 0:
|
||||||
# validate channels off-sync with scripts
|
# validate channels off-sync with scripts
|
||||||
evennia.CHANNEL_HANDLER.update()
|
evennia.CHANNEL_HANDLER.update()
|
||||||
|
if _MAINTENANCE_COUNT % (3600 * 7) == 0:
|
||||||
|
# drop database connection every 7 hrs to avoid default timeouts on MySQL
|
||||||
|
# (see https://github.com/evennia/evennia/issues/1376)
|
||||||
|
connection.close()
|
||||||
|
|
||||||
# handle idle timeouts
|
# handle idle timeouts
|
||||||
if _IDLE_TIMEOUT > 0:
|
if _IDLE_TIMEOUT > 0:
|
||||||
|
|
@ -139,11 +143,6 @@ def _server_maintenance():
|
||||||
session.account.access(session.account, "noidletimeout", default=False):
|
session.account.access(session.account, "noidletimeout", default=False):
|
||||||
SESSIONS.disconnect(session, reason=reason)
|
SESSIONS.disconnect(session, reason=reason)
|
||||||
|
|
||||||
# Commenting this out, it is probably not needed
|
|
||||||
# with CONN_MAX_AGE set. Keeping it as a reminder
|
|
||||||
# if database-gone-away errors appears again /Griatch
|
|
||||||
# if _MAINTENANCE_COUNT % 18000 == 0:
|
|
||||||
# connection.close()
|
|
||||||
maintenance_task = LoopingCall(_server_maintenance)
|
maintenance_task = LoopingCall(_server_maintenance)
|
||||||
maintenance_task.start(60, now=True) # call every minute
|
maintenance_task.start(60, now=True) # call every minute
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ class ServerSession(Session):
|
||||||
if not _ObjectDB:
|
if not _ObjectDB:
|
||||||
from evennia.objects.models import ObjectDB as _ObjectDB
|
from evennia.objects.models import ObjectDB as _ObjectDB
|
||||||
|
|
||||||
|
super(ServerSession, self).at_sync()
|
||||||
if not self.logged_in:
|
if not self.logged_in:
|
||||||
# assign the unloggedin-command set.
|
# assign the unloggedin-command set.
|
||||||
self.cmdset_storage = settings.CMDSET_UNLOGGEDIN
|
self.cmdset_storage = settings.CMDSET_UNLOGGEDIN
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,8 @@ class Session(object):
|
||||||
on uid etc).
|
on uid etc).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.protocol_flags.update(self.account.attributs.get("_saved_protocol_flags"), {})
|
if self.account:
|
||||||
|
self.protocol_flags.update(self.account.attributes.get("_saved_protocol_flags", {}))
|
||||||
|
|
||||||
# access hooks
|
# access hooks
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -614,10 +614,14 @@ class ServerSessionHandler(SessionHandler):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
uid = curr_session.uid
|
uid = curr_session.uid
|
||||||
|
# we can't compare sessions directly since this will compare addresses and
|
||||||
|
# mean connecting from the same host would not catch duplicates
|
||||||
|
sid = id(curr_session)
|
||||||
doublet_sessions = [sess for sess in self.values()
|
doublet_sessions = [sess for sess in self.values()
|
||||||
if sess.logged_in and
|
if sess.logged_in and
|
||||||
sess.uid == uid and
|
sess.uid == uid and
|
||||||
sess != curr_session]
|
id(sess) != sid]
|
||||||
|
|
||||||
for session in doublet_sessions:
|
for session in doublet_sessions:
|
||||||
self.disconnect(session, reason)
|
self.disconnect(session, reason)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,8 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
|
|
||||||
|
|
||||||
_LOG_FILE_HANDLES = {} # holds open log handles
|
_LOG_FILE_HANDLES = {} # holds open log handles
|
||||||
|
_LOG_FILE_HANDLE_COUNTS = {}
|
||||||
|
_LOG_FILE_HANDLE_RESET = 500
|
||||||
|
|
||||||
|
|
||||||
def _open_log_file(filename):
|
def _open_log_file(filename):
|
||||||
|
|
@ -318,10 +320,15 @@ def _open_log_file(filename):
|
||||||
Helper to open the log file (always in the log dir) and cache its
|
Helper to open the log file (always in the log dir) and cache its
|
||||||
handle. Will create a new file in the log dir if one didn't
|
handle. Will create a new file in the log dir if one didn't
|
||||||
exist.
|
exist.
|
||||||
|
|
||||||
|
To avoid keeping the filehandle open indefinitely we reset it every
|
||||||
|
_LOG_FILE_HANDLE_RESET accesses. This may help resolve issues for very
|
||||||
|
long uptimes and heavy log use.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# we delay import of settings to keep logger module as free
|
# we delay import of settings to keep logger module as free
|
||||||
# from django as possible.
|
# from django as possible.
|
||||||
global _LOG_FILE_HANDLES, _LOGDIR, _LOG_ROTATE_SIZE
|
global _LOG_FILE_HANDLES, _LOG_FILE_HANDLE_COUNTS, _LOGDIR, _LOG_ROTATE_SIZE
|
||||||
if not _LOGDIR:
|
if not _LOGDIR:
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
_LOGDIR = settings.LOG_DIR
|
_LOGDIR = settings.LOG_DIR
|
||||||
|
|
@ -329,16 +336,22 @@ def _open_log_file(filename):
|
||||||
|
|
||||||
filename = os.path.join(_LOGDIR, filename)
|
filename = os.path.join(_LOGDIR, filename)
|
||||||
if filename in _LOG_FILE_HANDLES:
|
if filename in _LOG_FILE_HANDLES:
|
||||||
# cache the handle
|
_LOG_FILE_HANDLE_COUNTS[filename] += 1
|
||||||
return _LOG_FILE_HANDLES[filename]
|
if _LOG_FILE_HANDLE_COUNTS[filename] > _LOG_FILE_HANDLE_RESET:
|
||||||
else:
|
# close/refresh handle
|
||||||
try:
|
_LOG_FILE_HANDLES[filename].close()
|
||||||
filehandle = EvenniaLogFile.fromFullPath(filename, rotateLength=_LOG_ROTATE_SIZE)
|
del _LOG_FILE_HANDLES[filename]
|
||||||
# filehandle = open(filename, "a+") # append mode + reading
|
else:
|
||||||
_LOG_FILE_HANDLES[filename] = filehandle
|
# return cached handle
|
||||||
return filehandle
|
return _LOG_FILE_HANDLES[filename]
|
||||||
except IOError:
|
try:
|
||||||
log_trace()
|
filehandle = EvenniaLogFile.fromFullPath(filename, rotateLength=_LOG_ROTATE_SIZE)
|
||||||
|
# filehandle = open(filename, "a+") # append mode + reading
|
||||||
|
_LOG_FILE_HANDLES[filename] = filehandle
|
||||||
|
_LOG_FILE_HANDLE_COUNTS[filename] = 0
|
||||||
|
return filehandle
|
||||||
|
except IOError:
|
||||||
|
log_trace()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue