Note

You are reading an old version of the Evennia documentation. The latest version is here

.

# Source code for evennia.contrib.game_systems.mail.mail

    """
    In-Game Mail system

    Evennia Contribution - grungies1138 2016

    A simple Brandymail style @mail system that uses the Msg class from Evennia
    Core. It has two Commands, both of which can be used on their own:

       - CmdMail - this should sit on the Account cmdset and makes the `mail` command
        available both IC and OOC. Mails will always go to Accounts (other players).
       - CmdMailCharacter - this should sit on the Character cmdset and makes the `mail`
        command ONLY available when puppeting a character. Mails will be sent to other
        Characters only and will not be available when OOC.
       - If adding *both* commands to their respective cmdsets, you'll get two separate
        IC and OOC mailing systems, with different lists of mail for IC and OOC modes.

    Installation:

    Install one or both of the following (see above):

    - CmdMail (IC + OOC mail, sent between players)

        # mygame/commands/default_cmds.py

        from evennia.contrib.game_systems import mail

        # in AccountCmdSet.at_cmdset_creation:
            self.add(mail.CmdMail())

    - CmdMailCharacter (optional, IC only mail, sent between characters)

        # mygame/commands/default_cmds.py

        from evennia.contrib.game_systems import mail

        # in CharacterCmdSet.at_cmdset_creation:
            self.add(mail.CmdMailCharacter())

    Once installed, use `help mail` in game for help with the mail command. Use
    ic/ooc to switch in and out of IC/OOC modes.

    """

    import re

    from evennia import AccountDB, ObjectDB, default_cmds
    from evennia.comms.models import Msg
    from evennia.utils import create, datetime_format, evtable, inherits_from, make_iter

    _HEAD_CHAR = "|015-|n"
    _SUB_HEAD_CHAR = "-"
    _WIDTH = 78

    class CmdMail(default_cmds.MuxAccountCommand):
        """
        Communicate with others by sending mail.

        Usage:
          @mail       - Displays all the mail an account has in their mailbox
          @mail <#>   - Displays a specific message
          @mail <accounts>=<subject>/<message>
                  - Sends a message to the comma separated list of accounts.
          @mail/delete <#> - Deletes a specific message
          @mail/forward <account list>=<#>[/<Message>]
                  - Forwards an existing message to the specified list of accounts,
                    original message is delivered with optional Message prepended.
          @mail/reply <#>=<message>
                  - Replies to a message #. Prepends message to the original
                    message text.
        Switches:
          delete  - deletes a message
          forward - forward a received message to another object with an optional message attached.
          reply   - Replies to a received message, appending the original message to the bottom.
        Examples:
          @mail 2
          @mail Griatch=New mail/Hey man, I am sending you a message!
          @mail/delete 6
          @mail/forward feend78 Griatch=4/You guys should read this.
          @mail/reply 9=Thanks for the info!

        """

        key = "@mail"
        aliases = ["mail"]
        lock = "cmd:all()"
        help_category = "General"

        def parse(self):
            """
            Add convenience check to know if caller is an Account or not since this cmd
            will be able to add to either Object- or Account level.

            """
            super().parse()
            self.caller_is_account = bool(
                inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
            )

        def search_targets(self, namelist):
            """
            Search a list of targets of the same type as caller.

            Args:
                caller (Object or Account): The type of object to search.
                namelist (list): List of strings for objects to search for.

            Returns:
                targetlist (Queryset): Any target matches.

            """
            nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
            if self.caller_is_account:
                matches = AccountDB.objects.filter(username__iregex=nameregex)
            else:
                matches = ObjectDB.objects.filter(db_key__iregex=nameregex)
            return matches

        def get_all_mail(self):
            """
            Returns a list of all the messages where the caller is a recipient. These
                are all messages tagged with tags of the `mail` category.

            Returns:
                messages (QuerySet): Matching Msg objects.

            """
            if self.caller_is_account:
                return Msg.objects.get_by_tag(category="mail").filter(db_receivers_accounts=self.caller)
            else:
                return Msg.objects.get_by_tag(category="mail").filter(db_receivers_objects=self.caller)

        def send_mail(self, recipients, subject, message, caller):
            """
            Function for sending new mail.  Also useful for sending notifications
            from objects or systems.

            Args:
                recipients (list): list of Account or Character objects to receive
                    the newly created mails.
                subject (str): The header or subject of the message to be delivered.
                message (str): The body of the message being sent.
                caller (obj): The object (or Account or Character) that is sending the message.

            """
            for recipient in recipients:
                recipient.msg("You have received a new @mail from %s" % caller)
                new_message = create.create_message(
                    self.caller, message, receivers=recipient, header=subject
                )
                new_message.tags.add("new", category="mail")

            if recipients:
                caller.msg("You sent your message.")
                return
            else:
                caller.msg("No valid target(s) found. Cannot send message.")
                return

        def func(self):
            """
            Do the main command functionality
            """

            subject = ""
            body = ""

            if self.switches or self.args:
                if "delete" in self.switches or "del" in self.switches:
                    try:
                        if not self.lhs:
                            self.caller.msg("No Message ID given. Unable to delete.")
                            return
                        else:
                            all_mail = self.get_all_mail()
                            mind_max = max(0, all_mail.count() - 1)
                            mind = max(0, min(mind_max, int(self.lhs) - 1))
                            if all_mail[mind]:
                                mail = all_mail[mind]
                                question = "Delete message {} ({}) [Y]/N?".format(mind + 1, mail.header)
                                ret = yield (question)
                                # handle not ret, it will be None during unit testing
                                if not ret or ret.strip().upper() not in ("N", "No"):
                                    all_mail[mind].delete()
                                    self.caller.msg("Message %s deleted" % (mind + 1,))
                                else:
                                    self.caller.msg("Message not deleted.")
                            else:
                                raise IndexError
                    except IndexError:
                        self.caller.msg("That message does not exist.")
                    except ValueError:
                        self.caller.msg("Usage: @mail/delete <message ID>")
                elif "forward" in self.switches or "fwd" in self.switches:
                    try:
                        if not self.rhs:
                            self.caller.msg(
                                "Cannot forward a message without a target list. " "Please try again."
                            )
                            return
                        elif not self.lhs:
                            self.caller.msg("You must define a message to forward.")
                            return
                        else:
                            all_mail = self.get_all_mail()
                            mind_max = max(0, all_mail.count() - 1)
                            if "/" in self.rhs:
                                message_number, message = self.rhs.split("/", 1)
                                mind = max(0, min(mind_max, int(message_number) - 1))

                                if all_mail[mind]:
                                    old_message = all_mail[mind]

                                    self.send_mail(
                                        self.search_targets(self.lhslist),
                                        "FWD: " + old_message.header,
                                        message
                                        + "\n---- Original Message ----\n"
                                        + old_message.message,
                                        self.caller,
                                    )
                                    self.caller.msg("Message forwarded.")
                                else:
                                    raise IndexError
                            else:
                                mind = max(0, min(mind_max, int(self.rhs) - 1))
                                if all_mail[mind]:
                                    old_message = all_mail[mind]
                                    self.send_mail(
                                        self.search_targets(self.lhslist),
                                        "FWD: " + old_message.header,
                                        "\n---- Original Message ----\n" + old_message.message,
                                        self.caller,
                                    )
                                    self.caller.msg("Message forwarded.")
                                    old_message.tags.remove("new", category="mail")
                                    old_message.tags.add("fwd", category="mail")
                                else:
                                    raise IndexError
                    except IndexError:
                        self.caller.msg("Message does not exist.")
                    except ValueError:
                        self.caller.msg("Usage: @mail/forward <account list>=<#>[/<Message>]")
                elif "reply" in self.switches or "rep" in self.switches:
                    try:
                        if not self.rhs:
                            self.caller.msg("You must define a message to reply to.")
                            return
                        elif not self.lhs:
                            self.caller.msg("You must supply a reply message")
                            return
                        else:
                            all_mail = self.get_all_mail()
                            mind_max = max(0, all_mail.count() - 1)
                            mind = max(0, min(mind_max, int(self.lhs) - 1))
                            if all_mail[mind]:
                                old_message = all_mail[mind]
                                self.send_mail(
                                    old_message.senders,
                                    "RE: " + old_message.header,
                                    self.rhs + "\n---- Original Message ----\n" + old_message.message,
                                    self.caller,
                                )
                                old_message.tags.remove("new", category="mail")
                                old_message.tags.add("-", category="mail")
                                return
                            else:
                                raise IndexError
                    except IndexError:
                        self.caller.msg("Message does not exist.")
                    except ValueError:
                        self.caller.msg("Usage: @mail/reply <#>=<message>")
                else:
                    # normal send
                    if self.rhs:
                        if "/" in self.rhs:
                            subject, body = self.rhs.split("/", 1)
                        else:
                            body = self.rhs
                        self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
                    else:
                        all_mail = self.get_all_mail()
                        mind_max = max(0, all_mail.count() - 1)
                        try:
                            mind = max(0, min(mind_max, int(self.lhs) - 1))
                            message = all_mail[mind]
                        except (ValueError, IndexError):
                            self.caller.msg("'%s' is not a valid mail id." % self.lhs)
                            return

                        messageForm = []
                        if message:
                            messageForm.append(_HEAD_CHAR * _WIDTH)
                            messageForm.append(
                                "|wFrom:|n %s" % (message.senders[0].get_display_name(self.caller))
                            )
                            # note that we cannot use %-d format here since Windows does not support it
                            day = message.db_date_created.day
                            messageForm.append(
                                "|wSent:|n %s"
                                % message.db_date_created.strftime(f"%b {day}, %Y - %H:%M:%S")
                            )
                            messageForm.append("|wSubject:|n %s" % message.header)
                            messageForm.append(_SUB_HEAD_CHAR * _WIDTH)
                            messageForm.append(message.message)
                            messageForm.append(_HEAD_CHAR * _WIDTH)
                        self.caller.msg("\n".join(messageForm))
                        message.tags.remove("new", category="mail")
                        message.tags.add("-", category="mail")

            else:
                # list messages
                messages = self.get_all_mail()

                if messages:
                    table = evtable.EvTable(
                        "|wID|n",
                        "|wFrom|n",
                        "|wSubject|n",
                        "|wArrived|n",
                        "",
                        table=None,
                        border="header",
                        header_line_char=_SUB_HEAD_CHAR,
                        width=_WIDTH,
                    )
                    index = 1
                    for message in messages:
                        status = str(message.db_tags.last().db_key.upper())
                        if status == "NEW":
                            status = "|gNEW|n"

                        table.add_row(
                            index,
                            message.senders[0].get_display_name(self.caller),
                            message.header,
                            datetime_format(message.db_date_created),
                            status,
                        )
                        index += 1

                    table.reformat_column(0, width=6)
                    table.reformat_column(1, width=18)
                    table.reformat_column(2, width=34)
                    table.reformat_column(3, width=13)
                    table.reformat_column(4, width=7)

                    self.caller.msg(_HEAD_CHAR * _WIDTH)
                    self.caller.msg(str(table))
                    self.caller.msg(_HEAD_CHAR * _WIDTH)
                else:
                    self.caller.msg("There are no messages in your inbox.")

    # character - level version of the command

    class CmdMailCharacter(CmdMail):
        account_caller = False

Note

You are reading an old version of the Evennia documentation. The latest version is here

.