253 lines
8.1 KiB
Python
253 lines
8.1 KiB
Python
"""
|
|
A login menu using EvMenu.
|
|
|
|
Contribution - Vincent-lg 2016, Griatch 2019 (rework for modern EvMenu)
|
|
|
|
This changes the Evennia login to ask for the account name and password in
|
|
sequence instead of requiring you to enter both at once.
|
|
|
|
To install, add this line to the settings file (`mygame/server/conf/settings.py`):
|
|
|
|
CMDSET_UNLOGGEDIN = "evennia.contrib.base_systems.menu_login.UnloggedinCmdSet"
|
|
|
|
Reload the server and the new connection method will be active. Note that you must
|
|
independently change the connection screen to match this login style, by editing
|
|
`mygame/server/conf/connection_screens.py`.
|
|
|
|
This uses Evennia's menu system EvMenu and is triggered by a command that is
|
|
called automatically when a new user connects.
|
|
|
|
"""
|
|
|
|
from django.conf import settings
|
|
|
|
from evennia import Command, CmdSet
|
|
from evennia import syscmdkeys
|
|
from evennia.utils.evmenu import EvMenu
|
|
from evennia.utils.utils import random_string_from_module, class_from_module, callables_from_module
|
|
|
|
_CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
|
_GUEST_ENABLED = settings.GUEST_ENABLED
|
|
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
|
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
|
|
|
|
_ACCOUNT_HELP = (
|
|
"Enter a new or existing login name.")
|
|
_PASSWORD_HELP = (
|
|
"Password should be a minimum of 8 characters (preferably longer) and "
|
|
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
|
|
)
|
|
|
|
# Menu nodes
|
|
|
|
|
|
def _show_help(caller, raw_string, **kwargs):
|
|
"""Echo help message, then re-run node that triggered it"""
|
|
help_entry = kwargs["help_entry"]
|
|
caller.msg(help_entry)
|
|
return None # re-run calling node
|
|
|
|
|
|
def node_enter_username(caller, raw_text, **kwargs):
|
|
"""
|
|
Start node of menu
|
|
Start login by displaying the connection screen and ask for a user name.
|
|
|
|
"""
|
|
|
|
def _check_input(caller, username, **kwargs):
|
|
"""
|
|
'Goto-callable', set up to be called from the _default option below.
|
|
|
|
Called when user enters a username string. Check if this username already exists and set the flag
|
|
'new_user' if not. Will also directly login if the username is 'guest'
|
|
and GUEST_ENABLED is True.
|
|
|
|
The return from this goto-callable determines which node we go to next
|
|
and what kwarg it will be called with.
|
|
|
|
"""
|
|
username = username.rstrip("\n")
|
|
|
|
if username == "guest" and _GUEST_ENABLED:
|
|
# do an immediate guest login
|
|
session = caller
|
|
address = session.address
|
|
account, errors = _GUEST.authenticate(ip=address)
|
|
if account:
|
|
return "node_quit_or_login", {"login": True, "account": account}
|
|
else:
|
|
session.msg("|R{}|n".format("\n".join(errors)))
|
|
return None # re-run the username node
|
|
|
|
try:
|
|
_ACCOUNT.objects.get(username__iexact=username)
|
|
except _ACCOUNT.DoesNotExist:
|
|
new_user = True
|
|
else:
|
|
new_user = False
|
|
|
|
# pass username/new_user into next node as kwargs
|
|
return "node_enter_password", {"new_user": new_user, "username": username}
|
|
|
|
callables = callables_from_module(_CONNECTION_SCREEN_MODULE)
|
|
if "connection_screen" in callables:
|
|
connection_screen = callables["connection_screen"]()
|
|
else:
|
|
connection_screen = random_string_from_module(_CONNECTION_SCREEN_MODULE)
|
|
|
|
if _GUEST_ENABLED:
|
|
text = "Enter a new or existing user name to login (write 'guest' for a guest login):"
|
|
else:
|
|
text = "Enter a new or existing user name to login:"
|
|
text = "{}\n\n{}".format(connection_screen, text)
|
|
|
|
options = (
|
|
{"key": "", "goto": "node_enter_username"},
|
|
{"key": ("quit", "q"), "goto": "node_quit_or_login"},
|
|
{"key": ("help", "h"), "goto": (_show_help, {"help_entry": _ACCOUNT_HELP, **kwargs})},
|
|
{"key": "_default", "goto": _check_input},
|
|
)
|
|
return text, options
|
|
|
|
|
|
def node_enter_password(caller, raw_string, **kwargs):
|
|
"""
|
|
Handle password input.
|
|
|
|
"""
|
|
|
|
def _check_input(caller, password, **kwargs):
|
|
"""
|
|
'Goto-callable', set up to be called from the _default option below.
|
|
|
|
Called when user enters a password string. Check username + password
|
|
viability. If it passes, the account will have been created and login
|
|
will be initiated.
|
|
|
|
The return from this goto-callable determines which node we go to next
|
|
and what kwarg it will be called with.
|
|
|
|
"""
|
|
# these flags were set by the goto-callable
|
|
username = kwargs["username"]
|
|
new_user = kwargs["new_user"]
|
|
password = password.rstrip("\n")
|
|
|
|
session = caller
|
|
address = session.address
|
|
if new_user:
|
|
# create a new account
|
|
account, errors = _ACCOUNT.create(
|
|
username=username, password=password, ip=address, session=session
|
|
)
|
|
else:
|
|
# check password against existing account
|
|
account, errors = _ACCOUNT.authenticate(
|
|
username=username, password=password, ip=address, session=session
|
|
)
|
|
|
|
if account:
|
|
if new_user:
|
|
session.msg("|gA new account |c{}|g was created. Welcome!|n".format(username))
|
|
# pass login info to login node
|
|
return "node_quit_or_login", {"login": True, "account": account}
|
|
else:
|
|
# restart due to errors
|
|
session.msg("|R{}".format("\n".join(errors)))
|
|
kwargs["retry_password"] = True
|
|
return "node_enter_password", kwargs
|
|
|
|
def _restart_login(caller, *args, **kwargs):
|
|
caller.msg("|yCancelled login.|n")
|
|
return "node_enter_username"
|
|
|
|
username = kwargs["username"]
|
|
if kwargs["new_user"]:
|
|
|
|
if kwargs.get("retry_password"):
|
|
# Attempting to fix password
|
|
text = "Enter a new password:"
|
|
else:
|
|
text = "Creating a new account |c{}|n. " "Enter a password (empty to abort):".format(
|
|
username
|
|
)
|
|
else:
|
|
text = "Enter the password for account |c{}|n (empty to abort):".format(username)
|
|
options = (
|
|
{"key": "", "goto": _restart_login},
|
|
{"key": ("quit", "q"), "goto": "node_quit_or_login"},
|
|
{"key": ("help", "h"), "goto": (_show_help, {"help_entry": _PASSWORD_HELP, **kwargs})},
|
|
{"key": "_default", "goto": (_check_input, kwargs)},
|
|
)
|
|
return text, options
|
|
|
|
|
|
def node_quit_or_login(caller, raw_text, **kwargs):
|
|
"""
|
|
Exit menu, either by disconnecting or logging in.
|
|
|
|
"""
|
|
session = caller
|
|
if kwargs.get("login"):
|
|
account = kwargs.get("account")
|
|
session.msg("|gLogging in ...|n")
|
|
session.sessionhandler.login(session, account)
|
|
else:
|
|
session.sessionhandler.disconnect(session, "Goodbye! Logging off.")
|
|
return "", {}
|
|
|
|
|
|
# EvMenu helper function
|
|
|
|
|
|
def _node_formatter(nodetext, optionstext, caller=None):
|
|
"""Do not display the options, only the text.
|
|
|
|
This function is used by EvMenu to format the text of nodes. The menu login
|
|
is just a series of prompts so we disable all automatic display decoration
|
|
and let the nodes handle everything on their own.
|
|
|
|
"""
|
|
return nodetext
|
|
|
|
|
|
# Commands and CmdSets
|
|
|
|
|
|
class UnloggedinCmdSet(CmdSet):
|
|
"Cmdset for the unloggedin state"
|
|
key = "DefaultUnloggedin"
|
|
priority = 0
|
|
|
|
def at_cmdset_creation(self):
|
|
"Called when cmdset is first created."
|
|
self.add(CmdUnloggedinLook())
|
|
|
|
|
|
class CmdUnloggedinLook(Command):
|
|
"""
|
|
An unloggedin version of the look command. This is called by the server
|
|
when the account first connects. It sets up the menu before handing off
|
|
to the menu's own look command.
|
|
|
|
"""
|
|
|
|
key = syscmdkeys.CMD_LOGINSTART
|
|
locks = "cmd:all()"
|
|
arg_regex = r"^$"
|
|
|
|
def func(self):
|
|
"""
|
|
Run the menu using the nodes in this module.
|
|
|
|
"""
|
|
EvMenu(
|
|
self.caller,
|
|
"evennia.contrib.base_systems.menu_login",
|
|
startnode="node_enter_username",
|
|
auto_look=False,
|
|
auto_quit=False,
|
|
cmd_on_exit=None,
|
|
node_formatter=_node_formatter,
|
|
)
|