Merge branch 'develop' into accounts
This commit is contained in:
commit
030a83bf9c
26 changed files with 338 additions and 115 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ class AccountCmdSet(CmdSet):
|
|||
self.add(system.CmdPy())
|
||||
|
||||
# Admin commands
|
||||
self.add(admin.CmdDelAccount())
|
||||
self.add(admin.CmdNewPassword())
|
||||
|
||||
# Comm commands
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue