Added new docs for Channels and Msg

This commit is contained in:
Griatch 2021-05-09 22:36:44 +02:00
parent 43651ac867
commit 7e2a446bda
9 changed files with 474 additions and 193 deletions

View file

@ -1,3 +1,308 @@
# Channels
TODO: Channels are covered in [Communications](./Communications) right now.
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_<channelname>.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.

View file

@ -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_<channelname>.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`.
- [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.

View file

@ -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.

View file

@ -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)

View file

@ -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}")

View file

@ -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)

View file

@ -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

View file

@ -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):
"""

View file

@ -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: