diff --git a/docs/source/Components/Channels.md b/docs/source/Components/Channels.md index 005eef01c3..2fb5706773 100644 --- a/docs/source/Components/Channels.md +++ b/docs/source/Components/Channels.md @@ -1,3 +1,308 @@ # Channels -TODO: Channels are covered in [Communications](./Communications) right now. \ No newline at end of file +In a multiplayer game, players often need other means of in-game communication +than just walking from room to room using `say` or `emote`. + +_Channels_ are Evennia's system for letting the server act as a fancy chat +program. When a player is connected to a channel, sending a message to it will +automatically distribute it to every other subscriber. + +Channels can be used both for chats between [Accounts](./Accounts) and between +[Objects](./Objects) (usually Characters) and (in principle) a mix of the two. +Chats could be both OOC (out-of-character) or IC (in-charcter) in nature. Some +examples: + +- A support channel for contacting staff (OOC) +- A general chat for discussing anything and foster community (OOC) +- Admin channel for private staff discussions (OOC) +- Private guild channels for planning and organization (IC/OOC depending on game) +- Cyberpunk-style retro chat rooms (IC) +- In-game radio channels (IC) +- Group telephathy (IC) +- Walkie talkies (IC) + +```versionchanged:: 1.0 + + Channel system changed to use a central 'channel' command and nicks instead of + auto-generated channel-commands and -cmdset. ChannelHandler was removed. + +``` + +## Using channels in-game + +In the default command set, channels are all handled via the mighty +[channel command](api:evennia.commands.default.comms.CmdChannel), `channel` (or +`chan`). By default, this command will assume all entities dealing with +channels are `Accounts`. + +### Viewing, joining and creating channels + + channel - shows your subscriptions + channel/all - shows all subs available to you + channel/who - shows who subscribes to this channel + +To join/unsub a channel do + + channel/sub channelname + channel/unsub channelname + +If you temporarily don't want to hear the channel for a while (without actually +unsubscribing), you can mute it: + + channel/mute channelname + channel/unmute channelname + +To create/destroy a new channel you can do + + channel/create channelname;alias;alias = description + channel/destroy channelname + +Aliases are optional but can be good for obvious shortcuts everyone may want to +use. The description is used in channel-listings. You will automatically join a +channel you created and will be controlling it. + +### Chat on channels + +To speak on a channel, do + + channel public Hello world! + +If the channel-name has spaces in it, you need to use a '`=`': + + channel rest room = Hello world! + +Now, this is more to type than we'd like, so when you join a channel, the +system automatically sets up an personal alias so you can do this instead: + + public Hello world + +```warning:: + + This shortcut will not work if the channel-name has spaces in it. + So channels with long names should make sure to provide a one-word alias as + well. +``` + +Any user can make up their own channel aliases: + + channel/alias public = foo;bar + +You can now just do + + foo Hello world! + bar Hello again! + +But you can also use your alias with the `channel` command: + + channel foo Hello world! + +> What happens when aliasing is that a [nick](./Nicks) is created that maps your +> alias + argument onto calling the `channel` command. So when you enter `foo hello`, +> what the server sees is actually `channel foo = hello`. The system is also +> clever enough to know that whenever you search for channels, your channel-nicks +> should first be considered. + +You can check if you missed something by viewing the channel's scrollback with + + channel/history public + +This retrieves the last 20 lines of text (also from a time when you were +offline). You can step further back by specifying how many lines back to start: + + channel/history public = 30 + +This again retrieve 20 lines, but starting 30 lines back (so you'll get lines +30-50 counting backwards). + + +### Channel administration + +If you control the channel (because you are an admin or created it) you have the +ability to control who can access it by use of [locks](./Locks): + + channel/lock buildchannel = listen:all();send:perm(Builders) + +Channels use three lock-types by default: + +- `listen` - who may listen to the channel. Users without this access will not + even be able to join the channel and it will not appear in listings for them. +- `send` - who may send to the channel. +- `control` - this is assigned to you automatically when you create the channel. With + control over the channel you can edit it, boot users and do other management tasks. + +If you control a channel you can also kick people off it: + + channel/boot mychannel = annoyinguser123 : stop spamming! + +The last part is an optional reason to send to the user before they are booted. +You can give a comma-separated list of channels to kick the same user from all +those channels at once. The user will be unsubbed from the channel and all +their aliases will be wiped. But they can still rejoin if they like. + + channel/ban mychannel = annoyinguser123 : spammed too much + channel/ban - view bans + channel/unban mychannel = annoyinguser123 + +The optional reason at the end shows in the banlist +Banning adds the user to the channels blacklist. This means they will not be +able to rejoin if you boot them. You will need to run `channel/boot` to +actually kick them. + +See the [Channel command](api:evennia.commands.default.comms.CmdChannel) api +docs (and in-game help) for more details. + + +## Allowing Characters to use Channels + +The default `channel` command ([evennia.commands.default.comms.CmdChannel](api:evennia.commands.default.comms.CmdChannel)) +sits in the `Account` [command set](./Command-Sets). It is set up such that it will +always operate on `Accounts`, even if you were to add it to the +`CharacterCmdSet`. + +It's a one-line change to make this command accept non-account callers. But for +convenience we provide a version for Characters/Objects. Just import +[evennia.commands.default.comms.CmdObjectChannel](api:evennia.commands.default.comms.CmdObjectChannel) +and inherit from that instead. + +## Customizing channel output and behavior + +When distributing a message, the channel will call a series of hooks on itself +and (more importantly) on each recipient. So you can customize things a lot by +just modifying hooks on your normal Object/Account typeclasses. + +Internally, the message is sent with +`channel.msg(message, senders=sender, bypass_mute=False, **kwargs)`, where +`bypass_mute=True` means the message ignores muting (good for alerts or if you +delete the channel etc) and `**kwargs` are any extra info you may want to pass +to the hooks. The `senders` (it's always only one in the default implementation +but could in principle be multiple) and `bypass_mute` are part of the `kwargs` +below: + + 1. `channel.at_pre_msg(message, **kwargs)` + 2. For each recipient: + - `message = recipient.at_pre_channel_msg(message, channel, **kwargs)` - + allows for the message to be tweaked per-receiver (for example coloring it depending + on the users' preferences). If this method returns `False/None`, that + recipient is skipped. + - `recipient.channel_msg(message, channel, **kwargs)` - actually sends to recipient. + - `recipient.at_post_channel_msg(message, channel, **kwargs)` - any post-receive effects. + 3. `channel.at_post_channel_msg(message, **kwargs)` + +Note that `Accounts` and `Objects` both have their have separate sets of hooks. +So make sure you modify the set actually used by your subcribers (or both). +Default channels all use `Account` subscribers. + +## Channels in code + +For most common changes, the default channel, the recipient hooks and possibly +overriding the `channel` command will get you very far. But you can also tweak +channels themselves. + +Channels are [Typeclassed](./Typeclasses) entities. This means they are +persistent in the database, can have [attributes](./Attributes) and [Tags](./Tags) +and can be easily extended. + +To change which channel typeclass Evennia uses for default commands, change +`settings.BASE_CHANNEL_TYPECLASS`. The base command class is +[`evennia.comms.comms.DefaultChannel`](api:evennia.comms.comms.DefaultChannel). +There is an empty child class in `mygame/typeclasses/channels.py`, same +as for other typelass-bases. + +In code you create a new channel with `evennia.create_channel` or +`Channel.create`: + +```python + from evennia import create_channel, search_object + from typeclasses.channels import Channel + + channel = create_channel("my channel", aliases=["mychan"], locks=..., typeclass=...) + # alternative + channel = Channel.create("my channel", aliases=["mychan"], locks=...) + + # connect to it + me = search_object(key="Foo")[0] + channel.connect(me) + + # send to it (this will trigger the channel_msg hooks described earlier) + channel.msg("Hello world!", senders=me) + + # view subscriptions (the SubscriptionHandler handles all subs under the hood) + channel.subscriptions.has(me) # check we subbed + channel.subscriptions.all() # get all subs + channel.subscriptions.online() # get only subs currently online + channel.subscriptions.clear() # unsub all + + # leave channel + channel.disconnect(me) + + # permanently delete channel (will unsub everyone) + channel.delete() + +``` + +The Channel's `.connect` method will accept both `Account` and `Object` subscribers +and will handle them transparently. + +The channel has many more hooks, both hooks shared with all typeclasses as well +as special ones related to muting/banning etc. See the channel class for +details. + +## Channel logging + +```versionchanged:: 0.7 + + Channels changed from using Msg to TmpMsg and optional log files. +``` +```versionchanged:: 1.0 + + Channels stopped supporting Msg and TmpMsg, using only log files. +``` + +The channel messages are not stored in the database. A channel is instead +always logged to a regular text log-file +`mygame/server/logs/channel_.log`. This is where `channels/history channelname` +gets its data from. A channel's log will rotate when it grows too big, which +thus also automatically limits the max amount of history a user can view with +`/history`. + +### Properties on Channels + +Channels have all the standard properties of a Typeclassed entity (`key`, +`aliases`, `attributes`, `tags`, `locks` etc). This is not an exhaustive list; +see the [Channel api docs](api:evennia.comms.comms.DefaultChannel) for details. + +- `send_to_online_only` - this class boolean defaults to `True` and is a + sensible optimization since people offline people will not see the message anyway. +- `log_to_file` - this is a string that determines the name of the channel log file. Default + is `"channel_{channel_key}.log"`. You should usually not change this. +- `channel_prefix_string` - this property is a string to easily change how + the channel is prefixed. It takes the `channel_key` format key. Default is `"[{channel_key}] "` + and produces output like `[public] ...``. +- `subscriptions` - this is the [SubscriptionHandler](`api:evennia.comms.comms.SubscriptionHandler`), which + has methods `has`, `add`, `remove`, `all`, `clear` and also `online` (to get + only actually online channel-members). +- `wholist`, `mutelist`, `banlist` are properties that return a list of subscribers, + as well as who are currently muted or banned. + +Notable `Channel` hooks: + +- `at_pre_channel_msg(message, **kwargs)` - called before sending a message, to + modify it. Not used by default. +- `msg(message, senders=..., bypass_mute=False, **kwargs)` - send the message onto + the channel. The `**kwargs` are passed on into the other call hooks (also on the recipient). +- `at_post_channel_msg(message, **kwargs)` - by default this is used to store the message + to the log file. +- `channel_prefix(message)` - this is called to allow the channel to prefix. This is called + by the object/account when they build the message, so if wanting something else one can + also just remove that call. +- every channel message. By default it just returns `channel_prefix_string`. +- `has_connection(subscriber)` - shortcut to check if an entity subscribes to + this channel +- `mute/unmute(subscriber)` - this mutes the channel for this user. +- `ban/unban(subscriber)` - adds/remove user from banlist. +- `connect/disconnect(subscriber)` - adds/removes a subscriber. +- `pre_join_channel(subscriber)` - if this returns `False`, connection will be refused. +- `post_join_channel(subscriber)` - unused by default. +- `pre_leave_channel(subscriber)` - if this returns `False`, the user is not allowed to leave. +- `post_leave_channel(subscriber)` - unused by default. + diff --git a/docs/source/Components/Communications.md b/docs/source/Components/Communications.md index f7c0130595..21570fa740 100644 --- a/docs/source/Components/Communications.md +++ b/docs/source/Components/Communications.md @@ -1,113 +1,8 @@ # Communications +TODO: Remove this page? -Apart from moving around in the game world and talking, players might need other forms of -communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like' -system of channels, but there is nothing stopping you from changing things to better suit your -taste. - -Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which -mimics the API of a `Msg` but has no connection to the database. - -## Msg - -The `Msg` object is the basic unit of communication in Evennia. A message works a little like an -e-mail; it always has a sender (a [Account](./Accounts)) and one or more recipients. The recipients -may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message -to both Channels and Accounts if you like. - -Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows -for comprehensive logging of communications. This could be useful for allowing senders/receivers to -have 'mailboxes' with the messages they want to keep. - -### Properties defined on `Msg` - -- `senders` - this is a reference to one or many [Account](./Accounts) or [Objects](./Objects) (normally -*Characters*) sending the message. This could also be an *External Connection* such as a message -coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be -mixed in any combination. -- `receivers` - a list of target [Accounts](./Accounts), [Objects](./Objects) (usually *Characters*) or -*Channels* to send the message to. The types of receivers can be mixed in any combination. -- `header` - this is a text field for storing a title or header for the message. -- `message` - the actual text being sent. -- `date_sent` - when message was sent (auto-created). -- `locks` - a [lock definition](./Locks). -- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg` -from. This relationship is stored in the database primarily for optimization reasons, allowing for -quickly post-filter out messages not intended for a given target. There is no in-game methods for -setting this, it's intended to be done in code. - -You create new messages in code using `evennia.create_message` (or -`evennia.utils.create.create_message.`) - -## TempMsg - -`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the -database. TempMsgs are used by Evennia for channel messages by default. They can be used for any -system expecting a `Msg` but when you don't actually want to save anything. - -## Channels - -Channels are [Typeclassed](./Typeclasses) entities, which mean they can be easily extended and their -functionality modified. To change which channel typeclass Evennia uses, change -settings.BASE_CHANNEL_TYPECLASS. - -Channels act as generic distributors of messages. Think of them as "switch boards" redistributing -`Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or -`TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have -[Locks](./Locks) to limit who may listen and/or send messages through them. - -The *sending* of text to a channel is handled by a dynamically created [Command](./Commands) that -always have the same name as the channel. This is created for each channel by the global -`ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are -used to determine which channels are possible to write to. When subscribing to a channel, you can -then just write the channel name and the text to send. - -The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to -your own command), implements a few convenient features: - - - It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead -dumps channel output a file log in `server/logs/channel_.log`. This is mainly for -practical reasons - we find one rarely need to query individual Msg objects at a later date. Just -stupidly dumping the log to a file also means a lot less database overhead. - - It adds a `/history` switch to view the 20 last messages in the channel. These are read from the -end of the log file. One can also supply a line number to start further back in the file (but always -20 entries at a time). It's used like this: - - > public/history - > public/history 35 - - -There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo` -receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on -(all new accounts are automatically joined to it when logging in, it is useful for asking -questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see -`evennia/settings_default.py` for more details). - -You create new channels with `evennia.create_channel` (or `evennia.utils.create.create_channel`). - -In code, messages are sent to a channel using the `msg` or `tempmsg` methods of channels: - - channel.msg(msgobj, header=None, senders=None, persistent=True) - -The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the -latter cases all the following keywords are ignored since the message objects already contains all -this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or -`TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted -for channel communication (since the default ChannelCommand instead logs to a file). - -```python - # assume we have a 'sender' object and a channel named 'mychan' - - # manually sending a message to a channel - mychan.msg("Hello!", senders=[sender]) -``` - -### Properties defined on `Channel` - -- `key` - main name for channel -- `aliases` - alternative native names for channels -- `desc` - optional description of channel (seen in listings) -- `keep_log` (bool) - if the channel should store messages (default) -- `locks` - A [lock definition](./Locks). Channels normally use the access_types `send, control` and -`listen`. \ No newline at end of file +- [Channels](./Channels) - are used for implementing in-game chat rooms. +- [Msg](./Msg)-objects are used for storing messages in the database (email-like) + and is a building block for implementing other game systems. It's used by the + `page` command by default. diff --git a/docs/source/Components/Msg.md b/docs/source/Components/Msg.md new file mode 100644 index 0000000000..9e69584dc1 --- /dev/null +++ b/docs/source/Components/Msg.md @@ -0,0 +1,91 @@ +# Msg + +The [Msg](api:evennia.comms.models.Msg) object represents a database-saved +piece of communication. Think of it as a discrete piece of email - it contains +a message, some metadata and will always have a sender and one or more +recipients. + +Once created, a Msg is normally not changed. It is persitently saved in the +database. This allows for comprehensive logging of communications. Here are some +good uses for `Msg` objects: + +- page/tells (the `page` command is how Evennia uses them out of the box) +- messages in a bulletin board +- game-wide email stored in 'mailboxes'. + + +```important:: + + A `Msg` does not have any in-game representation. So if you want to use them + to represent in-game mail/letters, the physical letters would never be + visible in a room (possible to steal, spy on etc) unless you make your + spy-system access the Msgs directly (or go to the trouble of spawning an + actual in-game letter-object based on the Msg) + + +``` + +```versionchanged:: 1.0 + Channels dropped Msg-support. Now only used in `page` command by default. +``` + +## Msg in code + +The Msg is intended to be used exclusively in code, to build other game systems. It is _not_ +a [Typeclassed](./Typeclasses) entity, which means it cannot (easily) be overridden. It +doesn't support Attributes (but it _does_ support [Tags](./Tags)). It tries to be lean +and small since a new one is created for every message. + +You create a new message with `evennia.create_message`: + +```python + from evennia import create_message + message = create_message(senders, message, receivers, + locks=..., tags=..., header=...) +``` + +You can search for `Msg` objects in various ways: + + +```python + from evennia import search_message, Msg + + # args are optional. Only a single sender/receiver should be passed + messages = search_message(sender=..., receiver=..., freetext=..., dbref=...) + + # get all messages for a given sender/receiver + messages = Msg.objects.get_msg_by_sender(sender) + messages = Msg.objects.get_msg_by_receiver(recipient) + +``` + +### Properties on Msg + +- `senders` - there must always be at least one sender. This is one of [Account](./Accounts), [Object](./Objects), [Script](./Scripts) + or _external_ - which is a string uniquely identifying the sender. The latter can be used by + a sender-system that doesn't fit into Evennia's normal typeclass-system. + While most systems expect a single sender, it's possible to have any number of them. +- `receivers` - these are the ones to see the Msg. These are again one of + [Account](./Accounts), [Object](./Objects) or [Script](./Scripts). It's in principle possible to have + zero receivers but most usages of Msg expects one or more. +- `header` - this is an optional text field that can contain meta-information about the message. For + an email-like system it would be the subject line. This can be independently searched, making + this a powerful place for quickly finding messages. +- `message` - the actual text being sent. +- `date_sent` - this is auto-set to the time the Msg was created (and thus presumably sent). +- `locks` - the Evennia [lock handler](./Locks). Use with `locks.add()` etc and check locks with `msg.access()` + like for all other lockable entities. This can be used to limit access to the contents + of the Msg. The default lock-type to check is `'read'`. +- `hide_from` - this is an optional list of [Accounts](./Accounts) or [Objects](./Objects) that + will not see this Msg. This relationship is available mainly for optimization + reasons since it allows quick filtering of messages not intended for a given + target. + + +## TempMsg + +[evennia.comms.models.TempMsg](api:evennia.comms.models.TempMsg) is an object +that implements the same API as the regular `Msg`, but which has no database +component (and thus cannot be searched). It's meant to plugged into systems +expecting a `Msg` but where you just want to process the message without saving +it. diff --git a/docs/source/toc.md b/docs/source/toc.md index fa1a27bec0..c713a94026 100644 --- a/docs/source/toc.md +++ b/docs/source/toc.md @@ -1,5 +1,5 @@ # Toc -- [API root](api/evennia-api.rst) + - [Coding/Coding Introduction](Coding/Coding-Introduction) - [Coding/Coding Overview](Coding/Coding-Overview) - [Coding/Continuous Integration](Coding/Continuous-Integration) @@ -34,6 +34,7 @@ - [Components/Inputfuncs](Components/Inputfuncs) - [Components/Locks](Components/Locks) - [Components/MonitorHandler](Components/MonitorHandler) +- [Components/Msg](Components/Msg) - [Components/Nicks](Components/Nicks) - [Components/Objects](Components/Objects) - [Components/Outputfuncs](Components/Outputfuncs) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 481eb4d46b..a681490640 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -152,8 +152,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): def msg_channel(self, channel, message, **kwargs): """ - Send a message to a given channel. At this point - any permissions should already be done. + Send a message to a given channel. This will check the 'send' + permission on the channel. Args: channel (Channel): The channel to send to. @@ -162,6 +162,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): all channel messaging hooks for custom overriding. """ + if not channel.access(self.caller, "send"): + caller.msg(f"You are not allowed to send messages to channel {channel}") + return + channel.msg(message, senders=self.caller, **kwargs) def get_channel_history(self, channel, start_index=0): @@ -587,7 +591,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS): name = subscriber.get_display_name(caller) conditions = ("muted" if subscriber in mute_list else "", "offline" if subscriber not in online_list else "") - conditions = (cond for cond in conditions if cond) + conditions = [cond for cond in conditions if cond] cond_text = "(" + ", ".join(conditions) + ")" if conditions else "" who_list.append(f"{name}{cond_text}") diff --git a/evennia/comms/comms.py b/evennia/comms/comms.py index 49bb722b9c..33e9365043 100644 --- a/evennia/comms/comms.py +++ b/evennia/comms/comms.py @@ -314,7 +314,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): ) @classmethod - def create(cls, key, account=None, *args, **kwargs): + def create(cls, key, creator=None, *args, **kwargs): """ Creates a basic Channel with default parameters, unless otherwise specified or extended. @@ -323,7 +323,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): Args: key (str): This must be unique. - account (Account): Account to attribute this object to. + creator (Account or Object): Entity to associate with this channel + (used for tracking) Keyword Args: aliases (list of str): List of alternative (likely shorter) keynames. @@ -351,8 +352,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase): # Record creator id and creation IP if ip: obj.db.creator_ip = ip - if account: - obj.db.creator_id = account.id + if creator: + obj.db.creator_id = creator.id except Exception as exc: errors.append("An error occurred while creating this '%s' object." % key) diff --git a/evennia/comms/managers.py b/evennia/comms/managers.py index 9b950ec129..8f685f4190 100644 --- a/evennia/comms/managers.py +++ b/evennia/comms/managers.py @@ -14,6 +14,7 @@ _GA = object.__getattribute__ _AccountDB = None _ObjectDB = None _ChannelDB = None +_ScriptDB = None _SESSIONS = None # error class @@ -54,6 +55,8 @@ def identify_object(inp): return inp, "object" elif clsname == "ChannelDB": return inp, "channel" + elif clsname == "ScriptDB": + return inp, "script" if isinstance(inp, str): return inp, "string" elif dbref(inp): @@ -103,6 +106,14 @@ def to_object(inp, objtype="account"): return _ChannelDB.objects.get(id=obj) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) raise CommError() + elif objtype == "script": + if typ == "string": + return _ScriptDB.objects.get(db_key__iexact=obj) + if typ == "dbref": + return _ScriptDB.objects.get(id=obj) + logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) + raise CommError() + # an unknown return None @@ -158,48 +169,30 @@ class MsgManager(TypedObjectManager): except Exception: return None - def get_messages_by_sender(self, sender, exclude_channel_messages=False): + def get_messages_by_sender(self, sender): """ Get all messages sent by one entity - this could be either a account or an object Args: sender (Account or Object): The sender of the message. - exclude_channel_messages (bool, optional): Only return messages - not aimed at a channel (that is, private tells for example) Returns: - messages (list): List of matching messages + QuerySet: Matching messages. Raises: CommError: For incorrect sender types. """ obj, typ = identify_object(sender) - if exclude_channel_messages: - # explicitly exclude channel recipients - if typ == "account": - return list( - self.filter(db_sender_accounts=obj, db_receivers_channels__isnull=True).exclude( - db_hide_from_accounts=obj - ) - ) - elif typ == "object": - return list( - self.filter(db_sender_objects=obj, db_receivers_channels__isnull=True).exclude( - db_hide_from_objects=obj - ) - ) - else: - raise CommError + if typ == "account": + return self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj) + elif typ == "object": + return self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj) + elif typ == "script": + return self.filter(db_sender_scripts=obj) else: - # get everything, channel or not - if typ == "account": - return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj)) - elif typ == "object": - return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj)) - else: - raise CommError + raise CommError def get_messages_by_receiver(self, recipient): """ @@ -209,7 +202,7 @@ class MsgManager(TypedObjectManager): recipient (Object, Account or Channel): The recipient of the messages to search for. Returns: - messages (list): Matching messages. + Queryset: Matching messages. Raises: CommError: If the `recipient` is not of a valid type. @@ -217,26 +210,14 @@ class MsgManager(TypedObjectManager): """ obj, typ = identify_object(recipient) if typ == "account": - return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)) + return self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj) elif typ == "object": - return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)) - elif typ == "channel": - return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj)) + return self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj) + elif typ == 'script': + return self.filter(db_receivers_scripts=obj) else: raise CommError - def get_messages_by_channel(self, channel): - """ - Get all persistent messages sent to one channel. - - Args: - channel (Channel): The channel to find messages for. - - Returns: - messages (list): Persistent Msg objects saved for this channel. - - """ - return self.filter(db_receivers_channels=channel).exclude(db_hide_from_channels=channel) def search_message(self, sender=None, receiver=None, freetext=None, dbref=None): """ @@ -244,7 +225,7 @@ class MsgManager(TypedObjectManager): one of the arguments must be given to do a search. Args: - sender (Object or Account, optional): Get messages sent by a particular account or object + sender (Object, Account or Script, optional): Get messages sent by a particular sender. receiver (Object, Account or Channel, optional): Get messages received by a certain account,object or channel freetext (str): Search for a text string in a message. NOTE: @@ -255,14 +236,12 @@ class MsgManager(TypedObjectManager): always gives only one match. Returns: - messages (list or Msg): A list of message matches or a single match if `dbref` was given. + Queryset: Message matches. """ # unique msg id if dbref: - msg = self.objects.filter(id=dbref) - if msg: - return msg[0] + return self.objects.filter(id=dbref) # We use Q objects to gradually build up the query - this way we only # need to do one database lookup at the end rather than gradually @@ -275,20 +254,23 @@ class MsgManager(TypedObjectManager): sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender) elif styp == "object": sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender) + elif styp == 'script': + sender_restrict = Q(db_sender_scripts=sender) else: sender_restrict = Q() # filter by receiver receiver, rtyp = identify_object(receiver) if rtyp == "account": - receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q( - db_hide_from_accounts=receiver - ) + receiver_restrict = ( + Q(db_receivers_accounts=receiver) & ~Q(db_hide_from_accounts=receiver )) elif rtyp == "object": receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver) + elif rtyp == 'script': + receiver_restrict = Q(db_receivers_scripts=receiver) elif rtyp == "channel": - receiver_restrict = Q(db_receivers_channels=receiver) & ~Q( - db_hide_from_channels=receiver - ) + raise DeprecationWarning( + "Msg.objects.search don't accept channel recipients since " + "Channels no longer accepts Msg objects.") else: receiver_restrict = Q() # filter by full text @@ -297,7 +279,7 @@ class MsgManager(TypedObjectManager): else: fulltext_restrict = Q() # execute the query - return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict)) + return self.filter(sender_restrict & receiver_restrict & fulltext_restrict) # back-compatibility alias message_search = search_message diff --git a/evennia/comms/models.py b/evennia/comms/models.py index 85f5a89581..267d1d8592 100644 --- a/evennia/comms/models.py +++ b/evennia/comms/models.py @@ -157,6 +157,7 @@ class Msg(SharedMemoryModel): db_hide_from_objects = models.ManyToManyField( "objects.ObjectDB", related_name="hide_from_objects_set", blank=True ) + # NOTE: deprecated in 1.0. Not used for channels anymore db_hide_from_channels = models.ManyToManyField( "ChannelDB", related_name="hide_from_channels_set", blank=True ) @@ -263,9 +264,8 @@ class Msg(SharedMemoryModel): elif clsname == "ScriptDB": self.db_sender_accounts.remove(sender) - # receivers property - # @property - def __receivers_get(self): + @property + def receivers(self): """ Getter. Allows for value = self.receivers. Returns four lists of receivers: accounts, objects, scripts and channels. @@ -277,8 +277,8 @@ class Msg(SharedMemoryModel): + list(self.db_receivers_channels.all()) ) - # @receivers.setter - def __receivers_set(self, receivers): + @receivers.setter + def receivers(self, receivers): """ Setter. Allows for self.receivers = value. This appends a new receiver to the message. @@ -298,8 +298,8 @@ class Msg(SharedMemoryModel): elif clsname == "ChannelDB": self.db_receivers_channels.add(receiver) - # @receivers.deleter - def __receivers_del(self): + @receivers.deleter + def receivers(self): "Deleter. Clears all receivers" self.db_receivers_accounts.clear() self.db_receivers_objects.clear() @@ -307,7 +307,6 @@ class Msg(SharedMemoryModel): self.db_receivers_channels.clear() self.save() - receivers = property(__receivers_get, __receivers_set, __receivers_del) def remove_receiver(self, receivers): """ diff --git a/evennia/utils/create.py b/evennia/utils/create.py index 4abea4e81c..44e99fe78e 100644 --- a/evennia/utils/create.py +++ b/evennia/utils/create.py @@ -360,22 +360,21 @@ help_entry = create_help_entry def create_message( - senderobj, message, channels=None, receivers=None, locks=None, tags=None, header=None -): + senderobj, message, receivers=None, locks=None, tags=None, + header=None, **kwargs): """ Create a new communication Msg. Msgs represent a unit of database-persistent communication between entites. Args: - senderobj (Object or Account): The entity sending the Msg. + senderobj (Object, Account, Script, str or list): The entity (or + entities) sending the Msg. If a `str`, this is the id-string + for an external sender type. message (str): Text with the message. Eventual headers, titles etc should all be included in this text string. Formatting will be retained. - channels (Channel, key or list): A channel or a list of channels to - send to. The channels may be actual channel objects or their - unique key strings. - receivers (Object, Account, str or list): An Account/Object to send - to, or a list of them. May be Account objects or accountnames. + receivers (Object, Account or list): An Account/Object to send + to, or a list of them. locks (str): Lock definition string. tags (list): A list of tags or tuples `(tag, category)`. header (str): Mime-type or other optional information for the message @@ -387,6 +386,12 @@ def create_message( limit this as desired. """ + if 'channels' in kwargs: + raise DeprecationWarning( + "create_message() does not accept 'channel' kwarg anymore " + "- channels no longer accept Msg objects." + ) + global _Msg if not _Msg: from evennia.comms.models import Msg as _Msg @@ -398,8 +403,6 @@ def create_message( for sender in make_iter(senderobj): new_message.senders = sender new_message.header = header - for channel in make_iter(channels): - new_message.channels = channel for receiver in make_iter(receivers): new_message.receivers = receiver if locks: