diff --git a/CHANGELOG.md b/CHANGELOG.md index 02548491ac..67ac882217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,10 @@ Up requirements to Django 3.2+, Twisted 21+ the standard calendar form ... there is no month 0 every year after all). - `AttributeProperty`/`NAttributeProperty` to allow managing Attributes/NAttributes on typeclasses in the same way as Django fields. +- Give build/system commands a `@name` to fall back to if the non-@ name is used + by another command (like `open` and `@open`. If no duplicate, @ is optional. +- Move legacy channel-management commands (`ccreate`, `addcom` etc) to a contrib + since their work is now fully handled by the single `channel` command. ### Evennia 0.9.5 (2019-2020) diff --git a/docs/source/Components/Commands.md b/docs/source/Components/Commands.md index 983149ba1e..02c502d392 100644 --- a/docs/source/Components/Commands.md +++ b/docs/source/Components/Commands.md @@ -279,6 +279,32 @@ incoming string is already split up and parsed in suitable ways by its parent. Before you can actually use the command in your game, you must now store it within a *command set*. See the [Command Sets](./Command-Sets.md) page. +### Command prefixes + +Historically, many MU* servers used to use prefix, such as `@` or `&` to signify that +a command is used for administration or requires staff privileges. The problem with this is that +newcomers to MU often find such extra symbols confusing. Evennia allows commands that can be +accessed both with- or without such a prefix. + + CMD_IGNORE_PREFIXES = "@&/+` + +This is a setting consisting of a string of characters. Each is a prefix that will be considered +a skippable prefix - _if the command is still unique in its cmdset when skipping the prefix_. + +So if you wanted to write `@look` instead of `look` you can do so - the `@` will be ignored. But If +we added an actual `@look` command (with a `key` or alias `@look`) then we would need to use the +`@` to separate between the two. + +This is also used in the default commands. For example, `@open` is a building +command that allows you to create new exits to link two rooms together. Its `key` is set to `@open`, +including the `@` (no alias is set). By default you can use both `@open` and `open` for +this command. But "open" is a pretty common word and let's say a developer adds a new `open` command +for opening a door. Now `@open` and `open` are two different commands and the `@` must be used to +separate them. + +> The `help` command will prefer to show all command names without prefix if +> possible. Only if there is a collision, will the prefix be shown in the help system. + ### On arg_regex The command parser is very general and does not require a space to end your command name. This means diff --git a/docs/source/Components/Default-Commands.md b/docs/source/Components/Default-Commands.md index 1147033527..75db817d9f 100644 --- a/docs/source/Components/Default-Commands.md +++ b/docs/source/Components/Default-Commands.md @@ -2,7 +2,7 @@ # Default Commands -The full set of default Evennia commands currently contains 97 commands in 9 source +The full set of default Evennia commands currently contains 88 commands in 9 source files. Our policy for adding default commands is outlined [here](../Concepts/Using-MUX-as-a-Standard.md). The [Commands](./Commands.md) documentation explains how Commands work as well as make new or customize existing ones. Note that this page is auto-generated. Report problems to the [issue @@ -14,93 +14,84 @@ with [EvEditor](./EvEditor.md), flipping pages in [EvMore](./EvMore.md) or using [Batch-Processor](./Batch-Processors.md)'s interactive mode. ``` +- [**@about** [@version]](evennia.commands.default.system.CmdAbout) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@accounts** [@account]](evennia.commands.default.system.CmdAccounts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@alias** [setobjalias]](evennia.commands.default.building.CmdSetObjAlias) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@channel** [@channels, @chan]](evennia.commands.default.comms.CmdChannel) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) +- [**@cmdsets**](evennia.commands.default.building.CmdListCmdSets) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@copy**](evennia.commands.default.building.CmdCopy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@cpattr**](evennia.commands.default.building.CmdCpAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@create**](evennia.commands.default.building.CmdCreate) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@desc**](evennia.commands.default.building.CmdDesc) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@destroy** [@del, @delete]](evennia.commands.default.building.CmdDestroy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@dig**](evennia.commands.default.building.CmdDig) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@examine** [@ex, @exam]](evennia.commands.default.building.CmdExamine) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Building_) +- [**@find** [@search, @locate]](evennia.commands.default.building.CmdFind) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@link**](evennia.commands.default.building.CmdLink) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@lock** [@locks]](evennia.commands.default.building.CmdLock) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@mvattr**](evennia.commands.default.building.CmdMvAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@name** [@rename]](evennia.commands.default.building.CmdName) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@objects**](evennia.commands.default.building.CmdObjects) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@open**](evennia.commands.default.building.CmdOpen) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@py** [@!]](evennia.commands.default.system.CmdPy) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) +- [**@reload** [@restart]](evennia.commands.default.system.CmdReload) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) +- [**@reset** [@reboot]](evennia.commands.default.system.CmdReset) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) +- [**@scripts** [@script]](evennia.commands.default.building.CmdScripts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@server** [@serverload]](evennia.commands.default.system.CmdServerLoad) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@service** [@services]](evennia.commands.default.system.CmdService) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@set**](evennia.commands.default.building.CmdSetAttribute) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@sethome**](evennia.commands.default.building.CmdSetHome) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@shutdown**](evennia.commands.default.system.CmdShutdown) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) +- [**@spawn** [@olc]](evennia.commands.default.building.CmdSpawn) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@tag** [@tags]](evennia.commands.default.building.CmdTag) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@tasks** [@delays, @task]](evennia.commands.default.system.CmdTasks) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@teleport** [@tel]](evennia.commands.default.building.CmdTeleport) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@tickers**](evennia.commands.default.system.CmdTickers) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@time** [@uptime]](evennia.commands.default.system.CmdTime) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) +- [**@tunnel** [@tun]](evennia.commands.default.building.CmdTunnel) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@typeclass** [@type, @swap, @update, @parent, @typeclasses]](evennia.commands.default.building.CmdTypeclass) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**@wipe**](evennia.commands.default.building.CmdWipe) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**__unloggedin_look_command** [look, l]](evennia.commands.default.unloggedin.CmdUnconnectedLook) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**about** [version]](evennia.commands.default.system.CmdAbout) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**access** [hierarchy, groups]](evennia.commands.default.general.CmdAccess) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**accounts** [account, listaccounts]](evennia.commands.default.system.CmdAccounts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**addcom** [aliaschan, chanalias]](evennia.commands.default.comms.CmdAddCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**alias** [setobjalias]](evennia.commands.default.building.CmdSetObjAlias) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**allcom**](evennia.commands.default.comms.CmdAllCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) +- [**access** [groups, hierarchy]](evennia.commands.default.general.CmdAccess) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**batchcode** [batchcodes]](evennia.commands.default.batchprocess.CmdBatchCode) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**batchcommands** [batchcmd, batchcommand]](evennia.commands.default.batchprocess.CmdBatchCommands) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**cboot**](evennia.commands.default.comms.CmdCBoot) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**ccreate** [channelcreate]](evennia.commands.default.comms.CmdChannelCreate) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cdesc**](evennia.commands.default.comms.CmdCdesc) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cdestroy**](evennia.commands.default.comms.CmdCdestroy) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**channel** [chan, channels]](evennia.commands.default.comms.CmdChannel) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) - [**charcreate**](evennia.commands.default.account.CmdCharCreate) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**chardelete**](evennia.commands.default.account.CmdCharDelete) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**clock**](evennia.commands.default.comms.CmdClock) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**cmdsets** [listcmsets]](evennia.commands.default.building.CmdListCmdSets) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**color**](evennia.commands.default.account.CmdColorTest) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**connect** [conn, co, con]](evennia.commands.default.unloggedin.CmdUnconnectedConnect) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**copy**](evennia.commands.default.building.CmdCopy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**cpattr**](evennia.commands.default.building.CmdCpAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**create**](evennia.commands.default.building.CmdCreate) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) +- [**connect** [co, con, conn]](evennia.commands.default.unloggedin.CmdUnconnectedConnect) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) - [**create** [cre, cr]](evennia.commands.default.unloggedin.CmdUnconnectedCreate) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**cwho**](evennia.commands.default.comms.CmdCWho) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**delcom** [delaliaschan, delchanalias]](evennia.commands.default.comms.CmdDelCom) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**desc** [describe]](evennia.commands.default.building.CmdDesc) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**destroy** [del, delete]](evennia.commands.default.building.CmdDestroy) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**dig**](evennia.commands.default.building.CmdDig) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**drop**](evennia.commands.default.general.CmdDrop) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**encoding** [encode]](evennia.commands.default.unloggedin.CmdUnconnectedEncoding) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**examine** [ex, exam]](evennia.commands.default.building.CmdExamine) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Building_) -- [**find** [locate, search]](evennia.commands.default.building.CmdFind) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**get** [grab]](evennia.commands.default.general.CmdGet) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**give**](evennia.commands.default.general.CmdGive) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**grapevine2chan**](evennia.commands.default.comms.CmdGrapevine2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) - [**help** [?]](evennia.commands.default.help.CmdHelp) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**help** [h, ?]](evennia.commands.default.unloggedin.CmdUnconnectedHelp) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) +- [**help** [?, h]](evennia.commands.default.unloggedin.CmdUnconnectedHelp) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) - [**home**](evennia.commands.default.general.CmdHome) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**ic** [puppet]](evennia.commands.default.account.CmdIC) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**info**](evennia.commands.default.unloggedin.CmdUnconnectedInfo) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**inventory** [i, inv]](evennia.commands.default.general.CmdInventory) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) +- [**inventory** [inv, i]](evennia.commands.default.general.CmdInventory) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**irc2chan**](evennia.commands.default.comms.CmdIRC2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) - [**ircstatus**](evennia.commands.default.comms.CmdIRCStatus) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) -- [**link**](evennia.commands.default.building.CmdLink) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**lock** [locks]](evennia.commands.default.building.CmdLock) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**look** [l, ls]](evennia.commands.default.account.CmdOOCLook) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**look** [l, ls]](evennia.commands.default.general.CmdLook) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**mvattr**](evennia.commands.default.building.CmdMvAttr) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**name** [rename]](evennia.commands.default.building.CmdName) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**nick** [nickname, nicks]](evennia.commands.default.general.CmdNick) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**objects** [listobjs, stats, db, listobjects]](evennia.commands.default.building.CmdObjects) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) - [**ooc** [unpuppet]](evennia.commands.default.account.CmdOOC) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**open**](evennia.commands.default.building.CmdOpen) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**option** [options]](evennia.commands.default.account.CmdOption) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**page** [tell]](evennia.commands.default.comms.CmdPage) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) - [**password**](evennia.commands.default.account.CmdPassword) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**pose** [:, emote]](evennia.commands.default.general.CmdPose) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) -- [**py** [!]](evennia.commands.default.system.CmdPy) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) - [**quell** [unquell]](evennia.commands.default.account.CmdQuell) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**quit**](evennia.commands.default.account.CmdQuit) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) - [**quit** [qu, q]](evennia.commands.default.unloggedin.CmdUnconnectedQuit) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**reload** [restart]](evennia.commands.default.system.CmdReload) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**reset** [reboot]](evennia.commands.default.system.CmdReset) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) - [**rss2chan**](evennia.commands.default.comms.CmdRSS2Chan) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _Comms_) - [**say** [", ']](evennia.commands.default.general.CmdSay) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**screenreader**](evennia.commands.default.unloggedin.CmdUnconnectedScreenreader) (cmdset: [UnloggedinCmdSet](evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet), help-category: _General_) -- [**scripts** [script]](evennia.commands.default.building.CmdScripts) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**server** [serverload, serverprocess]](evennia.commands.default.system.CmdServerLoad) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**service** [services]](evennia.commands.default.system.CmdService) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) - [**sessions**](evennia.commands.default.account.CmdSessions) (cmdset: [SessionCmdSet](evennia.commands.default.cmdset_session.SessionCmdSet), help-category: _General_) -- [**set**](evennia.commands.default.building.CmdSetAttribute) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**setdesc**](evennia.commands.default.general.CmdSetDesc) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**sethelp**](evennia.commands.default.help.CmdSetHelp) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**sethome**](evennia.commands.default.building.CmdSetHome) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**shutdown**](evennia.commands.default.system.CmdShutdown) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _System_) -- [**spawn** [olc]](evennia.commands.default.building.CmdSpawn) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**style**](evennia.commands.default.account.CmdStyle) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**tag** [tags]](evennia.commands.default.building.CmdTag) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**tasks** [task, delays]](evennia.commands.default.system.CmdTasks) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**tel** [teleport]](evennia.commands.default.building.CmdTeleport) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**tickers**](evennia.commands.default.system.CmdTickers) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**time** [uptime]](evennia.commands.default.system.CmdTime) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _System_) -- [**tunnel** [tun]](evennia.commands.default.building.CmdTunnel) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) -- [**typeclass** [swap, typeclasses, parent, type, update]](evennia.commands.default.building.CmdTypeclass) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**unlink**](evennia.commands.default.building.CmdUnLink) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) - [**whisper**](evennia.commands.default.general.CmdWhisper) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _General_) - [**who** [doing]](evennia.commands.default.account.CmdWho) (cmdset: [AccountCmdSet](evennia.commands.default.cmdset_account.AccountCmdSet), help-category: _General_) -- [**wipe**](evennia.commands.default.building.CmdWipe) (cmdset: [CharacterCmdSet](evennia.commands.default.cmdset_character.CharacterCmdSet), help-category: _Building_) diff --git a/docs/source/api/evennia.contrib.md b/docs/source/api/evennia.contrib.md index 7ce74eb5d3..7ef24e44e4 100644 --- a/docs/source/api/evennia.contrib.md +++ b/docs/source/api/evennia.contrib.md @@ -28,6 +28,7 @@ evennia.contrib evennia.contrib.mapbuilder evennia.contrib.menu_login evennia.contrib.multidescer + evennia.contrib.mux_comms_cmds evennia.contrib.puzzles evennia.contrib.random_string_generator evennia.contrib.rplanguage diff --git a/docs/source/api/evennia.server.md b/docs/source/api/evennia.server.md index e80c01b070..5a8fc21fb4 100644 --- a/docs/source/api/evennia.server.md +++ b/docs/source/api/evennia.server.md @@ -19,7 +19,6 @@ evennia.server evennia.server.initial_setup evennia.server.inputfuncs evennia.server.manager - evennia.server.markup evennia.server.models evennia.server.server evennia.server.serversession diff --git a/docs/source/toc.md b/docs/source/toc.md index 7148bd9d03..3a8f35a6f8 100644 --- a/docs/source/toc.md +++ b/docs/source/toc.md @@ -240,6 +240,7 @@ api/evennia.contrib.mail api/evennia.contrib.mapbuilder api/evennia.contrib.menu_login api/evennia.contrib.multidescer +api/evennia.contrib.mux_comms_cmds api/evennia.contrib.puzzles api/evennia.contrib.random_string_generator api/evennia.contrib.rplanguage @@ -321,7 +322,6 @@ api/evennia.server.game_index_client.service api/evennia.server.initial_setup api/evennia.server.inputfuncs api/evennia.server.manager -api/evennia.server.markup api/evennia.server.models api/evennia.server.portal api/evennia.server.portal.amp diff --git a/evennia/commands/command.py b/evennia/commands/command.py index ce1eb3397b..9676bf7095 100644 --- a/evennia/commands/command.py +++ b/evennia/commands/command.py @@ -18,6 +18,9 @@ from evennia.utils.evtable import EvTable from evennia.utils.ansi import ANSIString +CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES + + class InterruptCommand(Exception): """Cleanly interrupt a command.""" @@ -97,12 +100,19 @@ def _init_command(cls, **kwargs): cls.help_category = cls.help_category.lower() # pre-prepare a help index entry for quicker lookup + # strip the @- etc to allow help to be agnostic + stripped_key = cls.key[1:] if cls.key and cls.key[0] in CMD_IGNORE_PREFIXES else "" + stripped_aliases = ( + " ".join(al for al in cls.aliases + if al and al[0] in CMD_IGNORE_PREFIXES for al in cls.aliases) + ) cls.search_index_entry = { "key": cls.key, "aliases": " ".join(cls.aliases), + "no_prefix": f"{stripped_key} {stripped_aliases}", "category": cls.help_category, "text": cls.__doc__, - "tags": "", + "tags": "" } diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 91c7d3ad3f..50d9653cc7 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -157,7 +157,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS): by everyone. """ - key = "alias" + key = "@alias" aliases = "setobjalias" switch_options = ("category",) locks = "cmd:perm(setobjalias) or perm(Builder)" @@ -266,7 +266,7 @@ class CmdCopy(ObjManipCommand): one exact copy of the original object will be created with the name *_copy. """ - key = "copy" + key = "@copy" locks = "cmd:perm(copy) or perm(Builder)" help_category = "Building" @@ -357,7 +357,7 @@ class CmdCpAttr(ObjManipCommand): If you don't supply a source object, yourself is used. """ - key = "cpattr" + key = "@cpattr" switch_options = ("move",) locks = "cmd:perm(cpattr) or perm(Builder)" help_category = "Building" @@ -496,7 +496,7 @@ class CmdMvAttr(ObjManipCommand): object. If you don't supply a source object, yourself is used. """ - key = "mvattr" + key = "@mvattr" switch_options = ("copy",) locks = "cmd:perm(mvattr) or perm(Builder)" help_category = "Building" @@ -545,7 +545,7 @@ class CmdCreate(ObjManipCommand): """ - key = "create" + key = "@create" switch_options = ("drop",) locks = "cmd:perm(create) or perm(Builder)" help_category = "Building" @@ -637,8 +637,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS): describe the current room. """ - key = "desc" - aliases = "describe" + key = "@desc" switch_options = ("edit",) locks = "cmd:perm(desc) or perm(Builder)" help_category = "Building" @@ -722,8 +721,8 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS): You can specify the /force switch to bypass this confirmation. """ - key = "destroy" - aliases = ["delete", "del"] + key = "@destroy" + aliases = ["@delete", "@del"] switch_options = ("override", "force") locks = "cmd:perm(destroy) or perm(Builder)" help_category = "Building" @@ -864,7 +863,7 @@ class CmdDig(ObjManipCommand): would be 'north;no;n'. """ - key = "dig" + key = "@dig" switch_options = ("teleport",) locks = "cmd:perm(dig) or perm(Builder)" help_category = "Building" @@ -1020,8 +1019,8 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS): For more flexibility and power in creating rooms, use dig. """ - key = "tunnel" - aliases = ["tun"] + key = "@tunnel" + aliases = ["@tun"] switch_options = ("oneway", "tel") locks = "cmd: perm(tunnel) or perm(Builder)" help_category = "Building" @@ -1114,7 +1113,7 @@ class CmdLink(COMMAND_DEFAULT_CLASS): currently set destination. """ - key = "link" + key = "@link" locks = "cmd:perm(link) or perm(Builder)" help_category = "Building" @@ -1251,7 +1250,7 @@ class CmdSetHome(CmdLink): If no location is given, just view the object's home location. """ - key = "sethome" + key = "@sethome" locks = "cmd:perm(sethome) or perm(Builder)" help_category = "Building" @@ -1303,8 +1302,7 @@ class CmdListCmdSets(COMMAND_DEFAULT_CLASS): to a user. Defaults to yourself. """ - key = "cmdsets" - aliases = "listcmsets" + key = "@cmdsets" locks = "cmd:perm(listcmdsets) or perm(Builder)" help_category = "Building" @@ -1334,8 +1332,8 @@ class CmdName(ObjManipCommand): """ - key = "name" - aliases = ["rename"] + key = "@name" + aliases = ["@rename"] locks = "cmd:perm(rename) or perm(Builder)" help_category = "Building" @@ -1412,7 +1410,7 @@ class CmdOpen(ObjManipCommand): """ - key = "open" + key = "@open" locks = "cmd:perm(open) or perm(Builder)" help_category = "Building" @@ -1614,7 +1612,7 @@ class CmdSetAttribute(ObjManipCommand): """ - key = "set" + key = "@set" locks = "cmd:perm(set) or perm(Builder)" help_category = "Building" nested_re = re.compile(r"\[.*?\]") @@ -1978,8 +1976,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): """ - key = "typeclass" - aliases = ["type", "parent", "swap", "update", "typeclasses"] + key = "@typeclass" + aliases = ["@type", "@parent", "@swap", "@update", "@typeclasses"] switch_options = ("show", "examine", "update", "reset", "force", "list", "prototype") locks = "cmd:perm(typeclass) or perm(Builder)" help_category = "Building" @@ -2035,7 +2033,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): caller = self.caller - if "list" in self.switches or self.cmdname == 'typeclasses': + if "list" in self.switches or self.cmdname in ('typeclasses', '@typeclasses'): tclasses = get_all_typeclasses() contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [ "" @@ -2135,10 +2133,10 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): caller.msg(string) return - if self.cmdstring == "swap": + if self.cmdstring in ("swap", "@swap"): self.switches.append("force") self.switches.append("reset") - elif self.cmdstring == "update": + elif self.cmdstring in ("update", "@update"): self.switches.append("force") self.switches.append("update") @@ -2229,7 +2227,7 @@ class CmdWipe(ObjManipCommand): matching the given attribute-wildcard search string. """ - key = "wipe" + key = "@wipe" locks = "cmd:perm(wipe) or perm(Builder)" help_category = "Building" @@ -2298,8 +2296,8 @@ class CmdLock(ObjManipCommand): 'get:id(25); delete:perm(Builder)' """ - key = "lock" - aliases = ["locks"] + key = "@lock" + aliases = ["@locks"] locks = "cmd: perm(locks) or perm(Builder)" help_category = "Building" @@ -2419,8 +2417,8 @@ class CmdExamine(ObjManipCommand): """ - key = "examine" - aliases = ["ex", "exam"] + key = "@examine" + aliases = ["@ex", "@exam"] locks = "cmd:perm(examine) or perm(Builder)" help_category = "Building" arg_regex = r"(/\w+?(\s|$))|\s|$" @@ -2829,8 +2827,8 @@ class CmdFind(COMMAND_DEFAULT_CLASS): one is given. """ - key = "find" - aliases = "search, locate" + key = "@find" + aliases = ["@search", "@locate"] switch_options = ("room", "exit", "char", "exact", "loc", "startswith") locks = "cmd:perm(find) or perm(Builder)" help_category = "Building" @@ -3112,8 +3110,8 @@ class CmdScripts(COMMAND_DEFAULT_CLASS): """ - key = "scripts" - aliases = ["script"] + key = "@scripts" + aliases = ["@script"] switch_options = ("create", "start", "stop", "pause", "delete") locks = "cmd:perm(scripts) or perm(Builder)" help_category = "System" @@ -3255,8 +3253,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS): given, defaults to 10. """ - key = "objects" - aliases = ["listobjects", "listobjs", "stats", "db"] + key = "@objects" locks = "cmd:perm(listobjects) or perm(Builder)" help_category = "System" @@ -3365,8 +3362,8 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): """ - key = "tel" - aliases = "teleport" + key = "@teleport" + aliases = "@tel" switch_options = ("quiet", "intoexit", "tonone", "loc") rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. locks = "cmd:perm(teleport) or perm(Builder)" @@ -3495,8 +3492,8 @@ class CmdTag(COMMAND_DEFAULT_CLASS): enough to for most grouping schemes. """ - key = "tag" - aliases = ["tags"] + key = "@tag" + aliases = ["@tags"] options = ("search", "del") locks = "cmd:perm(tag) or perm(Builder)" help_category = "Building" @@ -3682,8 +3679,8 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): """ - key = "spawn" - aliases = ["olc"] + key = "@spawn" + aliases = ["@olc"] switch_options = ( "noloc", "search", diff --git a/evennia/commands/default/cmdset_account.py b/evennia/commands/default/cmdset_account.py index 0e058235ae..6f132d82f6 100644 --- a/evennia/commands/default/cmdset_account.py +++ b/evennia/commands/default/cmdset_account.py @@ -60,18 +60,18 @@ class AccountCmdSet(CmdSet): # Comm commands self.add(comms.CmdChannel()) - # self.add(comms.CmdChannels()) - self.add(comms.CmdAddCom()) - self.add(comms.CmdDelCom()) - self.add(comms.CmdAllCom()) - self.add(comms.CmdCdestroy()) - self.add(comms.CmdChannelCreate()) - self.add(comms.CmdClock()) - self.add(comms.CmdCBoot()) - self.add(comms.CmdCWho()) - self.add(comms.CmdCdesc()) self.add(comms.CmdPage()) self.add(comms.CmdIRC2Chan()) self.add(comms.CmdIRCStatus()) self.add(comms.CmdRSS2Chan()) self.add(comms.CmdGrapevine2Chan()) + # self.add(comms.CmdChannels()) + # self.add(comms.CmdAddCom()) + # self.add(comms.CmdDelCom()) + # self.add(comms.CmdAllCom()) + # self.add(comms.CmdCdestroy()) + # self.add(comms.CmdChannelCreate()) + # self.add(comms.CmdClock()) + # self.add(comms.CmdCBoot()) + # self.add(comms.CmdCWho()) + # self.add(comms.CmdCdesc()) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 6b1d8bcca3..9d56c29dea 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -28,16 +28,6 @@ __all__ = ( "CmdChannel", "CmdObjectChannel", - "CmdAddCom", - "CmdDelCom", - "CmdAllCom", - "CmdCdestroy", - "CmdCBoot", - "CmdCWho", - "CmdChannelCreate", - "CmdClock", - "CmdCdesc", - "CmdPage", "CmdIRC2Chan", @@ -217,8 +207,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): ban mychannel1,mychannel2= EvilUser : Was banned for spamming. """ - key = "channel" - aliases = ["chan", "channels"] + key = "@channel" + aliases = ["@chan", "@channels"] help_category = "Comms" # these cmd: lock controls access to the channel command itself # the admin: lock controls access to /boot/ban/unban switches @@ -1253,488 +1243,6 @@ class CmdObjectChannel(CmdChannel): account_caller = False -class CmdAddCom(CmdChannel): - """ - Add a channel alias and/or subscribe to a channel - - Usage: - addcom [alias=] - - Joins a given channel. If alias is given, this will allow you to - refer to the channel by this alias rather than the full channel - name. Subsequent calls of this command can be used to add multiple - aliases to an already joined channel. - """ - - key = "addcom" - aliases = ["aliaschan", "chanalias"] - help_category = "Comms" - locks = "cmd:not pperm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Implement the command""" - - caller = self.caller - args = self.args - - if not args: - self.msg("Usage: addcom [alias =] channelname.") - return - - if self.rhs: - # rhs holds the channelname - channelname = self.rhs - alias = self.lhs - else: - channelname = self.args - alias = None - - channel = self.search_channel(channelname) - if not channel: - return - - string = "" - if not channel.has_connection(caller): - # we want to connect as well. - success, err = self.sub_to_channel(channel) - if success: - # if this would have returned True, the account is connected - self.msg(f"You now listen to the channel {channel.key}") - else: - self.msg(f"{channel.key}: You are not allowed to join this channel.") - return - - if channel.unmute(caller): - self.msg(f"You unmute channel {channel.key}.") - else: - self.msg(f"You are already connected to channel {channel.key}.") - - if alias: - # create a nick and add it to the caller. - self.add_alias(channel, alias) - self.msg(f" You can now refer to the channel {channel} with the alias '{alias}'.") - else: - string += " No alias added." - self.msg(string) - - -class CmdDelCom(CmdChannel): - """ - remove a channel alias and/or unsubscribe from channel - - Usage: - delcom - delcom/all - - If the full channel name is given, unsubscribe from the - channel. If an alias is given, remove the alias but don't - unsubscribe. If the 'all' switch is used, remove all aliases - for that channel. - """ - - key = "delcom" - aliases = ["delaliaschan", "delchanalias"] - help_category = "Comms" - locks = "cmd:not perm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Implementing the command. """ - - caller = self.caller - - if not self.args: - self.msg("Usage: delcom ") - return - ostring = self.args.lower().strip() - - channel = self.search_channel(ostring) - if not channel: - return - - if not channel.has_connection(caller): - self.msg("You are not listening to that channel.") - return - - if ostring == channel.key.lower(): - # an exact channel name - unsubscribe - delnicks = "all" in self.switches - # find all nicks linked to this channel and delete them - if delnicks: - aliases = self.get_channel_aliases(channel) - for alias in aliases: - self.remove_alias(alias) - success, err = self.unsub_from_channel(channel) - if success: - wipednicks = " Eventual aliases were removed." if delnicks else "" - self.msg(f"You stop listening to channel '{channel.key}'.{wipednicks}") - else: - self.msg(err) - return - else: - # we are removing a channel nick - self.remove_alias(ostring) - self.msg(f"Any alias '{ostring}' for channel {channel.key} was cleared.") - - -class CmdAllCom(CmdChannel): - """ - perform admin operations on all channels - - Usage: - allcom [on | off | who | destroy] - - Allows the user to universally turn off or on all channels they are on, as - well as perform a 'who' for all channels they are on. Destroy deletes all - channels that you control. - - Without argument, works like comlist. - """ - - key = "allcom" - aliases = [] # important to not inherit parent's aliases - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Runs the function""" - - caller = self.caller - args = self.args - if not args: - subscribed, available = self.list_channels() - table = self.display_all_channels(subscribed, available) - self.msg( - "\n|wAvailable channels:\n{table}") - return - return - - if args == "on": - # get names of all channels available to listen to - # and activate them all - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "listen") - ] - for channel in channels: - self.execute_cmd("addcom %s" % channel.key) - elif args == "off": - # get names all subscribed channels and disconnect from them all - channels = CHANNEL_DEFAULT_TYPECLASS.objects.get_subscriptions(caller) - for channel in channels: - self.execute_cmd("delcom %s" % channel.key) - elif args == "destroy": - # destroy all channels you control - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "control") - ] - for channel in channels: - self.execute_cmd("cdestroy %s" % channel.key) - elif args == "who": - # run a who, listing the subscribers on visible channels. - string = "\n|CChannel subscriptions|n" - channels = [ - chan - for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() - if chan.access(caller, "listen") - ] - if not channels: - string += "No channels." - for channel in channels: - string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) - self.msg(string.strip()) - else: - # wrong input - self.msg("Usage: allcom on | off | who | clear") - -class CmdCdestroy(CmdChannel): - """ - destroy a channel you created - - Usage: - cdestroy - - Destroys a channel that you control. - """ - - key = "cdestroy" - aliases = [] - help_category = "Comms" - locks = "cmd: not pperm(channel_banned)" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Destroy objects cleanly.""" - - caller = self.caller - - if not self.args: - self.msg("Usage: cdestroy ") - return - - channel = self.search_channel(self.args) - - if not channel: - self.msg("Could not find channel %s." % self.args) - return - if not channel.access(caller, "control"): - self.msg("You are not allowed to do that.") - return - channel_key = channel.key - message = f"{channel.key} is being destroyed. Make sure to change your aliases." - self.destroy_channel(channel, message) - self.msg("Channel '%s' was destroyed." % channel_key) - logger.log_sec( - "Channel Deleted: %s (Caller: %s, IP: %s)." - % (channel_key, caller, self.session.address) - ) - - -class CmdCBoot(CmdChannel): - """ - kick an account from a channel you control - - Usage: - cboot[/quiet] = [:reason] - - Switch: - quiet - don't notify the channel - - Kicks an account or object from a channel you control. - - """ - - key = "cboot" - aliases = [] - switch_options = ("quiet",) - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """implement the function""" - - if not self.args or not self.rhs: - string = "Usage: cboot[/quiet] = [:reason]" - self.msg(string) - return - - channel = self.search_channel(self.lhs) - if not channel: - return - - reason = "" - if ":" in self.rhs: - target, reason = self.rhs.rsplit(":", 1) - is_account = target.strip().startswith("*") - searchstring = target.lstrip("*") - else: - is_account = target.strip().startswith("*") - searchstring = self.rhs.lstrip("*") - - target = self.caller.search(searchstring, account=is_account) - if not target: - return - if reason: - reason = " (reason: %s)" % reason - if not channel.access(self.caller, "control"): - string = "You don't control this channel." - self.msg(string) - return - - success, err = self.boot_user(target, quiet='quiet' in self.switches) - if success: - self.msg(f"Booted {target.key} from {channel.key}") - logger.log_sec( - "Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)." - % (self.caller, channel, reason, self.caller, self.session.address) - ) - else: - self.msg(err) - - -class CmdCWho(CmdChannel): - """ - show who is listening to a channel - - Usage: - cwho - - List who is connected to a given channel you have access to. - """ - - key = "cwho" - aliases = [] - locks = "cmd: not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """implement function""" - - if not self.args: - string = "Usage: cwho " - self.msg(string) - return - - channel = self.search_channel(self.lhs) - if not channel: - return - if not channel.access(self.caller, "listen"): - string = "You can't access this channel." - self.msg(string) - return - string = "\n|CChannel subscriptions|n" - string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) - self.msg(string.strip()) - - -class CmdChannelCreate(CmdChannel): - """ - create a new channel - - Usage: - ccreate [;alias;alias...] = description - - Creates a new channel owned by you. - """ - - key = "ccreate" - aliases = "channelcreate" - locks = "cmd:not pperm(channel_banned) and pperm(Player)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Implement the command""" - - if not self.args: - self.msg("Usage ccreate [;alias;alias..] = description") - return - - description = "" - - if self.rhs: - description = self.rhs - lhs = self.lhs - channame = lhs - aliases = None - if ";" in lhs: - channame, aliases = lhs.split(";", 1) - aliases = [alias.strip().lower() for alias in aliases.split(";")] - - new_chan, err = self.create_channel(channame, description, aliases=aliases) - if new_chan: - self.msg(f"Created channel {new_chan.key} and connected to it.") - else: - self.msg(err) - - -class CmdClock(CmdChannel): - """ - change channel locks of a channel you control - - Usage: - clock [= ] - - Changes the lock access restrictions of a channel. If no - lockstring was given, view the current lock definitions. - """ - - key = "clock" - aliases = ["clock"] - locks = "cmd:not pperm(channel_banned) and perm(Admin)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """run the function""" - - if not self.args: - string = "Usage: clock channel [= lockstring]" - self.msg(string) - return - - channel = self.search_channel(self.lhs) - if not channel: - return - - if not self.rhs: - # no =, so just view the current locks - self.msg(f"Current locks on {channel.key}\n{channel.locks}") - return - # we want to add/change a lock. - if not channel.access(self.caller, "control"): - string = "You don't control this channel." - self.msg(string) - return - # Try to add the lock - success, err = self.set_lock(channel, self.rhs) - if success: - self.msg(f"Lock(s) applied. Current locks on {channel.key}:\n{channel.locks}") - else: - self.msg(err) - -class CmdCdesc(CmdChannel): - """ - describe a channel you control - - Usage: - cdesc = - - Changes the description of the channel as shown in - channel lists. - - """ - - key = "cdesc" - aliases = [] - locks = "cmd:not pperm(channel_banned)" - help_category = "Comms" - - # this is used by the COMMAND_DEFAULT_CLASS parent - account_caller = True - - def func(self): - """Implement command""" - - caller = self.caller - - if not self.rhs: - self.msg("Usage: cdesc = ") - return - channel = self.search_channel(self.lhs) - if not channel: - return - # check permissions - if not channel.access(caller, "control"): - self.msg("You cannot admin this channel.") - return - self.set_desc(channel, self.rhs) - self.msg(f"Description of channel '{channel.key}' set to '{self.rhs}'.") - - class CmdPage(COMMAND_DEFAULT_CLASS): """ send a private message to another account diff --git a/evennia/commands/default/help.py b/evennia/commands/default/help.py index 707e17d13c..e19d963928 100644 --- a/evennia/commands/default/help.py +++ b/evennia/commands/default/help.py @@ -8,6 +8,8 @@ outside the game in modules given by ``settings.FILE_HELP_ENTRY_MODULES``. """ +import re +from itertools import chain from dataclasses import dataclass from django.conf import settings from collections import defaultdict @@ -24,6 +26,7 @@ from evennia.utils.utils import ( ) from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories +CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) HELP_MORE_ENABLED = settings.HELP_MORE_ENABLED DEFAULT_HELP_CATEGORY = settings.DEFAULT_HELP_CATEGORY @@ -46,6 +49,7 @@ class HelpCategory: "key": self.key, "aliases": "", "category": self.key, + "no_prefix": "", "tags": "", "text": "", } @@ -403,7 +407,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): key: entry for key, entry in file_help_topics.items() if self.can_list_topic(entry, caller)} else: - # query + # query - check the read lock on entries cmd_help_topics = { cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd @@ -437,7 +441,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): search_fields = [ {"field_name": "key", "boost": 10}, {"field_name": "aliases", "boost": 9}, - {"field_name": "category", "boost": 8}, + {"field_name": "no_prefix", "boost": 8}, + {"field_name": "category", "boost": 7}, {"field_name": "tags", "boost": 1}, # tags are not used by default ] match, suggestions = None, None @@ -479,6 +484,27 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): self.topic = "" self.subtopics = [] + def strip_cmd_prefix(self, key, all_keys): + """ + Conditional strip of a command prefix, such as @ in @desc. By default + this will be hidden unless there is a duplicate without the prefix + in the full command set (such as @open and open). + + Args: + key (str): Command key to analyze. + all_cmds (list): All command-keys (and potentially aliases). + + Returns: + str: Potentially modified key to use in help display. + + """ + if key and key[0] in CMD_IGNORE_PREFIXES and key[1:] not in all_keys: + # filter out e.g. `@` prefixes from display if there is duplicate + # with the prefix in the set (such as @open/open) + return key[1:] + return key + + def func(self): """ Run the dynamic help entry creator. @@ -500,7 +526,12 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # group by category (cmds are listed separately) cmd_help_by_category = defaultdict(list) file_db_help_by_category = defaultdict(list) + + # get a collection of all keys + aliases to be able to strip prefixes like @ + key_and_aliases = set(chain(*(cmd._keyaliases for cmd in cmd_help_topics.values()))) + for key, cmd in cmd_help_topics.items(): + key = self.strip_cmd_prefix(key, key_and_aliases) cmd_help_by_category[cmd.help_category].append(key) for key, entry in file_db_help_topics.items(): file_db_help_by_category[entry.help_category].append(key) @@ -548,7 +579,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): {"field_name": "text", "boost": 1}, ] - for match_query in [query, f"{query}*"]: + for match_query in [query, f"{query}*", f"*{query}"]: _, suggestions = help_search_with_index( match_query, entries, suggestion_maxnum=self.suggestion_maxnum, @@ -559,7 +590,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): help_text += ( "\n... But matches where found within the help " "texts of the suggestions below.") - break + # break output = self.format_help_entry( topic=None, # this will give a no-match style title @@ -652,10 +683,19 @@ class CmdHelp(COMMAND_DEFAULT_CLASS): # we reached the bottom of the topic tree help_text = subtopic_map[None] + # get a collection of all keys + aliases to be able to strip prefixes like @ + key_and_aliases = set(chain(*(cmd._keyaliases for cmd in cmd_help_topics.values()))) + topic = self.strip_cmd_prefix(topic, key_and_aliases) + if subtopics: + aliases = None + else: + aliases = [self.strip_cmd_prefix(alias, key_and_aliases) for alias in aliases] + suggested = [self.strip_cmd_prefix(sugg, key_and_aliases) for sugg in suggested] + output = self.format_help_entry( topic=topic, help_text=help_text, - aliases=aliases if not subtopics else None, + aliases=aliases, subtopics=subtopic_index, suggested=suggested, click_topics=clickable_topics diff --git a/evennia/commands/default/system.py b/evennia/commands/default/system.py index b98f22889a..8eae534042 100644 --- a/evennia/commands/default/system.py +++ b/evennia/commands/default/system.py @@ -60,8 +60,8 @@ class CmdReload(COMMAND_DEFAULT_CLASS): reset to purge) and at_reload() hooks will be called. """ - key = "reload" - aliases = ["restart"] + key = "@reload" + aliases = ["@restart"] locks = "cmd:perm(reload) or perm(Developer)" help_category = "System" @@ -98,8 +98,8 @@ class CmdReset(COMMAND_DEFAULT_CLASS): """ - key = "reset" - aliases = ["reboot"] + key = "@reset" + aliases = ["@reboot"] locks = "cmd:perm(reload) or perm(Developer)" help_category = "System" @@ -122,7 +122,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS): Gracefully shut down both Server and Portal. """ - key = "shutdown" + key = "@shutdown" locks = "cmd:perm(shutdown) or perm(Developer)" help_category = "System" @@ -345,8 +345,8 @@ class CmdPy(COMMAND_DEFAULT_CLASS): """ - key = "py" - aliases = ["!"] + key = "@py" + aliases = ["@!"] switch_options = ("time", "edit", "clientraw", "noecho") locks = "cmd:perm(py) or perm(Developer)" help_category = "System" @@ -427,8 +427,8 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS): If not given, defaults to 10. """ - key = "accounts" - aliases = ["account", "listaccounts"] + key = "@accounts" + aliases = ["@account"] switch_options = ("delete",) locks = "cmd:perm(listaccounts) or perm(Admin)" help_category = "System" @@ -541,8 +541,8 @@ class CmdService(COMMAND_DEFAULT_CLASS): in the list. """ - key = "service" - aliases = ["services"] + key = "@service" + aliases = ["@services"] switch_options = ("list", "start", "stop", "delete") locks = "cmd:perm(service) or perm(Developer)" help_category = "System" @@ -642,8 +642,8 @@ class CmdAbout(COMMAND_DEFAULT_CLASS): Display info about the game engine. """ - key = "about" - aliases = "version" + key = "@about" + aliases = "@version" locks = "cmd:all()" help_category = "System" @@ -690,8 +690,8 @@ class CmdTime(COMMAND_DEFAULT_CLASS): and the current time stamp. """ - key = "time" - aliases = "uptime" + key = "@time" + aliases = "@uptime" locks = "cmd:perm(time) or perm(Player)" help_category = "System" @@ -758,8 +758,8 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS): """ - key = "server" - aliases = ["serverload", "serverprocess"] + key = "@server" + aliases = ["@serverload"] switch_options = ("mem", "flushmem") locks = "cmd:perm(list) or perm(Developer)" help_category = "System" @@ -907,7 +907,7 @@ class CmdTickers(COMMAND_DEFAULT_CLASS): """ - key = "tickers" + key = "@tickers" help_category = "System" locks = "cmd:perm(tickers) or perm(Builder)" @@ -964,8 +964,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS): """ - key = "tasks" - aliases = ["delays", "task"] + key = "@tasks" + aliases = ["@delays", "@task"] switch_options = ("pause", "unpause", "do_task", "call", "remove", "cancel") locks = "perm(Developer)" help_category = "System" diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 4290063d22..5c066ec414 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -993,7 +993,7 @@ class TestBuilding(CommandTest): self.call( building.CmdCpAttr(), "/copy Obj2/test2 = Obj2/test3", - 'cpattr: Extra switch "/copy" ignored.|\nCopied Obj2.test2 -> Obj2.test3. ' + '@cpattr: Extra switch "/copy" ignored.|\nCopied Obj2.test2 -> Obj2.test3. ' "(value: 'value2')", ) self.call(building.CmdMvAttr(), "", "Usage: ") @@ -1864,90 +1864,6 @@ class TestBuilding(CommandTest): ) -class TestComms(CommandTest): - def setUp(self): - super(CommandTest, self).setUp() - self.call( - comms.CmdChannelCreate(), - "testchan;test=Test Channel", - "Created channel testchan and connected to it.", - receiver=self.account, - ) - - def test_toggle_com(self): - self.call( - comms.CmdAddCom(), - "tc = testchan", - "You are already connected to channel testchan.| You can now", - receiver=self.account, - ) - self.call( - comms.CmdDelCom(), - "tc", - "Any alias 'tc' for channel testchan was cleared.", - receiver=self.account, - ) - - def test_all_com(self): - self.call( - comms.CmdAllCom(), - "", - "Available channels:", - receiver=self.account, - ) - - def test_clock(self): - self.call( - comms.CmdClock(), - "testchan=send:all()", - "Lock(s) applied. Current locks on testchan:", - receiver=self.account, - ) - - def test_cdesc(self): - self.call( - comms.CmdCdesc(), - "testchan = Test Channel", - "Description of channel 'testchan' set to 'Test Channel'.", - receiver=self.account, - ) - - def test_cwho(self): - self.call( - comms.CmdCWho(), - "testchan", - "Channel subscriptions\ntestchan:\n TestAccount", - receiver=self.account, - ) - - def test_page(self): - self.call( - comms.CmdPage(), - "TestAccount2 = Test", - "TestAccount2 is offline. They will see your message if they list their pages later." - "|You paged TestAccount2 with: 'Test'.", - receiver=self.account, - ) - - def test_cboot(self): - # No one else connected to boot - self.call( - comms.CmdCBoot(), - "", - "Usage: cboot[/quiet] = [:reason]", - receiver=self.account, - ) - - def test_cdestroy(self): - self.call( - comms.CmdCdestroy(), - "testchan", - "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases." - "|Channel 'testchan' was destroyed.", - receiver=self.account, - ) - - from evennia.utils.create import create_channel # noqa class TestCommsChannel(CommandTest): diff --git a/evennia/contrib/mux_comms_cmds.py b/evennia/contrib/mux_comms_cmds.py new file mode 100644 index 0000000000..a50eb0b3b9 --- /dev/null +++ b/evennia/contrib/mux_comms_cmds.py @@ -0,0 +1,545 @@ +""" +Legacy Comms-commands + +Griatch 2021 + +In Evennia 1.0, the old Channel commands (originally inspired by MUX) were +replaced by the single `channel` command that performs all these function. +That command is still required to talk on channels. This contrib (extracted +from Evennia 0.9.5) reuses the channel-management of the base Channel command +but breaks out its functionality into separate Commands with MUX-familiar names. + +- `allcom` - `channel/all` and `channel` +- `addcom` - `channel/alias`, `channel/sub` and `channel/unmute` +- `delcom` - `channel/unalias`, `alias/unsub` and `channel/mute` +- `cboot` - `channel/boot` (`channel/ban` and `/unban` not supported) +- `cwho` - `channel/who` +- `ccreate` - `channel/create` +- `cdestroy` - `channel/destroy` +- `clock` - `channel/lock` +- `cdesc` - `channel/desc` + +Installation: + +- Import the `CmdSetLegacyComms` cmdset from this module into `mygame/commands/default_cmdsets.py` +- Add it to the CharacterCmdSet's `at_cmdset_creation` method. +- Reload the server. + +Example: + +```python +# in mygame/commands/default_cmdsets.py + +# ... +from evennia.contrib.legacy_comms import CmdSetLegacyComms # <---- + +class CharacterCmdSet(default_cmds.CharacterCmdSet): + # ... + def at_cmdset_creation(self): + # ... + self.add(CmdSetLegacyComms) # <---- +``` + +""" +from django.conf import settings +from evennia.commands.cmdset import CmdSet +from evennia.commands.default.comms import CmdChannel +from evennia.utils import logger + +CHANNEL_DEFAULT_TYPECLASS = settings.BASE_CHANNEL_TYPECLASS + + +class CmdAddCom(CmdChannel): + """ + Add a channel alias and/or subscribe to a channel + + Usage: + addcom [alias=] + + Joins a given channel. If alias is given, this will allow you to + refer to the channel by this alias rather than the full channel + name. Subsequent calls of this command can be used to add multiple + aliases to an already joined channel. + """ + + key = "addcom" + aliases = ["aliaschan", "chanalias"] + help_category = "Comms" + locks = "cmd:not pperm(channel_banned)" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Implement the command""" + + caller = self.caller + args = self.args + + if not args: + self.msg("Usage: addcom [alias =] channelname.") + return + + if self.rhs: + # rhs holds the channelname + channelname = self.rhs + alias = self.lhs + else: + channelname = self.args + alias = None + + channel = self.search_channel(channelname) + if not channel: + return + + string = "" + if not channel.has_connection(caller): + # we want to connect as well. + success, err = self.sub_to_channel(channel) + if success: + # if this would have returned True, the account is connected + self.msg(f"You now listen to the channel {channel.key}") + else: + self.msg(f"{channel.key}: You are not allowed to join this channel.") + return + + if channel.unmute(caller): + self.msg(f"You unmute channel {channel.key}.") + else: + self.msg(f"You are already connected to channel {channel.key}.") + + if alias: + # create a nick and add it to the caller. + self.add_alias(channel, alias) + self.msg(f" You can now refer to the channel {channel} with the alias '{alias}'.") + else: + string += " No alias added." + self.msg(string) + + +class CmdDelCom(CmdChannel): + """ + remove a channel alias and/or unsubscribe from channel + + Usage: + delcom + delcom/all + + If the full channel name is given, unsubscribe from the + channel. If an alias is given, remove the alias but don't + unsubscribe. If the 'all' switch is used, remove all aliases + for that channel. + """ + + key = "delcom" + aliases = ["delaliaschan", "delchanalias"] + help_category = "Comms" + locks = "cmd:not perm(channel_banned)" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Implementing the command. """ + + caller = self.caller + + if not self.args: + self.msg("Usage: delcom ") + return + ostring = self.args.lower().strip() + + channel = self.search_channel(ostring) + if not channel: + return + + if not channel.has_connection(caller): + self.msg("You are not listening to that channel.") + return + + if ostring == channel.key.lower(): + # an exact channel name - unsubscribe + delnicks = "all" in self.switches + # find all nicks linked to this channel and delete them + if delnicks: + aliases = self.get_channel_aliases(channel) + for alias in aliases: + self.remove_alias(alias) + success, err = self.unsub_from_channel(channel) + if success: + wipednicks = " Eventual aliases were removed." if delnicks else "" + self.msg(f"You stop listening to channel '{channel.key}'.{wipednicks}") + else: + self.msg(err) + return + else: + # we are removing a channel nick + self.remove_alias(ostring) + self.msg(f"Any alias '{ostring}' for channel {channel.key} was cleared.") + + +class CmdAllCom(CmdChannel): + """ + perform admin operations on all channels + + Usage: + allcom [on | off | who | destroy] + + Allows the user to universally turn off or on all channels they are on, as + well as perform a 'who' for all channels they are on. Destroy deletes all + channels that you control. + + Without argument, works like comlist. + """ + + key = "allcom" + aliases = [] # important to not inherit parent's aliases + locks = "cmd: not pperm(channel_banned)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Runs the function""" + + caller = self.caller + args = self.args + if not args: + subscribed, available = self.list_channels() + self.msg( + "\n|wAvailable channels:\n{table}") + return + return + + if args == "on": + # get names of all channels available to listen to + # and activate them all + channels = [ + chan + for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() + if chan.access(caller, "listen") + ] + for channel in channels: + self.execute_cmd("addcom %s" % channel.key) + elif args == "off": + # get names all subscribed channels and disconnect from them all + channels = CHANNEL_DEFAULT_TYPECLASS.objects.get_subscriptions(caller) + for channel in channels: + self.execute_cmd("delcom %s" % channel.key) + elif args == "destroy": + # destroy all channels you control + channels = [ + chan + for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() + if chan.access(caller, "control") + ] + for channel in channels: + self.execute_cmd("cdestroy %s" % channel.key) + elif args == "who": + # run a who, listing the subscribers on visible channels. + string = "\n|CChannel subscriptions|n" + channels = [ + chan + for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() + if chan.access(caller, "listen") + ] + if not channels: + string += "No channels." + for channel in channels: + string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) + self.msg(string.strip()) + else: + # wrong input + self.msg("Usage: allcom on | off | who | clear") + + +class CmdCdestroy(CmdChannel): + """ + destroy a channel you created + + Usage: + cdestroy + + Destroys a channel that you control. + """ + + key = "cdestroy" + aliases = [] + help_category = "Comms" + locks = "cmd: not pperm(channel_banned)" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Destroy objects cleanly.""" + + caller = self.caller + + if not self.args: + self.msg("Usage: cdestroy ") + return + + channel = self.search_channel(self.args) + + if not channel: + self.msg("Could not find channel %s." % self.args) + return + if not channel.access(caller, "control"): + self.msg("You are not allowed to do that.") + return + channel_key = channel.key + message = f"{channel.key} is being destroyed. Make sure to change your aliases." + self.destroy_channel(channel, message) + self.msg("Channel '%s' was destroyed." % channel_key) + logger.log_sec( + "Channel Deleted: %s (Caller: %s, IP: %s)." + % (channel_key, caller, self.session.address) + ) + + +class CmdCBoot(CmdChannel): + """ + kick an account from a channel you control + + Usage: + cboot[/quiet] = [:reason] + + Switch: + quiet - don't notify the channel + + Kicks an account or object from a channel you control. + + """ + + key = "cboot" + aliases = [] + switch_options = ("quiet",) + locks = "cmd: not pperm(channel_banned)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """implement the function""" + + if not self.args or not self.rhs: + string = "Usage: cboot[/quiet] = [:reason]" + self.msg(string) + return + + channel = self.search_channel(self.lhs) + if not channel: + return + + reason = "" + if ":" in self.rhs: + target, reason = self.rhs.rsplit(":", 1) + is_account = target.strip().startswith("*") + searchstring = target.lstrip("*") + else: + is_account = target.strip().startswith("*") + searchstring = self.rhs.lstrip("*") + + target = self.caller.search(searchstring, account=is_account) + if not target: + return + if reason: + reason = " (reason: %s)" % reason + if not channel.access(self.caller, "control"): + string = "You don't control this channel." + self.msg(string) + return + + success, err = self.boot_user(target, quiet='quiet' in self.switches) + if success: + self.msg(f"Booted {target.key} from {channel.key}") + logger.log_sec( + "Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)." + % (self.caller, channel, reason, self.caller, self.session.address) + ) + else: + self.msg(err) + + +class CmdCWho(CmdChannel): + """ + show who is listening to a channel + + Usage: + cwho + + List who is connected to a given channel you have access to. + """ + + key = "cwho" + aliases = [] + locks = "cmd: not pperm(channel_banned)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """implement function""" + + if not self.args: + string = "Usage: cwho " + self.msg(string) + return + + channel = self.search_channel(self.lhs) + if not channel: + return + if not channel.access(self.caller, "listen"): + string = "You can't access this channel." + self.msg(string) + return + string = "\n|CChannel subscriptions|n" + string += "\n|w%s:|n\n %s" % (channel.key, channel.wholist) + self.msg(string.strip()) + + +class CmdChannelCreate(CmdChannel): + """ + create a new channel + + Usage: + ccreate [;alias;alias...] = description + + Creates a new channel owned by you. + """ + + key = "ccreate" + aliases = "channelcreate" + locks = "cmd:not pperm(channel_banned) and pperm(Player)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Implement the command""" + + if not self.args: + self.msg("Usage ccreate [;alias;alias..] = description") + return + + description = "" + + if self.rhs: + description = self.rhs + lhs = self.lhs + channame = lhs + aliases = None + if ";" in lhs: + channame, aliases = lhs.split(";", 1) + aliases = [alias.strip().lower() for alias in aliases.split(";")] + + new_chan, err = self.create_channel(channame, description, aliases=aliases) + if new_chan: + self.msg(f"Created channel {new_chan.key} and connected to it.") + else: + self.msg(err) + + +class CmdClock(CmdChannel): + """ + change channel locks of a channel you control + + Usage: + clock [= ] + + Changes the lock access restrictions of a channel. If no + lockstring was given, view the current lock definitions. + """ + + key = "clock" + aliases = ["clock"] + locks = "cmd:not pperm(channel_banned) and perm(Admin)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """run the function""" + + if not self.args: + string = "Usage: clock channel [= lockstring]" + self.msg(string) + return + + channel = self.search_channel(self.lhs) + if not channel: + return + + if not self.rhs: + # no =, so just view the current locks + self.msg(f"Current locks on {channel.key}\n{channel.locks}") + return + # we want to add/change a lock. + if not channel.access(self.caller, "control"): + string = "You don't control this channel." + self.msg(string) + return + # Try to add the lock + success, err = self.set_lock(channel, self.rhs) + if success: + self.msg(f"Lock(s) applied. Current locks on {channel.key}:\n{channel.locks}") + else: + self.msg(err) + + +class CmdCdesc(CmdChannel): + """ + describe a channel you control + + Usage: + cdesc = + + Changes the description of the channel as shown in + channel lists. + + """ + + key = "cdesc" + aliases = [] + locks = "cmd:not pperm(channel_banned)" + help_category = "Comms" + + # this is used by the COMMAND_DEFAULT_CLASS parent + account_caller = True + + def func(self): + """Implement command""" + + caller = self.caller + + if not self.rhs: + self.msg("Usage: cdesc = ") + return + channel = self.search_channel(self.lhs) + if not channel: + return + # check permissions + if not channel.access(caller, "control"): + self.msg("You cannot admin this channel.") + return + self.set_desc(channel, self.rhs) + self.msg(f"Description of channel '{channel.key}' set to '{self.rhs}'.") + + +class CmdSetLegacyComms(CmdSet): + def at_cmdset_createion(self): + self.add(CmdAddCom()) + self.add(CmdAllCom()) + self.add(CmdDelCom()) + self.add(CmdCdestroy()) + self.add(CmdCBoot()) + self.add(CmdCWho()) + self.add(CmdChannelCreate()) + self.add(CmdClock()) + self.add(CmdCdesc()) diff --git a/evennia/contrib/tests.py b/evennia/contrib/tests.py index 9d77c7b488..f3d5cac3c4 100644 --- a/evennia/contrib/tests.py +++ b/evennia/contrib/tests.py @@ -3355,3 +3355,92 @@ class TestBuildingMenu(CommandTest): self.assertEqual(self.char1.ndb._building_menu.obj, self.room1) self.call(CmdNoMatch(building_menu=self.menu), "q") self.assertEqual(self.exit.key, "in") + + +from evennia.contrib import mux_comms_cmds as comms # noqa + +class TestLegacyMuxComms(CommandTest): + """ + Test the legacy comms contrib. + """ + def setUp(self): + super(CommandTest, self).setUp() + self.call( + comms.CmdChannelCreate(), + "testchan;test=Test Channel", + "Created channel testchan and connected to it.", + receiver=self.account, + ) + + def test_toggle_com(self): + self.call( + comms.CmdAddCom(), + "tc = testchan", + "You are already connected to channel testchan.| You can now", + receiver=self.account, + ) + self.call( + comms.CmdDelCom(), + "tc", + "Any alias 'tc' for channel testchan was cleared.", + receiver=self.account, + ) + + def test_all_com(self): + self.call( + comms.CmdAllCom(), + "", + "Available channels:", + receiver=self.account, + ) + + def test_clock(self): + self.call( + comms.CmdClock(), + "testchan=send:all()", + "Lock(s) applied. Current locks on testchan:", + receiver=self.account, + ) + + def test_cdesc(self): + self.call( + comms.CmdCdesc(), + "testchan = Test Channel", + "Description of channel 'testchan' set to 'Test Channel'.", + receiver=self.account, + ) + + def test_cwho(self): + self.call( + comms.CmdCWho(), + "testchan", + "Channel subscriptions\ntestchan:\n TestAccount", + receiver=self.account, + ) + + def test_page(self): + self.call( + comms.CmdPage(), + "TestAccount2 = Test", + "TestAccount2 is offline. They will see your message if they list their pages later." + "|You paged TestAccount2 with: 'Test'.", + receiver=self.account, + ) + + def test_cboot(self): + # No one else connected to boot + self.call( + comms.CmdCBoot(), + "", + "Usage: cboot[/quiet] = [:reason]", + receiver=self.account, + ) + + def test_cdestroy(self): + self.call( + comms.CmdCdestroy(), + "testchan", + "[testchan] TestAccount: testchan is being destroyed. Make sure to change your aliases." + "|Channel 'testchan' was destroyed.", + receiver=self.account, + ) diff --git a/evennia/help/filehelp.py b/evennia/help/filehelp.py index 4e0a9dac03..bd54469973 100644 --- a/evennia/help/filehelp.py +++ b/evennia/help/filehelp.py @@ -102,6 +102,7 @@ class FileHelpEntry: "key": self.key, "aliases": " ".join(self.aliases), "category": self.help_category, + "no_prefix": "", "tags": "", "locks": "", "text": self.entrytext, diff --git a/evennia/help/models.py b/evennia/help/models.py index 76391699e3..6f23154445 100644 --- a/evennia/help/models.py +++ b/evennia/help/models.py @@ -139,6 +139,7 @@ class HelpEntry(SharedMemoryModel): return { "key": self.db_key, "aliases": " ".join(self.aliases.all()), + "no_prefix": "", "category": self.db_help_category, "text": self.db_entrytext, "tags": " ".join(str(tag) for tag in self.tags.all()), diff --git a/evennia/help/utils.py b/evennia/help/utils.py index 4d237bc979..6b15e7a345 100644 --- a/evennia/help/utils.py +++ b/evennia/help/utils.py @@ -71,7 +71,8 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields pass custom_stop_words_filter = stop_word_filter.generate_stop_word_filter(stop_words) - _LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer) + # _LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer) + _LUNR_BUILDER_PIPELINE = (custom_stop_words_filter, stemmer) indx = [cnd.search_index_entry for cnd in candidate_entries] mapping = {indx[ix]["key"]: cand for ix, cand in enumerate(candidate_entries)}