From 1172067afc34122ef550c1bc2a4990ac582b553d Mon Sep 17 00:00:00 2001 From: Griatch Date: Thu, 27 Jun 2019 23:07:35 +0200 Subject: [PATCH] Add evennia connectsions wizard functionality --- CHANGELOG.md | 7 ++ evennia/server/connection_wizard.py | 124 ++++++++++++++++----- evennia/server/deprecations.py | 2 + evennia/server/game_index_client/client.py | 1 + evennia/server/portal/mssp.py | 6 +- evennia/server/tests/test_misc.py | 2 +- evennia/settings_default.py | 65 +++-------- 7 files changed, 126 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d62cbe6e..04f47c21a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,13 @@ - Change webclient from old txws version to use more supported/feature-rich Autobahn websocket library +#### Evennia game index + +- Made Evennia game index client a part of core - now configured from settings file (old configs + need to be moved) +- The `evennia connections` command starts a wizard that helps you connect your game to the game index. +- The game index now accepts games with no public telnet/webclient info (for early prototypes). + #### New golden-layout based Webclient UI (@friarzen) - Features - Much slicker behavior and more professional look diff --git a/evennia/server/connection_wizard.py b/evennia/server/connection_wizard.py index 28432c3fc..e8db990be 100644 --- a/evennia/server/connection_wizard.py +++ b/evennia/server/connection_wizard.py @@ -3,9 +3,10 @@ Link Evennia to external resources (wizard plugin for evennia_launcher) """ import sys +from os import path import pprint from django.conf import settings -from evennia.utils.utils import list_to_string +from evennia.utils.utils import list_to_string, mod_import class ConnectionWizard(object): @@ -33,7 +34,7 @@ class ConnectionWizard(object): """ opt_txt = "\n".join(f" {key}: {desc}" for key, (desc, _, _) in options.items()) - self.display(opt_txt) + self.display(opt_txt + "\n") while True: resp = input(prompt).strip() @@ -78,7 +79,7 @@ class ConnectionWizard(object): elif resp.lower() in ("quit", "q"): sys.exit() - def ask_choice(self, prompt="> ", options=None, default=None): + def ask_choice(self, prompt=" > ", options=None, default=None): """ Ask multiple-choice question, get response inline. @@ -91,7 +92,7 @@ class ConnectionWizard(object): """ opt_txt = "\n".join(f" {ind + 1}: {desc}" for ind, desc in enumerate(options)) - self.display(opt_txt) + self.display(opt_txt + "\n") while True: resp = input(prompt).strip() @@ -109,13 +110,16 @@ class ConnectionWizard(object): return selection self.display(" Select one of the given options.") - def ask_input(self, prompt="> ", default=None, verify=True, max_len=None): + def ask_input(self, prompt=" > ", default=None, validator=None): """ Get arbitrary input inline. Kwargs: prompt (str): The display prompt. - default (str, optional): If empty input, use this. + default (str): If empty input, use this. + validator (callable): If given, the input will be passed + into this callable. It should return True unless validation + fails (and is expected to echo why if so). Returns: inp (str): The input given, or default. @@ -123,11 +127,19 @@ class ConnectionWizard(object): """ while True: resp = input(prompt).strip() + if not resp and default: resp = str(default) + if resp.lower() in ('q', 'quit'): + sys.exit() + 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'): continue @@ -135,18 +147,15 @@ class ConnectionWizard(object): sys.exit() return resp - if verify: - self.display(resp) - if max_len: - nlen = len(resp) - if nlen > max_len: - self.display(f" This text is {nlen} characters long. Max is {max_len}.") - continue - ok = input("\n Is the above looking correct? [Y]/N: ") - if ok.lower() in ("n", "no"): - continue - elif ok.lower() in ('q', 'quit'): - sys.exit() + if validator and not validator(resp): + continue + + self.display(resp) + ok = input("\n Is this correct? [Y]/N: ") + if ok.lower() in ("n", "no"): + continue + elif ok.lower() in ('q', 'quit'): + sys.exit() return resp @@ -155,10 +164,12 @@ def node_start(wizard): This wizard helps activate external networks with Evennia. It will create a config that will be attached to the bottom of the game settings file. - Use `quit` at any time to abort and throw away any changes. + Make sure you have at least started the game once before continuing! + + Use `quit` at any time to abort and throw away unsaved changes. """ options = { - "1": ("Add game to Evennia game index (also for closed dev games)", + "1": ("Add game to Evennia game index (also for closed-dev games)", node_game_index_start, {}), "2": ("Add MSSP information (for mud-list crawlers)", node_mssp_start, {}), @@ -231,9 +242,18 @@ def node_game_index_fields(wizard, status=None): {sdesc_default} """ + def sdesc_validator(inp): + tmax = 255 + tlen = len(inp) + if tlen > 255: + print(f"The short desc must be shorter than {tmax} characters (was {tlen}).") + wizard.ask_continue() + return False + return True + wizard.display(text) wizard.game_index_listing['short_description'] = \ - wizard.ask_input(default=sdesc_default, max_len=255) + wizard.ask_input(default=sdesc_default, validator=sdesc_validator) # long desc @@ -265,9 +285,16 @@ def node_game_index_fields(wizard, status=None): {listing_default} """ + def contact_validator(inp): + if not inp or "@" not in inp: + print("This should be an email and cannot be blank.") + wizard.ask_continue() + return False + return True + wizard.display(text) wizard.game_index_listing['listing_contact'] = \ - wizard.ask_input(default=listing_default) + wizard.ask_input(default=listing_default, validator=contact_validator) # telnet hostname @@ -359,17 +386,29 @@ def node_game_index_fields(wizard, status=None): def node_mssp_start(wizard): + mssp_module = mod_import(settings.MSSP_META_MODULE) + filename = mssp_module.__file__ + text = f""" MSSP (Mud Server Status Protocol) allows online MUD-listing sites/crawlers to continuously monitor your game and list information about it. Some of this, like active player-count, Evennia will automatically add for you, - whereas many fields is info about your game. + whereas many fields are manually added info about your game. To use MSSP you should generally have a publicly open game that external - players can connect to. + players can connect to. You also need to register at a MUD listing site to + tell them to list your game. + + MSSP has a large number of configuration options and we found it was simply + a lot easier to set them in a file rather than using this wizard. So to + configure MSSP, edit the empty template listing found here: + + '{filename}' """ - wizard.mssp_table + wizard.display(text) + wizard.ask_continue() + node_start(wizard) # Admin @@ -378,7 +417,30 @@ def _save_changes(wizard): """ Perform the save """ - print("saving!") + + # 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: + 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") + + 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} ...") + def node_view_and_apply_settings(wizard): """ @@ -393,18 +455,22 @@ 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 = 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 - print("- Game index:\n" + text) + wizard.display(f"Settings to save:\n\n{text}") if saves: if wizard.ask_yesno("Do you want to save these settings?") == 'yes': + wizard.save_output = text _save_changes(wizard) + wizard.display("... saved!") else: - print("Cancelled. Returning ...") + wizard.display("... cancelled.") wizard.ask_continue() node_start(wizard) diff --git a/evennia/server/deprecations.py b/evennia/server/deprecations.py index e25c6636a..3920c5087 100644 --- a/evennia/server/deprecations.py +++ b/evennia/server/deprecations.py @@ -50,6 +50,8 @@ def check_errors(settings): if hasattr(settings, "ACCOUNT_TYPECLASS_PATHS"): raise DeprecationWarning(deprstring % "ACCOUNT_TYPECLASS_PATHS") if hasattr(settings, "CHANNEL_TYPECLASS_PATHS"): + raise DeprecationWarning(deprstring % "CHANNEL_TYPECLASS_PATHS") + if hasattr(settings, "SEARCH_MULTIMATCH_SEPARATOR"): raise DeprecationWarning( "settings.SEARCH_MULTIMATCH_SEPARATOR was replaced by " "SEARCH_MULTIMATCH_REGEX and SEARCH_MULTIMATCH_TEMPLATE. " diff --git a/evennia/server/game_index_client/client.py b/evennia/server/game_index_client/client.py index aea3e27f3..b87712b9f 100644 --- a/evennia/server/game_index_client/client.py +++ b/evennia/server/game_index_client/client.py @@ -66,6 +66,7 @@ class EvenniaGameIndexClient(object): '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: # Improperly formed request. Defer to the callback as far as what # to do. Probably not a great idea to continue attempting to send diff --git a/evennia/server/portal/mssp.py b/evennia/server/portal/mssp.py index dff20a37a..78dd12b69 100644 --- a/evennia/server/portal/mssp.py +++ b/evennia/server/portal/mssp.py @@ -18,6 +18,9 @@ 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={}) + class Mssp(object): """ @@ -109,7 +112,8 @@ class Mssp(object): } # update the static table with the custom one - self.mssp_table.update(settings.MSSP_TABLE) + if MSSPTable_CUSTOM: + self.mssp_table.update(MSSPTable_CUSTOM) varlist = '' for variable, value in self.mssp_table.items(): diff --git a/evennia/server/tests/test_misc.py b/evennia/server/tests/test_misc.py index e48ff6984..ff0ba8cda 100644 --- a/evennia/server/tests/test_misc.py +++ b/evennia/server/tests/test_misc.py @@ -39,7 +39,7 @@ class TestDeprecations(TestCase): "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") + "TIME_WEEK_PER_MONTH", "TIME_MONTH_PER_YEAR", "GAME_DIRECTORY_LISTING") def test_check_errors(self): """ diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 7dbc57872..f9ae9d2ab 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -25,6 +25,9 @@ import sys # This is the name of your game. Make it catchy! SERVERNAME = "Evennia" +# Short one-sentence blurb describing your game. Shown under the title +# on the website and could be used in online listings of your game etc. +GAME_SLOGAN = "Python MU* creation system" # Lockdown mode will cut off the game from any external connections # and only allow connections from localhost. Requires a cold reboot. LOCKDOWN_MODE = False @@ -343,6 +346,9 @@ SERVER_SERVICES_PLUGIN_MODULES = ["server.conf.server_services_plugins"] # main Evennia Portal application when the Portal is initiated. # It will be called last in the startup sequence. PORTAL_SERVICES_PLUGIN_MODULES = ["server.conf.portal_services_plugins"] +# Module holding MSSP meta data. This is used by MUD-crawlers to determine +# what type of game you are running, how many accounts you have etc. +MSSP_META_MODULE = "server.conf.mssp" # Module for web plugins. WEB_PLUGINS_MODULE = "server.conf.web_plugins" # Tuple of modules implementing lock functions. All callable functions @@ -683,13 +689,19 @@ CHANNEL_CONNECTINFO = None # External Connections ###################################################################### +# Note: You do *not* have to make your MUD open to +# the public to use the external connections, they +# operate as long as you have an internet connection, +# just like stand-alone chat clients. + # The Evennia Game Index is a dynamic listing of Evennia games. You can add your game # to this list also if it is in closed pre-alpha development. GAME_INDEX_ENABLED = False # This dict GAME_INDEX_LISTING = { - 'game_status': 'closed-dev', # closed-dev, pre-alpha, pre-alpha, alpha, beta or launched - 'short_description': '', + 'game_name': SERVERNAME, + 'game_status': 'pre-alpha', # pre-alpha, alpha, beta or launched + 'short_description': GAME_SLOGAN, 'long_description': '', 'listing_contact': '', # email 'telnet_hostname': '', # mygame.com @@ -697,57 +709,11 @@ GAME_INDEX_LISTING = { 'game_website': '', # http://mygame.com 'web_client_url': '' # http://mygame.com/webclient } - -# MSSP (Mud Server Status Protocol) is used by MUD-crawlers to determine -# what type of game you are running, how many players you have etc. Some of -# this (like server name and current number of players) is handled by Evennia -# automatically, other fields are set by you. -MSSP_TABLE = { - "HOSTNAME": "", "PORT": "", # telnet host/port - "CONTACT": "", "CREATED": "", # email, year of game creation - "IP": "", "ICON": "", # ip address; url to icon 32x32or larger; <32kb. - "LANGUAGE": "English", "LOCATION": "", # server country location, like "Sweden" - "MINIMUM AGE": "0", # set to 0 if not applicable - "WEBSITE": "www.evennia.com", - "GENRE": "None", # Adult, Fantasy, Historical, Horror, Modern, None, or Science Fiction - "GAMEPLAY": "None", # Adventure, Educational, Hack and Slash, None, - # Player versus Player, Player versus Environment, - # Roleplaying, Simulation, Social or Strategy - "STATUS": "Alpha", # Alpha, Closed Beta, Open Beta, Live - "GAMESYSTEM": "Custom", # D&D, d20 System, World of Darkness, etc. Use Custom if homebrew - "SUBGENRE": "None", # Freeform, like LASG, Medieval Fantasy, World War II, Frankenstein, - # Cyberpunk, Dragonlance, etc. Or None if not available. - # use 0 if not applicable or off - "AREAS": "0", "HELPFILES": "0", "MOBILES": "0", "OBJECTS": "0", - "ROOMS": "0", "CLASSES": "0", "LEVELS": "0", "RACES": "0", "SKILLS": "0", - - "PAY TO PLAY": "0", "PAY FOR PERKS": "0", - "HIRING BUILDERS": "0", "HIRING CODERS": "0", - - "DBSIZE": "0", "EXITS": "0", "EXTRA DESCRIPTIONS": "0", - "MUDPROGS": "0", "MUDTRIGS": "0", "RESETS": "0", - - "ADULT MATERIAL": "0", "MULTICLASSING": "0", "NEWBIE FRIENDLY": "0", "PLAYER CITIES": "0", - "PLAYER CLANS": "0", "PLAYER CRAFTING": "0", "PLAYER GUILDS": "0", - "EQUIPMENT SYSTEM": "None", # "None", "Level", "Skill", "Both" - "MULTIPLAYING": "None", # "None", "Restricted", "Full" - "PLAYERKILLING": "None", # "None", "Restricted", "Full" - "QUEST SYSTEM": "None", # "None", "Immortal Run", "Automated", "Integrated" - "ROLEPLAYING": "None", # "None", "Accepted", "Encouraged", "Enforced" - "TRAINING SYSTEM": "None", # "None", "Level", "Skill", "Both" - "WORLD ORIGINALITY": "None", # "All Stock", "Mostly Stock", "Mostly Original", "All Original" -} - -# Note: You do *not* have to make your MUD open to -# the public to use the external connections, they -# operate as long as you have an internet connection, -# just like stand-alone chat clients. IRC requires -# that you have twisted.words installed. - # Evennia can connect to external IRC channels and # echo what is said on the channel to IRC and vice # versa. Obs - make sure the IRC network allows bots. # When enabled, command @irc2chan will be available in-game +# IRC requires that you have twisted.words installed. IRC_ENABLED = False # RSS allows to connect RSS feeds (from forum updates, blogs etc) to # an in-game channel. The channel will be updated when the rss feed @@ -757,7 +723,6 @@ IRC_ENABLED = False # http://code.google.com/p/feedparser/) RSS_ENABLED = False RSS_UPDATE_INTERVAL = 60 * 10 # 10 minutes - # Grapevine (grapevine.haus) is a network for listing MUDs as well as allow # users of said MUDs to communicate with each other on shared channels. To use, # your game must first be registered by logging in and creating a game entry at