Format code with black. Add makefile to run fmt/tests
This commit is contained in:
parent
d00bce9288
commit
c2c7fa311a
299 changed files with 19037 additions and 11611 deletions
|
|
@ -12,10 +12,11 @@ class ServerConfigAdmin(admin.ModelAdmin):
|
|||
Custom admin for server configs
|
||||
|
||||
"""
|
||||
list_display = ('db_key', 'db_value')
|
||||
list_display_links = ('db_key',)
|
||||
ordering = ['db_key', 'db_value']
|
||||
search_fields = ['db_key']
|
||||
|
||||
list_display = ("db_key", "db_value")
|
||||
list_display_links = ("db_key",)
|
||||
ordering = ["db_key", "db_value"]
|
||||
search_fields = ["db_key"]
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class AMPClientFactory(protocol.ReconnectingClientFactory):
|
|||
connection error.
|
||||
|
||||
"""
|
||||
|
||||
# Initial reconnect delay in seconds.
|
||||
initialDelay = 1
|
||||
factor = 1.5
|
||||
|
|
@ -95,6 +96,7 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
Portal (which acts as the AMP-server)
|
||||
|
||||
"""
|
||||
|
||||
# sending AMP data
|
||||
|
||||
def connectionMade(self):
|
||||
|
|
@ -108,7 +110,8 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
# first thing we do is to request the Portal to sync all sessions
|
||||
# back with the Server side. We also need the startup mode (reload, reset, shutdown)
|
||||
self.send_AdminServer2Portal(
|
||||
amp.DUMMYSESSION, operation=amp.PSYNC, spid=os.getpid(), info_dict=info_dict)
|
||||
amp.DUMMYSESSION, operation=amp.PSYNC, spid=os.getpid(), info_dict=info_dict
|
||||
)
|
||||
# run the intial setup if needed
|
||||
self.factory.server.run_initial_setup()
|
||||
|
||||
|
|
@ -131,7 +134,8 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
"""
|
||||
# print("server data_to_portal: {}, {}, {}".format(command, sessid, kwargs))
|
||||
return self.callRemote(command, packed_data=amp.dumps((sessid, kwargs))).addErrback(
|
||||
self.errback, command.key)
|
||||
self.errback, command.key
|
||||
)
|
||||
|
||||
def send_MsgServer2Portal(self, session, **kwargs):
|
||||
"""
|
||||
|
|
@ -158,8 +162,9 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
kwargs (dict, optional): Data going into the adminstrative.
|
||||
|
||||
"""
|
||||
return self.data_to_portal(amp.AdminServer2Portal, session.sessid,
|
||||
operation=operation, **kwargs)
|
||||
return self.data_to_portal(
|
||||
amp.AdminServer2Portal, session.sessid, operation=operation, **kwargs
|
||||
)
|
||||
|
||||
# receiving AMP data
|
||||
|
||||
|
|
@ -228,18 +233,18 @@ class AMPServerClientProtocol(amp.AMPMultiConnectionProtocol):
|
|||
elif operation == amp.SRELOAD: # server reload
|
||||
# shut down in reload mode
|
||||
server_sessionhandler.all_sessions_portal_sync()
|
||||
server_sessionhandler.server.shutdown(mode='reload')
|
||||
server_sessionhandler.server.shutdown(mode="reload")
|
||||
|
||||
elif operation == amp.SRESET:
|
||||
# shut down in reset mode
|
||||
server_sessionhandler.all_sessions_portal_sync()
|
||||
server_sessionhandler.server.shutdown(mode='reset')
|
||||
server_sessionhandler.server.shutdown(mode="reset")
|
||||
|
||||
elif operation == amp.SSHUTD: # server shutdown
|
||||
# shutdown in stop mode
|
||||
server_sessionhandler.server.shutdown(mode='shutdown')
|
||||
server_sessionhandler.server.shutdown(mode="shutdown")
|
||||
|
||||
else:
|
||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||
raise Exception("operation %(op)s not recognized." % {"op": operation})
|
||||
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from evennia.utils.utils import list_to_string, mod_import
|
|||
|
||||
|
||||
class ConnectionWizard(object):
|
||||
|
||||
def __init__(self):
|
||||
self.data = {}
|
||||
self.prev_node = None
|
||||
|
|
@ -132,19 +131,19 @@ class ConnectionWizard(object):
|
|||
if not resp and default:
|
||||
resp = str(default)
|
||||
|
||||
if resp.lower() in ('q', 'quit'):
|
||||
if resp.lower() in ("q", "quit"):
|
||||
sys.exit()
|
||||
|
||||
if resp.lower() == 'none':
|
||||
resp = ''
|
||||
if resp.lower() == "none":
|
||||
resp = ""
|
||||
|
||||
if validator and not validator(resp):
|
||||
continue
|
||||
|
||||
ok = input("\n Leave blank? [Y]/N: ")
|
||||
if ok.lower() in ('n', 'no'):
|
||||
if ok.lower() in ("n", "no"):
|
||||
continue
|
||||
elif ok.lower() in ('q', 'quit'):
|
||||
elif ok.lower() in ("q", "quit"):
|
||||
sys.exit()
|
||||
return resp
|
||||
|
||||
|
|
@ -155,7 +154,7 @@ class ConnectionWizard(object):
|
|||
ok = input("\n Is this correct? [Y]/N: ")
|
||||
if ok.lower() in ("n", "no"):
|
||||
continue
|
||||
elif ok.lower() in ('q', 'quit'):
|
||||
elif ok.lower() in ("q", "quit"):
|
||||
sys.exit()
|
||||
return resp
|
||||
|
||||
|
|
@ -170,15 +169,17 @@ def node_start(wizard):
|
|||
Use `quit` at any time to abort and throw away unsaved changes.
|
||||
"""
|
||||
options = {
|
||||
"1": ("Add your game to the Evennia game index (also for closed-dev games)",
|
||||
node_game_index_start, {}),
|
||||
"1": (
|
||||
"Add your game to the Evennia game index (also for closed-dev games)",
|
||||
node_game_index_start,
|
||||
{},
|
||||
),
|
||||
# "2": ("Add MSSP information (for mud-list crawlers)",
|
||||
# node_mssp_start, {}),
|
||||
# "3": ("Add Grapevine listing",
|
||||
# node_grapevine_start, {}),
|
||||
"2": ("View and Save created settings",
|
||||
node_view_and_apply_settings, {}),
|
||||
}
|
||||
"2": ("View and Save created settings", node_view_and_apply_settings, {}),
|
||||
}
|
||||
|
||||
wizard.display(text)
|
||||
wizard.ask_node(options)
|
||||
|
|
@ -201,7 +202,7 @@ def node_game_index_start(wizard, **kwargs):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
if wizard.ask_yesno("Continue adding/editing an Index entry?") == 'yes':
|
||||
if wizard.ask_yesno("Continue adding/editing an Index entry?") == "yes":
|
||||
node_game_index_fields(wizard)
|
||||
else:
|
||||
node_start(wizard)
|
||||
|
|
@ -215,7 +216,7 @@ def node_game_index_fields(wizard, status=None):
|
|||
|
||||
# game status
|
||||
|
||||
status_default = wizard.game_index_listing['game_status']
|
||||
status_default = wizard.game_index_listing["game_status"]
|
||||
text = f"""
|
||||
What is the status of your game?
|
||||
- pre-alpha: a game in its very early stages, mostly unfinished or unstarted
|
||||
|
|
@ -230,12 +231,11 @@ def node_game_index_fields(wizard, status=None):
|
|||
options = ["pre-alpha", "alpha", "beta", "launched"]
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['game_status'] = \
|
||||
wizard.ask_choice("Select one: ", options)
|
||||
wizard.game_index_listing["game_status"] = wizard.ask_choice("Select one: ", options)
|
||||
|
||||
# short desc
|
||||
|
||||
sdesc_default = wizard.game_index_listing.get('short_description', None)
|
||||
sdesc_default = wizard.game_index_listing.get("short_description", None)
|
||||
|
||||
text = f"""
|
||||
Enter a short description of your game. Make it snappy and interesting!
|
||||
|
|
@ -256,8 +256,9 @@ def node_game_index_fields(wizard, status=None):
|
|||
return True
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['short_description'] = \
|
||||
wizard.ask_input(default=sdesc_default, validator=sdesc_validator)
|
||||
wizard.game_index_listing["short_description"] = wizard.ask_input(
|
||||
default=sdesc_default, validator=sdesc_validator
|
||||
)
|
||||
|
||||
# long desc
|
||||
|
||||
|
|
@ -273,8 +274,7 @@ def node_game_index_fields(wizard, status=None):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['long_description'] = \
|
||||
wizard.ask_input(default=long_default)
|
||||
wizard.game_index_listing["long_description"] = wizard.ask_input(default=long_default)
|
||||
|
||||
# listing contact
|
||||
|
||||
|
|
@ -297,12 +297,13 @@ def node_game_index_fields(wizard, status=None):
|
|||
return True
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['listing_contact'] = \
|
||||
wizard.ask_input(default=listing_default, validator=contact_validator)
|
||||
wizard.game_index_listing["listing_contact"] = wizard.ask_input(
|
||||
default=listing_default, validator=contact_validator
|
||||
)
|
||||
|
||||
# telnet hostname
|
||||
|
||||
hostname_default = wizard.game_index_listing.get('telnet_hostname', None)
|
||||
hostname_default = wizard.game_index_listing.get("telnet_hostname", None)
|
||||
text = f"""
|
||||
Enter the hostname to which third-party telnet mud clients can connect to
|
||||
your game. This would be the name of the server your game is hosted on,
|
||||
|
|
@ -315,13 +316,11 @@ def node_game_index_fields(wizard, status=None):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['telnet_hostname'] = \
|
||||
wizard.ask_input(default=hostname_default)
|
||||
|
||||
wizard.game_index_listing["telnet_hostname"] = wizard.ask_input(default=hostname_default)
|
||||
|
||||
# telnet port
|
||||
|
||||
port_default = wizard.game_index_listing.get('telnet_port', None)
|
||||
port_default = wizard.game_index_listing.get("telnet_port", None)
|
||||
text = f"""
|
||||
Enter the main telnet port. The Evennia default is 4000. You can change
|
||||
this with the TELNET_PORTS server setting.
|
||||
|
|
@ -333,13 +332,11 @@ def node_game_index_fields(wizard, status=None):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['telnet_port'] = \
|
||||
wizard.ask_input(default=port_default)
|
||||
|
||||
wizard.game_index_listing["telnet_port"] = wizard.ask_input(default=port_default)
|
||||
|
||||
# website
|
||||
|
||||
website_default = wizard.game_index_listing.get('game_website', None)
|
||||
website_default = wizard.game_index_listing.get("game_website", None)
|
||||
text = f"""
|
||||
Evennia is its own web server and runs your game's website. Enter the
|
||||
URL of the website here, like http://yourwebsite.com, here.
|
||||
|
|
@ -351,12 +348,11 @@ def node_game_index_fields(wizard, status=None):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['game_website'] = \
|
||||
wizard.ask_input(default=website_default)
|
||||
wizard.game_index_listing["game_website"] = wizard.ask_input(default=website_default)
|
||||
|
||||
# webclient
|
||||
|
||||
webclient_default = wizard.game_index_listing.get('web_client_url', None)
|
||||
webclient_default = wizard.game_index_listing.get("web_client_url", None)
|
||||
text = f"""
|
||||
Evennia offers its own native webclient. Normally it will be found from the
|
||||
game homepage at something like http://yourwebsite.com/webclient. Enter
|
||||
|
|
@ -370,15 +366,17 @@ def node_game_index_fields(wizard, status=None):
|
|||
"""
|
||||
|
||||
wizard.display(text)
|
||||
wizard.game_index_listing['web_client_url'] = \
|
||||
wizard.ask_input(default=webclient_default)
|
||||
wizard.game_index_listing["web_client_url"] = wizard.ask_input(default=webclient_default)
|
||||
|
||||
if not (wizard.game_index_listing.get('web_client_url') or
|
||||
(wizard.game_index_listing.get('telnet_host'))):
|
||||
if not (
|
||||
wizard.game_index_listing.get("web_client_url")
|
||||
or (wizard.game_index_listing.get("telnet_host"))
|
||||
):
|
||||
wizard.display(
|
||||
"\nNote: You have not specified any connection options. This means "
|
||||
"your game \nwill be marked as being in 'closed development' in "
|
||||
"the index.")
|
||||
"the index."
|
||||
)
|
||||
|
||||
wizard.display("\nDon't forget to inspect and save your changes.")
|
||||
|
||||
|
|
@ -417,6 +415,7 @@ def node_mssp_start(wizard):
|
|||
|
||||
# Admin
|
||||
|
||||
|
||||
def _save_changes(wizard):
|
||||
"""
|
||||
Perform the save
|
||||
|
|
@ -425,22 +424,25 @@ def _save_changes(wizard):
|
|||
# add import statement to settings file
|
||||
import_stanza = "from .connection_settings import *"
|
||||
setting_module = mod_import("server.conf.settings")
|
||||
with open(setting_module.__file__, 'r+') as f:
|
||||
with open(setting_module.__file__, "r+") as f:
|
||||
txt = f.read() # moves pointer to end of file
|
||||
if import_stanza not in txt:
|
||||
# add to the end of the file
|
||||
f.write("\n\n"
|
||||
"try:\n"
|
||||
" # Created by the `evennia connections` wizard\n"
|
||||
f" {import_stanza}\n"
|
||||
"except ImportError:\n"
|
||||
" pass")
|
||||
f.write(
|
||||
"\n\n"
|
||||
"try:\n"
|
||||
" # Created by the `evennia connections` wizard\n"
|
||||
f" {import_stanza}\n"
|
||||
"except ImportError:\n"
|
||||
" pass"
|
||||
)
|
||||
|
||||
connect_settings_file = path.join(settings.GAME_DIR,
|
||||
"server", "conf", "connection_settings.py")
|
||||
with open(connect_settings_file, 'w') as f:
|
||||
f.write("# This file is auto-generated by the `evennia connections` wizard.\n"
|
||||
"# Don't edit manually, your changes will be overwritten.\n\n")
|
||||
connect_settings_file = path.join(settings.GAME_DIR, "server", "conf", "connection_settings.py")
|
||||
with open(connect_settings_file, "w") as f:
|
||||
f.write(
|
||||
"# This file is auto-generated by the `evennia connections` wizard.\n"
|
||||
"# Don't edit manually, your changes will be overwritten.\n\n"
|
||||
)
|
||||
|
||||
f.write(wizard.save_output)
|
||||
wizard.display(f"saving to {connect_settings_file} ...")
|
||||
|
|
@ -459,9 +461,9 @@ def node_view_and_apply_settings(wizard):
|
|||
if wizard.game_index_listing != settings.GAME_INDEX_LISTING:
|
||||
game_index_txt = "No changes to save for Game Index."
|
||||
else:
|
||||
game_index_txt = ("GAME_INDEX_ENABLED = True\n"
|
||||
"GAME_INDEX_LISTING = \\\n" +
|
||||
pp.pformat(wizard.game_index_listing))
|
||||
game_index_txt = "GAME_INDEX_ENABLED = True\n" "GAME_INDEX_LISTING = \\\n" + pp.pformat(
|
||||
wizard.game_index_listing
|
||||
)
|
||||
saves = True
|
||||
|
||||
text = game_index_txt
|
||||
|
|
@ -469,7 +471,7 @@ def node_view_and_apply_settings(wizard):
|
|||
wizard.display(f"Settings to save:\n\n{text}")
|
||||
|
||||
if saves:
|
||||
if wizard.ask_yesno("Do you want to save these settings?") == 'yes':
|
||||
if wizard.ask_yesno("Do you want to save these settings?") == "yes":
|
||||
wizard.save_output = text
|
||||
_save_changes(wizard)
|
||||
wizard.display("... saved!")
|
||||
|
|
|
|||
|
|
@ -18,31 +18,32 @@ def check_errors(settings):
|
|||
DeprecationWarning if a critical deprecation is found.
|
||||
|
||||
"""
|
||||
deprstring = ("settings.%s should be renamed to %s. If defaults are used, "
|
||||
"their path/classname must be updated "
|
||||
"(see evennia/settings_default.py).")
|
||||
deprstring = (
|
||||
"settings.%s should be renamed to %s. If defaults are used, "
|
||||
"their path/classname must be updated "
|
||||
"(see evennia/settings_default.py)."
|
||||
)
|
||||
if hasattr(settings, "CMDSET_DEFAULT"):
|
||||
raise DeprecationWarning(deprstring % (
|
||||
"CMDSET_DEFAULT", "CMDSET_CHARACTER"))
|
||||
raise DeprecationWarning(deprstring % ("CMDSET_DEFAULT", "CMDSET_CHARACTER"))
|
||||
if hasattr(settings, "CMDSET_OOC"):
|
||||
raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_ACCOUNT"))
|
||||
if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple):
|
||||
raise DeprecationWarning(
|
||||
"settings.WEBSERVER_PORTS must be on the form "
|
||||
"[(proxyport, serverport), ...]")
|
||||
"settings.WEBSERVER_PORTS must be on the form " "[(proxyport, serverport), ...]"
|
||||
)
|
||||
if hasattr(settings, "BASE_COMM_TYPECLASS"):
|
||||
raise DeprecationWarning(deprstring % (
|
||||
"BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS"))
|
||||
raise DeprecationWarning(deprstring % ("BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS"))
|
||||
if hasattr(settings, "COMM_TYPECLASS_PATHS"):
|
||||
raise DeprecationWarning(deprstring % (
|
||||
"COMM_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS"))
|
||||
raise DeprecationWarning(deprstring % ("COMM_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS"))
|
||||
if hasattr(settings, "CHARACTER_DEFAULT_HOME"):
|
||||
raise DeprecationWarning(
|
||||
"settings.CHARACTER_DEFAULT_HOME should be renamed to "
|
||||
"DEFAULT_HOME. See also settings.START_LOCATION "
|
||||
"(see evennia/settings_default.py).")
|
||||
deprstring = ("settings.%s is now merged into settings.TYPECLASS_PATHS. "
|
||||
"Update your settings file.")
|
||||
"(see evennia/settings_default.py)."
|
||||
)
|
||||
deprstring = (
|
||||
"settings.%s is now merged into settings.TYPECLASS_PATHS. " "Update your settings file."
|
||||
)
|
||||
if hasattr(settings, "OBJECT_TYPECLASS_PATHS"):
|
||||
raise DeprecationWarning(deprstring % "OBJECT_TYPECLASS_PATHS")
|
||||
if hasattr(settings, "SCRIPT_TYPECLASS_PATHS"):
|
||||
|
|
@ -56,31 +57,45 @@ def check_errors(settings):
|
|||
"settings.SEARCH_MULTIMATCH_SEPARATOR was replaced by "
|
||||
"SEARCH_MULTIMATCH_REGEX and SEARCH_MULTIMATCH_TEMPLATE. "
|
||||
"Update your settings file (see evennia/settings_default.py "
|
||||
"for more info).")
|
||||
"for more info)."
|
||||
)
|
||||
|
||||
gametime_deprecation = ("The settings TIME_SEC_PER_MIN, TIME_MIN_PER_HOUR,"
|
||||
"TIME_HOUR_PER_DAY, TIME_DAY_PER_WEEK, \n"
|
||||
"TIME_WEEK_PER_MONTH and TIME_MONTH_PER_YEAR "
|
||||
"are no longer supported. Remove them from your "
|
||||
"settings file to continue.\nIf you want to use "
|
||||
"and manipulate these time units, the tools from utils.gametime "
|
||||
"are now found in contrib/convert_gametime.py instead.")
|
||||
if any(hasattr(settings, value) for value in ("TIME_SEC_PER_MIN", "TIME_MIN_PER_HOUR",
|
||||
"TIME_HOUR_PER_DAY", "TIME_DAY_PER_WEEK",
|
||||
"TIME_WEEK_PER_MONTH",
|
||||
"TIME_MONTH_PER_YEAR")):
|
||||
gametime_deprecation = (
|
||||
"The settings TIME_SEC_PER_MIN, TIME_MIN_PER_HOUR,"
|
||||
"TIME_HOUR_PER_DAY, TIME_DAY_PER_WEEK, \n"
|
||||
"TIME_WEEK_PER_MONTH and TIME_MONTH_PER_YEAR "
|
||||
"are no longer supported. Remove them from your "
|
||||
"settings file to continue.\nIf you want to use "
|
||||
"and manipulate these time units, the tools from utils.gametime "
|
||||
"are now found in contrib/convert_gametime.py instead."
|
||||
)
|
||||
if any(
|
||||
hasattr(settings, value)
|
||||
for value in (
|
||||
"TIME_SEC_PER_MIN",
|
||||
"TIME_MIN_PER_HOUR",
|
||||
"TIME_HOUR_PER_DAY",
|
||||
"TIME_DAY_PER_WEEK",
|
||||
"TIME_WEEK_PER_MONTH",
|
||||
"TIME_MONTH_PER_YEAR",
|
||||
)
|
||||
):
|
||||
raise DeprecationWarning(gametime_deprecation)
|
||||
|
||||
game_directory_deprecation = ("The setting GAME_DIRECTORY_LISTING was removed. It must be "
|
||||
"renamed to GAME_INDEX_LISTING instead.")
|
||||
game_directory_deprecation = (
|
||||
"The setting GAME_DIRECTORY_LISTING was removed. It must be "
|
||||
"renamed to GAME_INDEX_LISTING instead."
|
||||
)
|
||||
if hasattr(settings, "GAME_DIRECTORY_LISTING"):
|
||||
raise DeprecationWarning(game_directory_deprecation)
|
||||
|
||||
chan_connectinfo = settings.CHANNEL_CONNECTINFO
|
||||
if chan_connectinfo is not None and not isinstance(chan_connectinfo, dict):
|
||||
raise DeprecationWarning("settings.CHANNEL_CONNECTINFO has changed. It "
|
||||
"must now be either None or a dict "
|
||||
"specifying the properties of the channel to create.")
|
||||
raise DeprecationWarning(
|
||||
"settings.CHANNEL_CONNECTINFO has changed. It "
|
||||
"must now be either None or a dict "
|
||||
"specifying the properties of the channel to create."
|
||||
)
|
||||
|
||||
|
||||
def check_warnings(settings):
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -36,8 +36,8 @@ EVENNIA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||
EVENNIA_BIN = os.path.join(EVENNIA_ROOT, "bin")
|
||||
EVENNIA_LIB = os.path.dirname(evennia.__file__)
|
||||
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, 'server', 'server.py')
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, 'server', 'portal', 'portal.py')
|
||||
SERVER_PY_FILE = os.path.join(EVENNIA_LIB, "server", "server.py")
|
||||
PORTAL_PY_FILE = os.path.join(EVENNIA_LIB, "server", "portal", "portal.py")
|
||||
|
||||
GAMEDIR = None
|
||||
SERVERDIR = "server"
|
||||
|
|
@ -53,8 +53,7 @@ SPROFILER_LOGFILE = None
|
|||
|
||||
# messages
|
||||
|
||||
CMDLINE_HELP = \
|
||||
"""
|
||||
CMDLINE_HELP = """
|
||||
This program manages the running Evennia processes. It is called
|
||||
by evennia and should not be started manually. Its main task is to
|
||||
sit and watch the Server and restart it whenever the user reloads.
|
||||
|
|
@ -63,13 +62,11 @@ CMDLINE_HELP = \
|
|||
are stored in the game's server/ directory.
|
||||
"""
|
||||
|
||||
PROCESS_ERROR = \
|
||||
"""
|
||||
PROCESS_ERROR = """
|
||||
{component} process error: {traceback}.
|
||||
"""
|
||||
|
||||
PROCESS_IOERROR = \
|
||||
"""
|
||||
PROCESS_IOERROR = """
|
||||
{component} IOError: {traceback}
|
||||
One possible explanation is that 'twistd' was not found.
|
||||
"""
|
||||
|
|
@ -85,7 +82,7 @@ def set_restart_mode(restart_file, flag="reload"):
|
|||
"""
|
||||
This sets a flag file for the restart mode.
|
||||
"""
|
||||
with open(restart_file, 'w') as f:
|
||||
with open(restart_file, "w") as f:
|
||||
f.write(str(flag))
|
||||
|
||||
|
||||
|
|
@ -96,7 +93,7 @@ def getenv():
|
|||
sep = ";" if os.name == "nt" else ":"
|
||||
env = os.environ.copy()
|
||||
sys.path.insert(0, GAMEDIR)
|
||||
env['PYTHONPATH'] = sep.join(sys.path)
|
||||
env["PYTHONPATH"] = sep.join(sys.path)
|
||||
return env
|
||||
|
||||
|
||||
|
|
@ -105,7 +102,7 @@ def get_restart_mode(restart_file):
|
|||
Parse the server/portal restart status
|
||||
"""
|
||||
if os.path.exists(restart_file):
|
||||
with open(restart_file, 'r') as f:
|
||||
with open(restart_file, "r") as f:
|
||||
return f.read()
|
||||
return "shutdown"
|
||||
|
||||
|
|
@ -117,7 +114,7 @@ def get_pid(pidfile):
|
|||
"""
|
||||
pid = None
|
||||
if os.path.exists(pidfile):
|
||||
with open(pidfile, 'r') as f:
|
||||
with open(pidfile, "r") as f:
|
||||
pid = f.read()
|
||||
return pid
|
||||
|
||||
|
|
@ -126,7 +123,7 @@ def cycle_logfile(logfile):
|
|||
"""
|
||||
Rotate the old log files to <filename>.old
|
||||
"""
|
||||
logfile_old = logfile + '.old'
|
||||
logfile_old = logfile + ".old"
|
||||
if os.path.exists(logfile):
|
||||
# Cycle the old logfiles to *.old
|
||||
if os.path.exists(logfile_old):
|
||||
|
|
@ -134,6 +131,7 @@ def cycle_logfile(logfile):
|
|||
os.remove(logfile_old)
|
||||
os.rename(logfile, logfile_old)
|
||||
|
||||
|
||||
# Start program management
|
||||
|
||||
|
||||
|
|
@ -167,7 +165,7 @@ def start_services(server_argv, portal_argv, doexit=False):
|
|||
try:
|
||||
if not doexit and get_restart_mode(PORTAL_RESTART) == "True":
|
||||
# start portal as interactive, reloadable thread
|
||||
PORTAL = _thread.start_new_thread(portal_waiter, (processes, ))
|
||||
PORTAL = _thread.start_new_thread(portal_waiter, (processes,))
|
||||
else:
|
||||
# normal operation: start portal as a daemon;
|
||||
# we don't care to monitor it for restart
|
||||
|
|
@ -182,7 +180,7 @@ def start_services(server_argv, portal_argv, doexit=False):
|
|||
SERVER = Popen(server_argv, env=getenv())
|
||||
else:
|
||||
# start server as a reloadable thread
|
||||
SERVER = _thread.start_new_thread(server_waiter, (processes, ))
|
||||
SERVER = _thread.start_new_thread(server_waiter, (processes,))
|
||||
except IOError as e:
|
||||
print(PROCESS_IOERROR.format(component="Server", traceback=e))
|
||||
return
|
||||
|
|
@ -196,6 +194,7 @@ def start_services(server_argv, portal_argv, doexit=False):
|
|||
|
||||
# this blocks until something is actually returned.
|
||||
from twisted.internet.error import ReactorNotRunning
|
||||
|
||||
try:
|
||||
try:
|
||||
message, rc = processes.get()
|
||||
|
|
@ -204,17 +203,23 @@ def start_services(server_argv, portal_argv, doexit=False):
|
|||
break
|
||||
|
||||
# restart only if process stopped cleanly
|
||||
if (message == "server_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")):
|
||||
if (
|
||||
message == "server_stopped"
|
||||
and int(rc) == 0
|
||||
and get_restart_mode(SERVER_RESTART) in ("True", "reload", "reset")
|
||||
):
|
||||
print(PROCESS_RESTART.format(component="Server"))
|
||||
SERVER = _thread.start_new_thread(server_waiter, (processes, ))
|
||||
SERVER = _thread.start_new_thread(server_waiter, (processes,))
|
||||
continue
|
||||
|
||||
# normally the portal is not reloaded since it's run as a daemon.
|
||||
if (message == "portal_stopped" and int(rc) == 0 and
|
||||
get_restart_mode(PORTAL_RESTART) == "True"):
|
||||
if (
|
||||
message == "portal_stopped"
|
||||
and int(rc) == 0
|
||||
and get_restart_mode(PORTAL_RESTART) == "True"
|
||||
):
|
||||
print(PROCESS_RESTART.format(component="Portal"))
|
||||
PORTAL = _thread.start_new_thread(portal_waiter, (processes, ))
|
||||
PORTAL = _thread.start_new_thread(portal_waiter, (processes,))
|
||||
continue
|
||||
break
|
||||
except ReactorNotRunning:
|
||||
|
|
@ -228,29 +233,66 @@ def main():
|
|||
"""
|
||||
|
||||
parser = ArgumentParser(description=CMDLINE_HELP)
|
||||
parser.add_argument('--noserver', action='store_true', dest='noserver',
|
||||
default=False, help='Do not start Server process')
|
||||
parser.add_argument('--noportal', action='store_true', dest='noportal',
|
||||
default=False, help='Do not start Portal process')
|
||||
parser.add_argument('--logserver', action='store_true', dest='logserver',
|
||||
default=False, help='Log Server output to logfile')
|
||||
parser.add_argument('--iserver', action='store_true', dest='iserver',
|
||||
default=False, help='Server in interactive mode')
|
||||
parser.add_argument('--iportal', action='store_true', dest='iportal',
|
||||
default=False, help='Portal in interactive mode')
|
||||
parser.add_argument('--pserver', action='store_true', dest='pserver',
|
||||
default=False, help='Profile Server')
|
||||
parser.add_argument('--pportal', action='store_true', dest='pportal',
|
||||
default=False, help='Profile Portal')
|
||||
parser.add_argument('--nologcycle', action='store_false', dest='nologcycle',
|
||||
default=True, help='Do not cycle log files')
|
||||
parser.add_argument('--doexit', action='store_true', dest='doexit',
|
||||
default=False, help='Immediately exit after processes have started.')
|
||||
parser.add_argument('gamedir', help="path to game dir")
|
||||
parser.add_argument('twistdbinary', help="path to twistd binary")
|
||||
parser.add_argument('slogfile', help="path to server log file")
|
||||
parser.add_argument('plogfile', help="path to portal log file")
|
||||
parser.add_argument('hlogfile', help="path to http log file")
|
||||
parser.add_argument(
|
||||
"--noserver",
|
||||
action="store_true",
|
||||
dest="noserver",
|
||||
default=False,
|
||||
help="Do not start Server process",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--noportal",
|
||||
action="store_true",
|
||||
dest="noportal",
|
||||
default=False,
|
||||
help="Do not start Portal process",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--logserver",
|
||||
action="store_true",
|
||||
dest="logserver",
|
||||
default=False,
|
||||
help="Log Server output to logfile",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--iserver",
|
||||
action="store_true",
|
||||
dest="iserver",
|
||||
default=False,
|
||||
help="Server in interactive mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--iportal",
|
||||
action="store_true",
|
||||
dest="iportal",
|
||||
default=False,
|
||||
help="Portal in interactive mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pserver", action="store_true", dest="pserver", default=False, help="Profile Server"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pportal", action="store_true", dest="pportal", default=False, help="Profile Portal"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nologcycle",
|
||||
action="store_false",
|
||||
dest="nologcycle",
|
||||
default=True,
|
||||
help="Do not cycle log files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--doexit",
|
||||
action="store_true",
|
||||
dest="doexit",
|
||||
default=False,
|
||||
help="Immediately exit after processes have started.",
|
||||
)
|
||||
parser.add_argument("gamedir", help="path to game dir")
|
||||
parser.add_argument("twistdbinary", help="path to twistd binary")
|
||||
parser.add_argument("slogfile", help="path to server log file")
|
||||
parser.add_argument("plogfile", help="path to portal log file")
|
||||
parser.add_argument("hlogfile", help="path to http log file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -275,30 +317,32 @@ def main():
|
|||
PPROFILER_LOGFILE = os.path.join(GAMEDIR, SERVERDIR, "logs", "portal.prof")
|
||||
|
||||
# set up default project calls
|
||||
server_argv = [TWISTED_BINARY,
|
||||
'--nodaemon',
|
||||
'--logfile=%s' % SERVER_LOGFILE,
|
||||
'--pidfile=%s' % SERVER_PIDFILE,
|
||||
'--python=%s' % SERVER_PY_FILE]
|
||||
portal_argv = [TWISTED_BINARY,
|
||||
'--logfile=%s' % PORTAL_LOGFILE,
|
||||
'--pidfile=%s' % PORTAL_PIDFILE,
|
||||
'--python=%s' % PORTAL_PY_FILE]
|
||||
server_argv = [
|
||||
TWISTED_BINARY,
|
||||
"--nodaemon",
|
||||
"--logfile=%s" % SERVER_LOGFILE,
|
||||
"--pidfile=%s" % SERVER_PIDFILE,
|
||||
"--python=%s" % SERVER_PY_FILE,
|
||||
]
|
||||
portal_argv = [
|
||||
TWISTED_BINARY,
|
||||
"--logfile=%s" % PORTAL_LOGFILE,
|
||||
"--pidfile=%s" % PORTAL_PIDFILE,
|
||||
"--python=%s" % PORTAL_PY_FILE,
|
||||
]
|
||||
|
||||
# Profiling settings (read file from python shell e.g with
|
||||
# p = pstats.Stats('server.prof')
|
||||
pserver_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=%s' % SPROFILER_LOGFILE]
|
||||
pportal_argv = ['--savestats',
|
||||
'--profiler=cprofile',
|
||||
'--profile=%s' % PPROFILER_LOGFILE]
|
||||
pserver_argv = ["--savestats", "--profiler=cprofile", "--profile=%s" % SPROFILER_LOGFILE]
|
||||
pportal_argv = ["--savestats", "--profiler=cprofile", "--profile=%s" % PPROFILER_LOGFILE]
|
||||
|
||||
# Server
|
||||
|
||||
pid = get_pid(SERVER_PIDFILE)
|
||||
if pid and not args.noserver:
|
||||
print("\nEvennia Server is already running as process %(pid)s. Not restarted." % {'pid': pid})
|
||||
print(
|
||||
"\nEvennia Server is already running as process %(pid)s. Not restarted." % {"pid": pid}
|
||||
)
|
||||
args.noserver = True
|
||||
if args.noserver:
|
||||
server_argv = None
|
||||
|
|
@ -320,14 +364,16 @@ def main():
|
|||
|
||||
pid = get_pid(PORTAL_PIDFILE)
|
||||
if pid and not args.noportal:
|
||||
print("\nEvennia Portal is already running as process %(pid)s. Not restarted." % {'pid': pid})
|
||||
print(
|
||||
"\nEvennia Portal is already running as process %(pid)s. Not restarted." % {"pid": pid}
|
||||
)
|
||||
args.noportal = True
|
||||
if args.noportal:
|
||||
portal_argv = None
|
||||
else:
|
||||
if args.iportal:
|
||||
# make portal interactive
|
||||
portal_argv[1] = '--nodaemon'
|
||||
portal_argv[1] = "--nodaemon"
|
||||
set_restart_mode(PORTAL_RESTART, True)
|
||||
print("\nStarting Evennia Portal in non-Daemon mode (output to stdout).")
|
||||
else:
|
||||
|
|
@ -343,7 +389,7 @@ def main():
|
|||
print(PROCESS_DOEXIT)
|
||||
|
||||
# Windows fixes (Windows don't support pidfiles natively)
|
||||
if os.name == 'nt':
|
||||
if os.name == "nt":
|
||||
if server_argv:
|
||||
del server_argv[-2]
|
||||
if portal_argv:
|
||||
|
|
@ -353,5 +399,5 @@ def main():
|
|||
start_services(server_argv, portal_argv, doexit=args.doexit)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ from evennia.accounts.models import AccountDB
|
|||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import get_evennia_version, logger
|
||||
|
||||
_EGI_HOST = 'http://evennia-game-index.appspot.com'
|
||||
_EGI_REPORT_PATH = '/api/v1/game/check_in'
|
||||
_EGI_HOST = "http://evennia-game-index.appspot.com"
|
||||
_EGI_REPORT_PATH = "/api/v1/game/check_in"
|
||||
|
||||
|
||||
class EvenniaGameIndexClient(object):
|
||||
|
|
@ -56,15 +56,14 @@ class EvenniaGameIndexClient(object):
|
|||
status_code, response_body = yield self._form_and_send_request()
|
||||
if status_code == 200:
|
||||
if not self.logged_first_connect:
|
||||
logger.log_infomsg(
|
||||
"Successfully sent game details to Evennia Game Index.")
|
||||
logger.log_infomsg("Successfully sent game details to Evennia Game Index.")
|
||||
self.logged_first_connect = True
|
||||
return
|
||||
# At this point, either EGD is having issues or the payload we sent
|
||||
# is improperly formed (probably due to mis-configuration).
|
||||
logger.log_errmsg(
|
||||
'Failed to send game details to Evennia Game Index. HTTP '
|
||||
'status code was %s. Message was: %s' % (status_code, response_body)
|
||||
"Failed to send game details to Evennia Game Index. HTTP "
|
||||
"status code was %s. Message was: %s" % (status_code, response_body)
|
||||
)
|
||||
|
||||
if status_code == 400 and self._on_bad_request:
|
||||
|
|
@ -80,8 +79,8 @@ class EvenniaGameIndexClient(object):
|
|||
"""
|
||||
agent = Agent(reactor, pool=self._conn_pool)
|
||||
headers = {
|
||||
b'User-Agent': [b'Evennia Game Index Client'],
|
||||
b'Content-Type': [b'application/x-www-form-urlencoded'],
|
||||
b"User-Agent": [b"Evennia Game Index Client"],
|
||||
b"Content-Type": [b"application/x-www-form-urlencoded"],
|
||||
}
|
||||
egi_config = settings.GAME_INDEX_LISTING
|
||||
# We are using `or` statements below with dict.get() to avoid sending
|
||||
|
|
@ -89,27 +88,24 @@ class EvenniaGameIndexClient(object):
|
|||
try:
|
||||
values = {
|
||||
# Game listing stuff
|
||||
'game_name': egi_config.get('game_name', settings.SERVERNAME),
|
||||
'game_status': egi_config['game_status'],
|
||||
'game_website': egi_config.get('game_website', ''),
|
||||
'short_description': egi_config['short_description'],
|
||||
'long_description': egi_config.get('long_description', ''),
|
||||
'listing_contact': egi_config['listing_contact'],
|
||||
|
||||
"game_name": egi_config.get("game_name", settings.SERVERNAME),
|
||||
"game_status": egi_config["game_status"],
|
||||
"game_website": egi_config.get("game_website", ""),
|
||||
"short_description": egi_config["short_description"],
|
||||
"long_description": egi_config.get("long_description", ""),
|
||||
"listing_contact": egi_config["listing_contact"],
|
||||
# How to play
|
||||
'telnet_hostname': egi_config.get('telnet_hostname', ''),
|
||||
'telnet_port': egi_config.get('telnet_port', ''),
|
||||
'web_client_url': egi_config.get('web_client_url', ''),
|
||||
|
||||
"telnet_hostname": egi_config.get("telnet_hostname", ""),
|
||||
"telnet_port": egi_config.get("telnet_port", ""),
|
||||
"web_client_url": egi_config.get("web_client_url", ""),
|
||||
# Game stats
|
||||
'connected_account_count': SESSIONS.account_count(),
|
||||
'total_account_count': AccountDB.objects.num_total_accounts() or 0,
|
||||
|
||||
"connected_account_count": SESSIONS.account_count(),
|
||||
"total_account_count": AccountDB.objects.num_total_accounts() or 0,
|
||||
# System info
|
||||
'evennia_version': get_evennia_version(),
|
||||
'python_version': platform.python_version(),
|
||||
'django_version': django.get_version(),
|
||||
'server_platform': platform.platform(),
|
||||
"evennia_version": get_evennia_version(),
|
||||
"python_version": platform.python_version(),
|
||||
"django_version": django.get_version(),
|
||||
"server_platform": platform.platform(),
|
||||
}
|
||||
except KeyError as err:
|
||||
raise KeyError(f"Error loading GAME_INDEX_LISTING: {err}")
|
||||
|
|
@ -117,16 +113,18 @@ class EvenniaGameIndexClient(object):
|
|||
data = urllib.parse.urlencode(values)
|
||||
|
||||
d = agent.request(
|
||||
b'POST', bytes(self.report_url, 'utf-8'),
|
||||
b"POST",
|
||||
bytes(self.report_url, "utf-8"),
|
||||
headers=Headers(headers),
|
||||
bodyProducer=StringProducer(data))
|
||||
bodyProducer=StringProducer(data),
|
||||
)
|
||||
|
||||
d.addCallback(self.handle_egd_response)
|
||||
return d
|
||||
|
||||
def handle_egd_response(self, response):
|
||||
if 200 <= response.code < 300:
|
||||
d = defer.succeed((response.code, 'OK'))
|
||||
d = defer.succeed((response.code, "OK"))
|
||||
else:
|
||||
# Go through the horrifying process of getting the response body
|
||||
# out of Twisted's plumbing.
|
||||
|
|
@ -142,7 +140,7 @@ class SimpleResponseReceiver(protocol.Protocol):
|
|||
|
||||
def __init__(self, status_code, d):
|
||||
self.status_code = status_code
|
||||
self.buf = ''
|
||||
self.buf = ""
|
||||
self.d = d
|
||||
|
||||
def dataReceived(self, data):
|
||||
|
|
@ -159,7 +157,7 @@ class StringProducer(object):
|
|||
"""
|
||||
|
||||
def __init__(self, body):
|
||||
self.body = bytes(body, 'utf-8')
|
||||
self.body = bytes(body, "utf-8")
|
||||
self.length = len(body)
|
||||
|
||||
def startProducing(self, consumer):
|
||||
|
|
@ -177,4 +175,5 @@ class QuietHTTP11ClientFactory(_HTTP11ClientFactory):
|
|||
"""
|
||||
Silences the obnoxious factory start/stop messages in the default client.
|
||||
"""
|
||||
|
||||
noisy = False
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ class EvenniaGameIndexService(Service):
|
|||
to the Evennia Game Index.
|
||||
|
||||
"""
|
||||
|
||||
# We didn't stick the Evennia prefix on here because it'd get marked as
|
||||
# a core system service.
|
||||
name = 'GameIndexClient'
|
||||
name = "GameIndexClient"
|
||||
|
||||
def __init__(self):
|
||||
self.client = EvenniaGameIndexClient(
|
||||
on_bad_request=self._die_on_bad_request)
|
||||
self.client = EvenniaGameIndexClient(on_bad_request=self._die_on_bad_request)
|
||||
self.loop = LoopingCall(self.client.send_game_details)
|
||||
|
||||
def startService(self):
|
||||
|
|
@ -36,8 +36,7 @@ class EvenniaGameIndexService(Service):
|
|||
# Start the loop, but only after a short delay. This allows the
|
||||
# portal and the server time to sync up as far as total player counts.
|
||||
# Prevents always reporting a count of 0.
|
||||
reactor.callLater(
|
||||
_FIRST_UPDATE_DELAY, self.loop.start, _CLIENT_UPDATE_RATE)
|
||||
reactor.callLater(_FIRST_UPDATE_DELAY, self.loop.start, _CLIENT_UPDATE_RATE)
|
||||
|
||||
def stopService(self):
|
||||
if self.running == 0:
|
||||
|
|
@ -54,6 +53,6 @@ class EvenniaGameIndexService(Service):
|
|||
Stop the service so we're not wasting resources.
|
||||
"""
|
||||
logger.log_infomsg(
|
||||
"Shutting down Evennia Game Index client service due to "
|
||||
"invalid configuration.")
|
||||
"Shutting down Evennia Game Index client service due to " "invalid configuration."
|
||||
)
|
||||
self.stopService()
|
||||
|
|
|
|||
|
|
@ -25,11 +25,13 @@ ERROR_NO_SUPERUSER = """
|
|||
"""
|
||||
|
||||
|
||||
LIMBO_DESC = _("""
|
||||
LIMBO_DESC = _(
|
||||
"""
|
||||
Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
|
||||
help, want to contribute, report issues or just join the community.
|
||||
As Account #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
WARNING_POSTGRESQL_FIX = """
|
||||
|
|
@ -74,7 +76,9 @@ def create_objects():
|
|||
god_account.swap_typeclass(account_typeclass, clean_attributes=True)
|
||||
god_account.basetype_setup()
|
||||
god_account.at_account_creation()
|
||||
god_account.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()")
|
||||
god_account.locks.add(
|
||||
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all()"
|
||||
)
|
||||
# this is necessary for quelling to work correctly.
|
||||
god_account.permissions.add("Developer")
|
||||
|
||||
|
|
@ -83,14 +87,14 @@ def create_objects():
|
|||
# Create the in-game god-character for account #1 and set
|
||||
# it to exist in Limbo.
|
||||
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
god_character = create.create_object(character_typeclass,
|
||||
key=god_account.username,
|
||||
nohome=True)
|
||||
god_character = create.create_object(character_typeclass, key=god_account.username, nohome=True)
|
||||
|
||||
god_character.id = 1
|
||||
god_character.save()
|
||||
god_character.db.desc = _('This is User #1.')
|
||||
god_character.locks.add("examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()")
|
||||
god_character.db.desc = _("This is User #1.")
|
||||
god_character.locks.add(
|
||||
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
|
||||
)
|
||||
god_character.permissions.add("Developer")
|
||||
|
||||
god_account.attributes.add("_first_login", True)
|
||||
|
|
@ -102,7 +106,7 @@ def create_objects():
|
|||
god_account.db_playable_characters = [god_character]
|
||||
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
limbo_obj = create.create_object(room_typeclass, _('Limbo'), nohome=True)
|
||||
limbo_obj = create.create_object(room_typeclass, _("Limbo"), nohome=True)
|
||||
limbo_obj.id = 2
|
||||
limbo_obj.save()
|
||||
limbo_obj.db.desc = LIMBO_DESC.strip()
|
||||
|
|
@ -166,8 +170,9 @@ def collectstatic():
|
|||
|
||||
"""
|
||||
from django.core.management import call_command
|
||||
|
||||
logger.log_info("Initial setup: Gathering static resources using 'collectstatic'")
|
||||
call_command('collectstatic', '--noinput')
|
||||
call_command("collectstatic", "--noinput")
|
||||
|
||||
|
||||
def reset_server():
|
||||
|
|
@ -180,6 +185,7 @@ def reset_server():
|
|||
"""
|
||||
ServerConfig.objects.conf("server_epoch", time.time())
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
logger.log_info("Initial setup complete. Restarting Server once.")
|
||||
SESSIONS.portal_reset_server()
|
||||
|
||||
|
|
@ -204,11 +210,7 @@ def handle_setup(last_step):
|
|||
last_step = last_step or 0
|
||||
|
||||
# setting up the list of functions to run
|
||||
setup_queue = [create_objects,
|
||||
create_channels,
|
||||
at_initial_setup,
|
||||
collectstatic,
|
||||
reset_server]
|
||||
setup_queue = [create_objects, create_channels, at_initial_setup, collectstatic, reset_server]
|
||||
|
||||
# step through queue, from last completed function
|
||||
for num, setup_func in enumerate(setup_queue[last_step:]):
|
||||
|
|
@ -221,10 +223,12 @@ def handle_setup(last_step):
|
|||
except Exception:
|
||||
if last_step + num == 1:
|
||||
from evennia.objects.models import ObjectDB
|
||||
|
||||
for obj in ObjectDB.objects.all():
|
||||
obj.delete()
|
||||
elif last_step + num == 2:
|
||||
from evennia.comms.models import ChannelDB
|
||||
|
||||
ChannelDB.objects.all().delete()
|
||||
raise
|
||||
# save this step
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionSt
|
|||
|
||||
# always let "idle" work since we use this in the webclient
|
||||
_IDLE_COMMAND = settings.IDLE_COMMAND
|
||||
_IDLE_COMMAND = (_IDLE_COMMAND, ) if _IDLE_COMMAND == "idle" else (_IDLE_COMMAND, "idle")
|
||||
_IDLE_COMMAND = (_IDLE_COMMAND,) if _IDLE_COMMAND == "idle" else (_IDLE_COMMAND, "idle")
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
|
||||
|
|
@ -47,6 +47,7 @@ _ERROR_INPUT = "Inputfunc {name}({session}): Wrong/unrecognized input: {inp}"
|
|||
|
||||
# All global functions are inputfuncs available to process inputs
|
||||
|
||||
|
||||
def text(session, *args, **kwargs):
|
||||
"""
|
||||
Main text input from the client. This will execute a command
|
||||
|
|
@ -58,8 +59,8 @@ def text(session, *args, **kwargs):
|
|||
arguments are ignored.
|
||||
|
||||
"""
|
||||
#from evennia.server.profiling.timetrace import timetrace
|
||||
#text = timetrace(text, "ServerSession.data_in")
|
||||
# from evennia.server.profiling.timetrace import timetrace
|
||||
# text = timetrace(text, "ServerSession.data_in")
|
||||
|
||||
txt = args[0] if args else None
|
||||
|
||||
|
|
@ -76,11 +77,13 @@ def text(session, *args, **kwargs):
|
|||
# nick replacement
|
||||
puppet = session.puppet
|
||||
if puppet:
|
||||
txt = puppet.nicks.nickreplace(txt,
|
||||
categories=("inputline", "channel"), include_account=True)
|
||||
txt = puppet.nicks.nickreplace(
|
||||
txt, categories=("inputline", "channel"), include_account=True
|
||||
)
|
||||
else:
|
||||
txt = session.account.nicks.nickreplace(txt,
|
||||
categories=("inputline", "channel"), include_account=False)
|
||||
txt = session.account.nicks.nickreplace(
|
||||
txt, categories=("inputline", "channel"), include_account=False
|
||||
)
|
||||
kwargs.pop("options", None)
|
||||
cmdhandler(session, txt, callertype="session", session=session, **kwargs)
|
||||
session.update_session_counters()
|
||||
|
|
@ -127,20 +130,33 @@ def default(session, cmdname, *args, **kwargs):
|
|||
it will get `cmdname` as the first argument.
|
||||
|
||||
"""
|
||||
err = "Session {sessid}: Input command not recognized:\n" \
|
||||
" name: '{cmdname}'\n" \
|
||||
" args, kwargs: {args}, {kwargs}".format(sessid=session.sessid,
|
||||
cmdname=cmdname,
|
||||
args=args,
|
||||
kwargs=kwargs)
|
||||
err = (
|
||||
"Session {sessid}: Input command not recognized:\n"
|
||||
" name: '{cmdname}'\n"
|
||||
" args, kwargs: {args}, {kwargs}".format(
|
||||
sessid=session.sessid, cmdname=cmdname, args=args, kwargs=kwargs
|
||||
)
|
||||
)
|
||||
if session.protocol_flags.get("INPUTDEBUG", False):
|
||||
session.msg(err)
|
||||
log_err(err)
|
||||
|
||||
|
||||
_CLIENT_OPTIONS = \
|
||||
("ANSI", "XTERM256", "MXP", "UTF-8", "SCREENREADER", "ENCODING", "MCCP",
|
||||
"SCREENHEIGHT", "SCREENWIDTH", "INPUTDEBUG", "RAW", "NOCOLOR", "NOGOAHEAD")
|
||||
_CLIENT_OPTIONS = (
|
||||
"ANSI",
|
||||
"XTERM256",
|
||||
"MXP",
|
||||
"UTF-8",
|
||||
"SCREENREADER",
|
||||
"ENCODING",
|
||||
"MCCP",
|
||||
"SCREENHEIGHT",
|
||||
"SCREENWIDTH",
|
||||
"INPUTDEBUG",
|
||||
"RAW",
|
||||
"NOCOLOR",
|
||||
"NOGOAHEAD",
|
||||
)
|
||||
|
||||
|
||||
def client_options(session, *args, **kwargs):
|
||||
|
|
@ -169,8 +185,7 @@ def client_options(session, *args, **kwargs):
|
|||
old_flags = session.protocol_flags
|
||||
if not kwargs or kwargs.get("get", False):
|
||||
# return current settings
|
||||
options = dict((key, old_flags[key]) for key in old_flags
|
||||
if key.upper() in _CLIENT_OPTIONS)
|
||||
options = dict((key, old_flags[key]) for key in old_flags if key.upper() in _CLIENT_OPTIONS)
|
||||
session.msg(client_options=options)
|
||||
return
|
||||
|
||||
|
|
@ -224,19 +239,23 @@ def client_options(session, *args, **kwargs):
|
|||
flags["RAW"] = validate_bool(value)
|
||||
elif key == "nogoahead":
|
||||
flags["NOGOAHEAD"] = validate_bool(value)
|
||||
elif key in ('Char 1', 'Char.Skills 1', 'Char.Items 1',
|
||||
'Room 1', 'IRE.Rift 1', 'IRE.Composer 1'):
|
||||
elif key in (
|
||||
"Char 1",
|
||||
"Char.Skills 1",
|
||||
"Char.Items 1",
|
||||
"Room 1",
|
||||
"IRE.Rift 1",
|
||||
"IRE.Composer 1",
|
||||
):
|
||||
# ignore mudlet's default send (aimed at IRE games)
|
||||
pass
|
||||
elif key not in ("options", "cmdid"):
|
||||
err = _ERROR_INPUT.format(
|
||||
name="client_settings", session=session, inp=key)
|
||||
err = _ERROR_INPUT.format(name="client_settings", session=session, inp=key)
|
||||
session.msg(text=err)
|
||||
|
||||
session.protocol_flags.update(flags)
|
||||
# we must update the protocol flags on the portal session copy as well
|
||||
session.sessionhandler.session_portal_partial_sync(
|
||||
{session.sessid: {"protocol_flags": flags}})
|
||||
session.sessionhandler.session_portal_partial_sync({session.sessid: {"protocol_flags": flags}})
|
||||
|
||||
|
||||
def get_client_options(session, *args, **kwargs):
|
||||
|
|
@ -252,8 +271,9 @@ def get_inputfuncs(session, *args, **kwargs):
|
|||
it from this module alone since multiple modules could be added.
|
||||
So we get it from the sessionhandler.
|
||||
"""
|
||||
inputfuncsdict = dict((key, func.__doc__) for key, func
|
||||
in session.sessionhandler.get_inputfuncs().items())
|
||||
inputfuncsdict = dict(
|
||||
(key, func.__doc__) for key, func in session.sessionhandler.get_inputfuncs().items()
|
||||
)
|
||||
session.msg(get_inputfuncs=inputfuncsdict)
|
||||
|
||||
|
||||
|
|
@ -269,6 +289,7 @@ def login(session, *args, **kwargs):
|
|||
"""
|
||||
if not session.logged_in and "name" in kwargs and "password" in kwargs:
|
||||
from evennia.commands.default.unloggedin import create_normal_account
|
||||
|
||||
account = create_normal_account(session, kwargs["name"], kwargs["password"])
|
||||
if account:
|
||||
session.sessionhandler.login(session, account)
|
||||
|
|
@ -278,7 +299,7 @@ _gettable = {
|
|||
"name": lambda obj: obj.key,
|
||||
"key": lambda obj: obj.key,
|
||||
"location": lambda obj: obj.location.key if obj.location else "None",
|
||||
"servername": lambda obj: settings.SERVERNAME
|
||||
"servername": lambda obj: settings.SERVERNAME,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -308,11 +329,11 @@ def _testrepeat(**kwargs):
|
|||
session (Session): Session to return to.
|
||||
"""
|
||||
import time
|
||||
|
||||
kwargs["session"].msg(repeat="Repeat called: %s" % time.time())
|
||||
|
||||
|
||||
_repeatable = {"test1": _testrepeat, # example only
|
||||
"test2": _testrepeat} # "
|
||||
_repeatable = {"test1": _testrepeat, "test2": _testrepeat} # example only # "
|
||||
|
||||
|
||||
def repeat(session, *args, **kwargs):
|
||||
|
|
@ -333,14 +354,23 @@ def repeat(session, *args, **kwargs):
|
|||
|
||||
"""
|
||||
from evennia.scripts.tickerhandler import TICKER_HANDLER
|
||||
|
||||
name = kwargs.get("callback", "")
|
||||
interval = max(5, int(kwargs.get("interval", 60)))
|
||||
|
||||
if name in _repeatable:
|
||||
if kwargs.get("stop", False):
|
||||
TICKER_HANDLER.remove(interval, _repeatable[name], idstring=session.sessid, persistent=False)
|
||||
TICKER_HANDLER.remove(
|
||||
interval, _repeatable[name], idstring=session.sessid, persistent=False
|
||||
)
|
||||
else:
|
||||
TICKER_HANDLER.add(interval, _repeatable[name], idstring=session.sessid, persistent=False, session=session)
|
||||
TICKER_HANDLER.add(
|
||||
interval,
|
||||
_repeatable[name],
|
||||
idstring=session.sessid,
|
||||
persistent=False,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
session.msg("Allowed repeating functions are: %s" % (", ".join(_repeatable)))
|
||||
|
||||
|
|
@ -351,11 +381,7 @@ def unrepeat(session, *args, **kwargs):
|
|||
repeat(session, *args, **kwargs)
|
||||
|
||||
|
||||
_monitorable = {
|
||||
"name": "db_key",
|
||||
"location": "db_location",
|
||||
"desc": "desc"
|
||||
}
|
||||
_monitorable = {"name": "db_key", "location": "db_location", "desc": "desc"}
|
||||
|
||||
|
||||
def _on_monitor_change(**kwargs):
|
||||
|
|
@ -363,7 +389,7 @@ def _on_monitor_change(**kwargs):
|
|||
obj = kwargs["obj"]
|
||||
name = kwargs["name"]
|
||||
session = kwargs["session"]
|
||||
outputfunc_name = kwargs['outputfunc_name']
|
||||
outputfunc_name = kwargs["outputfunc_name"]
|
||||
|
||||
# the session may be None if the char quits and someone
|
||||
# else then edits the object
|
||||
|
|
@ -389,6 +415,7 @@ def monitor(session, *args, **kwargs):
|
|||
|
||||
"""
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
|
||||
name = kwargs.get("name", None)
|
||||
outputfunc_name = kwargs("outputfunc_name", "monitor")
|
||||
if name and name in _monitorable and session.puppet:
|
||||
|
|
@ -398,9 +425,16 @@ def monitor(session, *args, **kwargs):
|
|||
MONITOR_HANDLER.remove(obj, field_name, idstring=session.sessid)
|
||||
else:
|
||||
# the handler will add fieldname and obj to the kwargs automatically
|
||||
MONITOR_HANDLER.add(obj, field_name, _on_monitor_change, idstring=session.sessid,
|
||||
persistent=False, name=name, session=session,
|
||||
outputfunc_name=outputfunc_name)
|
||||
MONITOR_HANDLER.add(
|
||||
obj,
|
||||
field_name,
|
||||
_on_monitor_change,
|
||||
idstring=session.sessid,
|
||||
persistent=False,
|
||||
name=name,
|
||||
session=session,
|
||||
outputfunc_name=outputfunc_name,
|
||||
)
|
||||
|
||||
|
||||
def unmonitor(session, *args, **kwargs):
|
||||
|
|
@ -417,6 +451,7 @@ def monitored(session, *args, **kwargs):
|
|||
|
||||
"""
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
|
||||
obj = session.puppet
|
||||
monitors = MONITOR_HANDLER.all(obj=obj)
|
||||
session.msg(monitored=(monitors, {}))
|
||||
|
|
@ -476,10 +511,15 @@ def webclient_options(session, *args, **kwargs):
|
|||
# Create a monitor. If a monitor already exists then it will replace
|
||||
# the previous one since it would use the same idstring
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
MONITOR_HANDLER.add(account, "_saved_webclient_options",
|
||||
_on_webclient_options_change,
|
||||
idstring=session.sessid, persistent=False,
|
||||
session=session)
|
||||
|
||||
MONITOR_HANDLER.add(
|
||||
account,
|
||||
"_saved_webclient_options",
|
||||
_on_webclient_options_change,
|
||||
idstring=session.sessid,
|
||||
persistent=False,
|
||||
session=session,
|
||||
)
|
||||
else:
|
||||
# kwargs provided: persist them to the account object
|
||||
for key, value in kwargs.items():
|
||||
|
|
@ -504,14 +544,28 @@ def msdp_list(session, *args, **kwargs):
|
|||
|
||||
"""
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
|
||||
args_lower = [arg.lower() for arg in args]
|
||||
if "commands" in args_lower:
|
||||
inputfuncs = [key[5:] if key.startswith("msdp_") else key
|
||||
for key in session.sessionhandler.get_inputfuncs().keys()]
|
||||
inputfuncs = [
|
||||
key[5:] if key.startswith("msdp_") else key
|
||||
for key in session.sessionhandler.get_inputfuncs().keys()
|
||||
]
|
||||
session.msg(commands=(inputfuncs, {}))
|
||||
if "lists" in args_lower:
|
||||
session.msg(lists=(['commands', 'lists', 'configurable_variables', 'reportable_variables',
|
||||
'reported_variables', 'sendable_variables'], {}))
|
||||
session.msg(
|
||||
lists=(
|
||||
[
|
||||
"commands",
|
||||
"lists",
|
||||
"configurable_variables",
|
||||
"reportable_variables",
|
||||
"reported_variables",
|
||||
"sendable_variables",
|
||||
],
|
||||
{},
|
||||
)
|
||||
)
|
||||
if "configurable_variables" in args_lower:
|
||||
session.msg(configurable_variables=(_CLIENT_OPTIONS, {}))
|
||||
if "reportable_variables" in args_lower:
|
||||
|
|
@ -531,7 +585,7 @@ def msdp_report(session, *args, **kwargs):
|
|||
MSDP REPORT command
|
||||
|
||||
"""
|
||||
kwargs['outputfunc_name': 'report']
|
||||
kwargs["outputfunc_name":"report"]
|
||||
monitor(session, *args, **kwargs)
|
||||
|
||||
|
||||
|
|
@ -545,6 +599,7 @@ def msdp_unreport(session, *args, **kwargs):
|
|||
|
||||
# client specific
|
||||
|
||||
|
||||
def external_discord_hello(session, *args, **kwargs):
|
||||
"""
|
||||
Sent by Mudlet as a greeting; added here to avoid
|
||||
|
|
|
|||
|
|
@ -6,21 +6,25 @@ from django.db import models, migrations
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ServerConfig',
|
||||
name="ServerConfig",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('db_key', models.CharField(unique=True, max_length=64)),
|
||||
('db_value', models.TextField(blank=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID", serialize=False, auto_created=True, primary_key=True
|
||||
),
|
||||
),
|
||||
("db_key", models.CharField(unique=True, max_length=64)),
|
||||
("db_value", models.TextField(blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Server Config value',
|
||||
'verbose_name_plural': 'Server Config values',
|
||||
"verbose_name": "Server Config value",
|
||||
"verbose_name_plural": "Server Config values",
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,26 +10,27 @@ from copy import deepcopy
|
|||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
ServerConfig = apps.get_model('server', 'ServerConfig')
|
||||
ServerConfig = apps.get_model("server", "ServerConfig")
|
||||
for conf in ServerConfig.objects.all():
|
||||
# picklefield requires base64 encoding
|
||||
value = loads(to_bytes(conf.db_value), encoding='bytes') # py2->py3 byte encoding
|
||||
value = loads(to_bytes(conf.db_value), encoding="bytes") # py2->py3 byte encoding
|
||||
conf.db_value = b64encode(dumps(deepcopy(value), protocol=4)).decode()
|
||||
conf.save(update_fields=['db_value'])
|
||||
conf.save(update_fields=["db_value"])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('server', '0001_initial'),
|
||||
]
|
||||
dependencies = [("server", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='serverconfig',
|
||||
name='db_value',
|
||||
field=evennia.utils.picklefield.PickledObjectField(help_text='The data returned when the config value is accessed. Must be written as a Python literal if editing through the admin interface. Attribute values which are not Python literals cannot be edited through the admin interface.', null=True, verbose_name='value'),
|
||||
model_name="serverconfig",
|
||||
name="db_value",
|
||||
field=evennia.utils.picklefield.PickledObjectField(
|
||||
help_text="The data returned when the config value is accessed. Must be written as a Python literal if editing through the admin interface. Attribute values which are not Python literals cannot be edited through the admin interface.",
|
||||
null=True,
|
||||
verbose_name="value",
|
||||
),
|
||||
),
|
||||
|
||||
]
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@ from evennia.server.manager import ServerConfigManager
|
|||
from evennia.utils import picklefield
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# ServerConfig
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class ServerConfig(WeakSharedMemoryModel):
|
||||
"""
|
||||
|
|
@ -48,11 +49,13 @@ class ServerConfig(WeakSharedMemoryModel):
|
|||
# db_value = models.BinaryField(blank=True)
|
||||
|
||||
db_value = picklefield.PickledObjectField(
|
||||
'value', null=True,
|
||||
"value",
|
||||
null=True,
|
||||
help_text="The data returned when the config value is accessed. Must be "
|
||||
"written as a Python literal if editing through the admin "
|
||||
"interface. Attribute values which are not Python literals "
|
||||
"cannot be edited through the admin interface.")
|
||||
"written as a Python literal if editing through the admin "
|
||||
"interface. Attribute values which are not Python literals "
|
||||
"cannot be edited through the admin interface.",
|
||||
)
|
||||
|
||||
objects = ServerConfigManager()
|
||||
_is_deleted = False
|
||||
|
|
@ -66,43 +69,45 @@ class ServerConfig(WeakSharedMemoryModel):
|
|||
# is the object in question).
|
||||
|
||||
# key property (wraps db_key)
|
||||
#@property
|
||||
# @property
|
||||
def __key_get(self):
|
||||
"Getter. Allows for value = self.key"
|
||||
return self.db_key
|
||||
|
||||
#@key.setter
|
||||
# @key.setter
|
||||
def __key_set(self, value):
|
||||
"Setter. Allows for self.key = value"
|
||||
self.db_key = value
|
||||
self.save()
|
||||
|
||||
#@key.deleter
|
||||
# @key.deleter
|
||||
def __key_del(self):
|
||||
"Deleter. Allows for del self.key. Deletes entry."
|
||||
self.delete()
|
||||
|
||||
key = property(__key_get, __key_set, __key_del)
|
||||
|
||||
# value property (wraps db_value)
|
||||
#@property
|
||||
# @property
|
||||
def __value_get(self):
|
||||
"Getter. Allows for value = self.value"
|
||||
return from_pickle(self.db_value, db_obj=self)
|
||||
|
||||
#@value.setter
|
||||
# @value.setter
|
||||
def __value_set(self, value):
|
||||
"Setter. Allows for self.value = value"
|
||||
if utils.has_parent('django.db.models.base.Model', value):
|
||||
if utils.has_parent("django.db.models.base.Model", value):
|
||||
# we have to protect against storing db objects.
|
||||
logger.log_err("ServerConfig cannot store db objects! (%s)" % value)
|
||||
return
|
||||
self.db_value = to_pickle(value)
|
||||
self.save()
|
||||
|
||||
#@value.deleter
|
||||
# @value.deleter
|
||||
def __value_del(self):
|
||||
"Deleter. Allows for del self.value. Deletes entry."
|
||||
self.delete()
|
||||
|
||||
value = property(__value_get, __value_set, __value_del)
|
||||
|
||||
class Meta(object):
|
||||
|
|
|
|||
|
|
@ -23,28 +23,28 @@ _LOGGER = None
|
|||
# communication bits
|
||||
# (chr(9) and chr(10) are \t and \n, so skipping them)
|
||||
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
SCONN = chr(11) # server creating new connection (for irc bots and etc)
|
||||
PCONNSYNC = chr(12) # portal post-syncing a session
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
SCONN = chr(11) # server creating new connection (for irc bots and etc)
|
||||
PCONNSYNC = chr(12) # portal post-syncing a session
|
||||
PDISCONNALL = chr(13) # portal session disconnect all
|
||||
SRELOAD = chr(14) # server shutdown in reload mode
|
||||
SSTART = chr(15) # server start (portal must already be running anyway)
|
||||
PSHUTD = chr(16) # portal (+server) shutdown
|
||||
SSHUTD = chr(17) # server shutdown
|
||||
PSTATUS = chr(18) # ping server or portal status
|
||||
SRESET = chr(19) # server shutdown in reset mode
|
||||
SRELOAD = chr(14) # server shutdown in reload mode
|
||||
SSTART = chr(15) # server start (portal must already be running anyway)
|
||||
PSHUTD = chr(16) # portal (+server) shutdown
|
||||
SSHUTD = chr(17) # server shutdown
|
||||
PSTATUS = chr(18) # ping server or portal status
|
||||
SRESET = chr(19) # server shutdown in reset mode
|
||||
|
||||
NUL = b'\x00'
|
||||
NULNUL = b'\x00\x00'
|
||||
NUL = b"\x00"
|
||||
NULNUL = b"\x00\x00"
|
||||
|
||||
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
|
||||
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
|
||||
|
||||
# buffers
|
||||
_SENDBATCH = defaultdict(list)
|
||||
|
|
@ -52,10 +52,11 @@ _MSGBUFFER = defaultdict(list)
|
|||
|
||||
# resources
|
||||
|
||||
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
|
||||
DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
|
||||
|
||||
|
||||
_HTTP_WARNING = bytes("""
|
||||
_HTTP_WARNING = bytes(
|
||||
"""
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/html
|
||||
|
||||
|
|
@ -67,11 +68,14 @@ Content-Type: text/html
|
|||
<h3>This port should NOT be publicly visible.</h3>
|
||||
</p>
|
||||
</body>
|
||||
</html>""".strip(), 'utf-8')
|
||||
</html>""".strip(),
|
||||
"utf-8",
|
||||
)
|
||||
|
||||
|
||||
# Helper functions for pickling.
|
||||
|
||||
|
||||
def dumps(data):
|
||||
return pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
|
@ -91,6 +95,7 @@ def _get_logger():
|
|||
@wraps
|
||||
def catch_traceback(func):
|
||||
"Helper decorator"
|
||||
|
||||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
|
|
@ -98,11 +103,13 @@ def catch_traceback(func):
|
|||
_get_logger().log_trace()
|
||||
raise # make sure the error is visible on the other side of the connection too
|
||||
print(err)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# AMP Communication Command types
|
||||
|
||||
|
||||
class Compressed(amp.String):
|
||||
"""
|
||||
This is a custom AMP command Argument that both handles too-long
|
||||
|
|
@ -127,7 +134,7 @@ class Compressed(amp.String):
|
|||
if chunk is None:
|
||||
break
|
||||
value.write(self.fromStringProto(chunk, proto))
|
||||
objects[str(name, 'utf-8')] = value.getvalue()
|
||||
objects[str(name, "utf-8")] = value.getvalue()
|
||||
|
||||
def toBox(self, name, strings, objects, proto):
|
||||
"""
|
||||
|
|
@ -138,7 +145,7 @@ class Compressed(amp.String):
|
|||
|
||||
# print("toBox: name={}, strings={}, objects={}, proto{}".format(name, strings, objects, proto))
|
||||
|
||||
value = BytesIO(objects[str(name, 'utf-8')])
|
||||
value = BytesIO(objects[str(name, "utf-8")])
|
||||
strings[name] = self.toStringProto(value.read(AMP_MAXLEN), proto)
|
||||
|
||||
# print("toBox strings[name] = {}".format(strings[name]))
|
||||
|
|
@ -171,10 +178,10 @@ class MsgLauncher2Portal(amp.Command):
|
|||
Message Launcher -> Portal
|
||||
|
||||
"""
|
||||
|
||||
key = "MsgLauncher2Portal"
|
||||
arguments = [(b'operation', amp.String()),
|
||||
(b'arguments', amp.String())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
arguments = [(b"operation", amp.String()), (b"arguments", amp.String())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = []
|
||||
|
||||
|
||||
|
|
@ -183,9 +190,10 @@ class MsgPortal2Server(amp.Command):
|
|||
Message Portal -> Server
|
||||
|
||||
"""
|
||||
|
||||
key = b"MsgPortal2Server"
|
||||
arguments = [(b'packed_data', Compressed())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
arguments = [(b"packed_data", Compressed())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = []
|
||||
|
||||
|
||||
|
|
@ -194,9 +202,10 @@ class MsgServer2Portal(amp.Command):
|
|||
Message Server -> Portal
|
||||
|
||||
"""
|
||||
|
||||
key = "MsgServer2Portal"
|
||||
arguments = [(b'packed_data', Compressed())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
arguments = [(b"packed_data", Compressed())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = []
|
||||
|
||||
|
||||
|
|
@ -208,9 +217,10 @@ class AdminPortal2Server(amp.Command):
|
|||
server, such as when a new session connects or resyncs
|
||||
|
||||
"""
|
||||
|
||||
key = "AdminPortal2Server"
|
||||
arguments = [(b'packed_data', Compressed())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
arguments = [(b"packed_data", Compressed())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = []
|
||||
|
||||
|
||||
|
|
@ -222,9 +232,10 @@ class AdminServer2Portal(amp.Command):
|
|||
portal.
|
||||
|
||||
"""
|
||||
|
||||
key = "AdminServer2Portal"
|
||||
arguments = [(b'packed_data', Compressed())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
arguments = [(b"packed_data", Compressed())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = []
|
||||
|
||||
|
||||
|
|
@ -233,10 +244,11 @@ class MsgStatus(amp.Command):
|
|||
Check Status between AMP services
|
||||
|
||||
"""
|
||||
|
||||
key = "MsgStatus"
|
||||
arguments = [(b'status', amp.String())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
response = [(b'status', amp.String())]
|
||||
arguments = [(b"status", amp.String())]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = [(b"status", amp.String())]
|
||||
|
||||
|
||||
class FunctionCall(amp.Command):
|
||||
|
|
@ -247,19 +259,23 @@ class FunctionCall(amp.Command):
|
|||
the other. This does not use the batch-send functionality.
|
||||
|
||||
"""
|
||||
|
||||
key = "FunctionCall"
|
||||
arguments = [(b'module', amp.String()),
|
||||
(b'function', amp.String()),
|
||||
(b'args', amp.String()),
|
||||
(b'kwargs', amp.String())]
|
||||
errors = {Exception: b'EXCEPTION'}
|
||||
response = [(b'result', amp.String())]
|
||||
arguments = [
|
||||
(b"module", amp.String()),
|
||||
(b"function", amp.String()),
|
||||
(b"args", amp.String()),
|
||||
(b"kwargs", amp.String()),
|
||||
]
|
||||
errors = {Exception: b"EXCEPTION"}
|
||||
response = [(b"result", amp.String())]
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Core AMP protocol for communication Server <-> Portal
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class AMPMultiConnectionProtocol(amp.AMP):
|
||||
"""
|
||||
AMP protocol that safely handle multiple connections to the same
|
||||
|
|
@ -364,8 +380,11 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
|
||||
"""
|
||||
e.trap(Exception)
|
||||
_get_logger().log_err("AMP Error for {info}: {trcbck} {err}".format(
|
||||
info=info, trcbck=e.getTraceback(), err=e.getErrorMessage()))
|
||||
_get_logger().log_err(
|
||||
"AMP Error for {info}: {trcbck} {err}".format(
|
||||
info=info, trcbck=e.getTraceback(), err=e.getErrorMessage()
|
||||
)
|
||||
)
|
||||
|
||||
def data_in(self, packed_data):
|
||||
"""
|
||||
|
|
@ -400,8 +419,9 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
# print("broadcast: {} {}: {}".format(command, sessid, kwargs))
|
||||
|
||||
for protcl in self.factory.broadcasts:
|
||||
deferreds.append(protcl.callRemote(command, **kwargs).addErrback(
|
||||
self.errback, command.key))
|
||||
deferreds.append(
|
||||
protcl.callRemote(command, **kwargs).addErrback(self.errback, command.key)
|
||||
)
|
||||
|
||||
return DeferredList(deferreds)
|
||||
|
||||
|
|
@ -423,12 +443,17 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
function call
|
||||
|
||||
"""
|
||||
return self.callRemote(FunctionCall,
|
||||
module=modulepath,
|
||||
function=functionname,
|
||||
args=dumps(args),
|
||||
kwargs=dumps(kwargs)).addCallback(
|
||||
lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")
|
||||
return (
|
||||
self.callRemote(
|
||||
FunctionCall,
|
||||
module=modulepath,
|
||||
function=functionname,
|
||||
args=dumps(args),
|
||||
kwargs=dumps(kwargs),
|
||||
)
|
||||
.addCallback(lambda r: loads(r["result"]))
|
||||
.addErrback(self.errback, "FunctionCall")
|
||||
)
|
||||
|
||||
@FunctionCall.responder
|
||||
@catch_traceback
|
||||
|
|
@ -459,4 +484,4 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
result.addCallback(lambda r: {"result": dumps(r)})
|
||||
return result
|
||||
else:
|
||||
return {'result': dumps(result)}
|
||||
return {"result": dumps(result)}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from evennia.utils import logger
|
|||
|
||||
|
||||
def _is_windows():
|
||||
return os.name == 'nt'
|
||||
return os.name == "nt"
|
||||
|
||||
|
||||
def getenv():
|
||||
|
|
@ -27,7 +27,7 @@ def getenv():
|
|||
"""
|
||||
sep = ";" if _is_windows() else ":"
|
||||
env = os.environ.copy()
|
||||
env['PYTHONPATH'] = sep.join(sys.path)
|
||||
env["PYTHONPATH"] = sep.join(sys.path)
|
||||
return env
|
||||
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ class AMPServerFactory(protocol.ServerFactory):
|
|||
'Server' process.
|
||||
|
||||
"""
|
||||
|
||||
noisy = False
|
||||
|
||||
def logPrefix(self):
|
||||
|
|
@ -83,6 +84,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
Protocol subclass for the AMP-server run by the Portal.
|
||||
|
||||
"""
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
Set up a simple callback mechanism to let the amp-server wait for a connection to close.
|
||||
|
|
@ -112,8 +114,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
(portal_live, server_live, portal_PID, server_PID).
|
||||
|
||||
"""
|
||||
server_connected = bool(self.factory.server_connection and
|
||||
self.factory.server_connection.transport.connected)
|
||||
server_connected = bool(
|
||||
self.factory.server_connection and self.factory.server_connection.transport.connected
|
||||
)
|
||||
portal_info_dict = self.factory.portal.get_info_dict()
|
||||
server_info_dict = self.factory.portal.server_info_dict
|
||||
server_pid = self.factory.portal.server_process_id
|
||||
|
|
@ -140,8 +143,8 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
# print("portal data_to_server: {}, {}, {}".format(command, sessid, kwargs))
|
||||
if self.factory.server_connection:
|
||||
return self.factory.server_connection.callRemote(
|
||||
command, packed_data=amp.dumps((sessid, kwargs))).addErrback(
|
||||
self.errback, command.key)
|
||||
command, packed_data=amp.dumps((sessid, kwargs))
|
||||
).addErrback(self.errback, command.key)
|
||||
else:
|
||||
# if no server connection is available, broadcast
|
||||
return self.broadcast(command, sessid, packed_data=amp.dumps((sessid, kwargs)))
|
||||
|
|
@ -158,7 +161,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
# start the Server
|
||||
print("Portal starting server ... {}".format(server_twistd_cmd))
|
||||
process = None
|
||||
with open(settings.SERVER_LOG_FILE, 'a') as logfile:
|
||||
with open(settings.SERVER_LOG_FILE, "a") as logfile:
|
||||
# we link stdout to a file in order to catch
|
||||
# eventual errors happening before the Server has
|
||||
# opened its logger.
|
||||
|
|
@ -166,13 +169,19 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
if _is_windows():
|
||||
# Windows requires special care
|
||||
create_no_window = 0x08000000
|
||||
process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1,
|
||||
stdout=logfile, stderr=STDOUT,
|
||||
creationflags=create_no_window)
|
||||
process = Popen(
|
||||
server_twistd_cmd,
|
||||
env=getenv(),
|
||||
bufsize=-1,
|
||||
stdout=logfile,
|
||||
stderr=STDOUT,
|
||||
creationflags=create_no_window,
|
||||
)
|
||||
|
||||
else:
|
||||
process = Popen(server_twistd_cmd, env=getenv(), bufsize=-1,
|
||||
stdout=logfile, stderr=STDOUT)
|
||||
process = Popen(
|
||||
server_twistd_cmd, env=getenv(), bufsize=-1, stdout=logfile, stderr=STDOUT
|
||||
)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
||||
|
|
@ -205,7 +214,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
"""
|
||||
self.factory.server_connect_callbacks.append((callback, args, kwargs))
|
||||
|
||||
def stop_server(self, mode='shutdown'):
|
||||
def stop_server(self, mode="shutdown"):
|
||||
"""
|
||||
Shut down server in one or more modes.
|
||||
|
||||
|
|
@ -213,11 +222,11 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
mode (str): One of 'shutdown', 'reload' or 'reset'.
|
||||
|
||||
"""
|
||||
if mode == 'reload':
|
||||
if mode == "reload":
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRELOAD)
|
||||
elif mode == 'reset':
|
||||
elif mode == "reset":
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SRESET)
|
||||
elif mode == 'shutdown':
|
||||
elif mode == "shutdown":
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION, operation=amp.SSHUTD)
|
||||
self.factory.portal.server_restart_mode = mode
|
||||
|
||||
|
|
@ -232,9 +241,8 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
# print("self.get_status(): {}".format(self.get_status()))
|
||||
if self.factory.launcher_connection:
|
||||
self.factory.launcher_connection.callRemote(
|
||||
amp.MsgStatus,
|
||||
status=amp.dumps(self.get_status())).addErrback(
|
||||
self.errback, amp.MsgStatus.key)
|
||||
amp.MsgStatus, status=amp.dumps(self.get_status())
|
||||
).addErrback(self.errback, amp.MsgStatus.key)
|
||||
|
||||
def send_MsgPortal2Server(self, session, **kwargs):
|
||||
"""
|
||||
|
|
@ -262,8 +270,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
data (str or dict, optional): Data used in the administrative operation.
|
||||
|
||||
"""
|
||||
return self.data_to_server(amp.AdminPortal2Server, session.sessid,
|
||||
operation=operation, **kwargs)
|
||||
return self.data_to_server(
|
||||
amp.AdminPortal2Server, session.sessid, operation=operation, **kwargs
|
||||
)
|
||||
|
||||
# receive amp data
|
||||
|
||||
|
|
@ -303,7 +312,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
|
||||
"""
|
||||
# Since the launcher command uses amp.String() we need to convert from byte here.
|
||||
operation = str(operation, 'utf-8')
|
||||
operation = str(operation, "utf-8")
|
||||
self.factory.launcher_connection = self
|
||||
_, server_connected, _, _, _, _ = self.get_status()
|
||||
|
||||
|
|
@ -311,7 +320,7 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
|
||||
# logger.log_msg("operation == amp.SSTART: {}: {}".format(operation == amp.SSTART, amp.loads(arguments)))
|
||||
|
||||
if operation == amp.SSTART: # portal start #15
|
||||
if operation == amp.SSTART: # portal start #15
|
||||
# first, check if server is already running
|
||||
if not server_connected:
|
||||
self.wait_for_server_connect(self.send_Status2Launcher)
|
||||
|
|
@ -320,38 +329,34 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
elif operation == amp.SRELOAD: # reload server #14
|
||||
if server_connected:
|
||||
# We let the launcher restart us once they get the signal
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.send_Status2Launcher)
|
||||
self.stop_server(mode='reload')
|
||||
self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher)
|
||||
self.stop_server(mode="reload")
|
||||
else:
|
||||
self.wait_for_server_connect(self.send_Status2Launcher)
|
||||
self.start_server(amp.loads(arguments))
|
||||
|
||||
elif operation == amp.SRESET: # reload server #19
|
||||
if server_connected:
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.send_Status2Launcher)
|
||||
self.stop_server(mode='reset')
|
||||
self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher)
|
||||
self.stop_server(mode="reset")
|
||||
else:
|
||||
self.wait_for_server_connect(self.send_Status2Launcher)
|
||||
self.start_server(amp.loads(arguments))
|
||||
|
||||
elif operation == amp.SSHUTD: # server-only shutdown #17
|
||||
if server_connected:
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.send_Status2Launcher)
|
||||
self.stop_server(mode='shutdown')
|
||||
self.factory.server_connection.wait_for_disconnect(self.send_Status2Launcher)
|
||||
self.stop_server(mode="shutdown")
|
||||
|
||||
elif operation == amp.PSHUTD: # portal + server shutdown #16
|
||||
if server_connected:
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.factory.portal.shutdown )
|
||||
self.factory.server_connection.wait_for_disconnect(self.factory.portal.shutdown)
|
||||
else:
|
||||
self.factory.portal.shutdown()
|
||||
|
||||
else:
|
||||
logger.log_err("Operation {} not recognized".format(operation))
|
||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||
raise Exception("operation %(op)s not recognized." % {"op": operation})
|
||||
|
||||
return {}
|
||||
|
||||
|
|
@ -413,22 +418,23 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason"))
|
||||
|
||||
elif operation == amp.SRELOAD: # server reload
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twistd_cmd)
|
||||
self.stop_server(mode='reload')
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twistd_cmd
|
||||
)
|
||||
self.stop_server(mode="reload")
|
||||
|
||||
elif operation == amp.SRESET: # server reset
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twistd_cmd)
|
||||
self.stop_server(mode='reset')
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.start_server, self.factory.portal.server_twistd_cmd
|
||||
)
|
||||
self.stop_server(mode="reset")
|
||||
|
||||
elif operation == amp.SSHUTD: # server-only shutdown
|
||||
self.stop_server(mode='shutdown')
|
||||
self.stop_server(mode="shutdown")
|
||||
|
||||
elif operation == amp.PSHUTD: # full server+server shutdown
|
||||
self.factory.server_connection.wait_for_disconnect(
|
||||
self.factory.portal.shutdown)
|
||||
self.stop_server(mode='shutdown')
|
||||
self.factory.server_connection.wait_for_disconnect(self.factory.portal.shutdown)
|
||||
self.stop_server(mode="shutdown")
|
||||
|
||||
elif operation == amp.PSYNC: # portal sync
|
||||
# Server has (re-)connected and wants the session data from portal
|
||||
|
|
@ -438,11 +444,13 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
server_restart_mode = self.factory.portal.server_restart_mode
|
||||
|
||||
sessdata = self.factory.portal.sessions.get_all_sync_data()
|
||||
self.send_AdminPortal2Server(amp.DUMMYSESSION,
|
||||
amp.PSYNC,
|
||||
server_restart_mode=server_restart_mode,
|
||||
sessiondata=sessdata,
|
||||
portal_start_time=self.factory.portal.start_time)
|
||||
self.send_AdminPortal2Server(
|
||||
amp.DUMMYSESSION,
|
||||
amp.PSYNC,
|
||||
server_restart_mode=server_restart_mode,
|
||||
sessiondata=sessdata,
|
||||
portal_start_time=self.factory.portal.start_time,
|
||||
)
|
||||
self.factory.portal.sessions.at_server_connection()
|
||||
|
||||
if self.factory.server_connection:
|
||||
|
|
@ -458,8 +466,9 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
elif operation == amp.SSYNC: # server_session_sync
|
||||
# server wants to save session data to the portal,
|
||||
# maybe because it's about to shut down.
|
||||
portal_sessionhandler.server_session_sync(kwargs.get("sessiondata"),
|
||||
kwargs.get("clean", True))
|
||||
portal_sessionhandler.server_session_sync(
|
||||
kwargs.get("sessiondata"), kwargs.get("clean", True)
|
||||
)
|
||||
|
||||
# set a flag in case we are about to shut down soon
|
||||
self.factory.server_restart_mode = True
|
||||
|
|
@ -468,5 +477,5 @@ class AMPServerProtocol(amp.AMPMultiConnectionProtocol):
|
|||
portal_sessionhandler.server_connect(**kwargs)
|
||||
|
||||
else:
|
||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||
raise Exception("operation %(op)s not recognized." % {"op": operation})
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ from django.conf import settings
|
|||
from evennia.server.session import Session
|
||||
from evennia.utils import get_evennia_version
|
||||
from evennia.utils.logger import log_info, log_err
|
||||
from autobahn.twisted.websocket import (
|
||||
WebSocketClientProtocol, WebSocketClientFactory, connectWS)
|
||||
from autobahn.twisted.websocket import WebSocketClientProtocol, WebSocketClientFactory, connectWS
|
||||
|
||||
# There is only one at this time
|
||||
GRAPEVINE_URI = "wss://grapevine.haus/socket"
|
||||
|
|
@ -31,8 +30,7 @@ GRAPEVINE_AUTH_ERROR = 4000
|
|||
GRAPEVINE_HEARTBEAT_FAILURE = 4001
|
||||
|
||||
|
||||
class RestartingWebsocketServerFactory(WebSocketClientFactory,
|
||||
protocol.ReconnectingClientFactory):
|
||||
class RestartingWebsocketServerFactory(WebSocketClientFactory, protocol.ReconnectingClientFactory):
|
||||
"""
|
||||
A variant of the websocket-factory that auto-reconnects.
|
||||
|
||||
|
|
@ -44,8 +42,8 @@ class RestartingWebsocketServerFactory(WebSocketClientFactory,
|
|||
|
||||
def __init__(self, sessionhandler, *args, **kwargs):
|
||||
|
||||
self.uid = kwargs.pop('uid')
|
||||
self.channel = kwargs.pop('grapevine_channel')
|
||||
self.uid = kwargs.pop("uid")
|
||||
self.channel = kwargs.pop("grapevine_channel")
|
||||
self.sessionhandler = sessionhandler
|
||||
|
||||
# self.noisy = False
|
||||
|
|
@ -170,7 +168,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
|
||||
"""
|
||||
if not isBinary:
|
||||
data = json.loads(str(payload, 'utf-8'))
|
||||
data = json.loads(str(payload, "utf-8"))
|
||||
self.data_in(data=data)
|
||||
self.retry_task = None
|
||||
|
||||
|
|
@ -205,7 +203,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
data (str): Text to send.
|
||||
|
||||
"""
|
||||
return self.sendMessage(json.dumps(data).encode('utf-8'))
|
||||
return self.sendMessage(json.dumps(data).encode("utf-8"))
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
"""
|
||||
|
|
@ -238,9 +236,9 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
"supports": ["channels"],
|
||||
"channels": GRAPEVINE_CHANNELS,
|
||||
"version": "1.0.0",
|
||||
"user_agent": get_evennia_version('pretty')
|
||||
}
|
||||
}
|
||||
"user_agent": get_evennia_version("pretty"),
|
||||
},
|
||||
}
|
||||
# override on-the-fly
|
||||
data.update(kwargs)
|
||||
|
||||
|
|
@ -252,14 +250,11 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
|
||||
"""
|
||||
# pass along all connected players
|
||||
data = {
|
||||
"event": "heartbeat",
|
||||
"payload": {
|
||||
}
|
||||
}
|
||||
data = {"event": "heartbeat", "payload": {}}
|
||||
sessions = self.sessionhandler.get_sessions(include_unloggedin=False)
|
||||
data['payload']['players'] = [sess.account.key for sess in sessions
|
||||
if hasattr(sess, "account")]
|
||||
data["payload"]["players"] = [
|
||||
sess.account.key for sess in sessions if hasattr(sess, "account")
|
||||
]
|
||||
|
||||
self._send_json(data)
|
||||
|
||||
|
|
@ -269,12 +264,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
|
||||
Use with session.msg(subscribe="channelname")
|
||||
"""
|
||||
data = {
|
||||
"event": "channels/subscribe",
|
||||
"payload": {
|
||||
"channel": channelname
|
||||
}
|
||||
}
|
||||
data = {"event": "channels/subscribe", "payload": {"channel": channelname}}
|
||||
self._send_json(data)
|
||||
|
||||
def send_unsubscribe(self, channelname, *args, **kwargs):
|
||||
|
|
@ -283,12 +273,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
|
||||
Use with session.msg(unsubscribe="channelname")
|
||||
"""
|
||||
data = {
|
||||
"event": "channels/unsubscribe",
|
||||
"payload": {
|
||||
"channel": channelname
|
||||
}
|
||||
}
|
||||
data = {"event": "channels/unsubscribe", "payload": {"channel": channelname}}
|
||||
self._send_json(data)
|
||||
|
||||
def send_channel(self, text, channel, sender, *args, **kwargs):
|
||||
|
|
@ -303,11 +288,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
|
||||
data = {
|
||||
"event": "channels/send",
|
||||
"payload": {
|
||||
"message": text,
|
||||
"channel": channel,
|
||||
"name": sender
|
||||
}
|
||||
"payload": {"message": text, "channel": channel, "name": sender},
|
||||
}
|
||||
self._send_json(data)
|
||||
|
||||
|
|
@ -326,10 +307,10 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
data (dict): Converted json data.
|
||||
|
||||
"""
|
||||
event = data['event']
|
||||
event = data["event"]
|
||||
if event == "authenticate":
|
||||
# server replies to our auth handshake
|
||||
if data['status'] != "success":
|
||||
if data["status"] != "success":
|
||||
log_err("Grapevine authentication failed.")
|
||||
self.disconnect()
|
||||
else:
|
||||
|
|
@ -339,13 +320,14 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
self.send_heartbeat()
|
||||
elif event == "restart":
|
||||
# set the expected downtime
|
||||
self.restart_downtime = data['payload']['downtime']
|
||||
self.restart_downtime = data["payload"]["downtime"]
|
||||
elif event == "channels/subscribe":
|
||||
# subscription verification
|
||||
if data.get('status', 'success') == "failure":
|
||||
if data.get("status", "success") == "failure":
|
||||
err = data.get("error", "N/A")
|
||||
self.sessionhandler.data_in(bot_data_in=((f"Grapevine error: {err}"),
|
||||
{'event': event}))
|
||||
self.sessionhandler.data_in(
|
||||
bot_data_in=((f"Grapevine error: {err}"), {"event": event})
|
||||
)
|
||||
elif event == "channels/unsubscribe":
|
||||
# unsubscribe-verification
|
||||
pass
|
||||
|
|
@ -353,19 +335,24 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
# incoming broadcast from network
|
||||
payload = data["payload"]
|
||||
|
||||
print("channels/broadcast:", payload['channel'], self.channel)
|
||||
if str(payload['channel']) != self.channel:
|
||||
print("channels/broadcast:", payload["channel"], self.channel)
|
||||
if str(payload["channel"]) != self.channel:
|
||||
# only echo from channels this particular bot actually listens to
|
||||
return
|
||||
else:
|
||||
# correct channel
|
||||
self.sessionhandler.data_in(
|
||||
self, bot_data_in=(
|
||||
str(payload['message'],),
|
||||
{"event": event,
|
||||
"grapevine_channel": str(payload['channel']),
|
||||
"sender": str(payload['name']),
|
||||
"game": str(payload['game'])}))
|
||||
self,
|
||||
bot_data_in=(
|
||||
str(payload["message"]),
|
||||
{
|
||||
"event": event,
|
||||
"grapevine_channel": str(payload["channel"]),
|
||||
"sender": str(payload["name"]),
|
||||
"game": str(payload["game"]),
|
||||
},
|
||||
),
|
||||
)
|
||||
elif event == "channels/send":
|
||||
pass
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -49,52 +49,51 @@ IRC_GREY = "15"
|
|||
# test irc->evennia
|
||||
# Use Ctrl+C <num> to produce mIRC colors in e.g. irssi
|
||||
|
||||
IRC_COLOR_MAP = dict((
|
||||
(r'|n', IRC_COLOR + IRC_NORMAL), # normal mode
|
||||
(r'|H', IRC_RESET), # un-highlight
|
||||
(r'|/', "\n"), # line break
|
||||
(r'|t', " "), # tab
|
||||
(r'|-', " "), # fixed tab
|
||||
(r'|_', " "), # space
|
||||
(r'|*', IRC_INVERT), # invert
|
||||
(r'|^', ""), # blinking text
|
||||
(r'|h', IRC_BOLD), # highlight, use bold instead
|
||||
|
||||
(r'|r', IRC_COLOR + IRC_RED),
|
||||
(r'|g', IRC_COLOR + IRC_GREEN),
|
||||
(r'|y', IRC_COLOR + IRC_YELLOW),
|
||||
(r'|b', IRC_COLOR + IRC_BLUE),
|
||||
(r'|m', IRC_COLOR + IRC_MAGENTA),
|
||||
(r'|c', IRC_COLOR + IRC_CYAN),
|
||||
(r'|w', IRC_COLOR + IRC_WHITE), # pure white
|
||||
(r'|x', IRC_COLOR + IRC_DGREY), # dark grey
|
||||
|
||||
(r'|R', IRC_COLOR + IRC_DRED),
|
||||
(r'|G', IRC_COLOR + IRC_DGREEN),
|
||||
(r'|Y', IRC_COLOR + IRC_DYELLOW),
|
||||
(r'|B', IRC_COLOR + IRC_DBLUE),
|
||||
(r'|M', IRC_COLOR + IRC_DMAGENTA),
|
||||
(r'|C', IRC_COLOR + IRC_DCYAN),
|
||||
(r'|W', IRC_COLOR + IRC_GREY), # light grey
|
||||
(r'|X', IRC_COLOR + IRC_BLACK), # pure black
|
||||
|
||||
(r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
|
||||
(r'|[g', IRC_COLOR + IRC_NORMAL + "," + IRC_DGREEN),
|
||||
(r'|[y', IRC_COLOR + IRC_NORMAL + "," + IRC_DYELLOW),
|
||||
(r'|[b', IRC_COLOR + IRC_NORMAL + "," + IRC_DBLUE),
|
||||
(r'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
|
||||
(r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
|
||||
(r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GREY), # light grey background
|
||||
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
|
||||
))
|
||||
IRC_COLOR_MAP = dict(
|
||||
(
|
||||
(r"|n", IRC_COLOR + IRC_NORMAL), # normal mode
|
||||
(r"|H", IRC_RESET), # un-highlight
|
||||
(r"|/", "\n"), # line break
|
||||
(r"|t", " "), # tab
|
||||
(r"|-", " "), # fixed tab
|
||||
(r"|_", " "), # space
|
||||
(r"|*", IRC_INVERT), # invert
|
||||
(r"|^", ""), # blinking text
|
||||
(r"|h", IRC_BOLD), # highlight, use bold instead
|
||||
(r"|r", IRC_COLOR + IRC_RED),
|
||||
(r"|g", IRC_COLOR + IRC_GREEN),
|
||||
(r"|y", IRC_COLOR + IRC_YELLOW),
|
||||
(r"|b", IRC_COLOR + IRC_BLUE),
|
||||
(r"|m", IRC_COLOR + IRC_MAGENTA),
|
||||
(r"|c", IRC_COLOR + IRC_CYAN),
|
||||
(r"|w", IRC_COLOR + IRC_WHITE), # pure white
|
||||
(r"|x", IRC_COLOR + IRC_DGREY), # dark grey
|
||||
(r"|R", IRC_COLOR + IRC_DRED),
|
||||
(r"|G", IRC_COLOR + IRC_DGREEN),
|
||||
(r"|Y", IRC_COLOR + IRC_DYELLOW),
|
||||
(r"|B", IRC_COLOR + IRC_DBLUE),
|
||||
(r"|M", IRC_COLOR + IRC_DMAGENTA),
|
||||
(r"|C", IRC_COLOR + IRC_DCYAN),
|
||||
(r"|W", IRC_COLOR + IRC_GREY), # light grey
|
||||
(r"|X", IRC_COLOR + IRC_BLACK), # pure black
|
||||
(r"|[r", IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
|
||||
(r"|[g", IRC_COLOR + IRC_NORMAL + "," + IRC_DGREEN),
|
||||
(r"|[y", IRC_COLOR + IRC_NORMAL + "," + IRC_DYELLOW),
|
||||
(r"|[b", IRC_COLOR + IRC_NORMAL + "," + IRC_DBLUE),
|
||||
(r"|[m", IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
|
||||
(r"|[c", IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
|
||||
(r"|[w", IRC_COLOR + IRC_NORMAL + "," + IRC_GREY), # light grey background
|
||||
(r"|[x", IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
|
||||
)
|
||||
)
|
||||
# ansi->irc
|
||||
RE_ANSI_COLOR = re.compile(r"|".join(
|
||||
[re.escape(key) for key in IRC_COLOR_MAP.keys()]), re.DOTALL)
|
||||
RE_MXP = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL)
|
||||
RE_ANSI_COLOR = re.compile(r"|".join([re.escape(key) for key in IRC_COLOR_MAP.keys()]), re.DOTALL)
|
||||
RE_MXP = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
|
||||
# irc->ansi
|
||||
_CLR_LIST = [re.escape(val)
|
||||
for val in sorted(IRC_COLOR_MAP.values(), key=len, reverse=True) if val.strip()]
|
||||
_CLR_LIST = [
|
||||
re.escape(val) for val in sorted(IRC_COLOR_MAP.values(), key=len, reverse=True) if val.strip()
|
||||
]
|
||||
_CLR_LIST = _CLR_LIST[-2:] + _CLR_LIST[:-2]
|
||||
RE_IRC_COLOR = re.compile(r"|".join(_CLR_LIST), re.DOTALL)
|
||||
ANSI_COLOR_MAP = dict((tup[1], tup[0]) for tup in IRC_COLOR_MAP.items() if tup[1].strip())
|
||||
|
|
@ -122,7 +121,7 @@ def parse_ansi_to_irc(string):
|
|||
pstring = RE_ANSI_COLOR.sub(_sub_to_irc, part)
|
||||
parsed_string.append("%s%s" % (pstring, sep[0].strip()))
|
||||
# strip mxp
|
||||
parsed_string = RE_MXP.sub(r'\2', "".join(parsed_string))
|
||||
parsed_string = RE_MXP.sub(r"\2", "".join(parsed_string))
|
||||
return parsed_string
|
||||
|
||||
|
||||
|
|
@ -148,12 +147,14 @@ def parse_irc_to_ansi(string):
|
|||
|
||||
# IRC bot
|
||||
|
||||
|
||||
class IRCBot(irc.IRCClient, Session):
|
||||
"""
|
||||
An IRC bot that tracks activity in a channel as well
|
||||
as sends text to it when prompted
|
||||
|
||||
"""
|
||||
|
||||
lineRate = 1
|
||||
|
||||
# assigned by factory at creation
|
||||
|
|
@ -179,8 +180,10 @@ class IRCBot(irc.IRCClient, Session):
|
|||
self.uid = int(self.factory.uid)
|
||||
self.logged_in = True
|
||||
self.factory.sessionhandler.connect(self)
|
||||
logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel,
|
||||
self.network, self.port))
|
||||
logger.log_info(
|
||||
"IRC bot '%s' connected to %s at %s:%s."
|
||||
% (self.nickname, self.channel, self.network, self.port)
|
||||
)
|
||||
|
||||
def disconnect(self, reason=""):
|
||||
"""
|
||||
|
|
@ -209,11 +212,11 @@ class IRCBot(irc.IRCClient, Session):
|
|||
"""
|
||||
if channel == self.nickname:
|
||||
# private message
|
||||
user = user.split('!', 1)[0]
|
||||
user = user.split("!", 1)[0]
|
||||
self.data_in(text=msg, type="privmsg", user=user, channel=channel)
|
||||
elif not msg.startswith('***'):
|
||||
elif not msg.startswith("***"):
|
||||
# channel message
|
||||
user = user.split('!', 1)[0]
|
||||
user = user.split("!", 1)[0]
|
||||
user = ansi.raw(user)
|
||||
self.data_in(text=msg, type="msg", user=user, channel=channel)
|
||||
|
||||
|
|
@ -227,8 +230,8 @@ class IRCBot(irc.IRCClient, Session):
|
|||
msg (str): The message arriving from channel.
|
||||
|
||||
"""
|
||||
if not msg.startswith('**'):
|
||||
user = user.split('!', 1)[0]
|
||||
if not msg.startswith("**"):
|
||||
user = user.split("!", 1)[0]
|
||||
self.data_in(text=msg, type="action", user=user, channel=channel)
|
||||
|
||||
def get_nicklist(self):
|
||||
|
|
@ -245,14 +248,16 @@ class IRCBot(irc.IRCClient, Session):
|
|||
channel = params[2].lower()
|
||||
if channel != self.channel.lower():
|
||||
return
|
||||
self.nicklist += params[3].split(' ')
|
||||
self.nicklist += params[3].split(" ")
|
||||
|
||||
def irc_RPL_ENDOFNAMES(self, prefix, params):
|
||||
"""Called when the nicklist has finished being returned."""
|
||||
channel = params[1].lower()
|
||||
if channel != self.channel.lower():
|
||||
return
|
||||
self.data_in(text="", type="nicklist", user="server", channel=channel, nicklist=self.nicklist)
|
||||
self.data_in(
|
||||
text="", type="nicklist", user="server", channel=channel, nicklist=self.nicklist
|
||||
)
|
||||
self.nicklist = []
|
||||
|
||||
def pong(self, user, time):
|
||||
|
|
@ -348,12 +353,22 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
|||
increase in delay
|
||||
|
||||
"""
|
||||
|
||||
# scaling reconnect time
|
||||
initialDelay = 1
|
||||
factor = 1.5
|
||||
maxDelay = 60
|
||||
|
||||
def __init__(self, sessionhandler, uid=None, botname=None, channel=None, network=None, port=None, ssl=None):
|
||||
def __init__(
|
||||
self,
|
||||
sessionhandler,
|
||||
uid=None,
|
||||
botname=None,
|
||||
channel=None,
|
||||
network=None,
|
||||
port=None,
|
||||
ssl=None,
|
||||
):
|
||||
"""
|
||||
Storing some important protocol properties.
|
||||
|
||||
|
|
@ -452,7 +467,10 @@ class IRCBotFactory(protocol.ReconnectingClientFactory):
|
|||
if self.ssl:
|
||||
try:
|
||||
from twisted.internet import ssl
|
||||
service = reactor.connectSSL(self.network, int(self.port), self, ssl.ClientContextFactory())
|
||||
|
||||
service = reactor.connectSSL(
|
||||
self.network, int(self.port), self, ssl.ClientContextFactory()
|
||||
)
|
||||
except ImportError:
|
||||
logger.log_err("To use SSL, the PyOpenSSL module must be installed.")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ mccp_compress and calling it from its write methods.
|
|||
import zlib
|
||||
|
||||
# negotiations for v1 and v2 of the protocol
|
||||
MCCP = b'\x56'
|
||||
MCCP = b"\x56"
|
||||
FLUSH = zlib.Z_SYNC_FLUSH
|
||||
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ def mccp_compress(protocol, data):
|
|||
stream (binary): Zlib-compressed data.
|
||||
|
||||
"""
|
||||
if hasattr(protocol, 'zlib'):
|
||||
if hasattr(protocol, "zlib"):
|
||||
return protocol.zlib.compress(data) + protocol.zlib.flush(FLUSH)
|
||||
return data
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ class Mccp(object):
|
|||
"""
|
||||
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['MCCP'] = False
|
||||
self.protocol.protocol_flags["MCCP"] = False
|
||||
# ask if client will mccp, connect callbacks to handle answer
|
||||
self.protocol.will(MCCP).addCallbacks(self.do_mccp, self.no_mccp)
|
||||
|
||||
|
|
@ -69,9 +69,9 @@ class Mccp(object):
|
|||
option (Option): Option dict (not used).
|
||||
|
||||
"""
|
||||
if hasattr(self.protocol, 'zlib'):
|
||||
if hasattr(self.protocol, "zlib"):
|
||||
del self.protocol.zlib
|
||||
self.protocol.protocol_flags['MCCP'] = False
|
||||
self.protocol.protocol_flags["MCCP"] = False
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def do_mccp(self, option):
|
||||
|
|
@ -83,7 +83,7 @@ class Mccp(object):
|
|||
option (Option): Option dict (not used).
|
||||
|
||||
"""
|
||||
self.protocol.protocol_flags['MCCP'] = True
|
||||
self.protocol.requestNegotiation(MCCP, b'')
|
||||
self.protocol.protocol_flags["MCCP"] = True
|
||||
self.protocol.requestNegotiation(MCCP, b"")
|
||||
self.protocol.zlib = zlib.compressobj(9)
|
||||
self.protocol.handshake_done()
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ active players and so on.
|
|||
from django.conf import settings
|
||||
from evennia.utils import utils
|
||||
|
||||
MSSP = b'\x46'
|
||||
MSSP_VAR = b'\x01'
|
||||
MSSP_VAL = b'\x02'
|
||||
MSSP = b"\x46"
|
||||
MSSP_VAR = b"\x01"
|
||||
MSSP_VAL = b"\x02"
|
||||
|
||||
# try to get the customized mssp info, if it exists.
|
||||
MSSPTable_CUSTOM = utils.variable_from_module(settings.MSSP_META_MODULE, "MSSPTable", default={})
|
||||
|
|
@ -81,18 +81,16 @@ class Mssp(object):
|
|||
"""
|
||||
|
||||
self.mssp_table = {
|
||||
|
||||
# Required fields
|
||||
|
||||
"NAME": settings.SERVERNAME,
|
||||
"PLAYERS": self.get_player_count,
|
||||
"UPTIME": self.get_uptime,
|
||||
|
||||
"PORT": list(reversed(settings.TELNET_PORTS)), # most important port should be last in list
|
||||
|
||||
"PORT": list(
|
||||
reversed(settings.TELNET_PORTS)
|
||||
), # most important port should be last in list
|
||||
# Evennia auto-filled
|
||||
"CRAWL DELAY": "-1",
|
||||
"CODEBASE": utils.get_evennia_version(mode='pretty'),
|
||||
"CODEBASE": utils.get_evennia_version(mode="pretty"),
|
||||
"FAMILY": "Custom",
|
||||
"ANSI": "1",
|
||||
"GMCP": "1" if settings.TELNET_OOB_ENABLED else "0",
|
||||
|
|
@ -114,16 +112,17 @@ class Mssp(object):
|
|||
if MSSPTable_CUSTOM:
|
||||
self.mssp_table.update(MSSPTable_CUSTOM)
|
||||
|
||||
varlist = b''
|
||||
varlist = b""
|
||||
for variable, value in self.mssp_table.items():
|
||||
if callable(value):
|
||||
value = value()
|
||||
if utils.is_iter(value):
|
||||
for partval in value:
|
||||
varlist += (MSSP_VAR + bytes(variable, 'utf-8') +
|
||||
MSSP_VAL + bytes(partval, 'utf-8'))
|
||||
varlist += (
|
||||
MSSP_VAR + bytes(variable, "utf-8") + MSSP_VAL + bytes(partval, "utf-8")
|
||||
)
|
||||
else:
|
||||
varlist += MSSP_VAR + bytes(variable, 'utf-8') + MSSP_VAL + bytes(value, 'utf-8')
|
||||
varlist += MSSP_VAR + bytes(variable, "utf-8") + MSSP_VAL + bytes(value, "utf-8")
|
||||
|
||||
# send to crawler by subnegotiation
|
||||
self.protocol.requestNegotiation(MSSP, varlist)
|
||||
|
|
|
|||
|
|
@ -15,17 +15,13 @@ http://www.gammon.com.au/mushclient/addingservermxp.htm
|
|||
"""
|
||||
import re
|
||||
|
||||
LINKS_SUB = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL)
|
||||
LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||
|
||||
# MXP Telnet option
|
||||
MXP = b'\x5b'
|
||||
MXP = b"\x5b"
|
||||
|
||||
MXP_TEMPSECURE = "\x1B[4z"
|
||||
MXP_SEND = MXP_TEMPSECURE + \
|
||||
"<SEND HREF=\"\\1\">" + \
|
||||
"\\2" + \
|
||||
MXP_TEMPSECURE + \
|
||||
"</SEND>"
|
||||
MXP_SEND = MXP_TEMPSECURE + '<SEND HREF="\\1">' + "\\2" + MXP_TEMPSECURE + "</SEND>"
|
||||
|
||||
|
||||
def mxp_parse(text):
|
||||
|
|
@ -39,9 +35,7 @@ def mxp_parse(text):
|
|||
parsed (str): The parsed text.
|
||||
|
||||
"""
|
||||
text = text.replace("&", "&") \
|
||||
.replace("<", "<") \
|
||||
.replace(">", ">")
|
||||
text = text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
text = LINKS_SUB.sub(MXP_SEND, text)
|
||||
return text
|
||||
|
|
@ -85,5 +79,5 @@ class Mxp(object):
|
|||
|
||||
"""
|
||||
self.protocol.protocol_flags["MXP"] = True
|
||||
self.protocol.requestNegotiation(MXP, b'')
|
||||
self.protocol.requestNegotiation(MXP, b"")
|
||||
self.protocol.handshake_done()
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ client and update it when the size changes
|
|||
from codecs import encode as codecs_encode
|
||||
from django.conf import settings
|
||||
|
||||
NAWS = b'\x1f'
|
||||
IS = b'\x00'
|
||||
NAWS = b"\x1f"
|
||||
IS = b"\x00"
|
||||
# default taken from telnet specification
|
||||
DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
DEFAULT_HEIGHT = settings.CLIENT_DEFAULT_HEIGHT
|
||||
|
|
@ -39,8 +39,10 @@ class Naws(object):
|
|||
"""
|
||||
self.naws_step = 0
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['SCREENWIDTH'] = {0: DEFAULT_WIDTH} # windowID (0 is root):width
|
||||
self.protocol.protocol_flags['SCREENHEIGHT'] = {0: DEFAULT_HEIGHT} # windowID:width
|
||||
self.protocol.protocol_flags["SCREENWIDTH"] = {
|
||||
0: DEFAULT_WIDTH
|
||||
} # windowID (0 is root):width
|
||||
self.protocol.protocol_flags["SCREENHEIGHT"] = {0: DEFAULT_HEIGHT} # windowID:width
|
||||
self.protocol.negotiationMap[NAWS] = self.negotiate_sizes
|
||||
self.protocol.do(NAWS).addCallbacks(self.do_naws, self.no_naws)
|
||||
|
||||
|
|
@ -76,6 +78,6 @@ class Naws(object):
|
|||
if len(options) == 4:
|
||||
# NAWS is negotiated with 16bit words
|
||||
width = options[0] + options[1]
|
||||
self.protocol.protocol_flags['SCREENWIDTH'][0] = int(codecs_encode(width, 'hex'), 16)
|
||||
self.protocol.protocol_flags["SCREENWIDTH"][0] = int(codecs_encode(width, "hex"), 16)
|
||||
height = options[2] + options[3]
|
||||
self.protocol.protocol_flags['SCREENHEIGHT'][0] = int(codecs_encode(height, 'hex'), 16)
|
||||
self.protocol.protocol_flags["SCREENHEIGHT"][0] = int(codecs_encode(height, "hex"), 16)
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ from twisted.internet import protocol, reactor
|
|||
from twisted.python.log import ILogObserver
|
||||
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
from django.conf import settings
|
||||
|
||||
import evennia
|
||||
|
||||
evennia._init()
|
||||
|
||||
from evennia.utils.utils import get_evennia_version, mod_import, make_iter
|
||||
|
|
@ -36,7 +38,9 @@ try:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
PORTAL_SERVICES_PLUGIN_MODULES = [mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)]
|
||||
PORTAL_SERVICES_PLUGIN_MODULES = [
|
||||
mod_import(module) for module in make_iter(settings.PORTAL_SERVICES_PLUGIN_MODULES)
|
||||
]
|
||||
LOCKDOWN_MODE = settings.LOCKDOWN_MODE
|
||||
|
||||
# -------------------------------------------------------------
|
||||
|
|
@ -47,7 +51,7 @@ VERSION = get_evennia_version()
|
|||
|
||||
SERVERNAME = settings.SERVERNAME
|
||||
|
||||
PORTAL_RESTART = os.path.join(settings.GAME_DIR, "server", 'portal.restart')
|
||||
PORTAL_RESTART = os.path.join(settings.GAME_DIR, "server", "portal.restart")
|
||||
|
||||
TELNET_PORTS = settings.TELNET_PORTS
|
||||
SSL_PORTS = settings.SSL_PORTS
|
||||
|
|
@ -55,11 +59,11 @@ SSH_PORTS = settings.SSH_PORTS
|
|||
WEBSERVER_PORTS = settings.WEBSERVER_PORTS
|
||||
WEBSOCKET_CLIENT_PORT = settings.WEBSOCKET_CLIENT_PORT
|
||||
|
||||
TELNET_INTERFACES = ['127.0.0.1'] if LOCKDOWN_MODE else settings.TELNET_INTERFACES
|
||||
SSL_INTERFACES = ['127.0.0.1'] if LOCKDOWN_MODE else settings.SSL_INTERFACES
|
||||
SSH_INTERFACES = ['127.0.0.1'] if LOCKDOWN_MODE else settings.SSH_INTERFACES
|
||||
WEBSERVER_INTERFACES = ['127.0.0.1'] if LOCKDOWN_MODE else settings.WEBSERVER_INTERFACES
|
||||
WEBSOCKET_CLIENT_INTERFACE = '127.0.0.1' if LOCKDOWN_MODE else settings.WEBSOCKET_CLIENT_INTERFACE
|
||||
TELNET_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.TELNET_INTERFACES
|
||||
SSL_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.SSL_INTERFACES
|
||||
SSH_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.SSH_INTERFACES
|
||||
WEBSERVER_INTERFACES = ["127.0.0.1"] if LOCKDOWN_MODE else settings.WEBSERVER_INTERFACES
|
||||
WEBSOCKET_CLIENT_INTERFACE = "127.0.0.1" if LOCKDOWN_MODE else settings.WEBSOCKET_CLIENT_INTERFACE
|
||||
WEBSOCKET_CLIENT_URL = settings.WEBSOCKET_CLIENT_URL
|
||||
|
||||
TELNET_ENABLED = settings.TELNET_ENABLED and TELNET_PORTS and TELNET_INTERFACES
|
||||
|
|
@ -67,16 +71,29 @@ SSL_ENABLED = settings.SSL_ENABLED and SSL_PORTS and SSL_INTERFACES
|
|||
SSH_ENABLED = settings.SSH_ENABLED and SSH_PORTS and SSH_INTERFACES
|
||||
WEBSERVER_ENABLED = settings.WEBSERVER_ENABLED and WEBSERVER_PORTS and WEBSERVER_INTERFACES
|
||||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||
WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED and WEBSOCKET_CLIENT_PORT and WEBSOCKET_CLIENT_INTERFACE
|
||||
WEBSOCKET_CLIENT_ENABLED = (
|
||||
settings.WEBSOCKET_CLIENT_ENABLED and WEBSOCKET_CLIENT_PORT and WEBSOCKET_CLIENT_INTERFACE
|
||||
)
|
||||
|
||||
AMP_HOST = settings.AMP_HOST
|
||||
AMP_PORT = settings.AMP_PORT
|
||||
AMP_INTERFACE = settings.AMP_INTERFACE
|
||||
AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
|
||||
|
||||
INFO_DICT = {"servername": SERVERNAME, "version": VERSION, "errors": "", "info": "",
|
||||
"lockdown_mode": "", "amp": "", "telnet": [], "telnet_ssl": [], "ssh": [],
|
||||
"webclient": [], "webserver_proxy": [], "webserver_internal": []}
|
||||
INFO_DICT = {
|
||||
"servername": SERVERNAME,
|
||||
"version": VERSION,
|
||||
"errors": "",
|
||||
"info": "",
|
||||
"lockdown_mode": "",
|
||||
"amp": "",
|
||||
"telnet": [],
|
||||
"telnet_ssl": [],
|
||||
"ssh": [],
|
||||
"webclient": [],
|
||||
"webserver_proxy": [],
|
||||
"webserver_internal": [],
|
||||
}
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Portal Service object
|
||||
|
|
@ -100,7 +117,7 @@ class Portal(object):
|
|||
application (Application): An instantiated Twisted application
|
||||
|
||||
"""
|
||||
sys.path.append('.')
|
||||
sys.path.append(".")
|
||||
|
||||
# create a store of services
|
||||
self.services = service.MultiService()
|
||||
|
|
@ -122,8 +139,9 @@ class Portal(object):
|
|||
|
||||
# set a callback if the server is killed abruptly,
|
||||
# by Ctrl-C, reboot etc.
|
||||
reactor.addSystemEventTrigger('before', 'shutdown',
|
||||
self.shutdown, _reactor_stopping=True, _stop_server=True)
|
||||
reactor.addSystemEventTrigger(
|
||||
"before", "shutdown", self.shutdown, _reactor_stopping=True, _stop_server=True
|
||||
)
|
||||
|
||||
def _get_backup_server_twistd_cmd(self):
|
||||
"""
|
||||
|
|
@ -135,11 +153,13 @@ class Portal(object):
|
|||
"""
|
||||
server_twistd_cmd = [
|
||||
"twistd",
|
||||
"--python={}".format(os.path.join(dirname(dirname(abspath(__file__))), "server.py"))]
|
||||
if os.name != 'nt':
|
||||
"--python={}".format(os.path.join(dirname(dirname(abspath(__file__))), "server.py")),
|
||||
]
|
||||
if os.name != "nt":
|
||||
gamedir = os.getcwd()
|
||||
server_twistd_cmd.append("--pidfile={}".format(
|
||||
os.path.join(gamedir, "server", "server.pid")))
|
||||
server_twistd_cmd.append(
|
||||
"--pidfile={}".format(os.path.join(gamedir, "server", "server.pid"))
|
||||
)
|
||||
return server_twistd_cmd
|
||||
|
||||
def get_info_dict(self):
|
||||
|
|
@ -169,7 +189,7 @@ class Portal(object):
|
|||
|
||||
self.sessions.disconnect_all()
|
||||
if _stop_server:
|
||||
self.amp_protocol.stop_server(mode='shutdown')
|
||||
self.amp_protocol.stop_server(mode="shutdown")
|
||||
|
||||
if not _reactor_stopping:
|
||||
# shutting down the reactor will trigger another signal. We set
|
||||
|
|
@ -177,6 +197,7 @@ class Portal(object):
|
|||
self.shutdown_complete = True
|
||||
reactor.callLater(0, reactor.stop)
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Start the Portal proxy server and add all active services
|
||||
|
|
@ -186,13 +207,14 @@ class Portal(object):
|
|||
|
||||
# twistd requires us to define the variable 'application' so it knows
|
||||
# what to execute from.
|
||||
application = service.Application('Portal')
|
||||
application = service.Application("Portal")
|
||||
|
||||
# custom logging
|
||||
|
||||
if "--nodaemon" not in sys.argv:
|
||||
logfile = logger.WeeklyLogFile(os.path.basename(settings.PORTAL_LOG_FILE),
|
||||
os.path.dirname(settings.PORTAL_LOG_FILE))
|
||||
logfile = logger.WeeklyLogFile(
|
||||
os.path.basename(settings.PORTAL_LOG_FILE), os.path.dirname(settings.PORTAL_LOG_FILE)
|
||||
)
|
||||
application.setComponent(ILogObserver, logger.PortalLogObserver(logfile).emit)
|
||||
|
||||
# The main Portal server program. This sets up the database
|
||||
|
|
@ -201,7 +223,7 @@ PORTAL = Portal(application)
|
|||
|
||||
if LOCKDOWN_MODE:
|
||||
|
||||
INFO_DICT["lockdown_mode"] = ' LOCKDOWN_MODE active: Only local connections.'
|
||||
INFO_DICT["lockdown_mode"] = " LOCKDOWN_MODE active: Only local connections."
|
||||
|
||||
if AMP_ENABLED:
|
||||
|
||||
|
|
@ -211,7 +233,7 @@ if AMP_ENABLED:
|
|||
|
||||
from evennia.server.portal import amp_server
|
||||
|
||||
INFO_DICT["amp"] = 'amp: %s' % AMP_PORT
|
||||
INFO_DICT["amp"] = "amp: %s" % AMP_PORT
|
||||
|
||||
factory = amp_server.AMPServerFactory(PORTAL)
|
||||
amp_service = internet.TCPServer(AMP_PORT, factory, interface=AMP_INTERFACE)
|
||||
|
|
@ -230,7 +252,7 @@ if TELNET_ENABLED:
|
|||
|
||||
for interface in TELNET_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface not in ('0.0.0.0', '::') or len(TELNET_INTERFACES) > 1:
|
||||
if interface not in ("0.0.0.0", "::") or len(TELNET_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in TELNET_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
|
|
@ -239,10 +261,10 @@ if TELNET_ENABLED:
|
|||
factory.protocol = telnet.TelnetProtocol
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
telnet_service = internet.TCPServer(port, factory, interface=interface)
|
||||
telnet_service.setName('EvenniaTelnet%s' % pstring)
|
||||
telnet_service.setName("EvenniaTelnet%s" % pstring)
|
||||
PORTAL.services.addService(telnet_service)
|
||||
|
||||
INFO_DICT["telnet"].append('telnet%s: %s' % (ifacestr, port))
|
||||
INFO_DICT["telnet"].append("telnet%s: %s" % (ifacestr, port))
|
||||
|
||||
|
||||
if SSL_ENABLED:
|
||||
|
|
@ -253,7 +275,7 @@ if SSL_ENABLED:
|
|||
|
||||
for interface in SSL_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface not in ('0.0.0.0', '::') or len(SSL_INTERFACES) > 1:
|
||||
if interface not in ("0.0.0.0", "::") or len(SSL_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSL_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
|
|
@ -264,17 +286,17 @@ if SSL_ENABLED:
|
|||
|
||||
ssl_context = telnet_ssl.getSSLContext()
|
||||
if ssl_context:
|
||||
ssl_service = internet.SSLServer(port,
|
||||
factory,
|
||||
telnet_ssl.getSSLContext(),
|
||||
interface=interface)
|
||||
ssl_service.setName('EvenniaSSL%s' % pstring)
|
||||
ssl_service = internet.SSLServer(
|
||||
port, factory, telnet_ssl.getSSLContext(), interface=interface
|
||||
)
|
||||
ssl_service.setName("EvenniaSSL%s" % pstring)
|
||||
PORTAL.services.addService(ssl_service)
|
||||
|
||||
INFO_DICT["telnet_ssl"].append("telnet+ssl%s: %s" % (ifacestr, port))
|
||||
else:
|
||||
INFO_DICT["telnet_ssl"].append(
|
||||
"telnet+ssl%s: %s (deactivated - keys/cert unset)" % (ifacestr, port))
|
||||
"telnet+ssl%s: %s (deactivated - keys/cert unset)" % (ifacestr, port)
|
||||
)
|
||||
|
||||
|
||||
if SSH_ENABLED:
|
||||
|
|
@ -286,16 +308,20 @@ if SSH_ENABLED:
|
|||
|
||||
for interface in SSH_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface not in ('0.0.0.0', '::') or len(SSH_INTERFACES) > 1:
|
||||
if interface not in ("0.0.0.0", "::") or len(SSH_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for port in SSH_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = ssh.makeFactory({'protocolFactory': ssh.SshProtocol,
|
||||
'protocolArgs': (),
|
||||
'sessions': PORTAL_SESSIONS})
|
||||
factory = ssh.makeFactory(
|
||||
{
|
||||
"protocolFactory": ssh.SshProtocol,
|
||||
"protocolArgs": (),
|
||||
"sessions": PORTAL_SESSIONS,
|
||||
}
|
||||
)
|
||||
factory.noisy = False
|
||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||
ssh_service.setName('EvenniaSSH%s' % pstring)
|
||||
ssh_service.setName("EvenniaSSH%s" % pstring)
|
||||
PORTAL.services.addService(ssh_service)
|
||||
|
||||
INFO_DICT["ssh"].append("ssh%s: %s" % (ifacestr, port))
|
||||
|
|
@ -309,10 +335,10 @@ if WEBSERVER_ENABLED:
|
|||
websocket_started = False
|
||||
for interface in WEBSERVER_INTERFACES:
|
||||
ifacestr = ""
|
||||
if interface not in ('0.0.0.0', '::') or len(WEBSERVER_INTERFACES) > 1:
|
||||
if interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1:
|
||||
ifacestr = "-%s" % interface
|
||||
for proxyport, serverport in WEBSERVER_PORTS:
|
||||
web_root = EvenniaReverseProxyResource('127.0.0.1', serverport, '')
|
||||
web_root = EvenniaReverseProxyResource("127.0.0.1", serverport, "")
|
||||
webclientstr = ""
|
||||
if WEBCLIENT_ENABLED:
|
||||
# create ajax client processes at /webclientdata
|
||||
|
|
@ -330,8 +356,8 @@ if WEBSERVER_ENABLED:
|
|||
from autobahn.twisted.websocket import WebSocketServerFactory
|
||||
|
||||
w_interface = WEBSOCKET_CLIENT_INTERFACE
|
||||
w_ifacestr = ''
|
||||
if w_interface not in ('0.0.0.0', '::') or len(WEBSERVER_INTERFACES) > 1:
|
||||
w_ifacestr = ""
|
||||
if w_interface not in ("0.0.0.0", "::") or len(WEBSERVER_INTERFACES) > 1:
|
||||
w_ifacestr = "-%s" % interface
|
||||
port = WEBSOCKET_CLIENT_PORT
|
||||
|
||||
|
|
@ -344,7 +370,7 @@ if WEBSERVER_ENABLED:
|
|||
factory.protocol = webclient.WebSocketClient
|
||||
factory.sessionhandler = PORTAL_SESSIONS
|
||||
websocket_service = internet.TCPServer(port, factory, interface=w_interface)
|
||||
websocket_service.setName('EvenniaWebSocket%s:%s' % (w_ifacestr, port))
|
||||
websocket_service.setName("EvenniaWebSocket%s:%s" % (w_ifacestr, port))
|
||||
PORTAL.services.addService(websocket_service)
|
||||
websocket_started = True
|
||||
webclientstr = "webclient-websocket%s: %s" % (w_ifacestr, port)
|
||||
|
|
@ -352,10 +378,8 @@ if WEBSERVER_ENABLED:
|
|||
|
||||
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||
web_root.is_portal = True
|
||||
proxy_service = internet.TCPServer(proxyport,
|
||||
web_root,
|
||||
interface=interface)
|
||||
proxy_service.setName('EvenniaWebProxy%s:%s' % (ifacestr, proxyport))
|
||||
proxy_service = internet.TCPServer(proxyport, web_root, interface=interface)
|
||||
proxy_service.setName("EvenniaWebProxy%s:%s" % (ifacestr, proxyport))
|
||||
PORTAL.services.addService(proxy_service)
|
||||
INFO_DICT["webserver_proxy"].append("webserver-proxy%s: %s" % (ifacestr, proxyport))
|
||||
INFO_DICT["webserver_internal"].append("webserver: %s" % serverport)
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ Sessionhandler for portal sessions
|
|||
"""
|
||||
|
||||
|
||||
|
||||
import time
|
||||
from collections import deque, namedtuple
|
||||
from twisted.internet import reactor
|
||||
from django.conf import settings
|
||||
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, \
|
||||
PCONNSYNC, PDISCONNALL
|
||||
from evennia.server.sessionhandler import SessionHandler, PCONN, PDISCONN, PCONNSYNC, PDISCONNALL
|
||||
from evennia.utils.logger import log_trace
|
||||
|
||||
# module import
|
||||
|
|
@ -29,7 +27,7 @@ _ERROR_MAX_CHAR = settings.MAX_CHAR_LIMIT_WARNING
|
|||
|
||||
_CONNECTION_QUEUE = deque()
|
||||
|
||||
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
|
||||
DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Portal-SessionHandler class
|
||||
|
|
@ -98,19 +96,34 @@ class PortalSessionHandler(SessionHandler):
|
|||
session.server_connected = False
|
||||
_CONNECTION_QUEUE.appendleft(session)
|
||||
if len(_CONNECTION_QUEUE) > 1:
|
||||
session.data_out(text=[["%s DoS protection is active. You are queued to connect in %g seconds ..." % (
|
||||
settings.SERVERNAME,
|
||||
len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS)], {}])
|
||||
session.data_out(
|
||||
text=[
|
||||
[
|
||||
"%s DoS protection is active. You are queued to connect in %g seconds ..."
|
||||
% (
|
||||
settings.SERVERNAME,
|
||||
len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS,
|
||||
)
|
||||
],
|
||||
{},
|
||||
]
|
||||
)
|
||||
now = time.time()
|
||||
if (now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS) or not self.portal.amp_protocol:
|
||||
if (
|
||||
now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS
|
||||
) or not self.portal.amp_protocol:
|
||||
if not session or not self.connection_task:
|
||||
self.connection_task = reactor.callLater(_MIN_TIME_BETWEEN_CONNECTS, self.connect, None)
|
||||
self.connection_task = reactor.callLater(
|
||||
_MIN_TIME_BETWEEN_CONNECTS, self.connect, None
|
||||
)
|
||||
self.connection_last = now
|
||||
return
|
||||
elif not session:
|
||||
if _CONNECTION_QUEUE:
|
||||
# keep launching tasks until queue is empty
|
||||
self.connection_task = reactor.callLater(_MIN_TIME_BETWEEN_CONNECTS, self.connect, None)
|
||||
self.connection_task = reactor.callLater(
|
||||
_MIN_TIME_BETWEEN_CONNECTS, self.connect, None
|
||||
)
|
||||
else:
|
||||
self.connection_task = None
|
||||
self.connection_last = now
|
||||
|
|
@ -122,9 +135,9 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
self[session.sessid] = session
|
||||
session.server_connected = True
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(session,
|
||||
operation=PCONN,
|
||||
sessiondata=sessdata)
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(
|
||||
session, operation=PCONN, sessiondata=sessdata
|
||||
)
|
||||
|
||||
def sync(self, session):
|
||||
"""
|
||||
|
|
@ -144,16 +157,23 @@ class PortalSessionHandler(SessionHandler):
|
|||
if self.portal.amp_protocol:
|
||||
# we only send sessdata that should not have changed
|
||||
# at the server level at this point
|
||||
sessdata = dict((key, val) for key, val in sessdata.items() if key in ("protocol_key",
|
||||
"address",
|
||||
"sessid",
|
||||
"csessid",
|
||||
"conn_time",
|
||||
"protocol_flags",
|
||||
"server_data",))
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(session,
|
||||
operation=PCONNSYNC,
|
||||
sessiondata=sessdata)
|
||||
sessdata = dict(
|
||||
(key, val)
|
||||
for key, val in sessdata.items()
|
||||
if key
|
||||
in (
|
||||
"protocol_key",
|
||||
"address",
|
||||
"sessid",
|
||||
"csessid",
|
||||
"conn_time",
|
||||
"protocol_flags",
|
||||
"server_data",
|
||||
)
|
||||
)
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(
|
||||
session, operation=PCONNSYNC, sessiondata=sessdata
|
||||
)
|
||||
|
||||
def disconnect(self, session):
|
||||
"""
|
||||
|
|
@ -187,6 +207,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
"""
|
||||
Disconnect all sessions, informing the Server.
|
||||
"""
|
||||
|
||||
def _callback(result, sessionhandler):
|
||||
# we set a watchdog to stop self.disconnect from deleting
|
||||
# sessions while we are looping over them.
|
||||
|
|
@ -197,8 +218,9 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
# inform Server; wait until finished sending before we continue
|
||||
# removing all the sessions.
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION,
|
||||
operation=PDISCONNALL).addCallback(_callback, self)
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(
|
||||
DUMMYSESSION, operation=PDISCONNALL
|
||||
).addCallback(_callback, self)
|
||||
|
||||
def server_connect(self, protocol_path="", config=dict()):
|
||||
"""
|
||||
|
|
@ -324,8 +346,11 @@ class PortalSessionHandler(SessionHandler):
|
|||
session (list): The matching session, if found.
|
||||
|
||||
"""
|
||||
return [sess for sess in self.get_sessions(include_unloggedin=True)
|
||||
if hasattr(sess, 'csessid') and sess.csessid and sess.csessid == csessid]
|
||||
return [
|
||||
sess
|
||||
for sess in self.get_sessions(include_unloggedin=True)
|
||||
if hasattr(sess, "csessid") and sess.csessid and sess.csessid == csessid
|
||||
]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
|
|
@ -358,7 +383,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
"""
|
||||
try:
|
||||
text = kwargs['text']
|
||||
text = kwargs["text"]
|
||||
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
|
||||
if session:
|
||||
self.data_out(session, text=[[_ERROR_MAX_CHAR], {}])
|
||||
|
|
@ -398,8 +423,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
# relay data to Server
|
||||
session.cmd_last = now
|
||||
self.portal.amp_protocol.send_MsgPortal2Server(session,
|
||||
**kwargs)
|
||||
self.portal.amp_protocol.send_MsgPortal2Server(session, **kwargs)
|
||||
|
||||
def data_out(self, session, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ from evennia.server.session import Session
|
|||
from evennia.utils import logger
|
||||
|
||||
RSS_ENABLED = settings.RSS_ENABLED
|
||||
#RETAG = re.compile(r'<[^>]*?>')
|
||||
# RETAG = re.compile(r'<[^>]*?>')
|
||||
|
||||
if RSS_ENABLED:
|
||||
try:
|
||||
import feedparser
|
||||
except ImportError:
|
||||
raise ImportError("RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False.")
|
||||
raise ImportError(
|
||||
"RSS requires python-feedparser to be installed. Install or set RSS_ENABLED=False."
|
||||
)
|
||||
|
||||
|
||||
class RSSReader(Session):
|
||||
|
|
@ -48,8 +50,8 @@ class RSSReader(Session):
|
|||
"""
|
||||
feed = feedparser.parse(self.url)
|
||||
new_entries = []
|
||||
for entry in feed['entries']:
|
||||
idval = entry['id'] + entry.get("updated", "")
|
||||
for entry in feed["entries"]:
|
||||
idval = entry["id"] + entry.get("updated", "")
|
||||
if idval not in self.old_entries:
|
||||
self.old_entries[idval] = entry
|
||||
new_entries.append(entry)
|
||||
|
|
@ -110,7 +112,11 @@ class RSSReader(Session):
|
|||
on slow connections.
|
||||
|
||||
"""
|
||||
return threads.deferToThread(self.get_new).addCallback(self._callback, init).addErrback(self._errback)
|
||||
return (
|
||||
threads.deferToThread(self.get_new)
|
||||
.addCallback(self._callback, init)
|
||||
.addErrback(self._errback)
|
||||
)
|
||||
|
||||
|
||||
class RSSBotFactory(object):
|
||||
|
|
@ -140,6 +146,7 @@ class RSSBotFactory(object):
|
|||
"""
|
||||
Called by portalsessionhandler. Starts the bot.
|
||||
"""
|
||||
|
||||
def errback(fail):
|
||||
logger.log_err(fail.value)
|
||||
|
||||
|
|
|
|||
|
|
@ -49,16 +49,18 @@ from evennia.utils import ansi
|
|||
from evennia.utils.utils import to_str
|
||||
|
||||
_RE_N = re.compile(r"\|n$")
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_RE_SCREENREADER_REGEX = re.compile(
|
||||
r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
|
||||
)
|
||||
_GAME_DIR = settings.GAME_DIR
|
||||
_PRIVATE_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssh-private.key")
|
||||
_PUBLIC_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssh-public.key")
|
||||
_KEY_LENGTH = 2048
|
||||
|
||||
CTRL_C = '\x03'
|
||||
CTRL_D = '\x04'
|
||||
CTRL_BACKSLASH = '\x1c'
|
||||
CTRL_L = '\x0c'
|
||||
CTRL_C = "\x03"
|
||||
CTRL_D = "\x04"
|
||||
CTRL_BACKSLASH = "\x1c"
|
||||
CTRL_L = "\x0c"
|
||||
|
||||
_NO_AUTOGEN = """
|
||||
Evennia could not generate SSH private- and public keys ({{err}})
|
||||
|
|
@ -68,7 +70,9 @@ If this error persists, create the keys manually (using the tools for your OS)
|
|||
and put them here:
|
||||
{}
|
||||
{}
|
||||
""".format(_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE)
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
||||
)
|
||||
|
||||
|
||||
# not used atm
|
||||
|
|
@ -87,6 +91,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
here.
|
||||
|
||||
"""
|
||||
|
||||
noisy = False
|
||||
|
||||
def __init__(self, starttuple):
|
||||
|
|
@ -162,7 +167,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
|
||||
"""
|
||||
if self.lineBuffer:
|
||||
self.terminal.write('\a')
|
||||
self.terminal.write("\a")
|
||||
else:
|
||||
self.handle_QUIT()
|
||||
|
||||
|
|
@ -229,7 +234,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
string (str): Output text.
|
||||
|
||||
"""
|
||||
for line in string.split('\n'):
|
||||
for line in string.split("\n"):
|
||||
# the telnet-specific method for sending
|
||||
self.terminal.write(line)
|
||||
self.terminal.nextLine()
|
||||
|
|
@ -251,7 +256,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
|
||||
"""
|
||||
if reason:
|
||||
self.data_out(text=((reason, ), {}))
|
||||
self.data_out(text=((reason,), {}))
|
||||
self.connectionLost(reason)
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
|
|
@ -294,8 +299,8 @@ class SshProtocol(Manhole, session.Session):
|
|||
# handle arguments
|
||||
options = kwargs.get("options", {})
|
||||
flags = self.protocol_flags
|
||||
xterm256 = options.get("xterm256", flags.get('XTERM256', True))
|
||||
useansi = options.get("ansi", flags.get('ANSI', True))
|
||||
xterm256 = options.get("xterm256", flags.get("XTERM256", True))
|
||||
useansi = options.get("ansi", flags.get("ANSI", True))
|
||||
raw = options.get("raw", flags.get("RAW", False))
|
||||
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
|
||||
# echo = options.get("echo", None) # DEBUG
|
||||
|
|
@ -313,8 +318,12 @@ class SshProtocol(Manhole, session.Session):
|
|||
else:
|
||||
# we need to make sure to kill the color at the end in order
|
||||
# to match the webclient output.
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256, mxp=False)
|
||||
linetosend = ansi.parse_ansi(
|
||||
_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor,
|
||||
xterm256=xterm256,
|
||||
mxp=False,
|
||||
)
|
||||
self.sendLine(linetosend)
|
||||
|
||||
def send_prompt(self, *args, **kwargs):
|
||||
|
|
@ -342,8 +351,7 @@ class ExtraInfoAuthServer(SSHUserAuthServer):
|
|||
password = common.getNS(packet[1:])[0]
|
||||
c = credentials.UsernamePassword(self.user, password)
|
||||
c.transport = self.transport
|
||||
return self.portal.login(c, None, IConchUser).addErrback(
|
||||
self._ebPassword)
|
||||
return self.portal.login(c, None, IConchUser).addErrback(self._ebPassword)
|
||||
|
||||
|
||||
class AccountDBPasswordChecker(object):
|
||||
|
|
@ -353,6 +361,7 @@ class AccountDBPasswordChecker(object):
|
|||
useful for the Realm.
|
||||
|
||||
"""
|
||||
|
||||
noisy = False
|
||||
credentialInterfaces = (credentials.IUsernamePassword,)
|
||||
|
||||
|
|
@ -422,9 +431,12 @@ class TerminalSessionTransport_getPeer(object):
|
|||
session = self.proto.session
|
||||
|
||||
self.proto.makeConnection(
|
||||
_Glue(write=self.chainedProtocol.dataReceived,
|
||||
loseConnection=lambda: avatar.conn.sendClose(session),
|
||||
name="SSH Proto Transport"))
|
||||
_Glue(
|
||||
write=self.chainedProtocol.dataReceived,
|
||||
loseConnection=lambda: avatar.conn.sendClose(session),
|
||||
name="SSH Proto Transport",
|
||||
)
|
||||
)
|
||||
|
||||
def loseConnection():
|
||||
self.proto.loseConnection()
|
||||
|
|
@ -433,9 +445,13 @@ class TerminalSessionTransport_getPeer(object):
|
|||
return session.conn.transport.transport.getPeer()
|
||||
|
||||
self.chainedProtocol.makeConnection(
|
||||
_Glue(getPeer=getPeer, write=self.proto.write,
|
||||
loseConnection=loseConnection,
|
||||
name="Chained Proto Transport"))
|
||||
_Glue(
|
||||
getPeer=getPeer,
|
||||
write=self.proto.write,
|
||||
loseConnection=loseConnection,
|
||||
name="Chained Proto Transport",
|
||||
)
|
||||
)
|
||||
|
||||
self.chainedProtocol.terminalProtocol.terminalSize(width, height)
|
||||
|
||||
|
|
@ -455,10 +471,10 @@ def getKeyPair(pubkeyfile, privkeyfile):
|
|||
private_key_string = rsa_key.toString(type="OPENSSH")
|
||||
|
||||
# save keys for the future.
|
||||
with open(privkeyfile, 'wt') as pfile:
|
||||
with open(privkeyfile, "wt") as pfile:
|
||||
pfile.write(private_key_string)
|
||||
print("Created SSH private key in '{}'".format(_PRIVATE_KEY_FILE))
|
||||
with open(pubkeyfile, 'wt') as pfile:
|
||||
with open(pubkeyfile, "wt") as pfile:
|
||||
pfile.write(public_key_string)
|
||||
print("Created SSH public key in '{}'".format(_PUBLIC_KEY_FILE))
|
||||
else:
|
||||
|
|
@ -474,28 +490,30 @@ def makeFactory(configdict):
|
|||
"""
|
||||
Creates the ssh server factory.
|
||||
"""
|
||||
|
||||
def chainProtocolFactory(username=None):
|
||||
return insults.ServerProtocol(
|
||||
configdict['protocolFactory'],
|
||||
*configdict.get('protocolConfigdict', (username,)),
|
||||
**configdict.get('protocolKwArgs', {}))
|
||||
configdict["protocolFactory"],
|
||||
*configdict.get("protocolConfigdict", (username,)),
|
||||
**configdict.get("protocolKwArgs", {}),
|
||||
)
|
||||
|
||||
rlm = PassAvatarIdTerminalRealm()
|
||||
rlm.transportFactory = TerminalSessionTransport_getPeer
|
||||
rlm.chainedProtocolFactory = chainProtocolFactory
|
||||
factory = ConchFactory(Portal(rlm))
|
||||
factory.sessionhandler = configdict['sessions']
|
||||
factory.sessionhandler = configdict["sessions"]
|
||||
|
||||
try:
|
||||
# create/get RSA keypair
|
||||
publicKey, privateKey = getKeyPair(_PUBLIC_KEY_FILE, _PRIVATE_KEY_FILE)
|
||||
factory.publicKeys = {b'ssh-rsa': publicKey}
|
||||
factory.privateKeys = {b'ssh-rsa': privateKey}
|
||||
factory.publicKeys = {b"ssh-rsa": publicKey}
|
||||
factory.privateKeys = {b"ssh-rsa": privateKey}
|
||||
except Exception as err:
|
||||
print(_NO_AUTOGEN.format(err=err))
|
||||
|
||||
factory.services = factory.services.copy()
|
||||
factory.services['ssh-userauth'] = ExtraInfoAuthServer
|
||||
factory.services["ssh-userauth"] = ExtraInfoAuthServer
|
||||
|
||||
factory.portal.registerChecker(AccountDBPasswordChecker(factory))
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ SSL keys and certificates.
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
from twisted.internet import ssl as twisted_ssl
|
||||
|
|
@ -67,14 +68,14 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
from Crypto.PublicKey import RSA
|
||||
from twisted.conch.ssh.keys import Key
|
||||
|
||||
print(" Creating SSL key and certificate ... ", end=' ')
|
||||
print(" Creating SSL key and certificate ... ", end=" ")
|
||||
|
||||
try:
|
||||
# create the RSA key and store it.
|
||||
KEY_LENGTH = 1024
|
||||
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
||||
keyString = rsaKey.toString(type="OPENSSH")
|
||||
file(keyfile, 'w+b').write(keyString)
|
||||
file(keyfile, "w+b").write(keyString)
|
||||
except Exception as err:
|
||||
print(NO_AUTOGEN.format(err=err, keyfile=keyfile))
|
||||
sys.exit(5)
|
||||
|
|
@ -83,11 +84,17 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
||||
# default:
|
||||
# openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
|
||||
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
|
||||
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (
|
||||
keyfile,
|
||||
certfile,
|
||||
CERT_EXPIRE,
|
||||
)
|
||||
try:
|
||||
subprocess.call(exestring)
|
||||
except OSError as err:
|
||||
raise OSError(NO_AUTOCERT.format(err=err, certfile=certfile, keyfile=keyfile, exestring=exestring))
|
||||
raise OSError(
|
||||
NO_AUTOCERT.format(err=err, certfile=certfile, keyfile=keyfile, exestring=exestring)
|
||||
)
|
||||
print("done.")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ It is set as the NOGOAHEAD protocol_flag option.
|
|||
http://www.faqs.org/rfcs/rfc858.html
|
||||
|
||||
"""
|
||||
SUPPRESS_GA = b'\x03'
|
||||
SUPPRESS_GA = b"\x03"
|
||||
|
||||
# default taken from telnet specification
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,19 @@ import re
|
|||
from twisted.internet import protocol
|
||||
from twisted.internet.task import LoopingCall
|
||||
from twisted.conch.telnet import Telnet, StatefulTelnetProtocol
|
||||
from twisted.conch.telnet import (IAC, NOP, LINEMODE, GA, WILL, WONT, ECHO, NULL,
|
||||
MODE, LINEMODE_EDIT, LINEMODE_TRAPSIG)
|
||||
from twisted.conch.telnet import (
|
||||
IAC,
|
||||
NOP,
|
||||
LINEMODE,
|
||||
GA,
|
||||
WILL,
|
||||
WONT,
|
||||
ECHO,
|
||||
NULL,
|
||||
MODE,
|
||||
LINEMODE_EDIT,
|
||||
LINEMODE_TRAPSIG,
|
||||
)
|
||||
from django.conf import settings
|
||||
from evennia.server.session import Session
|
||||
from evennia.server.portal import ttype, mssp, telnet_oob, naws, suppress_ga
|
||||
|
|
@ -24,7 +35,9 @@ from evennia.utils.utils import to_bytes
|
|||
_RE_N = re.compile(r"\|n$")
|
||||
_RE_LEND = re.compile(br"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
|
||||
_RE_LINEBREAK = re.compile(br"\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)
|
||||
_RE_SCREENREADER_REGEX = re.compile(
|
||||
r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
|
||||
)
|
||||
_IDLE_COMMAND = str.encode(settings.IDLE_COMMAND + "\n")
|
||||
|
||||
|
||||
|
|
@ -63,7 +76,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp
|
||||
|
||||
self.init_session(self.protocol_key, client_address, self.factory.sessionhandler)
|
||||
self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] if settings.ENCODINGS else 'utf-8'
|
||||
self.protocol_flags["ENCODING"] = settings.ENCODINGS[0] if settings.ENCODINGS else "utf-8"
|
||||
# add this new connection to sessionhandler so
|
||||
# the Server becomes aware of it.
|
||||
self.sessionhandler.connect(self)
|
||||
|
|
@ -86,6 +99,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.mxp = Mxp(self)
|
||||
|
||||
from evennia.utils.utils import delay
|
||||
|
||||
# timeout the handshakes in case the client doesn't reply at all
|
||||
self._handshake_delay = delay(2, callback=self.handshake_done, timeout=True)
|
||||
|
||||
|
|
@ -151,16 +165,18 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
"""
|
||||
if option == LINEMODE:
|
||||
# make sure to activate line mode with local editing for all clients
|
||||
self.requestNegotiation(LINEMODE, MODE +
|
||||
bytes(chr(ord(LINEMODE_EDIT) +
|
||||
ord(LINEMODE_TRAPSIG)), 'ascii'))
|
||||
self.requestNegotiation(
|
||||
LINEMODE, MODE + bytes(chr(ord(LINEMODE_EDIT) + ord(LINEMODE_TRAPSIG)), "ascii")
|
||||
)
|
||||
return True
|
||||
else:
|
||||
return (option == ttype.TTYPE or
|
||||
option == naws.NAWS or
|
||||
option == MCCP or
|
||||
option == mssp.MSSP or
|
||||
option == suppress_ga.SUPPRESS_GA)
|
||||
return (
|
||||
option == ttype.TTYPE
|
||||
or option == naws.NAWS
|
||||
or option == MCCP
|
||||
or option == mssp.MSSP
|
||||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
def enableLocal(self, option):
|
||||
"""
|
||||
|
|
@ -173,10 +189,12 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
enable (bool): If this option should be enabled.
|
||||
|
||||
"""
|
||||
return (option == LINEMODE or
|
||||
option == MCCP or
|
||||
option == ECHO or
|
||||
option == suppress_ga.SUPPRESS_GA)
|
||||
return (
|
||||
option == LINEMODE
|
||||
or option == MCCP
|
||||
or option == ECHO
|
||||
or option == suppress_ga.SUPPRESS_GA
|
||||
)
|
||||
|
||||
def disableLocal(self, option):
|
||||
"""
|
||||
|
|
@ -242,7 +260,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
|
||||
def _write(self, data):
|
||||
"""hook overloading the one used in plain telnet"""
|
||||
data = data.replace(b'\n', b'\r\n').replace(b'\r\r\n', b'\r\n')
|
||||
data = data.replace(b"\n", b"\r\n").replace(b"\r\r\n", b"\r\n")
|
||||
super()._write(mccp_compress(self, data))
|
||||
|
||||
def sendLine(self, line):
|
||||
|
|
@ -256,7 +274,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
line = to_bytes(line, self)
|
||||
# escape IAC in line mode, and correctly add \r\n (the TELNET end-of-line)
|
||||
line = line.replace(IAC, IAC + IAC)
|
||||
line = line.replace(b'\n', b'\r\n')
|
||||
line = line.replace(b"\n", b"\r\n")
|
||||
if not line.endswith(b"\r\n") and self.protocol_flags.get("FORCEDENDLINE", True):
|
||||
line += b"\r\n"
|
||||
if not self.protocol_flags.get("NOGOAHEAD", True):
|
||||
|
|
@ -330,8 +348,12 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
# handle arguments
|
||||
options = kwargs.get("options", {})
|
||||
flags = self.protocol_flags
|
||||
xterm256 = options.get("xterm256", flags.get('XTERM256', False) if flags.get("TTYPE", False) else True)
|
||||
useansi = options.get("ansi", flags.get('ANSI', False) if flags.get("TTYPE", False) else True)
|
||||
xterm256 = options.get(
|
||||
"xterm256", flags.get("XTERM256", False) if flags.get("TTYPE", False) else True
|
||||
)
|
||||
useansi = options.get(
|
||||
"ansi", flags.get("ANSI", False) if flags.get("TTYPE", False) else True
|
||||
)
|
||||
raw = options.get("raw", flags.get("RAW", False))
|
||||
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
|
||||
echo = options.get("echo", None)
|
||||
|
|
@ -348,12 +370,15 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
prompt = text
|
||||
if not raw:
|
||||
# processing
|
||||
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("||n" if prompt.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256)
|
||||
prompt = ansi.parse_ansi(
|
||||
_RE_N.sub("", prompt) + ("||n" if prompt.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor,
|
||||
xterm256=xterm256,
|
||||
)
|
||||
if mxp:
|
||||
prompt = mxp_parse(prompt)
|
||||
prompt = to_bytes(prompt, self)
|
||||
prompt = prompt.replace(IAC, IAC + IAC).replace(b'\n', b'\r\n')
|
||||
prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n")
|
||||
prompt += IAC + GA
|
||||
self.transport.write(mccp_compress(self, prompt))
|
||||
else:
|
||||
|
|
@ -377,8 +402,12 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
else:
|
||||
# we need to make sure to kill the color at the end in order
|
||||
# to match the webclient output.
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
|
||||
linetosend = ansi.parse_ansi(
|
||||
_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
|
||||
strip_ansi=nocolor,
|
||||
xterm256=xterm256,
|
||||
mxp=mxp,
|
||||
)
|
||||
if mxp:
|
||||
linetosend = mxp_parse(linetosend)
|
||||
self.sendLine(linetosend)
|
||||
|
|
|
|||
|
|
@ -30,16 +30,16 @@ import json
|
|||
from evennia.utils.utils import is_iter
|
||||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = b'\x45'
|
||||
MSDP_VAR = b'\x01' # ^A
|
||||
MSDP_VAL = b'\x02' # ^B
|
||||
MSDP_TABLE_OPEN = b'\x03' # ^C
|
||||
MSDP_TABLE_CLOSE = b'\x04' # ^D
|
||||
MSDP_ARRAY_OPEN = b'\x05' # ^E
|
||||
MSDP_ARRAY_CLOSE = b'\x06' # ^F
|
||||
MSDP = b"\x45"
|
||||
MSDP_VAR = b"\x01" # ^A
|
||||
MSDP_VAL = b"\x02" # ^B
|
||||
MSDP_TABLE_OPEN = b"\x03" # ^C
|
||||
MSDP_TABLE_CLOSE = b"\x04" # ^D
|
||||
MSDP_ARRAY_OPEN = b"\x05" # ^E
|
||||
MSDP_ARRAY_CLOSE = b"\x06" # ^F
|
||||
|
||||
# GMCP
|
||||
GMCP = b'\xc9'
|
||||
GMCP = b"\xc9"
|
||||
|
||||
# General Telnet
|
||||
from twisted.conch.telnet import IAC, SB, SE
|
||||
|
|
@ -47,27 +47,28 @@ from twisted.conch.telnet import IAC, SB, SE
|
|||
|
||||
# pre-compiled regexes
|
||||
# returns 2-tuple
|
||||
msdp_regex_table = re.compile(br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
|
||||
% (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
msdp_regex_table = re.compile(
|
||||
br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_TABLE_OPEN, MSDP_TABLE_CLOSE)
|
||||
)
|
||||
# returns 2-tuple
|
||||
msdp_regex_array = re.compile(br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
|
||||
% (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
msdp_regex_array = re.compile(
|
||||
br"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL, MSDP_ARRAY_OPEN, MSDP_ARRAY_CLOSE)
|
||||
)
|
||||
msdp_regex_var = re.compile(br"%s" % MSDP_VAR)
|
||||
msdp_regex_val = re.compile(br"%s" % MSDP_VAL)
|
||||
|
||||
EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get",
|
||||
"get_inputfuncs": "Core.Commands.Get",
|
||||
"get_value": "Char.Value.Get",
|
||||
"repeat": "Char.Repeat.Update",
|
||||
"monitor": "Char.Monitor.Update"}
|
||||
EVENNIA_TO_GMCP = {
|
||||
"client_options": "Core.Supports.Get",
|
||||
"get_inputfuncs": "Core.Commands.Get",
|
||||
"get_value": "Char.Value.Get",
|
||||
"repeat": "Char.Repeat.Update",
|
||||
"monitor": "Char.Monitor.Update",
|
||||
}
|
||||
|
||||
|
||||
# MSDP/GMCP communication handler
|
||||
|
||||
|
||||
class TelnetOOB(object):
|
||||
"""
|
||||
Implements the MSDP and GMCP protocols.
|
||||
|
|
@ -83,7 +84,7 @@ class TelnetOOB(object):
|
|||
|
||||
"""
|
||||
self.protocol = protocol
|
||||
self.protocol.protocol_flags['OOB'] = False
|
||||
self.protocol.protocol_flags["OOB"] = False
|
||||
self.MSDP = False
|
||||
self.GMCP = False
|
||||
# ask for the available protocols and assign decoders
|
||||
|
|
@ -114,7 +115,7 @@ class TelnetOOB(object):
|
|||
|
||||
"""
|
||||
self.MSDP = True
|
||||
self.protocol.protocol_flags['OOB'] = True
|
||||
self.protocol.protocol_flags["OOB"] = True
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def no_gmcp(self, option):
|
||||
|
|
@ -137,7 +138,7 @@ class TelnetOOB(object):
|
|||
|
||||
"""
|
||||
self.GMCP = True
|
||||
self.protocol.protocol_flags['OOB'] = True
|
||||
self.protocol.protocol_flags["OOB"] = True
|
||||
self.protocol.handshake_done()
|
||||
|
||||
# encoders
|
||||
|
|
@ -167,40 +168,45 @@ class TelnetOOB(object):
|
|||
|
||||
"""
|
||||
msdp_cmdname = "{msdp_var}{msdp_cmdname}{msdp_val}".format(
|
||||
msdp_var=MSDP_VAR, msdp_cmdname=cmdname, msdp_val=MSDP_VAL)
|
||||
msdp_var=MSDP_VAR, msdp_cmdname=cmdname, msdp_val=MSDP_VAL
|
||||
)
|
||||
|
||||
if not (args or kwargs):
|
||||
return msdp_cmdname.encode()
|
||||
|
||||
# print("encode_msdp in:", cmdname, args, kwargs) # DEBUG
|
||||
|
||||
msdp_args = ''
|
||||
msdp_args = ""
|
||||
if args:
|
||||
msdp_args = msdp_cmdname
|
||||
if len(args) == 1:
|
||||
msdp_args += args[0]
|
||||
else:
|
||||
msdp_args += "{msdp_array_open}" \
|
||||
"{msdp_args}" \
|
||||
"{msdp_array_close}".format(
|
||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
||||
msdp_args="".join("%s%s"
|
||||
% (MSDP_VAL, json.dumps(val))
|
||||
for val in args))
|
||||
msdp_args += (
|
||||
"{msdp_array_open}"
|
||||
"{msdp_args}"
|
||||
"{msdp_array_close}".format(
|
||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
||||
msdp_args="".join("%s%s" % (MSDP_VAL, json.dumps(val)) for val in args),
|
||||
)
|
||||
)
|
||||
|
||||
msdp_kwargs = ""
|
||||
if kwargs:
|
||||
msdp_kwargs = msdp_cmdname
|
||||
msdp_kwargs += "{msdp_table_open}" \
|
||||
"{msdp_kwargs}" \
|
||||
"{msdp_table_close}".format(
|
||||
msdp_table_open=MSDP_TABLE_OPEN,
|
||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
||||
msdp_kwargs="".join("%s%s%s%s"
|
||||
% (MSDP_VAR, key, MSDP_VAL,
|
||||
json.dumps(val))
|
||||
for key, val in kwargs.items()))
|
||||
msdp_kwargs += (
|
||||
"{msdp_table_open}"
|
||||
"{msdp_kwargs}"
|
||||
"{msdp_table_close}".format(
|
||||
msdp_table_open=MSDP_TABLE_OPEN,
|
||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
||||
msdp_kwargs="".join(
|
||||
"%s%s%s%s" % (MSDP_VAR, key, MSDP_VAL, json.dumps(val))
|
||||
for key, val in kwargs.items()
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
msdp_string = msdp_args + msdp_kwargs
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ ssl.cert in mygame/server/.
|
|||
|
||||
"""
|
||||
import os
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
from twisted.internet import ssl as twisted_ssl
|
||||
|
|
@ -34,8 +35,14 @@ _PRIVATE_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl.key")
|
|||
_PUBLIC_KEY_FILE = os.path.join(_GAME_DIR, "server", "ssl-public.key")
|
||||
_CERTIFICATE_FILE = os.path.join(_GAME_DIR, "server", "ssl.cert")
|
||||
_CERTIFICATE_EXPIRE = 365 * 24 * 60 * 60 * 20 # 20 years
|
||||
_CERTIFICATE_ISSUER = {"C": "EV", "ST": "Evennia", "L": "Evennia", "O":
|
||||
"Evennia Security", "OU": "Evennia Department", "CN": "evennia"}
|
||||
_CERTIFICATE_ISSUER = {
|
||||
"C": "EV",
|
||||
"ST": "Evennia",
|
||||
"L": "Evennia",
|
||||
"O": "Evennia Security",
|
||||
"OU": "Evennia Department",
|
||||
"CN": "evennia",
|
||||
}
|
||||
|
||||
# messages
|
||||
|
||||
|
|
@ -45,7 +52,9 @@ If this error persists, create them manually (using the tools for your OS). The
|
|||
should be placed and named like this:
|
||||
{}
|
||||
{}
|
||||
""".format(_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE)
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
||||
)
|
||||
|
||||
NO_AUTOCERT = """
|
||||
Evennia's could not auto-generate the SSL certificate ({{err}}).
|
||||
|
|
@ -54,7 +63,9 @@ The private key already exists here:
|
|||
If this error persists, create the certificate manually (using the private key and
|
||||
the tools for your OS). The file should be placed and named like this:
|
||||
{}
|
||||
""".format(_PRIVATE_KEY_FILE, _CERTIFICATE_FILE)
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _CERTIFICATE_FILE
|
||||
)
|
||||
|
||||
|
||||
class SSLProtocol(TelnetProtocol):
|
||||
|
|
@ -88,11 +99,11 @@ def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
|||
keypair = crypto.PKey()
|
||||
keypair.generate_key(crypto.TYPE_RSA, _PRIVATE_KEY_LENGTH)
|
||||
|
||||
with open(_PRIVATE_KEY_FILE, 'wt') as pfile:
|
||||
with open(_PRIVATE_KEY_FILE, "wt") as pfile:
|
||||
pfile.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, keypair))
|
||||
print("Created SSL private key in '{}'.".format(_PRIVATE_KEY_FILE))
|
||||
|
||||
with open(_PUBLIC_KEY_FILE, 'wt') as pfile:
|
||||
with open(_PUBLIC_KEY_FILE, "wt") as pfile:
|
||||
pfile.write(crypto.dump_publickey(crypto.FILETYPE_PEM, keypair))
|
||||
print("Created SSL public key in '{}'.".format(_PUBLIC_KEY_FILE))
|
||||
|
||||
|
|
@ -114,9 +125,9 @@ def verify_or_create_SSL_key_and_cert(keyfile, certfile):
|
|||
cert.gmtime_adj_notBefore(0)
|
||||
cert.gmtime_adj_notAfter(_CERTIFICATE_EXPIRE)
|
||||
cert.set_pubkey(keypair)
|
||||
cert.sign(keypair, 'sha1')
|
||||
cert.sign(keypair, "sha1")
|
||||
|
||||
with open(_CERTIFICATE_FILE, 'wt') as cfile:
|
||||
with open(_CERTIFICATE_FILE, "wt") as cfile:
|
||||
cfile.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
print("Created SSL certificate in '{}'.".format(_CERTIFICATE_FILE))
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class TestAMPServer(TwistedTestCase):
|
|||
"""
|
||||
Test AMP communication
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestAMPServer, self).setUp()
|
||||
portal = Mock()
|
||||
|
|
@ -49,9 +50,11 @@ class TestAMPServer(TwistedTestCase):
|
|||
self.proto.makeConnection(self.transport)
|
||||
|
||||
self.proto.data_to_server(MsgServer2Portal, 1, test=2)
|
||||
byte_out = (b'\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0b'
|
||||
b'packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR'
|
||||
b'\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00')
|
||||
byte_out = (
|
||||
b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0b"
|
||||
b"packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR"
|
||||
b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00"
|
||||
)
|
||||
self.transport.write.assert_called_with(byte_out)
|
||||
with mock.patch("evennia.server.portal.amp.amp.AMP.dataReceived") as mocked_amprecv:
|
||||
self.proto.dataReceived(byte_out)
|
||||
|
|
@ -61,9 +64,11 @@ class TestAMPServer(TwistedTestCase):
|
|||
self.proto.makeConnection(self.transport)
|
||||
|
||||
self.proto.data_to_server(MsgPortal2Server, 1, test=2)
|
||||
byte_out = (b'\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgPortal2Server\x00\x0b'
|
||||
b'packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR'
|
||||
b'\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00')
|
||||
byte_out = (
|
||||
b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgPortal2Server\x00\x0b"
|
||||
b"packed_data\x00 x\xdak`\x99*\xc8\x00\x01\xde\x8c\xb5SzXJR"
|
||||
b"\x8bK\xa6x3\x15\xb7M\xd1\x03\x00V:\x07t\x00\x00"
|
||||
)
|
||||
self.transport.write.assert_called_with(byte_out)
|
||||
with mock.patch("evennia.server.portal.amp.amp.AMP.dataReceived") as mocked_amprecv:
|
||||
self.proto.dataReceived(byte_out)
|
||||
|
|
@ -74,52 +79,52 @@ class TestAMPServer(TwistedTestCase):
|
|||
Send message larger than AMP_MAXLEN - should be split into several
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
outstr = 'test' * AMP_MAXLEN
|
||||
outstr = "test" * AMP_MAXLEN
|
||||
self.proto.data_to_server(MsgServer2Portal, 1, test=outstr)
|
||||
|
||||
if sys.version < "3.7":
|
||||
self.transport.write.assert_called_with(
|
||||
b'\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data'
|
||||
b'\x00xx\xda\xed\xc6\xc1\t\x800\x10\x00\xc1\x13\xaf\x01\xeb\xb2\x01\x1bH'
|
||||
b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data"
|
||||
b"\x00xx\xda\xed\xc6\xc1\t\x800\x10\x00\xc1\x13\xaf\x01\xeb\xb2\x01\x1bH"
|
||||
b'\x05\xe6+X\x80\xcf\xd8m@I\x1d\x99\x85\x81\xbd\xf3\xdd"c\xb4/W{'
|
||||
b'\xb2\x96\xb3\xb6\xa3\x7fk\x8c\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x0e?Pv\x02\x16\x00\r'
|
||||
b'packed_data.2\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0\xb4&\xf0\xfdg\x10a'
|
||||
b'\xa3\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\xf5\xfb'
|
||||
b'\x03m\xe0\x06\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0'
|
||||
b'\xb4&\xf0\xfdg\x10a\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'
|
||||
b'UUUUU\xf5\xfb\x03n\x1c\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00'
|
||||
b'\x00\x0c\x03\xa0\xb4O\xb0\xf5gA\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xdf\x0fnI'
|
||||
b'\x06,\x00\rpacked_data.5\x00\x14x\xdaK-.)I\xc5\x8e\xa7\x14\xb7M\xd1\x03\x00'
|
||||
b'\xe7s\x0e\x1c\x00\x00')
|
||||
b"\xb2\x96\xb3\xb6\xa3\x7fk\x8c\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x0e?Pv\x02\x16\x00\r"
|
||||
b"packed_data.2\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0\xb4&\xf0\xfdg\x10a"
|
||||
b"\xa3\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\xf5\xfb"
|
||||
b"\x03m\xe0\x06\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08\xc0\xa0"
|
||||
b"\xb4&\xf0\xfdg\x10a\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"
|
||||
b"UUUUU\xf5\xfb\x03n\x1c\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00"
|
||||
b"\x00\x0c\x03\xa0\xb4O\xb0\xf5gA\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xdf\x0fnI"
|
||||
b"\x06,\x00\rpacked_data.5\x00\x14x\xdaK-.)I\xc5\x8e\xa7\x14\xb7M\xd1\x03\x00"
|
||||
b"\xe7s\x0e\x1c\x00\x00"
|
||||
)
|
||||
else:
|
||||
self.transport.write.assert_called_with(
|
||||
b'\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data'
|
||||
b'\x00wx\xda\xed\xc6\xc1\t\x80 \x00@Q#o\x8e\xd6\x02-\xe0\x04z\r\x1a\xa0\xa3m+$\xd2'
|
||||
b'\x18\xbe\x0f\x0f\xfe\x1d\xdf\x14\xfe\x8e\xedjO\xac\xb9\xd4v\xf6o\x0f\xf3\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
b'\x00X\xc3\x00P\x10\x02\x0c\x00\rpacked_data.2\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08'
|
||||
b'\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'
|
||||
b'\xf5\xfb\x03m\xe0\x06\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08'
|
||||
b'\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'
|
||||
b'\xf5\xfb\x03n\x1c\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00\x00\x0c'
|
||||
b'\x03\xa0\xb4O\xb0\xf5gA\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa'
|
||||
b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xdf\x0fnI\x06,\x00\rpacked_data.5'
|
||||
b'\x00\x18x\xdaK-.)I\xc5\x8e\xa7\xb22@\xc0\x94\xe2\xb6)z\x00Z\x1e\x0e\xb6\x00\x00')
|
||||
|
||||
b"\x00\x04_ask\x00\x011\x00\x08_command\x00\x10MsgServer2Portal\x00\x0bpacked_data"
|
||||
b"\x00wx\xda\xed\xc6\xc1\t\x80 \x00@Q#o\x8e\xd6\x02-\xe0\x04z\r\x1a\xa0\xa3m+$\xd2"
|
||||
b"\x18\xbe\x0f\x0f\xfe\x1d\xdf\x14\xfe\x8e\xedjO\xac\xb9\xd4v\xf6o\x0f\xf3\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00X\xc3\x00P\x10\x02\x0c\x00\rpacked_data.2\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08"
|
||||
b"\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3\xd9RUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"
|
||||
b"\xf5\xfb\x03m\xe0\x06\x1d\x00\rpacked_data.3\x00Zx\xda\xed\xc3\x01\r\x00\x00\x08"
|
||||
b"\xc0\xa0\xb4&\xf0\xfdg\x10a\xa3fSUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"
|
||||
b"\xf5\xfb\x03n\x1c\x06\x1e\x00\rpacked_data.4\x00Zx\xda\xed\xc3\x01\t\x00\x00\x0c"
|
||||
b"\x03\xa0\xb4O\xb0\xf5gA\xae`\xda\x8b\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
|
||||
b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xdf\x0fnI\x06,\x00\rpacked_data.5"
|
||||
b"\x00\x18x\xdaK-.)I\xc5\x8e\xa7\xb22@\xc0\x94\xe2\xb6)z\x00Z\x1e\x0e\xb6\x00\x00"
|
||||
)
|
||||
|
||||
|
||||
class TestIRC(TestCase):
|
||||
|
||||
def test_plain_ansi(self):
|
||||
"""
|
||||
Test that printable characters do not get mangled.
|
||||
|
|
@ -131,40 +136,42 @@ class TestIRC(TestCase):
|
|||
|
||||
def test_bold(self):
|
||||
s_irc = "\x02thisisatest"
|
||||
s_eve = r'|hthisisatest'
|
||||
s_eve = r"|hthisisatest"
|
||||
self.assertEqual(irc.parse_ansi_to_irc(s_eve), s_irc)
|
||||
self.assertEqual(s_eve, irc.parse_irc_to_ansi(s_irc))
|
||||
|
||||
def test_italic(self):
|
||||
s_irc = "\x02thisisatest"
|
||||
s_eve = r'|hthisisatest'
|
||||
s_eve = r"|hthisisatest"
|
||||
self.assertEqual(irc.parse_ansi_to_irc(s_eve), s_irc)
|
||||
|
||||
def test_colors(self):
|
||||
color_map = (("\0030", r'|w'),
|
||||
("\0031", r'|X'),
|
||||
("\0032", r'|B'),
|
||||
("\0033", r'|G'),
|
||||
("\0034", r'|r'),
|
||||
("\0035", r'|R'),
|
||||
("\0036", r'|M'),
|
||||
("\0037", r'|Y'),
|
||||
("\0038", r'|y'),
|
||||
("\0039", r'|g'),
|
||||
("\00310", r'|C'),
|
||||
("\00311", r'|c'),
|
||||
("\00312", r'|b'),
|
||||
("\00313", r'|m'),
|
||||
("\00314", r'|x'),
|
||||
("\00315", r'|W'),
|
||||
("\00399,5", r'|[r'),
|
||||
("\00399,3", r'|[g'),
|
||||
("\00399,7", r'|[y'),
|
||||
("\00399,2", r'|[b'),
|
||||
("\00399,6", r'|[m'),
|
||||
("\00399,10", r'|[c'),
|
||||
("\00399,15", r'|[w'),
|
||||
("\00399,1", r'|[x'))
|
||||
color_map = (
|
||||
("\0030", r"|w"),
|
||||
("\0031", r"|X"),
|
||||
("\0032", r"|B"),
|
||||
("\0033", r"|G"),
|
||||
("\0034", r"|r"),
|
||||
("\0035", r"|R"),
|
||||
("\0036", r"|M"),
|
||||
("\0037", r"|Y"),
|
||||
("\0038", r"|y"),
|
||||
("\0039", r"|g"),
|
||||
("\00310", r"|C"),
|
||||
("\00311", r"|c"),
|
||||
("\00312", r"|b"),
|
||||
("\00313", r"|m"),
|
||||
("\00314", r"|x"),
|
||||
("\00315", r"|W"),
|
||||
("\00399,5", r"|[r"),
|
||||
("\00399,3", r"|[g"),
|
||||
("\00399,7", r"|[y"),
|
||||
("\00399,2", r"|[b"),
|
||||
("\00399,6", r"|[m"),
|
||||
("\00399,10", r"|[c"),
|
||||
("\00399,15", r"|[w"),
|
||||
("\00399,1", r"|[x"),
|
||||
)
|
||||
|
||||
for m in color_map:
|
||||
self.assertEqual(irc.parse_irc_to_ansi(m[0]), m[1])
|
||||
|
|
@ -176,7 +183,7 @@ class TestIRC(TestCase):
|
|||
its inverse gives the correct string.
|
||||
"""
|
||||
|
||||
s = r'|wthis|Xis|gis|Ma|C|complex|*string'
|
||||
s = r"|wthis|Xis|gis|Ma|C|complex|*string"
|
||||
|
||||
self.assertEqual(irc.parse_irc_to_ansi(irc.parse_ansi_to_irc(s)), s)
|
||||
|
||||
|
|
@ -202,12 +209,12 @@ class TestTelnet(TwistedTestCase):
|
|||
self.assertFalse(self.proto.protocol_flags["NOGOAHEAD"])
|
||||
self.assertEqual(self.proto.handshakes, 7)
|
||||
# test naws
|
||||
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'], {0: DEFAULT_WIDTH})
|
||||
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'], {0: DEFAULT_HEIGHT})
|
||||
self.assertEqual(self.proto.protocol_flags["SCREENWIDTH"], {0: DEFAULT_WIDTH})
|
||||
self.assertEqual(self.proto.protocol_flags["SCREENHEIGHT"], {0: DEFAULT_HEIGHT})
|
||||
self.proto.dataReceived(IAC + WILL + NAWS)
|
||||
self.proto.dataReceived(b"".join([IAC, SB, NAWS, b'', b'x', b'', b'd', IAC, SE]))
|
||||
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'][0], 78)
|
||||
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'][0], 45)
|
||||
self.proto.dataReceived(b"".join([IAC, SB, NAWS, b"", b"x", b"", b"d", IAC, SE]))
|
||||
self.assertEqual(self.proto.protocol_flags["SCREENWIDTH"][0], 78)
|
||||
self.assertEqual(self.proto.protocol_flags["SCREENHEIGHT"][0], 45)
|
||||
self.assertEqual(self.proto.handshakes, 6)
|
||||
# test ttype
|
||||
self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"])
|
||||
|
|
@ -222,19 +229,21 @@ class TestTelnet(TwistedTestCase):
|
|||
self.assertEqual(self.proto.handshakes, 5)
|
||||
# test mccp
|
||||
self.proto.dataReceived(IAC + DONT + MCCP)
|
||||
self.assertFalse(self.proto.protocol_flags['MCCP'])
|
||||
self.assertFalse(self.proto.protocol_flags["MCCP"])
|
||||
self.assertEqual(self.proto.handshakes, 4)
|
||||
# test mssp
|
||||
self.proto.dataReceived(IAC + DONT + MSSP)
|
||||
self.assertEqual(self.proto.handshakes, 3)
|
||||
# test oob
|
||||
self.proto.dataReceived(IAC + DO + MSDP)
|
||||
self.proto.dataReceived(b"".join([IAC, SB, MSDP, MSDP_VAR, b"LIST", MSDP_VAL, b"COMMANDS", IAC, SE]))
|
||||
self.assertTrue(self.proto.protocol_flags['OOB'])
|
||||
self.proto.dataReceived(
|
||||
b"".join([IAC, SB, MSDP, MSDP_VAR, b"LIST", MSDP_VAL, b"COMMANDS", IAC, SE])
|
||||
)
|
||||
self.assertTrue(self.proto.protocol_flags["OOB"])
|
||||
self.assertEqual(self.proto.handshakes, 2)
|
||||
# test mxp
|
||||
self.proto.dataReceived(IAC + DONT + MXP)
|
||||
self.assertFalse(self.proto.protocol_flags['MXP'])
|
||||
self.assertFalse(self.proto.protocol_flags["MXP"])
|
||||
self.assertEqual(self.proto.handshakes, 1)
|
||||
# clean up to prevent Unclean reactor
|
||||
self.proto.nop_keep_alive.stop()
|
||||
|
|
|
|||
|
|
@ -12,19 +12,21 @@ under the 'TTYPE' key.
|
|||
"""
|
||||
|
||||
# telnet option codes
|
||||
TTYPE = b'\x18'
|
||||
IS = b'\x00'
|
||||
SEND = b'\x01'
|
||||
TTYPE = b"\x18"
|
||||
IS = b"\x00"
|
||||
SEND = b"\x01"
|
||||
|
||||
# terminal capabilities and their codes
|
||||
MTTS = [(128, 'PROXY'),
|
||||
(64, 'SCREENREADER'),
|
||||
(32, 'OSC_COLOR_PALETTE'),
|
||||
(16, 'MOUSE_TRACKING'),
|
||||
(8, 'XTERM256'),
|
||||
(4, 'UTF-8'),
|
||||
(2, 'VT100'),
|
||||
(1, 'ANSI')]
|
||||
MTTS = [
|
||||
(128, "PROXY"),
|
||||
(64, "SCREENREADER"),
|
||||
(32, "OSC_COLOR_PALETTE"),
|
||||
(16, "MOUSE_TRACKING"),
|
||||
(8, "XTERM256"),
|
||||
(4, "UTF-8"),
|
||||
(2, "VT100"),
|
||||
(1, "ANSI"),
|
||||
]
|
||||
|
||||
|
||||
class Ttype(object):
|
||||
|
|
@ -51,9 +53,9 @@ class Ttype(object):
|
|||
self.protocol = protocol
|
||||
# we set FORCEDENDLINE for clients not supporting ttype
|
||||
self.protocol.protocol_flags["FORCEDENDLINE"] = True
|
||||
self.protocol.protocol_flags['TTYPE'] = False
|
||||
self.protocol.protocol_flags["TTYPE"] = False
|
||||
# is it a safe bet to assume ANSI is always supported?
|
||||
self.protocol.protocol_flags['ANSI'] = True
|
||||
self.protocol.protocol_flags["ANSI"] = True
|
||||
# setup protocol to handle ttype initialization and negotiation
|
||||
self.protocol.negotiationMap[TTYPE] = self.will_ttype
|
||||
# ask if client will ttype, connect callback if it does.
|
||||
|
|
@ -67,7 +69,7 @@ class Ttype(object):
|
|||
option (Option): Not used.
|
||||
|
||||
"""
|
||||
self.protocol.protocol_flags['TTYPE'] = False
|
||||
self.protocol.protocol_flags["TTYPE"] = False
|
||||
self.protocol.handshake_done()
|
||||
|
||||
def will_ttype(self, option):
|
||||
|
|
@ -86,7 +88,7 @@ class Ttype(object):
|
|||
"""
|
||||
options = self.protocol.protocol_flags
|
||||
|
||||
if options and options.get('TTYPE', False) or self.ttype_step > 3:
|
||||
if options and options.get("TTYPE", False) or self.ttype_step > 3:
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
@ -121,25 +123,28 @@ class Ttype(object):
|
|||
if clientname.startswith("TINTIN++"):
|
||||
self.protocol.protocol_flags["FORCEDENDLINE"] = True
|
||||
|
||||
if (clientname.startswith("XTERM") or
|
||||
clientname.endswith("-256COLOR") or
|
||||
clientname in (
|
||||
"ATLANTIS", # > 0.9.9.0 (aug 2009)
|
||||
"CMUD", # > 3.04 (mar 2009)
|
||||
"KILDCLIENT", # > 2.2.0 (sep 2005)
|
||||
"MUDLET", # > beta 15 (sep 2009)
|
||||
"MUSHCLIENT", # > 4.02 (apr 2007)
|
||||
"PUTTY", # > 0.58 (apr 2005)
|
||||
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
|
||||
"POTATO", # > 2.00 (maybe earlier)
|
||||
"TINYFUGUE" # > 4.x (maybe earlier)
|
||||
)):
|
||||
xterm256 = True
|
||||
if (
|
||||
clientname.startswith("XTERM")
|
||||
or clientname.endswith("-256COLOR")
|
||||
or clientname
|
||||
in (
|
||||
"ATLANTIS", # > 0.9.9.0 (aug 2009)
|
||||
"CMUD", # > 3.04 (mar 2009)
|
||||
"KILDCLIENT", # > 2.2.0 (sep 2005)
|
||||
"MUDLET", # > beta 15 (sep 2009)
|
||||
"MUSHCLIENT", # > 4.02 (apr 2007)
|
||||
"PUTTY", # > 0.58 (apr 2005)
|
||||
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
|
||||
"POTATO", # > 2.00 (maybe earlier)
|
||||
"TINYFUGUE", # > 4.x (maybe earlier)
|
||||
)
|
||||
):
|
||||
xterm256 = True
|
||||
|
||||
# all clients supporting TTYPE at all seem to support ANSI
|
||||
self.protocol.protocol_flags['ANSI'] = True
|
||||
self.protocol.protocol_flags['XTERM256'] = xterm256
|
||||
self.protocol.protocol_flags['CLIENTNAME'] = clientname
|
||||
self.protocol.protocol_flags["ANSI"] = True
|
||||
self.protocol.protocol_flags["XTERM256"] = xterm256
|
||||
self.protocol.protocol_flags["CLIENTNAME"] = clientname
|
||||
self.protocol.requestNegotiation(TTYPE, SEND)
|
||||
|
||||
elif self.ttype_step == 2:
|
||||
|
|
@ -147,13 +152,15 @@ class Ttype(object):
|
|||
term = option
|
||||
tupper = term.upper()
|
||||
# identify xterm256 based on flag
|
||||
xterm256 = (tupper.endswith("-256COLOR") or # Apple Terminal, old Tintin
|
||||
tupper.endswith("XTERM") and # old Tintin, Putty
|
||||
not tupper.endswith("-COLOR"))
|
||||
xterm256 = (
|
||||
tupper.endswith("-256COLOR")
|
||||
or tupper.endswith("XTERM") # Apple Terminal, old Tintin
|
||||
and not tupper.endswith("-COLOR") # old Tintin, Putty
|
||||
)
|
||||
if xterm256:
|
||||
self.protocol.protocol_flags['ANSI'] = True
|
||||
self.protocol.protocol_flags['XTERM256'] = xterm256
|
||||
self.protocol.protocol_flags['TERM'] = term
|
||||
self.protocol.protocol_flags["ANSI"] = True
|
||||
self.protocol.protocol_flags["XTERM256"] = xterm256
|
||||
self.protocol.protocol_flags["TERM"] = term
|
||||
# request next information
|
||||
self.protocol.requestNegotiation(TTYPE, SEND)
|
||||
|
||||
|
|
@ -164,13 +171,15 @@ class Ttype(object):
|
|||
if option.isdigit():
|
||||
# a number - determine the actual capabilities
|
||||
option = int(option)
|
||||
support = dict((capability, True) for bitval, capability in MTTS if option & bitval > 0)
|
||||
support = dict(
|
||||
(capability, True) for bitval, capability in MTTS if option & bitval > 0
|
||||
)
|
||||
self.protocol.protocol_flags.update(support)
|
||||
else:
|
||||
# some clients send erroneous MTTS as a string. Add directly.
|
||||
self.protocol.protocol_flags[option.upper()] = True
|
||||
|
||||
self.protocol.protocol_flags['TTYPE'] = True
|
||||
self.protocol.protocol_flags["TTYPE"] = True
|
||||
# we must sync ttype once it'd done
|
||||
self.protocol.handshake_done()
|
||||
self.ttype_step += 1
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ from evennia.utils.ansi import parse_ansi
|
|||
from evennia.utils.text2html import parse_html
|
||||
from autobahn.twisted.websocket import WebSocketServerProtocol
|
||||
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_RE_SCREENREADER_REGEX = re.compile(
|
||||
r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
|
||||
)
|
||||
_CLIENT_SESSIONS = mod_import(settings.SESSION_ENGINE).SessionStore
|
||||
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
|||
"""
|
||||
Implements the server-side of the Websocket connection.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.protocol_key = "webclient/websocket"
|
||||
|
|
@ -58,6 +61,7 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
|||
return None
|
||||
except AttributeError:
|
||||
from evennia.utils import logger
|
||||
|
||||
self.csessid = None
|
||||
logger.log_trace(str(self))
|
||||
return None
|
||||
|
|
@ -82,8 +86,10 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
|||
self.logged_in = True
|
||||
|
||||
for old_session in self.sessionhandler.sessions_from_csessid(csessid):
|
||||
if (hasattr(old_session, "websocket_close_code") and
|
||||
old_session.websocket_close_code != CLOSE_NORMAL):
|
||||
if (
|
||||
hasattr(old_session, "websocket_close_code")
|
||||
and old_session.websocket_close_code != CLOSE_NORMAL
|
||||
):
|
||||
# if we have old sessions with the same csession, they are remnants
|
||||
self.sessid = old_session.sessid
|
||||
self.sessionhandler.disconnect(old_session)
|
||||
|
|
@ -143,7 +149,7 @@ class WebSocketClient(WebSocketServerProtocol, Session):
|
|||
UTF-8 encoded text.
|
||||
|
||||
"""
|
||||
cmdarray = json.loads(str(payload, 'utf-8'))
|
||||
cmdarray = json.loads(str(payload, "utf-8"))
|
||||
if cmdarray:
|
||||
self.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]})
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ from evennia.utils.text2html import parse_html
|
|||
from evennia.server import session
|
||||
|
||||
_CLIENT_SESSIONS = utils.mod_import(settings.SESSION_ENGINE).SessionStore
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_RE_SCREENREADER_REGEX = re.compile(
|
||||
r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE
|
||||
)
|
||||
_SERVERNAME = settings.SERVERNAME
|
||||
_KEEPALIVE = 30 # how often to check keepalive
|
||||
|
||||
|
|
@ -42,6 +44,7 @@ _KEEPALIVE = 30 # how often to check keepalive
|
|||
# extend this if one wants to send more
|
||||
# complex database objects too.
|
||||
|
||||
|
||||
class LazyEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Promise):
|
||||
|
|
@ -58,13 +61,15 @@ def jsonify(obj):
|
|||
# using POST requests to /webclientdata.
|
||||
#
|
||||
|
||||
|
||||
class AjaxWebClient(resource.Resource):
|
||||
"""
|
||||
An ajax/comet long-polling transport
|
||||
|
||||
"""
|
||||
|
||||
isLeaf = True
|
||||
allowedMethods = ('POST',)
|
||||
allowedMethods = ("POST",)
|
||||
|
||||
def __init__(self):
|
||||
self.requests = {}
|
||||
|
|
@ -87,8 +92,11 @@ class AjaxWebClient(resource.Resource):
|
|||
"""
|
||||
now = time.time()
|
||||
to_remove = []
|
||||
keep_alives = ((csessid, remove) for csessid, (t, remove)
|
||||
in self.last_alive.items() if now - t > _KEEPALIVE)
|
||||
keep_alives = (
|
||||
(csessid, remove)
|
||||
for csessid, (t, remove) in self.last_alive.items()
|
||||
if now - t > _KEEPALIVE
|
||||
)
|
||||
for csessid, remove in keep_alives:
|
||||
if remove:
|
||||
# keepalive timeout. Line is dead.
|
||||
|
|
@ -118,7 +126,7 @@ class AjaxWebClient(resource.Resource):
|
|||
csessid (int): The client-session id.
|
||||
|
||||
"""
|
||||
return html.escape(request.args[b'csessid'][0].decode("utf-8"))
|
||||
return html.escape(request.args[b"csessid"][0].decode("utf-8"))
|
||||
|
||||
def at_login(self):
|
||||
"""
|
||||
|
|
@ -174,9 +182,11 @@ class AjaxWebClient(resource.Resource):
|
|||
csessid = self.get_client_sessid(request)
|
||||
|
||||
remote_addr = request.getClientIP()
|
||||
host_string = "%s (%s:%s)" % (_SERVERNAME,
|
||||
request.getRequestHostname(),
|
||||
request.getHost().port)
|
||||
host_string = "%s (%s:%s)" % (
|
||||
_SERVERNAME,
|
||||
request.getRequestHostname(),
|
||||
request.getHost().port,
|
||||
)
|
||||
|
||||
sess = AjaxWebClientSession()
|
||||
sess.client = self
|
||||
|
|
@ -200,7 +210,7 @@ class AjaxWebClient(resource.Resource):
|
|||
# actually do the connection
|
||||
sess.sessionhandler.connect(sess)
|
||||
|
||||
return jsonify({'msg': host_string, 'csessid': csessid})
|
||||
return jsonify({"msg": host_string, "csessid": csessid})
|
||||
|
||||
def mode_keepalive(self, request):
|
||||
|
||||
|
|
@ -223,7 +233,7 @@ class AjaxWebClient(resource.Resource):
|
|||
"""
|
||||
csessid = self.get_client_sessid(request)
|
||||
self.last_alive[csessid] = (time.time(), False)
|
||||
cmdarray = json.loads(request.args.get(b'data')[0])
|
||||
cmdarray = json.loads(request.args.get(b"data")[0])
|
||||
for sess in self.sessionhandler.sessions_from_csessid(csessid):
|
||||
sess.data_in(**{cmdarray[0]: [cmdarray[1], cmdarray[2]]})
|
||||
return b'""'
|
||||
|
|
@ -239,7 +249,7 @@ class AjaxWebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
csessid = html.escape(request.args[b'csessid'][0].decode("utf-8"))
|
||||
csessid = html.escape(request.args[b"csessid"][0].decode("utf-8"))
|
||||
self.last_alive[csessid] = (time.time(), False)
|
||||
|
||||
dataentries = self.databuffer.get(csessid)
|
||||
|
|
@ -286,21 +296,21 @@ class AjaxWebClient(resource.Resource):
|
|||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
dmode = request.args.get(b'mode', [b'None'])[0].decode("utf-8")
|
||||
dmode = request.args.get(b"mode", [b"None"])[0].decode("utf-8")
|
||||
|
||||
if dmode == 'init':
|
||||
if dmode == "init":
|
||||
# startup. Setup the server.
|
||||
return self.mode_init(request)
|
||||
elif dmode == 'input':
|
||||
elif dmode == "input":
|
||||
# input from the client to the server
|
||||
return self.mode_input(request)
|
||||
elif dmode == 'receive':
|
||||
elif dmode == "receive":
|
||||
# the client is waiting to receive data.
|
||||
return self.mode_receive(request)
|
||||
elif dmode == 'close':
|
||||
elif dmode == "close":
|
||||
# the client is closing
|
||||
return self.mode_close(request)
|
||||
elif dmode == 'keepalive':
|
||||
elif dmode == "keepalive":
|
||||
# A reply to our keepalive request - all is well
|
||||
return self.mode_keepalive(request)
|
||||
else:
|
||||
|
|
@ -313,6 +323,7 @@ class AjaxWebClient(resource.Resource):
|
|||
# web client interface.
|
||||
#
|
||||
|
||||
|
||||
class AjaxWebClientSession(session.Session):
|
||||
"""
|
||||
This represents a session running in an AjaxWebclient.
|
||||
|
|
@ -405,8 +416,8 @@ class AjaxWebClientSession(session.Session):
|
|||
|
||||
options = kwargs.pop("options", {})
|
||||
raw = options.get("raw", flags.get("RAW", False))
|
||||
xterm256 = options.get("xterm256", flags.get('XTERM256', True))
|
||||
useansi = options.get("ansi", flags.get('ANSI', True))
|
||||
xterm256 = options.get("xterm256", flags.get("XTERM256", True))
|
||||
useansi = options.get("ansi", flags.get("ANSI", True))
|
||||
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
|
||||
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
|
||||
prompt = options.get("send_prompt", False)
|
||||
|
|
|
|||
|
|
@ -47,8 +47,10 @@ from evennia.utils import mod_import, time_format
|
|||
|
||||
DUMMYRUNNER_SETTINGS = mod_import(settings.DUMMYRUNNER_SETTINGS_MODULE)
|
||||
if not DUMMYRUNNER_SETTINGS:
|
||||
raise IOError("Error: Dummyrunner could not find settings file at %s" %
|
||||
settings.DUMMYRUNNER_SETTINGS_MODULE)
|
||||
raise IOError(
|
||||
"Error: Dummyrunner could not find settings file at %s"
|
||||
% settings.DUMMYRUNNER_SETTINGS_MODULE
|
||||
)
|
||||
|
||||
DATESTRING = "%Y%m%d%H%M%S"
|
||||
|
||||
|
|
@ -75,8 +77,7 @@ NLOGGED_IN = 0
|
|||
# Messages
|
||||
|
||||
|
||||
INFO_STARTING = \
|
||||
"""
|
||||
INFO_STARTING = """
|
||||
Dummyrunner starting using {N} dummy account(s). If you don't see
|
||||
any connection messages, make sure that the Evennia server is
|
||||
running.
|
||||
|
|
@ -84,8 +85,7 @@ INFO_STARTING = \
|
|||
Use Ctrl-C to stop/disconnect clients.
|
||||
"""
|
||||
|
||||
ERROR_NO_MIXIN = \
|
||||
"""
|
||||
ERROR_NO_MIXIN = """
|
||||
Error: Evennia is not set up for dummyrunner. Before starting the
|
||||
server, make sure to include the following at *the end* of your
|
||||
settings file (remove when not using dummyrunner!):
|
||||
|
|
@ -108,8 +108,7 @@ ERROR_NO_MIXIN = \
|
|||
"""
|
||||
|
||||
|
||||
ERROR_FEW_ACTIONS = \
|
||||
"""
|
||||
ERROR_FEW_ACTIONS = """
|
||||
Dummyrunner settings error: The ACTIONS tuple is too short: it must
|
||||
contain at least login- and logout functions.
|
||||
"""
|
||||
|
|
@ -169,9 +168,9 @@ until you see the initial login slows things too much.
|
|||
|
||||
"""
|
||||
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
# Helper functions
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
ICOUNT = 0
|
||||
|
|
@ -216,11 +215,12 @@ def makeiter(obj):
|
|||
Returns:
|
||||
iterable (iterable): An iterable object.
|
||||
"""
|
||||
return obj if hasattr(obj, '__iter__') else [obj]
|
||||
return obj if hasattr(obj, "__iter__") else [obj]
|
||||
|
||||
#------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Client classes
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class DummyClient(telnet.StatefulTelnetProtocol):
|
||||
|
|
@ -253,7 +253,7 @@ class DummyClient(telnet.StatefulTelnetProtocol):
|
|||
self._logout = self.factory.actions[1]
|
||||
self._actions = self.factory.actions[2:]
|
||||
|
||||
reactor.addSystemEventTrigger('before', 'shutdown', self.logout)
|
||||
reactor.addSystemEventTrigger("before", "shutdown", self.logout)
|
||||
|
||||
def dataReceived(self, data):
|
||||
"""
|
||||
|
|
@ -358,10 +358,11 @@ class DummyFactory(protocol.ClientFactory):
|
|||
"Setup the factory base (shared by all clients)"
|
||||
self.actions = actions
|
||||
|
||||
#------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Access method:
|
||||
# Starts clients and connects them to a running server.
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
def start_all_dummy_clients(nclients):
|
||||
|
|
@ -382,7 +383,12 @@ def start_all_dummy_clients(nclients):
|
|||
|
||||
# make sure the probabilities add up to 1
|
||||
pratio = 1.0 / sum(tup[0] for tup in actions[2:])
|
||||
flogin, flogout, probs, cfuncs = actions[0], actions[1], [tup[0] * pratio for tup in actions[2:]], [tup[1] for tup in actions[2:]]
|
||||
flogin, flogout, probs, cfuncs = (
|
||||
actions[0],
|
||||
actions[1],
|
||||
[tup[0] * pratio for tup in actions[2:]],
|
||||
[tup[1] for tup in actions[2:]],
|
||||
)
|
||||
# create cumulative probabilies for the random actions
|
||||
cprobs = [sum(v for i, v in enumerate(probs) if i <= k) for k in range(len(probs))]
|
||||
# rebuild a new, optimized action structure
|
||||
|
|
@ -395,12 +401,13 @@ def start_all_dummy_clients(nclients):
|
|||
# start reactor
|
||||
reactor.run()
|
||||
|
||||
#------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Command line interface
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
settings.DUMMYRUNNER_MIXIN
|
||||
|
|
@ -410,8 +417,9 @@ if __name__ == '__main__':
|
|||
|
||||
# parsing command line with default vals
|
||||
parser = ArgumentParser(description=HELPTEXT)
|
||||
parser.add_argument("-N", nargs=1, default=1, dest="nclients",
|
||||
help="Number of clients to start")
|
||||
parser.add_argument(
|
||||
"-N", nargs=1, default=1, dest="nclients", help="Number of clients to start"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ TOBJ_TYPECLASS = "contrib.tutorial_examples.red_button.RedButton"
|
|||
|
||||
# login/logout
|
||||
|
||||
|
||||
def c_login(client):
|
||||
"logins to the game"
|
||||
# we always use a new client name
|
||||
|
|
@ -102,12 +103,13 @@ def c_login(client):
|
|||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd),
|
||||
'@dig %s' % START_ROOM % client.gid,
|
||||
'@teleport %s' % START_ROOM % client.gid,
|
||||
'@dig %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
)
|
||||
cmds = (
|
||||
"create %s %s" % (cname, cpwd),
|
||||
"connect %s %s" % (cname, cpwd),
|
||||
"@dig %s" % START_ROOM % client.gid,
|
||||
"@teleport %s" % START_ROOM % client.gid,
|
||||
"@dig %s = %s, %s" % (roomname, exitname1, exitname2),
|
||||
)
|
||||
return cmds
|
||||
|
||||
|
||||
|
|
@ -116,8 +118,7 @@ def c_login_nodig(client):
|
|||
cname = DUMMY_NAME % client.gid
|
||||
cpwd = DUMMY_PWD % client.gid
|
||||
|
||||
cmds = ('create %s %s' % (cname, cpwd),
|
||||
'connect %s %s' % (cname, cpwd))
|
||||
cmds = ("create %s %s" % (cname, cpwd), "connect %s %s" % (cname, cpwd))
|
||||
return cmds
|
||||
|
||||
|
||||
|
|
@ -125,6 +126,7 @@ def c_logout(client):
|
|||
"logouts of the game"
|
||||
return "@quit"
|
||||
|
||||
|
||||
# random commands
|
||||
|
||||
|
||||
|
|
@ -150,17 +152,13 @@ def c_examines(client):
|
|||
|
||||
def c_idles(client):
|
||||
"idles"
|
||||
cmds = ('idle', 'idle')
|
||||
cmds = ("idle", "idle")
|
||||
return cmds
|
||||
|
||||
|
||||
def c_help(client):
|
||||
"reads help files"
|
||||
cmds = ('help',
|
||||
'help @teleport',
|
||||
'help look',
|
||||
'help @tunnel',
|
||||
'help @dig')
|
||||
cmds = ("help", "help @teleport", "help look", "help @tunnel", "help @dig")
|
||||
return cmds
|
||||
|
||||
|
||||
|
|
@ -170,17 +168,19 @@ def c_digs(client):
|
|||
exitname1 = EXIT_TEMPLATE % client.counter()
|
||||
exitname2 = EXIT_TEMPLATE % client.counter()
|
||||
client.exits.extend([exitname1, exitname2])
|
||||
return '@dig/tel %s = %s, %s' % (roomname, exitname1, exitname2)
|
||||
return "@dig/tel %s = %s, %s" % (roomname, exitname1, exitname2)
|
||||
|
||||
|
||||
def c_creates_obj(client):
|
||||
"creates normal objects, storing their name on client"
|
||||
objname = OBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s' % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
'@set %s/testattr = this is a test attribute value.' % objname,
|
||||
'@set %s/testattr2 = this is a second test attribute.' % objname)
|
||||
cmds = (
|
||||
"@create %s" % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
"@set %s/testattr = this is a test attribute value." % objname,
|
||||
"@set %s/testattr2 = this is a second test attribute." % objname,
|
||||
)
|
||||
return cmds
|
||||
|
||||
|
||||
|
|
@ -188,18 +188,19 @@ def c_creates_button(client):
|
|||
"creates example button, storing name on client"
|
||||
objname = TOBJ_TEMPLATE % client.counter()
|
||||
client.objs.append(objname)
|
||||
cmds = ('@create %s:%s' % (objname, TOBJ_TYPECLASS),
|
||||
'@desc %s = test red button!' % objname)
|
||||
cmds = ("@create %s:%s" % (objname, TOBJ_TYPECLASS), "@desc %s = test red button!" % objname)
|
||||
return cmds
|
||||
|
||||
|
||||
def c_socialize(client):
|
||||
"socializechats on channel"
|
||||
cmds = ('ooc Hello!',
|
||||
'ooc Testing ...',
|
||||
'ooc Testing ... times 2',
|
||||
'say Yo!',
|
||||
'emote stands looking around.')
|
||||
cmds = (
|
||||
"ooc Hello!",
|
||||
"ooc Testing ...",
|
||||
"ooc Testing ... times 2",
|
||||
"say Yo!",
|
||||
"emote stands looking around.",
|
||||
)
|
||||
return cmds
|
||||
|
||||
|
||||
|
|
@ -218,6 +219,7 @@ def c_moves_s(client):
|
|||
"move through south exit if available"
|
||||
return "south"
|
||||
|
||||
|
||||
# Action tuple (required)
|
||||
#
|
||||
# This is a tuple of client action functions. The first element is the
|
||||
|
|
@ -263,12 +265,7 @@ def c_moves_s(client):
|
|||
# c_logout,
|
||||
# (1.0, c_idles))
|
||||
# "normal account" definition
|
||||
ACTIONS = (c_login,
|
||||
c_logout,
|
||||
(0.01, c_digs),
|
||||
(0.39, c_looks),
|
||||
(0.2, c_help),
|
||||
(0.4, c_moves))
|
||||
ACTIONS = (c_login, c_logout, (0.01, c_digs), (0.39, c_looks), (0.2, c_help), (0.4, c_moves))
|
||||
# walking tester. This requires a pre-made
|
||||
# "loop" of multiple rooms that ties back
|
||||
# to limbo (using @tunnel and @open)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ Call this module directly to plot the log (requires matplotlib and numpy).
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
# TODO!
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
import evennia
|
||||
from evennia.utils.idmapper import models as _idmapper
|
||||
|
||||
|
|
@ -38,8 +39,12 @@ class Memplot(evennia.DefaultScript):
|
|||
def at_repeat(self):
|
||||
"Regularly save memory statistics."
|
||||
pid = os.getpid()
|
||||
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory
|
||||
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory
|
||||
rmem = (
|
||||
float(os.popen("ps -p %d -o %s | tail -1" % (pid, "rss")).read()) / 1000.0
|
||||
) # resident memory
|
||||
vmem = (
|
||||
float(os.popen("ps -p %d -o %s | tail -1" % (pid, "vsz")).read()) / 1000.0
|
||||
) # virtual memory
|
||||
total_num, cachedict = _idmapper.cache_size()
|
||||
t0 = (time.time() - self.db.starttime) / 60.0 # save in minutes
|
||||
|
||||
|
|
@ -61,8 +66,8 @@ if __name__ == "__main__":
|
|||
nobj = data[:, 3]
|
||||
|
||||
# calculate derivative of obj creation
|
||||
#oderiv = (0.5*(nobj[2:] - nobj[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
#oderiv = (0.5*(rmem[2:] - rmem[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
# oderiv = (0.5*(nobj[2:] - nobj[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
# oderiv = (0.5*(rmem[2:] - rmem[:-2]) / (secs[2:] - secs[:-2])).copy()
|
||||
|
||||
fig = pp.figure()
|
||||
ax1 = fig.add_subplot(111)
|
||||
|
|
@ -75,37 +80,37 @@ if __name__ == "__main__":
|
|||
|
||||
ax2 = ax1.twinx()
|
||||
ax2.plot(secs, nobj, "g--", label="objs in cache", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv/60.0, "g--", label="Objs/second", lw=2)
|
||||
#ax2.plot(secs[:-2], oderiv, "g--", label="Objs/second", lw=2)
|
||||
# ax2.plot(secs[:-2], oderiv/60.0, "g--", label="Objs/second", lw=2)
|
||||
# ax2.plot(secs[:-2], oderiv, "g--", label="Objs/second", lw=2)
|
||||
ax2.set_ylabel("Number of objects")
|
||||
ax2.legend(loc="lower right")
|
||||
ax2.annotate("First 500 bots\nconnecting", xy=(10, 4000))
|
||||
ax2.annotate("Next 500 bots\nconnecting", xy=(350, 10000))
|
||||
#ax2.annotate("@reload", xy=(185,600))
|
||||
# ax2.annotate("@reload", xy=(185,600))
|
||||
|
||||
# # plot mem vs cachesize
|
||||
# nobj, rmem, vmem = nobj[:262].copy(), rmem[:262].copy(), vmem[:262].copy()
|
||||
#
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Memory usage per cache size")
|
||||
# ax1.set_xlabel("Cache size (number of objects)")
|
||||
# ax1.set_ylabel("Memory usage (MB)")
|
||||
# ax1.plot(nobj, rmem, "r", label="RMEM", lw=2)
|
||||
# ax1.plot(nobj, vmem, "b", label="VMEM", lw=2)
|
||||
#
|
||||
# # plot mem vs cachesize
|
||||
# nobj, rmem, vmem = nobj[:262].copy(), rmem[:262].copy(), vmem[:262].copy()
|
||||
#
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Memory usage per cache size")
|
||||
# ax1.set_xlabel("Cache size (number of objects)")
|
||||
# ax1.set_ylabel("Memory usage (MB)")
|
||||
# ax1.plot(nobj, rmem, "r", label="RMEM", lw=2)
|
||||
# ax1.plot(nobj, vmem, "b", label="VMEM", lw=2)
|
||||
#
|
||||
|
||||
# empirical estimate of memory usage: rmem = 35.0 + 0.0157 * Ncache
|
||||
# Ncache = int((rmem - 35.0) / 0.0157) (rmem in MB)
|
||||
#
|
||||
# rderiv_aver = 0.0157
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Relation between memory and cache size")
|
||||
# ax1.set_xlabel("Memory usage (MB)")
|
||||
# ax1.set_ylabel("Idmapper Cache Size (number of objects)")
|
||||
# rmem = numpy.linspace(35, 2000, 2000)
|
||||
# nobjs = numpy.array([int((mem - 35.0) / 0.0157) for mem in rmem])
|
||||
# ax1.plot(rmem, nobjs, "r", lw=2)
|
||||
# empirical estimate of memory usage: rmem = 35.0 + 0.0157 * Ncache
|
||||
# Ncache = int((rmem - 35.0) / 0.0157) (rmem in MB)
|
||||
#
|
||||
# rderiv_aver = 0.0157
|
||||
# fig = pp.figure()
|
||||
# ax1 = fig.add_subplot(111)
|
||||
# ax1.set_title("Relation between memory and cache size")
|
||||
# ax1.set_xlabel("Memory usage (MB)")
|
||||
# ax1.set_ylabel("Idmapper Cache Size (number of objects)")
|
||||
# rmem = numpy.linspace(35, 2000, 2000)
|
||||
# nobjs = numpy.array([int((mem - 35.0) / 0.0157) for mem in rmem])
|
||||
# ax1.plot(rmem, nobjs, "r", lw=2)
|
||||
|
||||
pp.show()
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ DUMMYRUNNER_MIXIN = True
|
|||
# a faster password hasher suitable for multiple simultaneous
|
||||
# account creations. The default one is safer but deliberately
|
||||
# very slow to make cracking harder.
|
||||
PASSWORD_HASHERS = (
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
)
|
||||
PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)
|
||||
# make dummy clients able to test all commands
|
||||
PERMISSION_ACCOUNT_DEFAULT = "Developer"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ query as well as count them for optimization testing.
|
|||
|
||||
import sys
|
||||
import os
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
#os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
|
||||
|
||||
# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
|
||||
# os.environ["DJANGO_SETTINGS_MODULE"] = "game.settings"
|
||||
from django.db import connection
|
||||
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ def count_queries(exec_string, setup_string):
|
|||
exec(exec_string)
|
||||
nqueries = len(connection.queries) - num_queries_old
|
||||
|
||||
for query in connection.queries[-nqueries if nqueries else 1:]:
|
||||
for query in connection.queries[-nqueries if nqueries else 1 :]:
|
||||
print(query["time"], query["sql"])
|
||||
print("Number of queries: %s" % nqueries)
|
||||
|
||||
|
|
@ -32,13 +33,11 @@ if __name__ == "__main__":
|
|||
|
||||
# setup tests here
|
||||
|
||||
setup_string = \
|
||||
"""
|
||||
setup_string = """
|
||||
from evennia.objects.models import ObjectDB
|
||||
g = ObjectDB.objects.get(db_key="Griatch")
|
||||
"""
|
||||
exec_string = \
|
||||
"""
|
||||
exec_string = """
|
||||
g.tags.all()
|
||||
"""
|
||||
count_queries(exec_string, setup_string)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,21 @@
|
|||
from django.test import TestCase
|
||||
from mock import Mock, patch, mock_open
|
||||
from .dummyrunner_settings import (c_creates_button, c_creates_obj, c_digs, c_examines, c_help, c_idles, c_login,
|
||||
c_login_nodig, c_logout, c_looks, c_moves, c_moves_n, c_moves_s, c_socialize)
|
||||
from .dummyrunner_settings import (
|
||||
c_creates_button,
|
||||
c_creates_obj,
|
||||
c_digs,
|
||||
c_examines,
|
||||
c_help,
|
||||
c_idles,
|
||||
c_login,
|
||||
c_login_nodig,
|
||||
c_logout,
|
||||
c_looks,
|
||||
c_moves,
|
||||
c_moves_n,
|
||||
c_moves_s,
|
||||
c_socialize,
|
||||
)
|
||||
|
||||
try:
|
||||
import memplot
|
||||
|
|
@ -26,15 +40,25 @@ class TestDummyrunnerSettings(TestCase):
|
|||
self.client.exits = []
|
||||
|
||||
def test_c_login(self):
|
||||
self.assertEqual(c_login(self.client), ('create %s %s' % (self.client.name, self.client.password),
|
||||
'connect %s %s' % (self.client.name, self.client.password),
|
||||
'@dig %s' % self.client.start_room,
|
||||
'@teleport %s' % self.client.start_room,
|
||||
"@dig testing_room_1 = exit_1, exit_1"))
|
||||
self.assertEqual(
|
||||
c_login(self.client),
|
||||
(
|
||||
"create %s %s" % (self.client.name, self.client.password),
|
||||
"connect %s %s" % (self.client.name, self.client.password),
|
||||
"@dig %s" % self.client.start_room,
|
||||
"@teleport %s" % self.client.start_room,
|
||||
"@dig testing_room_1 = exit_1, exit_1",
|
||||
),
|
||||
)
|
||||
|
||||
def test_c_login_no_dig(self):
|
||||
self.assertEqual(c_login_nodig(self.client), ('create %s %s' % (self.client.name, self.client.password),
|
||||
'connect %s %s' % (self.client.name, self.client.password)))
|
||||
self.assertEqual(
|
||||
c_login_nodig(self.client),
|
||||
(
|
||||
"create %s %s" % (self.client.name, self.client.password),
|
||||
"connect %s %s" % (self.client.name, self.client.password),
|
||||
),
|
||||
)
|
||||
|
||||
def test_c_logout(self):
|
||||
self.assertEqual(c_logout(self.client), "@quit")
|
||||
|
|
@ -54,36 +78,54 @@ class TestDummyrunnerSettings(TestCase):
|
|||
self.perception_method_tests(c_examines, "examine", " me")
|
||||
|
||||
def test_idles(self):
|
||||
self.assertEqual(c_idles(self.client), ('idle', 'idle'))
|
||||
self.assertEqual(c_idles(self.client), ("idle", "idle"))
|
||||
|
||||
def test_c_help(self):
|
||||
self.assertEqual(c_help(self.client), ('help', 'help @teleport', 'help look', 'help @tunnel', 'help @dig'))
|
||||
self.assertEqual(
|
||||
c_help(self.client),
|
||||
("help", "help @teleport", "help look", "help @tunnel", "help @dig"),
|
||||
)
|
||||
|
||||
def test_c_digs(self):
|
||||
self.assertEqual(c_digs(self.client), ('@dig/tel testing_room_1 = exit_1, exit_1'))
|
||||
self.assertEqual(self.client.exits, ['exit_1', 'exit_1'])
|
||||
self.assertEqual(c_digs(self.client), ("@dig/tel testing_room_1 = exit_1, exit_1"))
|
||||
self.assertEqual(self.client.exits, ["exit_1", "exit_1"])
|
||||
self.clear_client_lists()
|
||||
|
||||
def test_c_creates_obj(self):
|
||||
objname = "testing_obj_1"
|
||||
self.assertEqual(c_creates_obj(self.client), ('@create %s' % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
'@set %s/testattr = this is a test attribute value.' % objname,
|
||||
'@set %s/testattr2 = this is a second test attribute.' % objname))
|
||||
self.assertEqual(
|
||||
c_creates_obj(self.client),
|
||||
(
|
||||
"@create %s" % objname,
|
||||
'@desc %s = "this is a test object' % objname,
|
||||
"@set %s/testattr = this is a test attribute value." % objname,
|
||||
"@set %s/testattr2 = this is a second test attribute." % objname,
|
||||
),
|
||||
)
|
||||
self.assertEqual(self.client.objs, [objname])
|
||||
self.clear_client_lists()
|
||||
|
||||
def test_c_creates_button(self):
|
||||
objname = "testing_button_1"
|
||||
typeclass_name = "contrib.tutorial_examples.red_button.RedButton"
|
||||
self.assertEqual(c_creates_button(self.client), ('@create %s:%s' % (objname, typeclass_name),
|
||||
'@desc %s = test red button!' % objname))
|
||||
self.assertEqual(
|
||||
c_creates_button(self.client),
|
||||
("@create %s:%s" % (objname, typeclass_name), "@desc %s = test red button!" % objname),
|
||||
)
|
||||
self.assertEqual(self.client.objs, [objname])
|
||||
self.clear_client_lists()
|
||||
|
||||
def test_c_socialize(self):
|
||||
self.assertEqual(c_socialize(self.client), ('ooc Hello!', 'ooc Testing ...', 'ooc Testing ... times 2',
|
||||
'say Yo!', 'emote stands looking around.'))
|
||||
self.assertEqual(
|
||||
c_socialize(self.client),
|
||||
(
|
||||
"ooc Hello!",
|
||||
"ooc Testing ...",
|
||||
"ooc Testing ... times 2",
|
||||
"say Yo!",
|
||||
"emote stands looking around.",
|
||||
),
|
||||
)
|
||||
|
||||
def test_c_moves(self):
|
||||
self.assertEqual(c_moves(self.client), "look")
|
||||
|
|
@ -108,6 +150,7 @@ class TestMemPlot(TestCase):
|
|||
if isinstance(memplot, Mock):
|
||||
return
|
||||
from evennia.utils.create import create_script
|
||||
|
||||
mocked_idmapper.cache_size.return_value = (9, 5000)
|
||||
mock_time.time = Mock(return_value=6000.0)
|
||||
script = create_script(memplot.Memplot)
|
||||
|
|
@ -115,5 +158,5 @@ class TestMemPlot(TestCase):
|
|||
mocked_os.popen.read.return_value = 5000.0
|
||||
script.at_repeat()
|
||||
handle = mocked_open()
|
||||
handle.write.assert_called_with('100.0, 0.001, 0.001, 9\n')
|
||||
handle.write.assert_called_with("100.0, 0.001, 0.001, 9\n")
|
||||
script.stop()
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ from twisted.internet.task import LoopingCall
|
|||
from twisted.python.log import ILogObserver
|
||||
|
||||
import django
|
||||
|
||||
django.setup()
|
||||
|
||||
import evennia
|
||||
|
||||
evennia._init()
|
||||
|
||||
from django.db import connection
|
||||
|
|
@ -41,7 +43,7 @@ from django.utils.translation import ugettext as _
|
|||
_SA = object.__setattr__
|
||||
|
||||
# a file with a flag telling the server to restart after shutdown or not.
|
||||
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", 'server.restart')
|
||||
SERVER_RESTART = os.path.join(settings.GAME_DIR, "server", "server.restart")
|
||||
|
||||
# module containing hook methods called during start_stop
|
||||
SERVER_STARTSTOP_MODULE = mod_import(settings.AT_SERVER_STARTSTOP_MODULE)
|
||||
|
|
@ -75,16 +77,24 @@ GRAPEVINE_ENABLED = settings.GRAPEVINE_ENABLED
|
|||
WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED
|
||||
GAME_INDEX_ENABLED = settings.GAME_INDEX_ENABLED
|
||||
|
||||
INFO_DICT = {"servername": SERVERNAME, "version": VERSION,
|
||||
"amp": "", "errors": "", "info": "", "webserver": "", "irc_rss": ""}
|
||||
INFO_DICT = {
|
||||
"servername": SERVERNAME,
|
||||
"version": VERSION,
|
||||
"amp": "",
|
||||
"errors": "",
|
||||
"info": "",
|
||||
"webserver": "",
|
||||
"irc_rss": "",
|
||||
}
|
||||
|
||||
try:
|
||||
WEB_PLUGINS_MODULE = mod_import(settings.WEB_PLUGINS_MODULE)
|
||||
except ImportError:
|
||||
WEB_PLUGINS_MODULE = None
|
||||
INFO_DICT["errors"] = (
|
||||
"WARNING: settings.WEB_PLUGINS_MODULE not found - "
|
||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf.")
|
||||
"WARNING: settings.WEB_PLUGINS_MODULE not found - "
|
||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
||||
)
|
||||
|
||||
|
||||
# Maintenance function - this is called repeatedly by the server
|
||||
|
|
@ -138,16 +148,18 @@ def _server_maintenance():
|
|||
# handle idle timeouts
|
||||
if _IDLE_TIMEOUT > 0:
|
||||
reason = _("idle timeout exceeded")
|
||||
for session in (sess for sess in SESSIONS.values()
|
||||
if (now - sess.cmd_last) > _IDLE_TIMEOUT):
|
||||
if not session.account or not \
|
||||
session.account.access(session.account, "noidletimeout", default=False):
|
||||
for session in (
|
||||
sess for sess in SESSIONS.values() if (now - sess.cmd_last) > _IDLE_TIMEOUT
|
||||
):
|
||||
if not session.account or not session.account.access(
|
||||
session.account, "noidletimeout", default=False
|
||||
):
|
||||
SESSIONS.disconnect(session, reason=reason)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
# Evennia Main Server object
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class Evennia(object):
|
||||
|
|
@ -165,7 +177,7 @@ class Evennia(object):
|
|||
application - an instantiated Twisted application
|
||||
|
||||
"""
|
||||
sys.path.insert(1, '.')
|
||||
sys.path.insert(1, ".")
|
||||
|
||||
# create a store of services
|
||||
self.services = service.MultiService()
|
||||
|
|
@ -189,6 +201,7 @@ class Evennia(object):
|
|||
# (see https://github.com/evennia/evennia/issues/1128)
|
||||
def _wrap_sigint_handler(*args):
|
||||
from twisted.internet.defer import Deferred
|
||||
|
||||
if hasattr(self, "web_root"):
|
||||
d = self.web_root.empty_threadpool()
|
||||
d.addCallback(lambda _: self.shutdown("reload", _reactor_stopping=True))
|
||||
|
|
@ -196,8 +209,8 @@ class Evennia(object):
|
|||
d = Deferred(lambda _: self.shutdown("reload", _reactor_stopping=True))
|
||||
d.addCallback(lambda _: reactor.stop())
|
||||
reactor.callLater(1, d.callback, None)
|
||||
reactor.sigInt = _wrap_sigint_handler
|
||||
|
||||
reactor.sigInt = _wrap_sigint_handler
|
||||
|
||||
# Server startup methods
|
||||
|
||||
|
|
@ -206,11 +219,14 @@ class Evennia(object):
|
|||
Optimize some SQLite stuff at startup since we
|
||||
can't save it to the database.
|
||||
"""
|
||||
if ((".".join(str(i) for i in django.VERSION) < "1.2" and
|
||||
settings.DATABASES.get('default', {}).get('ENGINE') == "sqlite3") or
|
||||
(hasattr(settings, 'DATABASES') and
|
||||
settings.DATABASES.get("default", {}).get('ENGINE', None) ==
|
||||
'django.db.backends.sqlite3')):
|
||||
if (
|
||||
".".join(str(i) for i in django.VERSION) < "1.2"
|
||||
and settings.DATABASES.get("default", {}).get("ENGINE") == "sqlite3"
|
||||
) or (
|
||||
hasattr(settings, "DATABASES")
|
||||
and settings.DATABASES.get("default", {}).get("ENGINE", None)
|
||||
== "django.db.backends.sqlite3"
|
||||
):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("PRAGMA cache_size=10000")
|
||||
cursor.execute("PRAGMA synchronous=OFF")
|
||||
|
|
@ -228,36 +244,68 @@ class Evennia(object):
|
|||
global INFO_DICT
|
||||
|
||||
# setting names
|
||||
settings_names = ("CMDSET_CHARACTER", "CMDSET_ACCOUNT",
|
||||
"BASE_ACCOUNT_TYPECLASS", "BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS", "BASE_ROOM_TYPECLASS",
|
||||
"BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS",
|
||||
"BASE_CHANNEL_TYPECLASS")
|
||||
settings_names = (
|
||||
"CMDSET_CHARACTER",
|
||||
"CMDSET_ACCOUNT",
|
||||
"BASE_ACCOUNT_TYPECLASS",
|
||||
"BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS",
|
||||
"BASE_ROOM_TYPECLASS",
|
||||
"BASE_EXIT_TYPECLASS",
|
||||
"BASE_SCRIPT_TYPECLASS",
|
||||
"BASE_CHANNEL_TYPECLASS",
|
||||
)
|
||||
# get previous and current settings so they can be compared
|
||||
settings_compare = list(zip([ServerConfig.objects.conf(name) for name in settings_names],
|
||||
[settings.__getattr__(name) for name in settings_names]))
|
||||
mismatches = [i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1]]
|
||||
if len(mismatches): # can't use any() since mismatches may be [0] which reads as False for any()
|
||||
settings_compare = list(
|
||||
zip(
|
||||
[ServerConfig.objects.conf(name) for name in settings_names],
|
||||
[settings.__getattr__(name) for name in settings_names],
|
||||
)
|
||||
)
|
||||
mismatches = [
|
||||
i for i, tup in enumerate(settings_compare) if tup[0] and tup[1] and tup[0] != tup[1]
|
||||
]
|
||||
if len(
|
||||
mismatches
|
||||
): # can't use any() since mismatches may be [0] which reads as False for any()
|
||||
# we have a changed default. Import relevant objects and
|
||||
# run the update
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.comms.models import ChannelDB
|
||||
#from evennia.accounts.models import AccountDB
|
||||
for i, prev, curr in ((i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches):
|
||||
|
||||
# from evennia.accounts.models import AccountDB
|
||||
for i, prev, curr in (
|
||||
(i, tup[0], tup[1]) for i, tup in enumerate(settings_compare) if i in mismatches
|
||||
):
|
||||
# update the database
|
||||
INFO_DICT['info'] = " %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..." % (settings_names[i], prev, curr)
|
||||
INFO_DICT["info"] = (
|
||||
" %s:\n '%s' changed to '%s'. Updating unchanged entries in database ..."
|
||||
% (settings_names[i], prev, curr)
|
||||
)
|
||||
if i == 0:
|
||||
ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update(db_cmdset_storage=curr)
|
||||
ObjectDB.objects.filter(db_cmdset_storage__exact=prev).update(
|
||||
db_cmdset_storage=curr
|
||||
)
|
||||
if i == 1:
|
||||
AccountDB.objects.filter(db_cmdset_storage__exact=prev).update(db_cmdset_storage=curr)
|
||||
AccountDB.objects.filter(db_cmdset_storage__exact=prev).update(
|
||||
db_cmdset_storage=curr
|
||||
)
|
||||
if i == 2:
|
||||
AccountDB.objects.filter(db_typeclass_path__exact=prev).update(db_typeclass_path=curr)
|
||||
AccountDB.objects.filter(db_typeclass_path__exact=prev).update(
|
||||
db_typeclass_path=curr
|
||||
)
|
||||
if i in (3, 4, 5, 6):
|
||||
ObjectDB.objects.filter(db_typeclass_path__exact=prev).update(db_typeclass_path=curr)
|
||||
ObjectDB.objects.filter(db_typeclass_path__exact=prev).update(
|
||||
db_typeclass_path=curr
|
||||
)
|
||||
if i == 7:
|
||||
ScriptDB.objects.filter(db_typeclass_path__exact=prev).update(db_typeclass_path=curr)
|
||||
ScriptDB.objects.filter(db_typeclass_path__exact=prev).update(
|
||||
db_typeclass_path=curr
|
||||
)
|
||||
if i == 8:
|
||||
ChannelDB.objects.filter(db_typeclass_path__exact=prev).update(db_typeclass_path=curr)
|
||||
ChannelDB.objects.filter(db_typeclass_path__exact=prev).update(
|
||||
db_typeclass_path=curr
|
||||
)
|
||||
# store the new default and clean caches
|
||||
ServerConfig.objects.conf(settings_names[i], curr)
|
||||
ObjectDB.flush_instance_cache()
|
||||
|
|
@ -266,8 +314,11 @@ class Evennia(object):
|
|||
ChannelDB.flush_instance_cache()
|
||||
# if this is the first start we might not have a "previous"
|
||||
# setup saved. Store it now.
|
||||
[ServerConfig.objects.conf(settings_names[i], tup[1])
|
||||
for i, tup in enumerate(settings_compare) if not tup[0]]
|
||||
[
|
||||
ServerConfig.objects.conf(settings_names[i], tup[1])
|
||||
for i, tup in enumerate(settings_compare)
|
||||
if not tup[0]
|
||||
]
|
||||
|
||||
def run_initial_setup(self):
|
||||
"""
|
||||
|
|
@ -278,19 +329,20 @@ class Evennia(object):
|
|||
Once finished the last_initial_setup_step is set to -1.
|
||||
"""
|
||||
global INFO_DICT
|
||||
last_initial_setup_step = ServerConfig.objects.conf('last_initial_setup_step')
|
||||
last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step")
|
||||
if not last_initial_setup_step:
|
||||
# None is only returned if the config does not exist,
|
||||
# i.e. this is an empty DB that needs populating.
|
||||
INFO_DICT['info'] = ' Server started for the first time. Setting defaults.'
|
||||
INFO_DICT["info"] = " Server started for the first time. Setting defaults."
|
||||
initial_setup.handle_setup(0)
|
||||
elif int(last_initial_setup_step) >= 0:
|
||||
# a positive value means the setup crashed on one of its
|
||||
# modules and setup will resume from this step, retrying
|
||||
# the last failed module. When all are finished, the step
|
||||
# is set to -1 to show it does not need to be run again.
|
||||
INFO_DICT['info'] = ' Resuming initial setup from step {last}.'.format(
|
||||
last=last_initial_setup_step)
|
||||
INFO_DICT["info"] = " Resuming initial setup from step {last}.".format(
|
||||
last=last_initial_setup_step
|
||||
)
|
||||
initial_setup.handle_setup(int(last_initial_setup_step))
|
||||
|
||||
def run_init_hooks(self, mode):
|
||||
|
|
@ -314,14 +366,14 @@ class Evennia(object):
|
|||
[p.at_init() for p in AccountDB.get_all_cached_instances()]
|
||||
|
||||
# call correct server hook based on start file value
|
||||
if mode == 'reload':
|
||||
if mode == "reload":
|
||||
logger.log_msg("Server successfully reloaded.")
|
||||
self.at_server_reload_start()
|
||||
elif mode == 'reset':
|
||||
elif mode == "reset":
|
||||
# only run hook, don't purge sessions
|
||||
self.at_server_cold_start()
|
||||
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
|
||||
elif mode == 'shutdown':
|
||||
elif mode == "shutdown":
|
||||
self.at_server_cold_start()
|
||||
# clear eventual lingering session storages
|
||||
ObjectDB.objects.clear_all_sessids()
|
||||
|
|
@ -331,7 +383,7 @@ class Evennia(object):
|
|||
self.at_server_start()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def shutdown(self, mode='reload', _reactor_stopping=False):
|
||||
def shutdown(self, mode="reload", _reactor_stopping=False):
|
||||
"""
|
||||
Shuts down the server from inside it.
|
||||
|
||||
|
|
@ -356,20 +408,24 @@ class Evennia(object):
|
|||
from evennia.server.models import ServerConfig
|
||||
from evennia.utils import gametime as _GAMETIME_MODULE
|
||||
|
||||
if mode == 'reload':
|
||||
if mode == "reload":
|
||||
# call restart hooks
|
||||
ServerConfig.objects.conf("server_restart_mode", "reload")
|
||||
yield [o.at_server_reload() for o in ObjectDB.get_all_cached_instances()]
|
||||
yield [p.at_server_reload() for p in AccountDB.get_all_cached_instances()]
|
||||
yield [(s.pause(manual_pause=False), s.at_server_reload())
|
||||
for s in ScriptDB.get_all_cached_instances() if s.is_active or s.attributes.has("_manual_pause")]
|
||||
yield [
|
||||
(s.pause(manual_pause=False), s.at_server_reload())
|
||||
for s in ScriptDB.get_all_cached_instances()
|
||||
if s.is_active or s.attributes.has("_manual_pause")
|
||||
]
|
||||
yield self.sessions.all_sessions_portal_sync()
|
||||
self.at_server_reload_stop()
|
||||
# only save monitor state on reload, not on shutdown/reset
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
|
||||
MONITOR_HANDLER.save()
|
||||
else:
|
||||
if mode == 'reset':
|
||||
if mode == "reset":
|
||||
# like shutdown but don't unset the is_connected flag and don't disconnect sessions
|
||||
yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()]
|
||||
yield [p.at_server_shutdown() for p in AccountDB.get_all_cached_instances()]
|
||||
|
|
@ -378,16 +434,24 @@ class Evennia(object):
|
|||
else: # shutdown
|
||||
yield [_SA(p, "is_connected", False) for p in AccountDB.get_all_cached_instances()]
|
||||
yield [o.at_server_shutdown() for o in ObjectDB.get_all_cached_instances()]
|
||||
yield [(p.unpuppet_all(), p.at_server_shutdown())
|
||||
for p in AccountDB.get_all_cached_instances()]
|
||||
yield [
|
||||
(p.unpuppet_all(), p.at_server_shutdown())
|
||||
for p in AccountDB.get_all_cached_instances()
|
||||
]
|
||||
yield ObjectDB.objects.clear_all_sessids()
|
||||
yield [(s.pause(manual_pause=s.attributes.get("_manual_pause", False)),
|
||||
s.at_server_shutdown()) for s in ScriptDB.get_all_cached_instances()]
|
||||
yield [
|
||||
(
|
||||
s.pause(manual_pause=s.attributes.get("_manual_pause", False)),
|
||||
s.at_server_shutdown(),
|
||||
)
|
||||
for s in ScriptDB.get_all_cached_instances()
|
||||
]
|
||||
ServerConfig.objects.conf("server_restart_mode", "reset")
|
||||
self.at_server_cold_stop()
|
||||
|
||||
# tickerhandler state should always be saved.
|
||||
from evennia.scripts.tickerhandler import TICKER_HANDLER
|
||||
|
||||
TICKER_HANDLER.save()
|
||||
|
||||
# always called, also for a reload
|
||||
|
|
@ -444,10 +508,12 @@ class Evennia(object):
|
|||
"""
|
||||
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
MONITOR_HANDLER.restore(mode == 'reload')
|
||||
|
||||
MONITOR_HANDLER.restore(mode == "reload")
|
||||
|
||||
from evennia.scripts.tickerhandler import TICKER_HANDLER
|
||||
TICKER_HANDLER.restore(mode == 'reload')
|
||||
|
||||
TICKER_HANDLER.restore(mode == "reload")
|
||||
|
||||
# after sync is complete we force-validate all scripts
|
||||
# (this also starts any that didn't yet start)
|
||||
|
|
@ -455,6 +521,7 @@ class Evennia(object):
|
|||
|
||||
# start the task handler
|
||||
from evennia.scripts.taskhandler import TASK_HANDLER
|
||||
|
||||
TASK_HANDLER.load()
|
||||
TASK_HANDLER.create_delays()
|
||||
|
||||
|
|
@ -468,17 +535,17 @@ class Evennia(object):
|
|||
mudinfo_chan = settings.CHANNEL_MUDINFO
|
||||
if not mudinfo_chan:
|
||||
raise RuntimeError("settings.CHANNEL_MUDINFO must be defined.")
|
||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan['key']):
|
||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
||||
channel = create_channel(**mudinfo_chan)
|
||||
channel.connect(god_account)
|
||||
# connectinfo
|
||||
connectinfo_chan = settings.CHANNEL_MUDINFO
|
||||
if connectinfo_chan:
|
||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan['key']):
|
||||
if not ChannelDB.objects.filter(db_key=mudinfo_chan["key"]):
|
||||
channel = create_channel(**connectinfo_chan)
|
||||
# default channels
|
||||
for chan_info in settings.DEFAULT_CHANNELS:
|
||||
if not ChannelDB.objects.filter(db_key=chan_info['key']):
|
||||
if not ChannelDB.objects.filter(db_key=chan_info["key"]):
|
||||
channel = create_channel(**chan_info)
|
||||
channel.connect(god_account)
|
||||
|
||||
|
|
@ -500,15 +567,19 @@ class Evennia(object):
|
|||
# We need to do this just in case the server was killed in a way where
|
||||
# the normal cleanup operations did not have time to run.
|
||||
from evennia.objects.models import ObjectDB
|
||||
|
||||
ObjectDB.objects.clear_all_sessids()
|
||||
|
||||
# Remove non-persistent scripts
|
||||
from evennia.scripts.models import ScriptDB
|
||||
|
||||
for script in ScriptDB.objects.filter(db_persistent=False):
|
||||
script.stop()
|
||||
|
||||
if GUEST_ENABLED:
|
||||
for guest in AccountDB.objects.all().filter(db_typeclass_path=settings.BASE_GUEST_TYPECLASS):
|
||||
for guest in AccountDB.objects.all().filter(
|
||||
db_typeclass_path=settings.BASE_GUEST_TYPECLASS
|
||||
):
|
||||
for character in guest.db._playable_characters:
|
||||
if character:
|
||||
character.delete()
|
||||
|
|
@ -523,11 +594,12 @@ class Evennia(object):
|
|||
if SERVER_STARTSTOP_MODULE:
|
||||
SERVER_STARTSTOP_MODULE.at_server_cold_stop()
|
||||
|
||||
#------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
# Start the Evennia game server and add all active services
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
# Tell the system the server is starting up; some things are not available yet
|
||||
|
|
@ -535,12 +607,13 @@ ServerConfig.objects.conf("server_starting_mode", True)
|
|||
|
||||
# twistd requires us to define the variable 'application' so it knows
|
||||
# what to execute from.
|
||||
application = service.Application('Evennia')
|
||||
application = service.Application("Evennia")
|
||||
|
||||
if "--nodaemon" not in sys.argv:
|
||||
# custom logging, but only if we are not running in interactive mode
|
||||
logfile = logger.WeeklyLogFile(os.path.basename(settings.SERVER_LOG_FILE),
|
||||
os.path.dirname(settings.SERVER_LOG_FILE))
|
||||
logfile = logger.WeeklyLogFile(
|
||||
os.path.basename(settings.SERVER_LOG_FILE), os.path.dirname(settings.SERVER_LOG_FILE)
|
||||
)
|
||||
application.setComponent(ILogObserver, logger.ServerLogObserver(logfile).emit)
|
||||
|
||||
# The main evennia server program. This sets up the database
|
||||
|
|
@ -554,28 +627,36 @@ if AMP_ENABLED:
|
|||
# it would be during testing and debugging.
|
||||
|
||||
ifacestr = ""
|
||||
if AMP_INTERFACE != '127.0.0.1':
|
||||
if AMP_INTERFACE != "127.0.0.1":
|
||||
ifacestr = "-%s" % AMP_INTERFACE
|
||||
|
||||
INFO_DICT["amp"] = 'amp %s: %s' % (ifacestr, AMP_PORT)
|
||||
INFO_DICT["amp"] = "amp %s: %s" % (ifacestr, AMP_PORT)
|
||||
|
||||
from evennia.server import amp_client
|
||||
|
||||
factory = amp_client.AMPClientFactory(EVENNIA)
|
||||
amp_service = internet.TCPClient(AMP_HOST, AMP_PORT, factory)
|
||||
amp_service.setName('ServerAMPClient')
|
||||
amp_service.setName("ServerAMPClient")
|
||||
EVENNIA.services.addService(amp_service)
|
||||
|
||||
if WEBSERVER_ENABLED:
|
||||
|
||||
# Start a django-compatible webserver.
|
||||
|
||||
from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, Website, LockableThreadPool, PrivateStaticRoot
|
||||
from evennia.server.webserver import (
|
||||
DjangoWebRoot,
|
||||
WSGIWebServer,
|
||||
Website,
|
||||
LockableThreadPool,
|
||||
PrivateStaticRoot,
|
||||
)
|
||||
|
||||
# start a thread pool and define the root url (/) as a wsgi resource
|
||||
# recognized by Django
|
||||
threads = LockableThreadPool(minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
|
||||
maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]))
|
||||
threads = LockableThreadPool(
|
||||
minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
|
||||
maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]),
|
||||
)
|
||||
|
||||
web_root = DjangoWebRoot(threads)
|
||||
# point our media resources to url /media
|
||||
|
|
@ -594,8 +675,8 @@ if WEBSERVER_ENABLED:
|
|||
INFO_DICT["webserver"] = ""
|
||||
for proxyport, serverport in WEBSERVER_PORTS:
|
||||
# create the webserver (we only need the port for this)
|
||||
webserver = WSGIWebServer(threads, serverport, web_site, interface='127.0.0.1')
|
||||
webserver.setName('EvenniaWebServer%s' % serverport)
|
||||
webserver = WSGIWebServer(threads, serverport, web_site, interface="127.0.0.1")
|
||||
webserver.setName("EvenniaWebServer%s" % serverport)
|
||||
EVENNIA.services.addService(webserver)
|
||||
|
||||
INFO_DICT["webserver"] += "webserver: %s" % serverport
|
||||
|
|
@ -603,18 +684,19 @@ if WEBSERVER_ENABLED:
|
|||
ENABLED = []
|
||||
if IRC_ENABLED:
|
||||
# IRC channel connections
|
||||
ENABLED.append('irc')
|
||||
ENABLED.append("irc")
|
||||
|
||||
if RSS_ENABLED:
|
||||
# RSS feed channel connections
|
||||
ENABLED.append('rss')
|
||||
ENABLED.append("rss")
|
||||
|
||||
if GRAPEVINE_ENABLED:
|
||||
# Grapevine channel connections
|
||||
ENABLED.append('grapevine')
|
||||
ENABLED.append("grapevine")
|
||||
|
||||
if GAME_INDEX_ENABLED:
|
||||
from evennia.server.game_index_client.service import EvenniaGameIndexService
|
||||
|
||||
egi_service = EvenniaGameIndexService()
|
||||
EVENNIA.services.addService(egi_service)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,25 +31,26 @@ from django.utils.translation import ugettext as _
|
|||
class NDbHolder(object):
|
||||
"""Holder for allowing property access of attributes"""
|
||||
|
||||
def __init__(self, obj, name, manager_name='attributes'):
|
||||
def __init__(self, obj, name, manager_name="attributes"):
|
||||
_SA(self, name, _GA(obj, manager_name))
|
||||
_SA(self, 'name', name)
|
||||
_SA(self, "name", name)
|
||||
|
||||
def __getattribute__(self, attrname):
|
||||
if attrname == 'all':
|
||||
if attrname == "all":
|
||||
# we allow to overload our default .all
|
||||
attr = _GA(self, _GA(self, 'name')).get("all")
|
||||
attr = _GA(self, _GA(self, "name")).get("all")
|
||||
return attr if attr else _GA(self, "all")
|
||||
return _GA(self, _GA(self, 'name')).get(attrname)
|
||||
return _GA(self, _GA(self, "name")).get(attrname)
|
||||
|
||||
def __setattr__(self, attrname, value):
|
||||
_GA(self, _GA(self, 'name')).add(attrname, value)
|
||||
_GA(self, _GA(self, "name")).add(attrname, value)
|
||||
|
||||
def __delattr__(self, attrname):
|
||||
_GA(self, _GA(self, 'name')).remove(attrname)
|
||||
_GA(self, _GA(self, "name")).remove(attrname)
|
||||
|
||||
def get_all(self):
|
||||
return _GA(self, _GA(self, 'name')).all()
|
||||
return _GA(self, _GA(self, "name")).all()
|
||||
|
||||
all = property(get_all)
|
||||
|
||||
|
||||
|
|
@ -147,6 +148,7 @@ class NAttributeHandler(object):
|
|||
# Server Session
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class ServerSession(Session):
|
||||
"""
|
||||
This class represents an account's session and is a template for
|
||||
|
|
@ -166,10 +168,11 @@ class ServerSession(Session):
|
|||
self.cmdset = CmdSetHandler(self, True)
|
||||
|
||||
def __cmdset_storage_get(self):
|
||||
return [path.strip() for path in self.cmdset_storage_string.split(',')]
|
||||
return [path.strip() for path in self.cmdset_storage_string.split(",")]
|
||||
|
||||
def __cmdset_storage_set(self, value):
|
||||
self.cmdset_storage_string = ",".join(str(val).strip() for val in make_iter(value))
|
||||
|
||||
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set)
|
||||
|
||||
def at_sync(self):
|
||||
|
|
@ -252,8 +255,7 @@ class ServerSession(Session):
|
|||
account.at_post_disconnect()
|
||||
# remove any webclient settings monitors associated with this
|
||||
# session
|
||||
MONITOR_HANDLER.remove(account, "_saved_webclient_options",
|
||||
self.sessid)
|
||||
MONITOR_HANDLER.remove(account, "_saved_webclient_options", self.sessid)
|
||||
|
||||
def get_account(self):
|
||||
"""
|
||||
|
|
@ -274,6 +276,7 @@ class ServerSession(Session):
|
|||
|
||||
"""
|
||||
return self.logged_in and self.puppet
|
||||
|
||||
get_character = get_puppet
|
||||
|
||||
def get_puppet_or_account(self):
|
||||
|
|
@ -302,7 +305,7 @@ class ServerSession(Session):
|
|||
cchan = channel and settings.CHANNEL_CONNECTINFO
|
||||
if cchan:
|
||||
try:
|
||||
cchan = ChannelDB.objects.get_channel(cchan['key'])
|
||||
cchan = ChannelDB.objects.get_channel(cchan["key"])
|
||||
cchan.msg("[%s]: %s" % (cchan.key, message))
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
|
@ -316,8 +319,8 @@ class ServerSession(Session):
|
|||
|
||||
"""
|
||||
flags = self.protocol_flags
|
||||
width = flags.get('SCREENWIDTH', {}).get(0, settings.CLIENT_DEFAULT_WIDTH)
|
||||
height = flags.get('SCREENHEIGHT', {}).get(0, settings.CLIENT_DEFAULT_HEIGHT)
|
||||
width = flags.get("SCREENWIDTH", {}).get(0, settings.CLIENT_DEFAULT_WIDTH)
|
||||
height = flags.get("SCREENHEIGHT", {}).get(0, settings.CLIENT_DEFAULT_HEIGHT)
|
||||
return width, height
|
||||
|
||||
def update_session_counters(self, idle=False):
|
||||
|
|
@ -461,7 +464,7 @@ class ServerSession(Session):
|
|||
if self.logged_in and hasattr(self, "account") and self.account:
|
||||
symbol = "(#%s)" % self.account.id
|
||||
try:
|
||||
if hasattr(self.address, '__iter__'):
|
||||
if hasattr(self.address, "__iter__"):
|
||||
address = ":".join([str(part) for part in self.address])
|
||||
else:
|
||||
address = self.address
|
||||
|
|
@ -525,6 +528,7 @@ class ServerSession(Session):
|
|||
def ndb_del(self):
|
||||
"""Stop accidental deletion."""
|
||||
raise Exception("Cannot delete the ndb object!")
|
||||
|
||||
ndb = property(ndb_get, ndb_set, ndb_del)
|
||||
db = property(ndb_get, ndb_set, ndb_del)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,10 +36,24 @@ class Session(object):
|
|||
"""
|
||||
|
||||
# names of attributes that should be affected by syncing.
|
||||
_attrs_to_sync = ('protocol_key', 'address', 'suid', 'sessid', 'uid', 'csessid',
|
||||
'uname', 'logged_in', 'puid',
|
||||
'conn_time', 'cmd_last', 'cmd_last_visible', 'cmd_total',
|
||||
'protocol_flags', 'server_data', "cmdset_storage_string")
|
||||
_attrs_to_sync = (
|
||||
"protocol_key",
|
||||
"address",
|
||||
"suid",
|
||||
"sessid",
|
||||
"uid",
|
||||
"csessid",
|
||||
"uname",
|
||||
"logged_in",
|
||||
"puid",
|
||||
"conn_time",
|
||||
"cmd_last",
|
||||
"cmd_last_visible",
|
||||
"cmd_total",
|
||||
"protocol_flags",
|
||||
"server_data",
|
||||
"cmdset_storage_string",
|
||||
)
|
||||
|
||||
def init_session(self, protocol_key, address, sessionhandler):
|
||||
"""
|
||||
|
|
@ -82,11 +96,13 @@ class Session(object):
|
|||
self.cmd_last = self.conn_time
|
||||
self.cmd_total = 0
|
||||
|
||||
self.protocol_flags = {"ENCODING": "utf-8",
|
||||
"SCREENREADER": False,
|
||||
"INPUTDEBUG": False,
|
||||
"RAW": False,
|
||||
"NOCOLOR": False}
|
||||
self.protocol_flags = {
|
||||
"ENCODING": "utf-8",
|
||||
"SCREENREADER": False,
|
||||
"INPUTDEBUG": False,
|
||||
"RAW": False,
|
||||
"NOCOLOR": False,
|
||||
}
|
||||
self.server_data = {}
|
||||
|
||||
# map of input data to session methods
|
||||
|
|
@ -105,8 +121,9 @@ class Session(object):
|
|||
the keys given by self._attrs_to_sync.
|
||||
|
||||
"""
|
||||
return dict((key, value) for key, value in self.__dict__.items()
|
||||
if key in self._attrs_to_sync)
|
||||
return dict(
|
||||
(key, value) for key, value in self.__dict__.items() if key in self._attrs_to_sync
|
||||
)
|
||||
|
||||
def load_sync_data(self, sessdata):
|
||||
"""
|
||||
|
|
@ -118,9 +135,12 @@ class Session(object):
|
|||
|
||||
"""
|
||||
for propname, value in sessdata.items():
|
||||
if (propname == "protocol_flags" and isinstance(value, dict) and
|
||||
hasattr(self, "protocol_flags") and
|
||||
isinstance(self.protocol_flags, dict)):
|
||||
if (
|
||||
propname == "protocol_flags"
|
||||
and isinstance(value, dict)
|
||||
and hasattr(self, "protocol_flags")
|
||||
and isinstance(self.protocol_flags, dict)
|
||||
):
|
||||
# special handling to allow partial update of protocol flags
|
||||
self.protocol_flags.update(value)
|
||||
else:
|
||||
|
|
@ -134,7 +154,9 @@ class Session(object):
|
|||
|
||||
"""
|
||||
if self.account:
|
||||
self.protocol_flags.update(self.account.attributes.get("_saved_protocol_flags", None) or {})
|
||||
self.protocol_flags.update(
|
||||
self.account.attributes.get("_saved_protocol_flags", None) or {}
|
||||
)
|
||||
|
||||
# access hooks
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,13 @@ import time
|
|||
from django.conf import settings
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.utils.logger import log_trace
|
||||
from evennia.utils.utils import (variable_from_module, is_iter,
|
||||
make_iter, delay, callables_from_module)
|
||||
from evennia.utils.utils import (
|
||||
variable_from_module,
|
||||
is_iter,
|
||||
make_iter,
|
||||
delay,
|
||||
callables_from_module,
|
||||
)
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
|
||||
from evennia.utils.inlinefuncs import parse_inlinefunc
|
||||
|
|
@ -33,7 +38,7 @@ _ServerConfig = None
|
|||
_ScriptDB = None
|
||||
_OOB_HANDLER = None
|
||||
|
||||
_ERR_BAD_UTF8 = 'Your client sent an incorrect UTF-8 sequence.'
|
||||
_ERR_BAD_UTF8 = "Your client sent an incorrect UTF-8 sequence."
|
||||
|
||||
|
||||
class DummySession(object):
|
||||
|
|
@ -43,23 +48,23 @@ class DummySession(object):
|
|||
DUMMYSESSION = DummySession()
|
||||
|
||||
# AMP signals
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
SCONN = chr(11) # server portal connection (for bots)
|
||||
PCONNSYNC = chr(12) # portal post-syncing session
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
SCONN = chr(11) # server portal connection (for bots)
|
||||
PCONNSYNC = chr(12) # portal post-syncing session
|
||||
PDISCONNALL = chr(13) # portal session discnnect all
|
||||
SRELOAD = chr(14) # server reloading (have portal start a new server)
|
||||
SSTART = chr(15) # server start (portal must already be running anyway)
|
||||
PSHUTD = chr(16) # portal (+server) shutdown
|
||||
SSHUTD = chr(17) # server shutdown
|
||||
PSTATUS = chr(18) # ping server or portal status
|
||||
SRESET = chr(19) # server shutdown in reset mode
|
||||
SRELOAD = chr(14) # server reloading (have portal start a new server)
|
||||
SSTART = chr(15) # server start (portal must already be running anyway)
|
||||
PSHUTD = chr(16) # portal (+server) shutdown
|
||||
SSHUTD = chr(17) # server shutdown
|
||||
PSTATUS = chr(18) # ping server or portal status
|
||||
SRESET = chr(19) # server shutdown in reset mode
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
|
|
@ -96,15 +101,16 @@ def delayed_import():
|
|||
if not _ScriptDB:
|
||||
from evennia.scripts.models import ScriptDB as _ScriptDB
|
||||
# including once to avoid warnings in Python syntax checkers
|
||||
assert(_ServerSession)
|
||||
assert(_AccountDB)
|
||||
assert(_ServerConfig)
|
||||
assert(_ScriptDB)
|
||||
assert _ServerSession
|
||||
assert _AccountDB
|
||||
assert _ServerConfig
|
||||
assert _ScriptDB
|
||||
|
||||
|
||||
#-----------------------------------------------------------
|
||||
# -----------------------------------------------------------
|
||||
# SessionHandler base class
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class SessionHandler(dict):
|
||||
"""
|
||||
|
|
@ -199,7 +205,7 @@ class SessionHandler(dict):
|
|||
except UnicodeDecodeError:
|
||||
# incorrect unicode sequence
|
||||
session.sendLine(_ERR_BAD_UTF8)
|
||||
data = ''
|
||||
data = ""
|
||||
|
||||
return data
|
||||
|
||||
|
|
@ -220,8 +226,11 @@ class SessionHandler(dict):
|
|||
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
||||
|
||||
return str(data)
|
||||
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
|
||||
and hasattr(data, '__dbclass__'):
|
||||
elif (
|
||||
hasattr(data, "id")
|
||||
and hasattr(data, "db_date_created")
|
||||
and hasattr(data, "__dbclass__")
|
||||
):
|
||||
# convert database-object to their string representation.
|
||||
return _validate(str(data))
|
||||
else:
|
||||
|
|
@ -259,6 +268,7 @@ class SessionHandler(dict):
|
|||
# Server-SessionHandler class
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class ServerSessionHandler(SessionHandler):
|
||||
"""
|
||||
This object holds the stack of sessions active in the game at
|
||||
|
|
@ -382,7 +392,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
self[sessid] = sess
|
||||
sess.at_sync()
|
||||
|
||||
mode = 'reload'
|
||||
mode = "reload"
|
||||
|
||||
# tell the server hook we synced
|
||||
self.server.at_post_portal_sync(mode)
|
||||
|
|
@ -440,9 +450,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
the Server.
|
||||
|
||||
"""
|
||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SCONN,
|
||||
protocol_path=protocol_path,
|
||||
config=configdict)
|
||||
self.server.amp_protocol.send_AdminServer2Portal(
|
||||
DUMMYSESSION, operation=SCONN, protocol_path=protocol_path, config=configdict
|
||||
)
|
||||
|
||||
def portal_restart_server(self):
|
||||
"""
|
||||
|
|
@ -464,8 +474,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
itself down)
|
||||
|
||||
"""
|
||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||
operation=PSHUTD)
|
||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=PSHUTD)
|
||||
|
||||
def login(self, session, account, force=False, testmode=False):
|
||||
"""
|
||||
|
|
@ -512,10 +521,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
session.logged_in = True
|
||||
# sync the portal to the session
|
||||
if not testmode:
|
||||
self.server.amp_protocol.send_AdminServer2Portal(session,
|
||||
operation=SLOGIN,
|
||||
sessiondata={"logged_in": True,
|
||||
"uid": session.uid})
|
||||
self.server.amp_protocol.send_AdminServer2Portal(
|
||||
session, operation=SLOGIN, sessiondata={"logged_in": True, "uid": session.uid}
|
||||
)
|
||||
account.at_post_login(session=session)
|
||||
if nsess < 2:
|
||||
SIGNAL_ACCOUNT_POST_FIRST_LOGIN.send(sender=account, session=session)
|
||||
|
|
@ -543,8 +551,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
nsess = len(self.sessions_from_account(session.account)) - 1
|
||||
sreason = " ({})".format(reason) if reason else ""
|
||||
string = "Logged out: {account} {address} ({nsessions} sessions(s) remaining){reason}"
|
||||
string = string.format(reason=sreason, account=session.account,
|
||||
address=session.address, nsessions=nsess)
|
||||
string = string.format(
|
||||
reason=sreason, account=session.account, address=session.address, nsessions=nsess
|
||||
)
|
||||
session.log(string)
|
||||
|
||||
if nsess == 0:
|
||||
|
|
@ -557,9 +566,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
del self[sessid]
|
||||
if sync_portal:
|
||||
# inform portal that session should be closed.
|
||||
self.server.amp_protocol.send_AdminServer2Portal(session,
|
||||
operation=SDISCONN,
|
||||
reason=reason)
|
||||
self.server.amp_protocol.send_AdminServer2Portal(
|
||||
session, operation=SDISCONN, reason=reason
|
||||
)
|
||||
|
||||
def all_sessions_portal_sync(self):
|
||||
"""
|
||||
|
|
@ -568,9 +577,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
|
||||
"""
|
||||
sessdata = self.get_all_sync_data()
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||
operation=SSYNC,
|
||||
sessiondata=sessdata)
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(
|
||||
DUMMYSESSION, operation=SSYNC, sessiondata=sessdata
|
||||
)
|
||||
|
||||
def session_portal_sync(self, session):
|
||||
"""
|
||||
|
|
@ -579,10 +588,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
|
||||
"""
|
||||
sessdata = {session.sessid: session.get_sync_data()}
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||
operation=SSYNC,
|
||||
sessiondata=sessdata,
|
||||
clean=False)
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(
|
||||
DUMMYSESSION, operation=SSYNC, sessiondata=sessdata, clean=False
|
||||
)
|
||||
|
||||
def session_portal_partial_sync(self, session_data):
|
||||
"""
|
||||
|
|
@ -593,10 +601,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
more sessions in detail.
|
||||
|
||||
"""
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||
operation=SSYNC,
|
||||
sessiondata=session_data,
|
||||
clean=False)
|
||||
return self.server.amp_protocol.send_AdminServer2Portal(
|
||||
DUMMYSESSION, operation=SSYNC, sessiondata=session_data, clean=False
|
||||
)
|
||||
|
||||
def disconnect_all_sessions(self, reason="You have been disconnected."):
|
||||
"""
|
||||
|
|
@ -610,12 +617,13 @@ class ServerSessionHandler(SessionHandler):
|
|||
for session in self:
|
||||
del session
|
||||
# tell portal to disconnect all sessions
|
||||
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
|
||||
operation=SDISCONNALL,
|
||||
reason=reason)
|
||||
self.server.amp_protocol.send_AdminServer2Portal(
|
||||
DUMMYSESSION, operation=SDISCONNALL, reason=reason
|
||||
)
|
||||
|
||||
def disconnect_duplicate_sessions(self, curr_session,
|
||||
reason=_("Logged in from elsewhere. Disconnecting.")):
|
||||
def disconnect_duplicate_sessions(
|
||||
self, curr_session, reason=_("Logged in from elsewhere. Disconnecting.")
|
||||
):
|
||||
"""
|
||||
Disconnects any existing sessions with the same user.
|
||||
|
||||
|
|
@ -628,10 +636,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
# 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()
|
||||
if sess.logged_in and
|
||||
sess.uid == uid and
|
||||
id(sess) != sid]
|
||||
doublet_sessions = [
|
||||
sess for sess in self.values() if sess.logged_in and sess.uid == uid and id(sess) != sid
|
||||
]
|
||||
|
||||
for session in doublet_sessions:
|
||||
self.disconnect(session, reason)
|
||||
|
|
@ -644,9 +651,13 @@ class ServerSessionHandler(SessionHandler):
|
|||
"""
|
||||
tcurr = time.time()
|
||||
reason = _("Idle timeout exceeded, disconnecting.")
|
||||
for session in (session for session in self.values()
|
||||
if session.logged_in and _IDLE_TIMEOUT > 0 and
|
||||
(tcurr - session.cmd_last) > _IDLE_TIMEOUT):
|
||||
for session in (
|
||||
session
|
||||
for session in self.values()
|
||||
if session.logged_in
|
||||
and _IDLE_TIMEOUT > 0
|
||||
and (tcurr - session.cmd_last) > _IDLE_TIMEOUT
|
||||
):
|
||||
self.disconnect(session, reason=reason)
|
||||
|
||||
def account_count(self):
|
||||
|
|
@ -670,8 +681,13 @@ class ServerSessionHandler(SessionHandler):
|
|||
amount of Sessions due to multi-playing).
|
||||
|
||||
"""
|
||||
return list(set(session.account for session in self.values()
|
||||
if session.logged_in and session.account))
|
||||
return list(
|
||||
set(
|
||||
session.account
|
||||
for session in self.values()
|
||||
if session.logged_in and session.account
|
||||
)
|
||||
)
|
||||
|
||||
def session_from_sessid(self, sessid):
|
||||
"""
|
||||
|
|
@ -702,8 +718,11 @@ class ServerSessionHandler(SessionHandler):
|
|||
sessions (Session or list): Session(s) found.
|
||||
|
||||
"""
|
||||
sessions = [self[sid] for sid in make_iter(sessid)
|
||||
if sid in self and self[sid].logged_in and account.uid == self[sid].uid]
|
||||
sessions = [
|
||||
self[sid]
|
||||
for sid in make_iter(sessid)
|
||||
if sid in self and self[sid].logged_in and account.uid == self[sid].uid
|
||||
]
|
||||
return sessions[0] if len(sessions) == 1 else sessions
|
||||
|
||||
def sessions_from_account(self, account):
|
||||
|
|
@ -734,6 +753,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
"""
|
||||
sessions = puppet.sessid.get()
|
||||
return sessions[0] if len(sessions) == 1 else sessions
|
||||
|
||||
sessions_from_character = sessions_from_puppet
|
||||
|
||||
def sessions_from_csessid(self, csessid):
|
||||
|
|
@ -749,8 +769,9 @@ class ServerSessionHandler(SessionHandler):
|
|||
"""
|
||||
if csessid:
|
||||
return []
|
||||
return [session for session in self.values()
|
||||
if session.csessid and session.csessid == csessid]
|
||||
return [
|
||||
session for session in self.values() if session.csessid and session.csessid == csessid
|
||||
]
|
||||
|
||||
def announce_all(self, message):
|
||||
"""
|
||||
|
|
@ -779,8 +800,7 @@ class ServerSessionHandler(SessionHandler):
|
|||
kwargs = self.clean_senddata(session, kwargs)
|
||||
|
||||
# send across AMP
|
||||
self.server.amp_protocol.send_MsgServer2Portal(session,
|
||||
**kwargs)
|
||||
self.server.amp_protocol.send_MsgServer2Portal(session, **kwargs)
|
||||
|
||||
def get_inputfuncs(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -24,45 +24,45 @@ from django.dispatch import Signal
|
|||
# Account.create() after the Account is created. Note that this will *not* fire
|
||||
# if calling create.create_account alone, since going through the Account.create()
|
||||
# is the most expected route.
|
||||
SIGNAL_ACCOUNT_POST_CREATE = Signal(providing_args=['ip', ])
|
||||
SIGNAL_ACCOUNT_POST_CREATE = Signal(providing_args=["ip"])
|
||||
|
||||
# The Sender is the renamed Account. This is triggered by the username setter in AccountDB.
|
||||
SIGNAL_ACCOUNT_POST_RENAME = Signal(providing_args=['old_name', 'new_name'])
|
||||
SIGNAL_ACCOUNT_POST_RENAME = Signal(providing_args=["old_name", "new_name"])
|
||||
|
||||
# The Sender is the connecting Account. This is triggered when an Account connects cold;
|
||||
# that is, it had no other sessions connected.
|
||||
SIGNAL_ACCOUNT_POST_FIRST_LOGIN = Signal(providing_args=['session', ])
|
||||
SIGNAL_ACCOUNT_POST_FIRST_LOGIN = Signal(providing_args=["session"])
|
||||
|
||||
# The sender is the connecting Account. This is triggered whenever a session authenticates
|
||||
# to an Account regardless of existing sessions. It then firest after FIRST_LOGIN signal
|
||||
SIGNAL_ACCOUNT_POST_LOGIN = Signal(providing_args=['session', ])
|
||||
SIGNAL_ACCOUNT_POST_LOGIN = Signal(providing_args=["session"])
|
||||
|
||||
# The Sender is the Account attempting to authenticate. This is triggered whenever a
|
||||
# session tries to login to an Account but fails.
|
||||
SIGNAL_ACCOUNT_POST_LOGIN_FAIL = Signal(providing_args=['session', ])
|
||||
SIGNAL_ACCOUNT_POST_LOGIN_FAIL = Signal(providing_args=["session"])
|
||||
|
||||
# The sender is the disconnecting Account. This is triggered whenever a session disconnects
|
||||
# from the account, regardless of how many it started with or remain.
|
||||
SIGNAL_ACCOUNT_POST_LOGOUT = Signal(providing_args=['session', ])
|
||||
SIGNAL_ACCOUNT_POST_LOGOUT = Signal(providing_args=["session"])
|
||||
|
||||
# The sender is the Account. This is triggered when an Account's final session disconnects.
|
||||
SIGNAL_ACCOUNT_POST_LAST_LOGOUT = Signal(providing_args=['session', ])
|
||||
SIGNAL_ACCOUNT_POST_LAST_LOGOUT = Signal(providing_args=["session"])
|
||||
|
||||
# The sender is an Object. This is triggered when Object has been created, after all hooks.
|
||||
SIGNAL_OBJECT_POST_CREATE = Signal()
|
||||
|
||||
# The sender is the Object being puppeted. This is triggered after all puppeting hooks have
|
||||
# been called. The Object has already been puppeted by this point.
|
||||
SIGNAL_OBJECT_POST_PUPPET = Signal(providing_args=['session', 'account'])
|
||||
SIGNAL_OBJECT_POST_PUPPET = Signal(providing_args=["session", "account"])
|
||||
|
||||
# The sender is the Object being released. This is triggered after all hooks are called.
|
||||
# The Object is no longer puppeted by this point.
|
||||
SIGNAL_OBJECT_POST_UNPUPPET = Signal(providing_args=['session', 'account'])
|
||||
SIGNAL_OBJECT_POST_UNPUPPET = Signal(providing_args=["session", "account"])
|
||||
|
||||
# The sender is the Typed Object being renamed. This isn't necessarily an Object;
|
||||
# it could be a script. It fires whenever the value of the Typed object's 'key'
|
||||
# changes. Will need to use isinstance() or other filtering on things that use this.
|
||||
SIGNAL_TYPED_OBJECT_POST_RENAME = Signal(providing_args=['old_key', 'new_key'])
|
||||
SIGNAL_TYPED_OBJECT_POST_RENAME = Signal(providing_args=["old_key", "new_key"])
|
||||
|
||||
# The sender is the created Script. This is called after the Script was first created,
|
||||
# after all hooks.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ from evennia.server import serversession, session
|
|||
from evennia.utils import create
|
||||
|
||||
from twisted.internet.base import DelayedCall
|
||||
|
||||
DelayedCall.debug = True
|
||||
|
||||
# @patch("evennia.server.initial_setup.get_god_account",
|
||||
# MagicMock(return_value=create.account("TestAMPAccount", "test@test.com", "testpassword")))
|
||||
class _TestAMP(TwistedTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(_TestAMP, self).setUp()
|
||||
self.account = mommy.make("accounts.AccountDB", id=1)
|
||||
|
|
@ -90,9 +90,9 @@ class TestAMPClientSend(_TestAMP):
|
|||
def test_adminserver2portal(self, mocktransport):
|
||||
self._connect_client(mocktransport)
|
||||
|
||||
self.amp_client.send_AdminServer2Portal(self.session,
|
||||
operation=amp.PSYNC,
|
||||
info_dict={}, spid=None)
|
||||
self.amp_client.send_AdminServer2Portal(
|
||||
self.session, operation=amp.PSYNC, info_dict={}, spid=None
|
||||
)
|
||||
wire_data = self._catch_wire_read(mocktransport)[0]
|
||||
|
||||
self._connect_server(mocktransport)
|
||||
|
|
@ -117,8 +117,7 @@ class TestAMPClientRecv(_TestAMP):
|
|||
def test_adminportal2server(self, mocktransport):
|
||||
self._connect_server(mocktransport)
|
||||
|
||||
self.amp_server.send_AdminPortal2Server(self.session,
|
||||
operation=amp.PDISCONNALL)
|
||||
self.amp_server.send_AdminPortal2Server(self.session, operation=amp.PDISCONNALL)
|
||||
wire_data = self._catch_wire_read(mocktransport)[0]
|
||||
|
||||
self._connect_client(mocktransport)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ from evennia.server import initial_setup
|
|||
|
||||
|
||||
class TestInitialSetup(TestCase):
|
||||
|
||||
@patch("evennia.server.initial_setup.AccountDB")
|
||||
def test_get_god_account(self, mocked_accountdb):
|
||||
mocked_accountdb.objects.get = MagicMock(return_value=1)
|
||||
|
|
|
|||
|
|
@ -13,22 +13,25 @@ from evennia.server import evennia_launcher
|
|||
from evennia.server.portal import amp
|
||||
|
||||
from twisted.internet.base import DelayedCall
|
||||
|
||||
DelayedCall.debug = True
|
||||
|
||||
|
||||
@patch("evennia.server.evennia_launcher.Popen", new=MagicMock())
|
||||
class TestLauncher(TwistedTestCase):
|
||||
|
||||
def test_is_windows(self):
|
||||
self.assertEqual(evennia_launcher._is_windows(), os.name == 'nt')
|
||||
self.assertEqual(evennia_launcher._is_windows(), os.name == "nt")
|
||||
|
||||
def test_file_compact(self):
|
||||
self.assertEqual(evennia_launcher._file_names_compact(
|
||||
"foo/bar/test1", "foo/bar/test2"),
|
||||
"foo/bar/test1 and test2")
|
||||
self.assertEqual(
|
||||
evennia_launcher._file_names_compact("foo/bar/test1", "foo/bar/test2"),
|
||||
"foo/bar/test1 and test2",
|
||||
)
|
||||
|
||||
self.assertEqual(evennia_launcher._file_names_compact(
|
||||
"foo/test1", "foo/bar/test2"),
|
||||
"foo/test1 and foo/bar/test2")
|
||||
self.assertEqual(
|
||||
evennia_launcher._file_names_compact("foo/test1", "foo/bar/test2"),
|
||||
"foo/test1 and foo/bar/test2",
|
||||
)
|
||||
|
||||
@patch("evennia.server.evennia_launcher.print")
|
||||
def test_print_info(self, mockprint):
|
||||
|
|
@ -41,7 +44,7 @@ class TestLauncher(TwistedTestCase):
|
|||
"webserver_proxy": 1234,
|
||||
"webclient": 1234,
|
||||
"webserver_internal": 1234,
|
||||
"amp": 1234
|
||||
"amp": 1234,
|
||||
}
|
||||
server_dict = {
|
||||
"servername": "testserver",
|
||||
|
|
@ -50,7 +53,7 @@ class TestLauncher(TwistedTestCase):
|
|||
"amp": 1234,
|
||||
"irc_rss": "irc.test",
|
||||
"info": "testing mode",
|
||||
"errors": ""
|
||||
"errors": "",
|
||||
}
|
||||
|
||||
evennia_launcher._print_info(portal_dict, server_dict)
|
||||
|
|
@ -129,14 +132,14 @@ class TestLauncher(TwistedTestCase):
|
|||
@patch("evennia.server.evennia_launcher.print")
|
||||
def test_query_status_run(self, mprint):
|
||||
evennia_launcher.query_status()
|
||||
mprint.assert_called_with('Portal: RUNNING (pid 100)\nServer: RUNNING (pid 100)')
|
||||
mprint.assert_called_with("Portal: RUNNING (pid 100)\nServer: RUNNING (pid 100)")
|
||||
|
||||
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_err)
|
||||
@patch("evennia.server.evennia_launcher.NO_REACTOR_STOP", True)
|
||||
@patch("evennia.server.evennia_launcher.print")
|
||||
def test_query_status_not_run(self, mprint):
|
||||
evennia_launcher.query_status()
|
||||
mprint.assert_called_with('Portal: NOT RUNNING\nServer: NOT RUNNING')
|
||||
mprint.assert_called_with("Portal: NOT RUNNING\nServer: NOT RUNNING")
|
||||
|
||||
@patch("evennia.server.evennia_launcher.send_instruction", _msend_status_ok)
|
||||
@patch("evennia.server.evennia_launcher.NO_REACTOR_STOP", True)
|
||||
|
|
@ -144,7 +147,7 @@ class TestLauncher(TwistedTestCase):
|
|||
mprint = MagicMock()
|
||||
|
||||
def testcall(response):
|
||||
resp = pickle.loads(response['status'])
|
||||
resp = pickle.loads(response["status"])
|
||||
mprint(resp)
|
||||
|
||||
evennia_launcher.query_status(callback=testcall)
|
||||
|
|
@ -173,10 +176,8 @@ class TestLauncher(TwistedTestCase):
|
|||
mcall = MagicMock()
|
||||
merr = MagicMock()
|
||||
evennia_launcher.wait_for_status(
|
||||
portal_running=True,
|
||||
server_running=True,
|
||||
callback=mcall,
|
||||
errback=merr)
|
||||
portal_running=True, server_running=True, callback=mcall, errback=merr
|
||||
)
|
||||
|
||||
mcall.assert_called_with(True, True)
|
||||
merr.assert_not_called()
|
||||
|
|
@ -187,10 +188,8 @@ class TestLauncher(TwistedTestCase):
|
|||
mcall = MagicMock()
|
||||
merr = MagicMock()
|
||||
evennia_launcher.wait_for_status(
|
||||
portal_running=True,
|
||||
server_running=True,
|
||||
callback=mcall,
|
||||
errback=merr)
|
||||
portal_running=True, server_running=True, callback=mcall, errback=merr
|
||||
)
|
||||
|
||||
mcall.assert_not_called()
|
||||
merr.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class MockSettings(object):
|
|||
Class for simulating django.conf.settings. Created with a single value, and then sets the required
|
||||
WEBSERVER_ENABLED setting to True or False depending if we're testing WEBSERVER_PORTS.
|
||||
"""
|
||||
|
||||
def __init__(self, setting, value=None):
|
||||
setattr(self, setting, value)
|
||||
if setting == "WEBSERVER_PORTS":
|
||||
|
|
@ -34,12 +35,26 @@ class TestDeprecations(TestCase):
|
|||
"""
|
||||
Class for testing deprecations.check_errors.
|
||||
"""
|
||||
|
||||
deprecated_settings = (
|
||||
"CMDSET_DEFAULT", "CMDSET_OOC", "BASE_COMM_TYPECLASS", "COMM_TYPECLASS_PATHS",
|
||||
"CHARACTER_DEFAULT_HOME", "OBJECT_TYPECLASS_PATHS", "SCRIPT_TYPECLASS_PATHS",
|
||||
"ACCOUNT_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS", "SEARCH_MULTIMATCH_SEPARATOR",
|
||||
"TIME_SEC_PER_MIN", "TIME_MIN_PER_HOUR", "TIME_HOUR_PER_DAY", "TIME_DAY_PER_WEEK",
|
||||
"TIME_WEEK_PER_MONTH", "TIME_MONTH_PER_YEAR", "GAME_DIRECTORY_LISTING")
|
||||
"CMDSET_DEFAULT",
|
||||
"CMDSET_OOC",
|
||||
"BASE_COMM_TYPECLASS",
|
||||
"COMM_TYPECLASS_PATHS",
|
||||
"CHARACTER_DEFAULT_HOME",
|
||||
"OBJECT_TYPECLASS_PATHS",
|
||||
"SCRIPT_TYPECLASS_PATHS",
|
||||
"ACCOUNT_TYPECLASS_PATHS",
|
||||
"CHANNEL_TYPECLASS_PATHS",
|
||||
"SEARCH_MULTIMATCH_SEPARATOR",
|
||||
"TIME_SEC_PER_MIN",
|
||||
"TIME_MIN_PER_HOUR",
|
||||
"TIME_HOUR_PER_DAY",
|
||||
"TIME_DAY_PER_WEEK",
|
||||
"TIME_WEEK_PER_MONTH",
|
||||
"TIME_MONTH_PER_YEAR",
|
||||
"GAME_DIRECTORY_LISTING",
|
||||
)
|
||||
|
||||
def test_check_errors(self):
|
||||
"""
|
||||
|
|
@ -51,34 +66,32 @@ class TestDeprecations(TestCase):
|
|||
self.assertRaises(DeprecationWarning, check_errors, MockSettings(setting))
|
||||
# test check for WEBSERVER_PORTS having correct value
|
||||
self.assertRaises(
|
||||
DeprecationWarning,
|
||||
check_errors, MockSettings("WEBSERVER_PORTS", value=["not a tuple"]))
|
||||
DeprecationWarning, check_errors, MockSettings("WEBSERVER_PORTS", value=["not a tuple"])
|
||||
)
|
||||
|
||||
|
||||
class ValidatorTest(EvenniaTest):
|
||||
|
||||
def test_validator(self):
|
||||
# Validator returns None on success and ValidationError on failure.
|
||||
validator = EvenniaPasswordValidator()
|
||||
|
||||
# This password should meet Evennia standards.
|
||||
self.assertFalse(validator.validate('testpassword', user=self.account))
|
||||
self.assertFalse(validator.validate("testpassword", user=self.account))
|
||||
|
||||
# This password contains illegal characters and should raise an Exception.
|
||||
from django.core.exceptions import ValidationError
|
||||
self.assertRaises(ValidationError, validator.validate, '(#)[#]<>', user=self.account)
|
||||
|
||||
self.assertRaises(ValidationError, validator.validate, "(#)[#]<>", user=self.account)
|
||||
|
||||
|
||||
class ThrottleTest(EvenniaTest):
|
||||
"""
|
||||
Class for testing the connection/IP throttle.
|
||||
"""
|
||||
|
||||
def test_throttle(self):
|
||||
ips = ('94.100.176.153', '45.56.148.77', '5.196.1.129')
|
||||
kwargs = {
|
||||
'limit': 5,
|
||||
'timeout': 15 * 60
|
||||
}
|
||||
ips = ("94.100.176.153", "45.56.148.77", "5.196.1.129")
|
||||
kwargs = {"limit": 5, "timeout": 15 * 60}
|
||||
|
||||
throttle = Throttle(**kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,97 +18,110 @@ class TestServer(TestCase):
|
|||
|
||||
def setUp(self):
|
||||
from evennia.server import server
|
||||
|
||||
self.server = server
|
||||
|
||||
def test__server_maintenance_reset(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=0,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
mocks['connection'].close = MagicMock()
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=456)
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=0,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=456)
|
||||
|
||||
# flush cache
|
||||
self.server._server_maintenance()
|
||||
mocks['ServerConfig'].objects.conf.assert_called_with('runtime', 456)
|
||||
mocks["ServerConfig"].objects.conf.assert_called_with("runtime", 456)
|
||||
|
||||
def test__server_maintenance_flush(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=600 - 1,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
mocks['connection'].close = MagicMock()
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=600 - 1,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=100)
|
||||
|
||||
# flush cache
|
||||
self.server._server_maintenance()
|
||||
mocks['_FLUSH_CACHE'].assert_called_with(1000)
|
||||
mocks["_FLUSH_CACHE"].assert_called_with(1000)
|
||||
|
||||
def test__server_maintenance_validate_scripts(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3600 - 1,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
mocks['connection'].close = MagicMock()
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3600 - 1,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=100)
|
||||
with patch("evennia.server.server.evennia.ScriptDB.objects.validate") as mock:
|
||||
self.server._server_maintenance()
|
||||
mocks['_FLUSH_CACHE'].assert_called_with(1000)
|
||||
mocks["_FLUSH_CACHE"].assert_called_with(1000)
|
||||
mock.assert_called()
|
||||
|
||||
def test__server_maintenance_channel_handler_update(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3700 - 1,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
mocks['connection'].close = MagicMock()
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3700 - 1,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=100)
|
||||
with patch("evennia.server.server.evennia.CHANNEL_HANDLER.update") as mock:
|
||||
self.server._server_maintenance()
|
||||
mock.assert_called()
|
||||
|
||||
def test__server_maintenance_close_connection(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
mocks['connection'].close = MagicMock()
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=100)
|
||||
self.server._server_maintenance()
|
||||
mocks['connection'].close.assert_called()
|
||||
mocks["connection"].close.assert_called()
|
||||
|
||||
def test__server_maintenance_idle_time(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
SESSIONS=DEFAULT,
|
||||
_IDLE_TIMEOUT=10,
|
||||
time=DEFAULT,
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
with patch.multiple(
|
||||
"evennia.server.server",
|
||||
LoopingCall=DEFAULT,
|
||||
Evennia=DEFAULT,
|
||||
_FLUSH_CACHE=DEFAULT,
|
||||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
SESSIONS=DEFAULT,
|
||||
_IDLE_TIMEOUT=10,
|
||||
time=DEFAULT,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
sess1 = MagicMock()
|
||||
sess2 = MagicMock()
|
||||
sess3 = MagicMock()
|
||||
|
|
@ -123,9 +136,9 @@ class TestServer(TestCase):
|
|||
sess3.account = MagicMock()
|
||||
sess4.account.access = MagicMock(return_value=False)
|
||||
|
||||
mocks['time'].time = MagicMock(return_value=1000)
|
||||
mocks["time"].time = MagicMock(return_value=1000)
|
||||
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=100)
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=100)
|
||||
mocks["SESSIONS"].values = MagicMock(return_value=[sess1, sess2, sess3, sess4])
|
||||
mocks["SESSIONS"].disconnect = MagicMock()
|
||||
|
||||
|
|
@ -135,11 +148,9 @@ class TestServer(TestCase):
|
|||
mocks["SESSIONS"].disconnect.assert_has_calls(calls, any_order=True)
|
||||
|
||||
def test_evennia_start(self):
|
||||
with patch.multiple("evennia.server.server",
|
||||
time=DEFAULT,
|
||||
service=DEFAULT) as mocks:
|
||||
with patch.multiple("evennia.server.server", time=DEFAULT, service=DEFAULT) as mocks:
|
||||
|
||||
mocks['time'].time = MagicMock(return_value=1000)
|
||||
mocks["time"].time = MagicMock(return_value=1000)
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
self.assertEqual(evennia.start_time, 1000)
|
||||
|
||||
|
|
@ -148,45 +159,49 @@ class TestServer(TestCase):
|
|||
@patch("evennia.server.server.ScriptDB")
|
||||
@patch("evennia.comms.models.ChannelDB")
|
||||
def test_update_defaults(self, mockchan, mockscript, mockacct, mockobj):
|
||||
with patch.multiple("evennia.server.server",
|
||||
ServerConfig=DEFAULT) as mocks:
|
||||
with patch.multiple("evennia.server.server", ServerConfig=DEFAULT) as mocks:
|
||||
|
||||
mockchan.objects.filter = MagicMock()
|
||||
mockscript.objects.filter = MagicMock()
|
||||
mockacct.objects.filter = MagicMock()
|
||||
mockobj.objects.filter = MagicMock()
|
||||
mockchan.objects.filter = MagicMock()
|
||||
mockscript.objects.filter = MagicMock()
|
||||
mockacct.objects.filter = MagicMock()
|
||||
mockobj.objects.filter = MagicMock()
|
||||
|
||||
# fake mismatches
|
||||
settings_names = ("CMDSET_CHARACTER", "CMDSET_ACCOUNT",
|
||||
"BASE_ACCOUNT_TYPECLASS", "BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS", "BASE_ROOM_TYPECLASS",
|
||||
"BASE_EXIT_TYPECLASS", "BASE_SCRIPT_TYPECLASS",
|
||||
"BASE_CHANNEL_TYPECLASS")
|
||||
fakes = {name: "Dummy.path" for name in settings_names}
|
||||
# fake mismatches
|
||||
settings_names = (
|
||||
"CMDSET_CHARACTER",
|
||||
"CMDSET_ACCOUNT",
|
||||
"BASE_ACCOUNT_TYPECLASS",
|
||||
"BASE_OBJECT_TYPECLASS",
|
||||
"BASE_CHARACTER_TYPECLASS",
|
||||
"BASE_ROOM_TYPECLASS",
|
||||
"BASE_EXIT_TYPECLASS",
|
||||
"BASE_SCRIPT_TYPECLASS",
|
||||
"BASE_CHANNEL_TYPECLASS",
|
||||
)
|
||||
fakes = {name: "Dummy.path" for name in settings_names}
|
||||
|
||||
def _mock_conf(key, *args):
|
||||
return fakes[key]
|
||||
def _mock_conf(key, *args):
|
||||
return fakes[key]
|
||||
|
||||
mocks['ServerConfig'].objects.conf = _mock_conf
|
||||
mocks["ServerConfig"].objects.conf = _mock_conf
|
||||
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
evennia.update_defaults()
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
evennia.update_defaults()
|
||||
|
||||
mockchan.objects.filter.assert_called()
|
||||
mockscript.objects.filter.assert_called()
|
||||
mockacct.objects.filter.assert_called()
|
||||
mockobj.objects.filter.assert_called()
|
||||
mockchan.objects.filter.assert_called()
|
||||
mockscript.objects.filter.assert_called()
|
||||
mockacct.objects.filter.assert_called()
|
||||
mockobj.objects.filter.assert_called()
|
||||
|
||||
def test_initial_setup(self):
|
||||
from evennia.utils.create import create_account
|
||||
|
||||
acct = create_account("TestSuperuser", "test@test.com", "testpassword",
|
||||
is_superuser=True)
|
||||
acct = create_account("TestSuperuser", "test@test.com", "testpassword", is_superuser=True)
|
||||
|
||||
with patch.multiple("evennia.server.initial_setup",
|
||||
reset_server=DEFAULT,
|
||||
AccountDB=DEFAULT) as mocks:
|
||||
mocks['AccountDB'].objects.get = MagicMock(return_value=acct)
|
||||
with patch.multiple(
|
||||
"evennia.server.initial_setup", reset_server=DEFAULT, AccountDB=DEFAULT
|
||||
) as mocks:
|
||||
mocks["AccountDB"].objects.get = MagicMock(return_value=acct)
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
evennia.run_initial_setup()
|
||||
acct.delete()
|
||||
|
|
@ -194,16 +209,17 @@ class TestServer(TestCase):
|
|||
def test_initial_setup_retry(self):
|
||||
from evennia.utils.create import create_account
|
||||
|
||||
acct = create_account("TestSuperuser2", "test@test.com", "testpassword",
|
||||
is_superuser=True)
|
||||
acct = create_account("TestSuperuser2", "test@test.com", "testpassword", is_superuser=True)
|
||||
|
||||
with patch.multiple("evennia.server.initial_setup",
|
||||
ServerConfig=DEFAULT,
|
||||
reset_server=DEFAULT,
|
||||
AccountDB=DEFAULT) as mocks:
|
||||
mocks['AccountDB'].objects.get = MagicMock(return_value=acct)
|
||||
with patch.multiple(
|
||||
"evennia.server.initial_setup",
|
||||
ServerConfig=DEFAULT,
|
||||
reset_server=DEFAULT,
|
||||
AccountDB=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["AccountDB"].objects.get = MagicMock(return_value=acct)
|
||||
# a last_initial_setup_step > 0
|
||||
mocks['ServerConfig'].objects.conf = MagicMock(return_value=4)
|
||||
mocks["ServerConfig"].objects.conf = MagicMock(return_value=4)
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
evennia.run_initial_setup()
|
||||
acct.delete()
|
||||
|
|
@ -211,6 +227,7 @@ class TestServer(TestCase):
|
|||
@override_settings(DEFAULT_HOME="#1")
|
||||
def test_run_init_hooks(self):
|
||||
from evennia.utils import create
|
||||
|
||||
obj1 = create.object(key="HookTestObj1")
|
||||
obj2 = create.object(key="HookTestObj2")
|
||||
acct1 = create.account("HookAcct1", "hooktest1@test.com", "testpasswd")
|
||||
|
|
@ -224,9 +241,9 @@ class TestServer(TestCase):
|
|||
mockobj.objects.clear_all_sessids = MagicMock()
|
||||
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
evennia.run_init_hooks('reload')
|
||||
evennia.run_init_hooks('reset')
|
||||
evennia.run_init_hooks('shutdown')
|
||||
evennia.run_init_hooks("reload")
|
||||
evennia.run_init_hooks("reset")
|
||||
evennia.run_init_hooks("shutdown")
|
||||
|
||||
mockacct.get_all_cached_instances.assert_called()
|
||||
mockobj.get_all_cached_instances.assert_called()
|
||||
|
|
@ -236,7 +253,7 @@ class TestServer(TestCase):
|
|||
acct1.delete()
|
||||
acct2.delete()
|
||||
|
||||
@patch('evennia.server.server.INFO_DICT', {"test": "foo"})
|
||||
@patch("evennia.server.server.INFO_DICT", {"test": "foo"})
|
||||
def test_get_info_dict(self):
|
||||
evennia = self.server.Evennia(MagicMock())
|
||||
self.assertEqual(evennia.get_info_dict(), {"test": "foo"})
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
|
|||
If not given, a subset of settings.INSTALLED_APPS will be used.
|
||||
"""
|
||||
import evennia
|
||||
|
||||
evennia._init()
|
||||
return super(EvenniaTestSuiteRunner, self).build_suite(
|
||||
test_labels, extra_tests=extra_tests, **kwargs)
|
||||
test_labels, extra_tests=extra_tests, **kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Throttle(object):
|
|||
no recent failures have been recorded.
|
||||
"""
|
||||
|
||||
error_msg = 'Too many failed attempts; you must wait a few minutes before trying again.'
|
||||
error_msg = "Too many failed attempts; you must wait a few minutes before trying again."
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -31,8 +31,8 @@ class Throttle(object):
|
|||
the throttle is imposed!
|
||||
"""
|
||||
self.storage = defaultdict(deque)
|
||||
self.cache_size = self.limit = kwargs.get('limit', 5)
|
||||
self.timeout = kwargs.get('timeout', 5 * 60)
|
||||
self.cache_size = self.limit = kwargs.get("limit", 5)
|
||||
self.timeout = kwargs.get("timeout", 5 * 60)
|
||||
|
||||
def get(self, ip=None):
|
||||
"""
|
||||
|
|
@ -49,10 +49,12 @@ class Throttle(object):
|
|||
timestamps of recent failures only for that IP.
|
||||
|
||||
"""
|
||||
if ip: return self.storage.get(ip, deque(maxlen=self.cache_size))
|
||||
else: return self.storage
|
||||
if ip:
|
||||
return self.storage.get(ip, deque(maxlen=self.cache_size))
|
||||
else:
|
||||
return self.storage
|
||||
|
||||
def update(self, ip, failmsg='Exceeded threshold.'):
|
||||
def update(self, ip, failmsg="Exceeded threshold."):
|
||||
"""
|
||||
Store the time of the latest failure.
|
||||
|
||||
|
|
@ -78,8 +80,11 @@ class Throttle(object):
|
|||
currently_throttled = self.check(ip)
|
||||
|
||||
# If this makes it engage, log a single activation event
|
||||
if (not previously_throttled and currently_throttled):
|
||||
logger.log_sec('Throttle Activated: %s (IP: %s, %i hits in %i seconds.)' % (failmsg, ip, self.limit, self.timeout))
|
||||
if not previously_throttled and currently_throttled:
|
||||
logger.log_sec(
|
||||
"Throttle Activated: %s (IP: %s, %i hits in %i seconds.)"
|
||||
% (failmsg, ip, self.limit, self.timeout)
|
||||
)
|
||||
|
||||
def check(self, ip):
|
||||
"""
|
||||
|
|
@ -107,7 +112,7 @@ class Throttle(object):
|
|||
return True
|
||||
else:
|
||||
# timeout has passed. clear faillist
|
||||
del(self.storage[ip])
|
||||
del self.storage[ip]
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -24,26 +24,28 @@ class EvenniaUsernameAvailabilityValidator:
|
|||
"""
|
||||
|
||||
# Check guest list
|
||||
if (settings.GUEST_LIST and username.lower() in (guest.lower() for guest in settings.GUEST_LIST)):
|
||||
if settings.GUEST_LIST and username.lower() in (
|
||||
guest.lower() for guest in settings.GUEST_LIST
|
||||
):
|
||||
raise ValidationError(
|
||||
_('Sorry, that username is reserved.'),
|
||||
code='evennia_username_reserved',
|
||||
_("Sorry, that username is reserved."), code="evennia_username_reserved"
|
||||
)
|
||||
|
||||
# Check database
|
||||
exists = AccountDB.objects.filter(username__iexact=username).exists()
|
||||
if exists:
|
||||
raise ValidationError(
|
||||
_('Sorry, that username is already taken.'),
|
||||
code='evennia_username_taken',
|
||||
_("Sorry, that username is already taken."), code="evennia_username_taken"
|
||||
)
|
||||
|
||||
|
||||
class EvenniaPasswordValidator:
|
||||
|
||||
def __init__(self, regex=r"^[\w. @+\-',]+$",
|
||||
policy="Password should contain a mix of letters, "
|
||||
"spaces, digits and @/./+/-/_/'/, only."):
|
||||
def __init__(
|
||||
self,
|
||||
regex=r"^[\w. @+\-',]+$",
|
||||
policy="Password should contain a mix of letters, "
|
||||
"spaces, digits and @/./+/-/_/'/, only.",
|
||||
):
|
||||
"""
|
||||
Constructs a standard Django password validator.
|
||||
|
||||
|
|
@ -71,10 +73,7 @@ class EvenniaPasswordValidator:
|
|||
"""
|
||||
# Check complexity
|
||||
if not re.findall(self.regex, password):
|
||||
raise ValidationError(
|
||||
_(self.policy),
|
||||
code='evennia_password_policy',
|
||||
)
|
||||
raise ValidationError(_(self.policy), code="evennia_password_policy")
|
||||
|
||||
def get_help_text(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class LockableThreadPool(threadpool.ThreadPool):
|
|||
# X-Forwarded-For Handler
|
||||
#
|
||||
|
||||
|
||||
class HTTPChannelWithXForwardedFor(http.HTTPChannel):
|
||||
"""
|
||||
HTTP xforward class
|
||||
|
|
@ -73,9 +74,9 @@ class HTTPChannelWithXForwardedFor(http.HTTPChannel):
|
|||
http.HTTPChannel.allHeadersReceived(self)
|
||||
req = self.requests[-1]
|
||||
client_ip, port = self.transport.client
|
||||
proxy_chain = req.getHeader('X-FORWARDED-FOR')
|
||||
proxy_chain = req.getHeader("X-FORWARDED-FOR")
|
||||
if proxy_chain and client_ip in _UPSTREAM_IPS:
|
||||
forwarded = proxy_chain.split(', ', 1)[CLIENT]
|
||||
forwarded = proxy_chain.split(", ", 1)[CLIENT]
|
||||
self.transport.client = (forwarded, port)
|
||||
|
||||
|
||||
|
|
@ -100,10 +101,11 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
|
||||
"""
|
||||
request.notifyFinish().addErrback(
|
||||
lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
|
||||
lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f)
|
||||
)
|
||||
return EvenniaReverseProxyResource(
|
||||
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
|
||||
self.reactor)
|
||||
self.host, self.port, self.path + "/" + urlquote(path, safe=""), self.reactor
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
|
|
@ -121,18 +123,24 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
request.content.seek(0, 0)
|
||||
qs = urllib.parse.urlparse(request.uri)[4]
|
||||
if qs:
|
||||
rest = self.path + '?' + qs.decode()
|
||||
rest = self.path + "?" + qs.decode()
|
||||
else:
|
||||
rest = self.path
|
||||
rest = rest.encode()
|
||||
clientFactory = self.proxyClientFactoryClass(
|
||||
request.method, rest, request.clientproto,
|
||||
request.getAllHeaders(), request.content.read(), request)
|
||||
request.method,
|
||||
rest,
|
||||
request.clientproto,
|
||||
request.getAllHeaders(),
|
||||
request.content.read(),
|
||||
request,
|
||||
)
|
||||
clientFactory.noisy = False
|
||||
self.reactor.connectTCP(self.host, self.port, clientFactory)
|
||||
# don't trigger traceback if connection is lost before request finish.
|
||||
request.notifyFinish().addErrback(
|
||||
lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
|
||||
lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f)
|
||||
)
|
||||
return NOT_DONE_YET
|
||||
|
||||
|
||||
|
|
@ -178,7 +186,7 @@ class DjangoWebRoot(resource.Resource):
|
|||
return defer.DeferredList(self._pending_requests, consumeErrors=True)
|
||||
|
||||
def _decrement_requests(self, *args, **kwargs):
|
||||
self._pending_requests.pop(kwargs.get('deferred', None), None)
|
||||
self._pending_requests.pop(kwargs.get("deferred", None), None)
|
||||
|
||||
def getChild(self, path, request):
|
||||
"""
|
||||
|
|
@ -209,10 +217,12 @@ class DjangoWebRoot(resource.Resource):
|
|||
# Site with deactivateable logging
|
||||
#
|
||||
|
||||
|
||||
class Website(server.Site):
|
||||
"""
|
||||
This class will only log http requests if settings.DEBUG is True.
|
||||
"""
|
||||
|
||||
noisy = False
|
||||
|
||||
def logPrefix(self):
|
||||
|
|
@ -231,6 +241,7 @@ class Website(server.Site):
|
|||
# Threaded Webserver
|
||||
#
|
||||
|
||||
|
||||
class WSGIWebServer(internet.TCPServer):
|
||||
"""
|
||||
This is a WSGI webserver. It makes sure to start
|
||||
|
|
@ -277,5 +288,6 @@ class PrivateStaticRoot(static.File):
|
|||
won't see an index of all static/media files on the server).
|
||||
|
||||
"""
|
||||
|
||||
def directoryListing(self):
|
||||
return resource.ForbiddenResource()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue