Merge branch 'develop' into accounts

This commit is contained in:
Johnny 2018-10-22 21:12:58 +00:00
commit 030a83bf9c
26 changed files with 338 additions and 115 deletions

View file

@ -16,8 +16,8 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
# limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelAccount",
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")
__all__ = ("CmdBoot", "CmdBan", "CmdUnban",
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall", "CmdForce")
class CmdBoot(COMMAND_DEFAULT_CLASS):
@ -133,7 +133,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
reason to be able to later remember why the ban was put in place.
It is often preferable to ban an account from the server than to
delete an account with @delaccount. If banned by name, that account
delete an account with @accounts/delete. If banned by name, that account
account can no longer be logged into.
IP (Internet Protocol) address banning allows blocking all access
@ -256,78 +256,6 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
logger.log_sec('Unbanned: %s (Caller: %s, IP: %s).' % (value.strip(), self.caller, self.session.address))
class CmdDelAccount(COMMAND_DEFAULT_CLASS):
"""
delete an account from the server
Usage:
@delaccount[/switch] <name> [: reason]
Switch:
delobj - also delete the account's currently
assigned in-game object.
Completely deletes a user from the server database,
making their nick and e-mail again available.
"""
key = "@delaccount"
switch_options = ("delobj",)
locks = "cmd:perm(delaccount) or perm(Developer)"
help_category = "Admin"
def func(self):
"""Implements the command."""
caller = self.caller
args = self.args
if hasattr(caller, 'account'):
caller = caller.account
if not args:
self.msg("Usage: @delaccount <account/user name or #id> [: reason]")
return
reason = ""
if ':' in args:
args, reason = [arg.strip() for arg in args.split(':', 1)]
# We use account_search since we want to be sure to find also accounts
# that lack characters.
accounts = search.account_search(args)
if not accounts:
self.msg('Could not find an account by that name.')
return
if len(accounts) > 1:
string = "There were multiple matches:\n"
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
self.msg(string)
return
# one single match
account = accounts.first()
if not account.access(caller, 'delete'):
string = "You don't have the permissions to delete that account."
self.msg(string)
return
uname = account.username
# boot the account then delete
self.msg("Informing and disconnecting account ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % uname
if reason:
string += " Reason given:\n '%s'" % reason
account.msg(string)
logger.log_sec('Account Deleted: %s (Reason: %s, Caller: %s, IP: %s).' % (account, reason, caller, self.session.address))
account.delete()
self.msg("Account %s was successfully deleted." % uname)
class CmdEmit(COMMAND_DEFAULT_CLASS):
"""
admin command for emitting message to multiple objects
@ -585,3 +513,33 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
message = "%s shouts \"%s\"" % (self.caller.name, self.args)
self.msg("Announcing to all connected sessions ...")
SESSIONS.announce_all(message)
class CmdForce(COMMAND_DEFAULT_CLASS):
"""
forces an object to execute a command
Usage:
@force <object>=<command string>
Example:
@force bob=get stick
"""
key = "@force"
locks = "cmd:perm(spawn) or perm(Builder)"
help_category = "Building"
perm_used = "edit"
def func(self):
"""Implements the force command"""
if not self.lhs or not self.rhs:
self.caller.msg("You must provide a target and a command string to execute.")
return
targ = self.caller.search(self.lhs)
if not targ:
return
if not targ.access(self.caller, self.perm_used):
self.caller.msg("You don't have permission to force them to execute commands.")
return
targ.execute_cmd(self.rhs)
self.caller.msg("You have forced %s to: %s" % (targ, self.rhs))

View file

@ -10,7 +10,7 @@ from evennia.objects.models import ObjectDB
from evennia.locks.lockhandler import LockException
from evennia.commands.cmdhandler import get_and_merge_cmdsets
from evennia.utils import create, utils, search
from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses
from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses, variable_from_module
from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
@ -612,12 +612,12 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
self.edit_handler()
return
if self.rhs:
if '=' in self.args:
# We have an =
obj = caller.search(self.lhs)
if not obj:
return
desc = self.rhs
desc = self.rhs or ''
else:
obj = caller.location or self.msg("|rYou can't describe oblivion.|n")
if not obj:
@ -737,12 +737,11 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
confirm += ", ".join(["#{}".format(obj.id) for obj in objs])
confirm += " [yes]/no?" if self.default_confirm == 'yes' else " yes/[no]"
answer = ""
while answer.strip().lower() not in ("y", "yes", "n", "no"):
answer = yield(confirm)
answer = self.default_confirm if answer == '' else answer
answer = yield(confirm)
answer = self.default_confirm if answer == '' else answer
if answer.strip().lower() in ("n", "no"):
caller.msg("Cancelled: no object was destroyed.")
caller.msg("Canceled: no object was destroyed.")
delete = False
if delete:
@ -1023,10 +1022,17 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
object_name = self.lhs
# get object
obj = caller.search(object_name, global_search=True)
if not obj:
return
# try to search locally first
results = caller.search(object_name, quiet=True)
if len(results) > 1: # local results was a multimatch. Inform them to be more specific
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))
return _AT_SEARCH_RESULT(results, caller, query=object_name)
elif len(results) == 1: # A unique local match
obj = results[0]
else: # No matches. Search globally
obj = caller.search(object_name, global_search=True)
if not obj:
return
if self.rhs:
# this means a target name was given

View file

@ -55,7 +55,6 @@ class AccountCmdSet(CmdSet):
self.add(system.CmdPy())
# Admin commands
self.add(admin.CmdDelAccount())
self.add(admin.CmdNewPassword())
# Comm commands

View file

@ -57,6 +57,7 @@ class CharacterCmdSet(CmdSet):
self.add(admin.CmdEmit())
self.add(admin.CmdPerm())
self.add(admin.CmdWall())
self.add(admin.CmdForce())
# Building and world manipulation
self.add(building.CmdTeleport())

View file

@ -7,6 +7,8 @@ make sure to homogenize self.caller to always be the account object
for easy handling.
"""
import hashlib
import time
from past.builtins import cmp
from django.conf import settings
from evennia.comms.models import ChannelDB, Msg
@ -921,8 +923,9 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
self.msg("Account '%s' already exists and is not a bot." % botname)
return
else:
password = hashlib.md5(str(time.time())).hexdigest()[:11]
try:
bot = create.create_account(botname, None, None, typeclass=botclass)
bot = create.create_account(botname, None, password, typeclass=botclass)
except Exception as err:
self.msg("|rError, could not create the bot:|n '%s'." % err)
return

View file

@ -18,7 +18,7 @@ from evennia.server.sessionhandler import SESSIONS
from evennia.scripts.models import ScriptDB
from evennia.objects.models import ObjectDB
from evennia.accounts.models import AccountDB
from evennia.utils import logger, utils, gametime, create
from evennia.utils import logger, utils, gametime, create, search
from evennia.utils.eveditor import EvEditor
from evennia.utils.evtable import EvTable
from evennia.utils.utils import crop, class_from_module
@ -460,17 +460,22 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
class CmdAccounts(COMMAND_DEFAULT_CLASS):
"""
list all registered accounts
Manage registered accounts
Usage:
@accounts [nr]
@accounts/delete <name or #id> [: reason]
Lists statistics about the Accounts registered with the game.
Switches:
delete - delete an account from the server
By default, lists statistics about the Accounts registered with the game.
It will list the <nr> amount of latest registered accounts
If not given, <nr> defaults to 10.
"""
key = "@accounts"
aliases = ["@listaccounts"]
aliases = ["@account", "@listaccounts"]
switch_options = ("delete", )
locks = "cmd:perm(listaccounts) or perm(Admin)"
help_category = "System"
@ -478,6 +483,56 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
"""List the accounts"""
caller = self.caller
args = self.args
if "delete" in self.switches:
account = getattr(caller, "account")
if not account or not account.check_permstring("Developer"):
caller.msg("You are not allowed to delete accounts.")
return
if not args:
caller.msg("Usage: @accounts/delete <name or #id> [: reason]")
return
reason = ""
if ":" in args:
args, reason = [arg.strip() for arg in args.split(":", 1)]
# We use account_search since we want to be sure to find also accounts
# that lack characters.
accounts = search.account_search(args)
if not accounts:
self.msg("Could not find an account by that name.")
return
if len(accounts) > 1:
string = "There were multiple matches:\n"
string += "\n".join(" %s %s" % (account.id, account.key) for account in accounts)
self.msg(string)
return
account = accounts.first()
if not account.access(caller, "delete"):
self.msg("You don't have the permissions to delete that account.")
return
username = account.username
# ask for confirmation
confirm = ("It is often better to block access to an account rather than to delete it. "
"|yAre you sure you want to permanently delete "
"account '|n{}|y'|n yes/[no]?".format(username))
answer = yield(confirm)
if answer.lower() not in ('y', 'yes'):
caller.msg("Canceled deletion.")
return
# Boot the account then delete it.
self.msg("Informing and disconnecting account ...")
string = "\nYour account '%s' is being *permanently* deleted.\n" % username
if reason:
string += " Reason given:\n '%s'" % reason
account.msg(string)
logger.log_sec("Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)." % (account, reason, caller, self.session.address))
account.delete()
self.msg("Account %s was successfully deleted." % username)
return
# No switches, default to displaying a list of accounts.
if self.args and self.args.isdigit():
nlim = int(self.args)
else:
@ -655,6 +710,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
"""Show server time data in a table."""
table1 = EvTable("|wServer time", "", align="l", width=78)
table1.add_row("Current uptime", utils.time_format(gametime.uptime(), 3))
table1.add_row("Portal uptime", utils.time_format(gametime.portal_uptime(), 3))
table1.add_row("Total runtime", utils.time_format(gametime.runtime(), 2))
table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch()))
table1.add_row("Current time", datetime.datetime.now())

View file

@ -243,6 +243,9 @@ class TestAdmin(CommandTest):
def test_ban(self):
self.call(admin.CmdBan(), "Char", "Name-Ban char was added.")
def test_force(self):
self.call(admin.CmdForce(), "Char2=say test", 'Char2(#7) says, "test"|You have forced Char2 to: say test')
class TestAccount(CommandTest):
@ -315,6 +318,24 @@ class TestBuilding(CommandTest):
def test_desc(self):
self.call(building.CmdDesc(), "Obj2=TestDesc", "The description was set on Obj2(#5).")
def test_empty_desc(self):
"""
empty desc sets desc as ''
"""
o2d = self.obj2.db.desc
r1d = self.room1.db.desc
self.call(building.CmdDesc(), "Obj2=", "The description was set on Obj2(#5).")
assert self.obj2.db.desc == '' and self.obj2.db.desc != o2d
assert self.room1.db.desc == r1d
def test_desc_default_to_room(self):
"""no rhs changes room's desc"""
o2d = self.obj2.db.desc
r1d = self.room1.db.desc
self.call(building.CmdDesc(), "Obj2", "The description was set on Room(#1).")
assert self.obj2.db.desc == o2d
assert self.room1.db.desc == 'Obj2' and self.room1.db.desc != r1d
def test_wipe(self):
confirm = building.CmdDestroy.confirm
building.CmdDestroy.confirm = False
@ -334,6 +355,14 @@ class TestBuilding(CommandTest):
self.call(building.CmdOpen(), "TestExit1=Room2", "Created new Exit 'TestExit1' from Room to Room2")
self.call(building.CmdLink(), "TestExit1=Room", "Link created TestExit1 -> Room (one way).")
self.call(building.CmdUnLink(), "TestExit1", "Former exit TestExit1 no longer links anywhere.")
self.char1.location = self.room2
self.call(building.CmdOpen(), "TestExit2=Room", "Created new Exit 'TestExit2' from Room2 to Room.")
# ensure it matches locally first
self.call(building.CmdLink(), "TestExit=Room2", "Link created TestExit2 -> Room2 (one way).")
# ensure can still match globally when not a local name
self.call(building.CmdLink(), "TestExit1=Room2", "Note: TestExit1(#8) did not have a destination set before. "
"Make sure you linked the right thing.\n"
"Link created TestExit1 -> Room2 (one way).")
def test_set_home(self):
self.call(building.CmdSetHome(), "Obj = Room2", "Obj's home location was changed from Room")