From ae3c3c526eb7101a9ba8a91bc09452a58584d3d7 Mon Sep 17 00:00:00 2001 From: Tehom Date: Thu, 9 Nov 2017 22:33:08 -0500 Subject: [PATCH 01/18] Fix search and timeout with large database --- evennia/comms/admin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/comms/admin.py b/evennia/comms/admin.py index 52ed7993fd..fde7cc4c42 100644 --- a/evennia/comms/admin.py +++ b/evennia/comms/admin.py @@ -53,10 +53,11 @@ class ChannelAdmin(admin.ModelAdmin): list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions") list_display_links = ("id", 'db_key') ordering = ["db_key"] - search_fields = ['id', 'db_key', 'db_aliases'] + search_fields = ['id', 'db_key', 'db_tags__db_key'] save_as = True save_on_top = True list_select_related = True + raw_id_fields = ('db_object_subscriptions', 'db_account_subscriptions',) fieldsets = ( (None, {'fields': (('db_key',), 'db_lock_storage', 'db_account_subscriptions', 'db_object_subscriptions')}), ) From 989a046bc99c269a0ee625113373ed5bf72ecac2 Mon Sep 17 00:00:00 2001 From: Tehom Date: Fri, 10 Nov 2017 00:34:20 -0500 Subject: [PATCH 02/18] Move deprecated TEMPLATE_DEBUG setting to the 'options' field of TEMPLATES. --- evennia/settings_default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 779d2e3245..b1e2a7a79e 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -636,8 +636,6 @@ RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes # browser to display. Note however that this will leak memory when # active, so make sure to turn it off for a production server! DEBUG = False -# While true, show "pretty" error messages for template syntax errors. -TEMPLATE_DEBUG = DEBUG # Emails are sent to these people if the above DEBUG value is False. If you'd # rather prefer nobody receives emails, leave this commented out or empty. ADMINS = () # 'Your Name', 'your_email@domain.com'),) @@ -730,7 +728,9 @@ TEMPLATES = [{ 'django.template.context_processors.media', 'django.template.context_processors.debug', 'sekizai.context_processors.sekizai', - 'evennia.web.utils.general_context.general_context'] + 'evennia.web.utils.general_context.general_context'], + # While true, show "pretty" error messages for template syntax errors. + "debug": DEBUG } }] From 4d7a1f9b9ada6715c95ba913da55d4bd47a40fc2 Mon Sep 17 00:00:00 2001 From: Tehom Date: Fri, 10 Nov 2017 14:42:21 -0500 Subject: [PATCH 03/18] Fix sethome's help file --- evennia/commands/default/building.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index ff487626a1..f4a001e6d2 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1087,7 +1087,7 @@ class CmdSetHome(CmdLink): set an object's home location Usage: - @home [= ] + @sethome [= ] The "home" location is a "safety" location for objects; they will be moved there if their current location ceases to exist. All @@ -1098,13 +1098,13 @@ class CmdSetHome(CmdLink): """ key = "@sethome" - locks = "cmd:perm(@home) or perm(Builder)" + locks = "cmd:perm(@sethome) or perm(Builder)" help_category = "Building" def func(self): """implement the command""" if not self.args: - string = "Usage: @home [= ]" + string = "Usage: @sethome [= ]" self.caller.msg(string) return From 74bbd0efeaa16a1fd7f6b07e88748af59a89f637 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Sat, 11 Nov 2017 12:29:07 -0500 Subject: [PATCH 04/18] Change pop() to first() --- evennia/commands/default/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/commands/default/admin.py b/evennia/commands/default/admin.py index bd6e55adaa..9da061cc04 100644 --- a/evennia/commands/default/admin.py +++ b/evennia/commands/default/admin.py @@ -301,7 +301,7 @@ class CmdDelAccount(COMMAND_DEFAULT_CLASS): # one single match - account = accounts.pop() + account = accounts.first() if not account.access(caller, 'delete'): string = "You don't have the permissions to delete that account." From 60e3e0e864917dde427037e3f5da0d5a19b267a6 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 29 Nov 2017 19:55:13 +0100 Subject: [PATCH 05/18] Update instructions for installing SSL requirements --- evennia/server/portal/ssl.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/evennia/server/portal/ssl.py b/evennia/server/portal/ssl.py index 209e08696f..8b638ed23d 100644 --- a/evennia/server/portal/ssl.py +++ b/evennia/server/portal/ssl.py @@ -13,8 +13,13 @@ try: except ImportError as error: errstr = """ {err} - SSL requires the PyOpenSSL library: - pip install pyopenssl + SSL requires the PyOpenSSL library and dependencies: + + pip install pyopenssl pycrypto enum pyasn1 service_identity + + Stop and start Evennia again. If no certificate can be generated, you'll + get a suggestion for a (linux) command to generate this locally. + """ raise ImportError(errstr.format(err=error)) From d4b2ed6b8a20606ca0f9b25b84dd08c7e064f178 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 5 Dec 2017 19:54:40 +0100 Subject: [PATCH 06/18] Fix and cleanup the rplanguage contrib a bit --- evennia/contrib/rplanguage.py | 96 ++++++++++++++++++++++++++--------- evennia/contrib/tests.py | 15 ++++-- 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/evennia/contrib/rplanguage.py b/evennia/contrib/rplanguage.py index 2159719641..f65f136baa 100644 --- a/evennia/contrib/rplanguage.py +++ b/evennia/contrib/rplanguage.py @@ -96,6 +96,7 @@ import re from random import choice, randint from collections import defaultdict from evennia import DefaultScript +from evennia.utils import logger #------------------------------------------------------------ @@ -105,7 +106,8 @@ from evennia import DefaultScript #------------------------------------------------------------ # default language grammar -_PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh s z sh zh ch jh k ng g m n l r w" +_PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh " \ + "s z sh zh ch jh k ng g m n l r w" _VOWELS = "eaoiuy" # these must be able to be constructed from phonemes (so for example, # if you have v here, there must exixt at least one single-character @@ -115,12 +117,16 @@ _GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv _RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE _RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS) _RE_WORD = re.compile(r'\w+', _RE_FLAGS) +_RE_EXTRA_CHARS = re.compile(r'\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])', _RE_FLAGS) class LanguageExistsError(Exception): message = "Language is already created. Re-adding it will re-build" \ " its dictionary map. Use 'force=True' keyword if you are sure." + def __str__(self): + return self.message + class LanguageHandler(DefaultScript): """ @@ -156,8 +162,11 @@ class LanguageHandler(DefaultScript): self.db.language_storage = {} def add(self, key="default", phonemes=_PHONEMES, - grammar=_GRAMMAR, word_length_variance=0, noun_prefix="", - noun_postfix="", vowels=_VOWELS, manual_translations=None, + grammar=_GRAMMAR, word_length_variance=0, + noun_translate=False, + noun_prefix="", + noun_postfix="", + vowels=_VOWELS, manual_translations=None, auto_translations=None, force=False): """ Add a new language. Note that you generally only need to do @@ -170,14 +179,21 @@ class LanguageHandler(DefaultScript): will be used as an identifier for the language so it should be short and unique. phonemes (str, optional): Space-separated string of all allowed - phonemes in this language. + phonemes in this language. If either of the base phonemes + (c, v, cc, vv) are present in the grammar, the phoneme list must + at least include one example of each. grammar (str): All allowed consonant (c) and vowel (v) combinations - allowed to build up words. For example cvv would be a consonant - followed by two vowels (would allow for a word like 'die'). + allowed to build up words. Grammars are broken into the base phonemes + (c, v, cc, vv) prioritizing the longer bases. So cvv would be a + the c + vv (would allow for a word like 'die' whereas + cvcvccc would be c+v+c+v+cc+c (a word like 'galosch'). word_length_variance (real): The variation of length of words. 0 means a minimal variance, higher variance may mean words have wildly varying length; this strongly affects how the language "looks". + noun_translate (bool, optional): If a proper noun, identified as a + capitalized word, should be translated or not. By default they + will not, allowing for e.g. the names of characters to be understandable. noun_prefix (str, optional): A prefix to go before every noun in this language (if any). noun_postfix (str, optuonal): A postfix to go after every noun @@ -261,6 +277,7 @@ class LanguageHandler(DefaultScript): "grammar": grammar, "grammar2phonemes": dict(grammar2phonemes), "word_length_variance": word_length_variance, + "noun_translate": noun_translate, "noun_prefix": noun_prefix, "noun_postfix": noun_postfix} self.db.language_storage[key] = storage @@ -282,34 +299,63 @@ class LanguageHandler(DefaultScript): """ word = match.group() lword = len(word) + if len(word) <= self.level: # below level. Don't translate new_word = word else: - # translate the word + # try to translate the word from dictionary new_word = self.language["translation"].get(word.lower(), "") if not new_word: - if word.istitle(): - # capitalized word we don't have a translation for - - # treat as a name (don't translate) - new_word = "%s%s%s" % (self.language["noun_prefix"], word, self.language["noun_postfix"]) - else: - # make up translation on the fly. Length can - # vary from un-translated word. - wlen = max(0, lword + sum(randint(-1, 1) for i - in range(self.language["word_length_variance"]))) - grammar = self.language["grammar"] - if wlen not in grammar: + # no dictionary translation. Generate one + + # find out what preceeded this word + wpos = match.start() + preceeding = match.string[:wpos].strip() + start_sentence = preceeding.endswith(".") or not preceeding + + # make up translation on the fly. Length can + # vary from un-translated word. + wlen = max(0, lword + sum(randint(-1, 1) for i + in range(self.language["word_length_variance"]))) + grammar = self.language["grammar"] + if wlen not in grammar: + if randint(0, 1) == 0: # this word has no direct translation! - return "" + wlen = 0 + new_word = '' + else: + # use random word length + wlen = choice(grammar.keys()) + + if wlen: structure = choice(grammar[wlen]) grammar2phonemes = self.language["grammar2phonemes"] for match in _RE_GRAMMAR.finditer(structure): # there are only four combinations: vv,cc,c,v - new_word += choice(grammar2phonemes[match.group()]) - if word.istitle(): - # capitalize words the same way - new_word = new_word.capitalize() + try: + new_word += choice(grammar2phonemes[match.group()]) + except KeyError: + logger.log_trace("You need to supply at least one example of each of " + "the four base phonemes (c, v, cc, vv)") + # abort translation here + new_word = '' + break + + if word.istitle(): + title_word = '' + if not start_sentence and not self.language.get("noun_translate", False): + # don't translate what we identify as proper nouns (names) + title_word = word + elif new_word: + title_word = new_word + + if title_word: + # Regardless of if we translate or not, we will add the custom prefix/postfixes + new_word = "%s%s%s" % (self.language["noun_prefix"], + title_word.capitalize(), + self.language["noun_postfix"]) + if len(word) > 1 and word.isupper(): # keep LOUD words loud also when translated new_word = new_word.upper() @@ -341,7 +387,9 @@ class LanguageHandler(DefaultScript): # configuring the translation self.level = int(10 * (1.0 - max(0, min(level, 1.0)))) - return _RE_WORD.sub(self._translate_sub, text) + translation = _RE_WORD.sub(self._translate_sub, text) + # the substitution may create too long empty spaces, remove those + return _RE_EXTRA_CHARS.sub("", translation) # Language access functions diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 1678d06567..03583c43f4 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -18,7 +18,7 @@ from evennia.contrib import rplanguage mtrans = {"testing": "1", "is": "2", "a": "3", "human": "4"} atrans = ["An", "automated", "advantageous", "repeatable", "faster"] -text = "Automated testing is advantageous for a number of reasons:" \ +text = "Automated testing is advantageous for a number of reasons: " \ "tests may be executed Continuously without the need for human " \ "intervention, They are easily repeatable, and often faster." @@ -33,6 +33,12 @@ class TestLanguage(EvenniaTest): manual_translations=mtrans, auto_translations=atrans, force=True) + rplanguage.add_language(key="binary", + phonemes="oo ii ck w b d t", + grammar="cvvv cvv cvvcv cvvcvv cvvvc cvvvcvv cvvc", + vowels="oei", + noun_prefix='beep-', + word_length_variance=4) def tearDown(self): super(TestLanguage, self).tearDown() @@ -50,16 +56,17 @@ class TestLanguage(EvenniaTest): self.assertEqual(result1[1], "1") self.assertEqual(result1[2], "2") self.assertEqual(result2[-1], result2[-1]) + print(rplanguage.obfuscate_language(text, level=1.0, language='binary')) def test_available_languages(self): - self.assertEqual(rplanguage.available_languages(), ["testlang"]) + self.assertEqual(rplanguage.available_languages(), ["testlang", "binary"]) def test_obfuscate_whisper(self): self.assertEqual(rplanguage.obfuscate_whisper(text, level=0.0), text) assert (rplanguage.obfuscate_whisper(text, level=0.1).startswith( - '-utom-t-d t-sting is -dv-nt-g-ous for - numb-r of r--sons:t-sts m-y b- -x-cut-d Continuously')) + '-utom-t-d t-sting is -dv-nt-g-ous for - numb-r of r--sons: t-sts m-y b- -x-cut-d Continuously')) assert(rplanguage.obfuscate_whisper(text, level=0.5).startswith( - '--------- --s---- -s -----------s f-- - ------ -f ---s--s:--s-s ')) + '--------- --s---- -s -----------s f-- - ------ -f ---s--s: --s-s ')) self.assertEqual(rplanguage.obfuscate_whisper(text, level=1.0), "...") # Testing of emoting / sdesc / recog system From a1dee8d1a13513769b6e28a8bedaa23b7a7b1b6f Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 5 Dec 2017 22:03:34 +0100 Subject: [PATCH 07/18] Add some more tests to catch faulty language definitions --- evennia/contrib/rplanguage.py | 33 ++++++++++++++++++++------------- evennia/contrib/tests.py | 18 +++++++++++++++--- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/evennia/contrib/rplanguage.py b/evennia/contrib/rplanguage.py index f65f136baa..2779231e12 100644 --- a/evennia/contrib/rplanguage.py +++ b/evennia/contrib/rplanguage.py @@ -110,7 +110,7 @@ _PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y "s z sh zh ch jh k ng g m n l r w" _VOWELS = "eaoiuy" # these must be able to be constructed from phonemes (so for example, -# if you have v here, there must exixt at least one single-character +# if you have v here, there must exist at least one single-character # vowel phoneme defined above) _GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv cvcvcvcvv" @@ -120,12 +120,12 @@ _RE_WORD = re.compile(r'\w+', _RE_FLAGS) _RE_EXTRA_CHARS = re.compile(r'\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])', _RE_FLAGS) -class LanguageExistsError(Exception): - message = "Language is already created. Re-adding it will re-build" \ - " its dictionary map. Use 'force=True' keyword if you are sure." +class LanguageError(RuntimeError): + pass - def __str__(self): - return self.message + +class LanguageExistsError(LanguageError): + pass class LanguageHandler(DefaultScript): @@ -229,21 +229,28 @@ class LanguageHandler(DefaultScript): """ if key in self.db.language_storage and not force: - raise LanguageExistsError - - # allowed grammar are grouped by length - gramdict = defaultdict(list) - for gram in grammar.split(): - gramdict[len(gram)].append(gram) - grammar = dict(gramdict) + raise LanguageExistsError( + "Language is already created. Re-adding it will re-build" + " its dictionary map. Use 'force=True' keyword if you are sure.") # create grammar_component->phoneme mapping # {"vv": ["ea", "oh", ...], ...} grammar2phonemes = defaultdict(list) for phoneme in phonemes.split(): + if re.search("\W", phoneme): + raise LanguageError("The phoneme '%s' contains an invalid character" % phoneme) gram = "".join(["v" if char in vowels else "c" for char in phoneme]) grammar2phonemes[gram].append(phoneme) + # allowed grammar are grouped by length + gramdict = defaultdict(list) + for gram in grammar.split(): + if re.search("\W|(!=[cv])", gram): + raise LanguageError("The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram) + gramdict[len(gram)].append(gram) + grammar = dict(gramdict) + + # create automatic translation translation = {} diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 03583c43f4..23e4662ec3 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -34,9 +34,8 @@ class TestLanguage(EvenniaTest): auto_translations=atrans, force=True) rplanguage.add_language(key="binary", - phonemes="oo ii ck w b d t", + phonemes="oo ii a ck w b d t", grammar="cvvv cvv cvvcv cvvcvv cvvvc cvvvcvv cvvc", - vowels="oei", noun_prefix='beep-', word_length_variance=4) @@ -50,13 +49,26 @@ class TestLanguage(EvenniaTest): self.assertEqual(result0, text) result1 = rplanguage.obfuscate_language(text, level=1.0, language="testlang") result2 = rplanguage.obfuscate_language(text, level=1.0, language="testlang") + result3 = rplanguage.obfuscate_language(text, level=1.0, language='binary') + self.assertNotEqual(result1, text) + self.assertNotEqual(result3, text) result1, result2 = result1.split(), result2.split() self.assertEqual(result1[:4], result2[:4]) self.assertEqual(result1[1], "1") self.assertEqual(result1[2], "2") self.assertEqual(result2[-1], result2[-1]) - print(rplanguage.obfuscate_language(text, level=1.0, language='binary')) + + def test_faulty_language(self): + self.assertRaises( + rplanguage.LanguageError, + rplanguage.add_language, + key='binary2', + phonemes="w b d t oe ee, oo e o a wh dw bw", # erroneous comma + grammar="cvvv cvv cvvcv cvvcvvo cvvvc cvvvcvv cvvc c v cc vv ccvvc ccvvccvv ", + vowels="oea", + word_length_variance=4) + def test_available_languages(self): self.assertEqual(rplanguage.available_languages(), ["testlang", "binary"]) From c2fbd10bca4b4822ffee45c3bff68b72749e3d79 Mon Sep 17 00:00:00 2001 From: Tehom Date: Fri, 8 Dec 2017 02:41:33 -0500 Subject: [PATCH 08/18] Fix msg_receivers to be used --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index f9b9510670..94e122ba30 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1624,7 +1624,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): # whisper mode msg_type = 'whisper' msg_self = '{self} whisper to {all_receivers}, "{speech}"' if msg_self is True else msg_self - msg_receivers = '{object} whispers: "{speech}"' + msg_receivers = msg_receivers or '{object} whispers: "{speech}"' msg_location = None else: msg_self = '{self} say, "{speech}"' if msg_self is True else msg_self From fa52cf917303f99439320dc855ed67abbf883171 Mon Sep 17 00:00:00 2001 From: Tehom Date: Fri, 8 Dec 2017 02:59:29 -0500 Subject: [PATCH 09/18] Make at_say more flexible by not ignoring parameters passed --- evennia/objects/objects.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index e0fd0dace4..e0f337d3ee 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1685,10 +1685,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): msg_type = 'whisper' msg_self = '{self} whisper to {all_receivers}, "{speech}"' if msg_self is True else msg_self msg_receivers = '{object} whispers: "{speech}"' - msg_location = None else: msg_self = '{self} say, "{speech}"' if msg_self is True else msg_self - msg_receivers = None msg_location = msg_location or '{object} says, "{speech}"' custom_mapping = kwargs.get('mapping', {}) @@ -1733,9 +1731,14 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): "receiver": None, "speech": message} location_mapping.update(custom_mapping) + exclude = [] + if msg_self: + exclude.append(self) + if receivers: + exclude.extend(receivers) self.location.msg_contents(text=(msg_location, {"type": msg_type}), from_obj=self, - exclude=(self, ) if msg_self else None, + exclude=exclude, mapping=location_mapping) From 0e1fadddca62e45d422453cd0d86017bd27d5c0b Mon Sep 17 00:00:00 2001 From: Tehom Date: Fri, 8 Dec 2017 03:00:43 -0500 Subject: [PATCH 10/18] Fix error in passing non-strings to str.join() --- evennia/objects/objects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 94e122ba30..aca4ec0924 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1669,7 +1669,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): location_mapping = {"self": "You", "object": self, "location": location, - "all_receivers": ", ".join(recv for recv in receivers) if receivers else None, + "all_receivers": ", ".join(str(recv) for recv in receivers) if receivers else None, "receiver": None, "speech": message} location_mapping.update(custom_mapping) From d9532d6a23e29aaec639b12643260715e6849ab2 Mon Sep 17 00:00:00 2001 From: arumford Date: Fri, 8 Dec 2017 10:14:19 -0600 Subject: [PATCH 11/18] Update rplanguage.py The docstring examples have a typo. It lists the contrib file as 'rplanguages' but the file is actually 'rplanguage'. It can be confusing. --- evennia/contrib/rplanguage.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/evennia/contrib/rplanguage.py b/evennia/contrib/rplanguage.py index 2779231e12..e8f3626eb2 100644 --- a/evennia/contrib/rplanguage.py +++ b/evennia/contrib/rplanguage.py @@ -21,30 +21,30 @@ in the game in various ways: Usage: ```python - from evennia.contrib import rplanguages + from evennia.contrib import rplanguage # need to be done once, here we create the "default" lang - rplanguages.add_language() + rplanguage.add_language() say = "This is me talking." whisper = "This is me whispering. - print rplanguages.obfuscate_language(say, level=0.0) + print rplanguage.obfuscate_language(say, level=0.0) <<< "This is me talking." - print rplanguages.obfuscate_language(say, level=0.5) + print rplanguage.obfuscate_language(say, level=0.5) <<< "This is me byngyry." - print rplanguages.obfuscate_language(say, level=1.0) + print rplanguage.obfuscate_language(say, level=1.0) <<< "Daly ly sy byngyry." - result = rplanguages.obfuscate_whisper(whisper, level=0.0) + result = rplanguage.obfuscate_whisper(whisper, level=0.0) <<< "This is me whispering" - result = rplanguages.obfuscate_whisper(whisper, level=0.2) + result = rplanguage.obfuscate_whisper(whisper, level=0.2) <<< "This is m- whisp-ring" - result = rplanguages.obfuscate_whisper(whisper, level=0.5) + result = rplanguage.obfuscate_whisper(whisper, level=0.5) <<< "---s -s -- ---s------" - result = rplanguages.obfuscate_whisper(whisper, level=0.7) + result = rplanguage.obfuscate_whisper(whisper, level=0.7) <<< "---- -- -- ----------" - result = rplanguages.obfuscate_whisper(whisper, level=1.0) + result = rplanguage.obfuscate_whisper(whisper, level=1.0) <<< "..." ``` @@ -71,7 +71,7 @@ Usage: manual_translations = {"the":"y'e", "we":"uyi", "she":"semi", "he":"emi", "you": "do", 'me':'mi','i':'me', 'be':"hy'e", 'and':'y'} - rplanguages.add_language(key="elvish", phonemes=phonemes, grammar=grammar, + rplanguage.add_language(key="elvish", phonemes=phonemes, grammar=grammar, word_length_variance=word_length_variance, noun_postfix=noun_postfix, vowels=vowels, manual_translations=manual_translations From 391132bed21390a2220785217baadb8a63e68791 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 9 Dec 2017 01:13:02 +0100 Subject: [PATCH 12/18] Change dockerfile entrypoint to launch evennia server, more suitable for docker-compose setups. Add websocket proxy envvar --- Dockerfile | 12 ++++++------ bin/unix/evennia-docker-start.sh | 13 +++++++++++++ evennia/settings_default.py | 7 ++++++- evennia/web/utils/general_context.py | 7 ++++++- 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 bin/unix/evennia-docker-start.sh diff --git a/Dockerfile b/Dockerfile index 27af6b2a3a..4ca00d254b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,11 @@ # install `docker` (http://docker.com) # # Usage: -# cd to a folder where you want your game data to be (or where it already is). +# cd to a folder where you want your game data to be (or where it already is). # # docker run -it -p 4000:4000 -p 4001:4001 -p 4005:4005 -v $PWD:/usr/src/game evennia/evennia -# -# (If your OS does not support $PWD, replace it with the full path to your current +# +# (If your OS does not support $PWD, replace it with the full path to your current # folder). # # You will end up in a shell where the `evennia` command is available. From here you @@ -30,10 +30,10 @@ RUN apk update && apk add python py-pip python-dev py-setuptools gcc musl-dev jp ADD . /usr/src/evennia # install dependencies -RUN pip install -e /usr/src/evennia --trusted-host pypi.python.org +RUN pip install --upgrade pip && pip install /usr/src/evennia --trusted-host pypi.python.org # add the game source when rebuilding a new docker image from inside -# a game dir +# a game dir ONBUILD ADD . /usr/src/game # make the game source hierarchy persistent with a named volume. @@ -48,7 +48,7 @@ WORKDIR /usr/src/game ENV PS1 "evennia|docker \w $ " # startup a shell when we start the container -ENTRYPOINT ["bash"] +ENTRYPOINT bash -c "source /usr/src/evennia/bin/unix/evennia-docker-start.sh" # expose the telnet, webserver and websocket client ports EXPOSE 4000 4001 4005 diff --git a/bin/unix/evennia-docker-start.sh b/bin/unix/evennia-docker-start.sh new file mode 100644 index 0000000000..270f6ec627 --- /dev/null +++ b/bin/unix/evennia-docker-start.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +# called by the Dockerfile to start the server in docker mode + +# remove leftover .pid files (such as from when dropping the container) +rm /usr/src/game/server/*.pid >& /dev/null || true + +# start evennia server; log to server.log but also output to stdout so it can +# be viewed with docker-compose logs +exec 3>&1; evennia start 2>&1 1>&3 | tee /usr/src/game/server/logs/server.log; exec 3>&- + +# start a shell to keep the container running +bash diff --git a/evennia/settings_default.py b/evennia/settings_default.py index b1e2a7a79e..fd213d333d 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -83,7 +83,12 @@ WEBCLIENT_ENABLED = True # default webclient will use this and only use the ajax version if the browser # is too old to support websockets. Requires WEBCLIENT_ENABLED. WEBSOCKET_CLIENT_ENABLED = True -# Server-side websocket port to open for the webclient. +# Server-side websocket port to open for the webclient. Note that this value will +# be dynamically encoded in the webclient html page to allow the webclient to call +# home. If the external encoded value needs to be different than this, due to +# working through a proxy or docker port-remapping, the environment variable +# WEBCLIENT_CLIENT_PROXY_PORT can be used to override this port only for the +# front-facing client's sake. WEBSOCKET_CLIENT_PORT = 4005 # Interface addresses to listen to. If 0.0.0.0, listen to all. Use :: for IPv6. WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0' diff --git a/evennia/web/utils/general_context.py b/evennia/web/utils/general_context.py index 27edd79c86..44bc8b3cb3 100644 --- a/evennia/web/utils/general_context.py +++ b/evennia/web/utils/general_context.py @@ -6,6 +6,7 @@ # tuple. # +import os from django.conf import settings from evennia.utils.utils import get_evennia_version @@ -52,7 +53,11 @@ def set_webclient_settings(): global WEBCLIENT_ENABLED, WEBSOCKET_CLIENT_ENABLED, WEBSOCKET_PORT, WEBSOCKET_URL WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED - WEBSOCKET_PORT = settings.WEBSOCKET_CLIENT_PORT + # if we are working through a proxy or uses docker port-remapping, the webclient port encoded + # in the webclient should be different than the one the server expects. Use the environment + # variable WEBSOCKET_CLIENT_PROXY_PORT if this is the case. + WEBSOCKET_PORT = int(os.environ.get("WEBSOCKET_CLIENT_PROXY_PORT", settings.WEBSOCKET_CLIENT_PORT)) + # this is determined dynamically by the client and is less of an issue WEBSOCKET_URL = settings.WEBSOCKET_CLIENT_URL set_webclient_settings() From 7e1900e617ed58b13f431b7ce66a6f4d465471b3 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 10 Dec 2017 10:26:20 +0100 Subject: [PATCH 13/18] fix unittests --- evennia/web/utils/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evennia/web/utils/tests.py b/evennia/web/utils/tests.py index b2d42891ae..e2b28c3510 100644 --- a/evennia/web/utils/tests.py +++ b/evennia/web/utils/tests.py @@ -51,9 +51,9 @@ class TestGeneralContext(TestCase): mock_settings.WEBCLIENT_ENABLED = "webclient" mock_settings.WEBSOCKET_CLIENT_URL = "websocket_url" mock_settings.WEBSOCKET_CLIENT_ENABLED = "websocket_client" - mock_settings.WEBSOCKET_CLIENT_PORT = "websocket_port" + mock_settings.WEBSOCKET_CLIENT_PORT = 5000 general_context.set_webclient_settings() self.assertEqual(general_context.WEBCLIENT_ENABLED, "webclient") self.assertEqual(general_context.WEBSOCKET_URL, "websocket_url") self.assertEqual(general_context.WEBSOCKET_CLIENT_ENABLED, "websocket_client") - self.assertEqual(general_context.WEBSOCKET_PORT, "websocket_port") + self.assertEqual(general_context.WEBSOCKET_PORT, 5000) From 5aa9dfc24c9dac902363d511268444232059a3a2 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 12 Dec 2017 18:56:36 +0100 Subject: [PATCH 14/18] Add a setting to change telnet default encoding --- evennia/server/portal/telnet.py | 5 +++-- evennia/server/session.py | 7 ++++++- evennia/settings_default.py | 6 ++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 4112d85e2a..86199ae4ac 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -24,7 +24,7 @@ _RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _IDLE_COMMAND = settings.IDLE_COMMAND + "\n" - +_TELNET_ENCODING = settings.TELNET_ENCODING class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ @@ -49,7 +49,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp - self.init_session(self.protocol_name, client_address, self.factory.sessionhandler) + self.init_session(self.protocol_name, client_address, self.factory.sessionhandler, + override_flags={"ENCODING": _TELNET_ENCODING}) # suppress go-ahead self.sga = suppress_ga.SuppressGA(self) diff --git a/evennia/server/session.py b/evennia/server/session.py index 70be0708d7..dc816ee59b 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -41,7 +41,7 @@ class Session(object): 'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'protocol_flags', 'server_data', "cmdset_storage_string") - def init_session(self, protocol_key, address, sessionhandler): + def init_session(self, protocol_key, address, sessionhandler, override_flags=None): """ Initialize the Session. This should be called by the protocol when a new session is established. @@ -52,6 +52,7 @@ class Session(object): address (str): Client address. sessionhandler (SessionHandler): Reference to the main sessionhandler instance. + override_flags (optional, dict): a dictionary of protocol flags to override. """ # This is currently 'telnet', 'ssh', 'ssl' or 'web' @@ -87,6 +88,10 @@ class Session(object): "INPUTDEBUG": False, "RAW": False, "NOCOLOR": False} + + if override_flags: + self.protocol_flags.update(override_flags) + self.server_data = {} # map of input data to session methods diff --git a/evennia/settings_default.py b/evennia/settings_default.py index fd213d333d..bb8e07fe44 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -166,6 +166,12 @@ IDLE_TIMEOUT = -1 # command-name is given here; this is because the webclient needs a default # to send to avoid proxy timeouts. IDLE_COMMAND = "idle" +# The encoding (character set) specific to Telnet. This will not influence +# other encoding settings: namely, the webclient, the website, the +# database encoding will remain (utf-8 by default). This setting only +# affects the telnet encoding and will be overridden by user settings +# (through one of their client's supported protocol or their account options). +TELNET_ENCODING = "utf-8" # The set of encodings tried. An Account object may set an attribute "encoding" on # itself to match the client used. If not set, or wrong encoding is # given, this list is tried, in order, aborting on the first match. From f3c909ed6aef62a60c130d9d63b0cb9977f69258 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 12 Dec 2017 19:46:28 +0100 Subject: [PATCH 15/18] Remove TELNET_ENCODING and set ENCODINGS[0] --- evennia/server/portal/telnet.py | 2 +- evennia/settings_default.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 86199ae4ac..9d7b31929d 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -24,7 +24,7 @@ _RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _IDLE_COMMAND = settings.IDLE_COMMAND + "\n" -_TELNET_ENCODING = settings.TELNET_ENCODING +_TELNET_ENCODING = settings.ENCODINGS[0] class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ diff --git a/evennia/settings_default.py b/evennia/settings_default.py index bb8e07fe44..5469fd46db 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -166,17 +166,12 @@ IDLE_TIMEOUT = -1 # command-name is given here; this is because the webclient needs a default # to send to avoid proxy timeouts. IDLE_COMMAND = "idle" -# The encoding (character set) specific to Telnet. This will not influence -# other encoding settings: namely, the webclient, the website, the -# database encoding will remain (utf-8 by default). This setting only -# affects the telnet encoding and will be overridden by user settings -# (through one of their client's supported protocol or their account options). -TELNET_ENCODING = "utf-8" # The set of encodings tried. An Account object may set an attribute "encoding" on # itself to match the client used. If not set, or wrong encoding is # given, this list is tried, in order, aborting on the first match. # Add sets for languages/regions your accounts are likely to use. # (see http://en.wikipedia.org/wiki/Character_encoding) +# Telnet default encoding, unless specified by the client, will be ENCODINGS[0]. ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] # Regular expression applied to all output to a given session in order # to strip away characters (usually various forms of decorations) for the benefit From 94e271c6931cf7c603dc06954f5b9b8da7d56188 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 12 Dec 2017 20:51:16 +0100 Subject: [PATCH 16/18] Simplify telnet edefault encoding --- evennia/server/portal/telnet.py | 6 +++--- evennia/server/session.py | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 9d7b31929d..08267f0ee9 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -24,7 +24,6 @@ _RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE) _RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _IDLE_COMMAND = settings.IDLE_COMMAND + "\n" -_TELNET_ENCODING = settings.ENCODINGS[0] class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): """ @@ -49,8 +48,9 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): # this number is counted down for every handshake that completes. # when it reaches 0 the portal/server syncs their data self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp - self.init_session(self.protocol_name, client_address, self.factory.sessionhandler, - override_flags={"ENCODING": _TELNET_ENCODING}) + self.init_session(self.protocol_name, client_address, self.factory.sessionhandler) + # change encoding to ENCODINGS[0] which reflects Telnet default encoding + self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] # suppress go-ahead self.sga = suppress_ga.SuppressGA(self) diff --git a/evennia/server/session.py b/evennia/server/session.py index dc816ee59b..96b68662a5 100644 --- a/evennia/server/session.py +++ b/evennia/server/session.py @@ -7,7 +7,6 @@ from builtins import object import time - #------------------------------------------------------------ # Server Session #------------------------------------------------------------ @@ -41,7 +40,7 @@ class Session(object): 'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total', 'protocol_flags', 'server_data', "cmdset_storage_string") - def init_session(self, protocol_key, address, sessionhandler, override_flags=None): + def init_session(self, protocol_key, address, sessionhandler): """ Initialize the Session. This should be called by the protocol when a new session is established. @@ -52,7 +51,6 @@ class Session(object): address (str): Client address. sessionhandler (SessionHandler): Reference to the main sessionhandler instance. - override_flags (optional, dict): a dictionary of protocol flags to override. """ # This is currently 'telnet', 'ssh', 'ssl' or 'web' @@ -88,10 +86,6 @@ class Session(object): "INPUTDEBUG": False, "RAW": False, "NOCOLOR": False} - - if override_flags: - self.protocol_flags.update(override_flags) - self.server_data = {} # map of input data to session methods From 7fb30c5bc3a4991fa5b1a701051f21822f5f6d68 Mon Sep 17 00:00:00 2001 From: Vincent Le Goff Date: Tue, 12 Dec 2017 20:59:55 +0100 Subject: [PATCH 17/18] Add a little check in case ENCODINGS is empty --- evennia/server/portal/telnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/server/portal/telnet.py b/evennia/server/portal/telnet.py index 08267f0ee9..dd07512b70 100644 --- a/evennia/server/portal/telnet.py +++ b/evennia/server/portal/telnet.py @@ -50,7 +50,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp self.init_session(self.protocol_name, client_address, self.factory.sessionhandler) # change encoding to ENCODINGS[0] which reflects Telnet default encoding - self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] + self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] if settings.ENCODINGS else 'utf-8' # suppress go-ahead self.sga = suppress_ga.SuppressGA(self) From af1057d190c4e6d91db07c3c94a0fe21b409329b Mon Sep 17 00:00:00 2001 From: Robert Bost Date: Tue, 12 Dec 2017 19:29:16 -0500 Subject: [PATCH 18/18] Update websocket URL so proxy port can be utilized. Resolves #1421. --- evennia/settings_default.py | 6 +++--- evennia/web/webclient/templates/webclient/base.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index b1e2a7a79e..9a0ec3ffa3 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -89,9 +89,9 @@ WEBSOCKET_CLIENT_PORT = 4005 WEBSOCKET_CLIENT_INTERFACE = '0.0.0.0' # Actual URL for webclient component to reach the websocket. You only need # to set this if you know you need it, like using some sort of proxy setup. -# If given it must be on the form "ws://hostname" (WEBSOCKET_CLIENT_PORT will -# be automatically appended). If left at None, the client will itself -# figure out this url based on the server's hostname. +# If given it must be on the form "ws[s]://hostname[:port]". If left at None, +# the client will itself figure out this url based on the server's hostname. +# e.g. ws://external.example.com or wss://external.example.com:443 WEBSOCKET_CLIENT_URL = None # This determine's whether Evennia's custom admin page is used, or if the # standard Django admin is used. diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 373ff0f357..30a68c498f 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -44,7 +44,7 @@ JQuery available. {% endif %} {% if websocket_url %} - var wsurl = "{{websocket_url}}:{{websocket_port}}"; + var wsurl = "{{websocket_url}}"; {% else %} var wsurl = "ws://" + this.location.hostname + ":{{websocket_port}}"; {% endif %}