Source code for evennia.contrib.base_systems.ingame_reports.reports

"""
In-Game Reporting System

This contrib provides an in-game reporting system, with player-facing commands and a staff
management interface.

# Installation

To install, just add the provided cmdset to your default AccountCmdSet:

    # in commands/default_cmdset.py

    from evennia.contrib.base_systems.ingame_reports import ReportsCmdSet

    class AccountCmdSet(default_cmds.AccountCmdSet):
        # ...

        def at_cmdset_creation(self):
            # ...
            self.add(ReportsCmdSet)

# Features

The contrib provides three commands by default and their associated report types: `CmdBug`, `CmdIdea`,
and `CmdReport` (which is for reporting other players).
            
The `ReportCmdBase` class holds most of the functionality for creating new reports, providing a
convenient parent class for adding your own categories of reports.

The contrib can be further configured through two settings, `INGAME_REPORT_TYPES` and `INGAME_REPORT_STATUS_TAGS`

"""

from django.conf import settings

from evennia import CmdSet
from evennia.utils import create, evmenu, logger, search
from evennia.utils.utils import class_from_module, datetime_format, is_iter, iter_to_str
from evennia.commands.default.muxcommand import MuxCommand
from evennia.comms.models import Msg

from . import menu

_DEFAULT_COMMAND_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)

# the default report types
_REPORT_TYPES = ("bugs", "ideas", "players")
if hasattr(settings, "INGAME_REPORT_TYPES"):
    if is_iter(settings.INGAME_REPORT_TYPES):
        _REPORT_TYPES = settings.INGAME_REPORT_TYPES
    else:
        logger.log_warn(
            "The 'INGAME_REPORT_TYPES' setting must be an iterable of strings; falling back to defaults."
        )


def _get_report_hub(report_type):
    """
    A helper function to retrieve the global script which acts as the hub for a given report type.

    Args:
        report_type (str):  The category of reports to retrieve the script for.

    Returns:
        Script or None:  The global script, or None if it couldn't be retrieved or created

    Note: If no matching valid script exists, this function will attempt to create it.
    """
    hub_key = f"{report_type}_reports"
    # NOTE: due to a regression in GLOBAL_SCRIPTS, we use search_script instead of the container
    if not (hub := search.search_script(hub_key)):
        hub = create.create_script(key=hub_key)
    return hub or None


[docs]class CmdManageReports(_DEFAULT_COMMAND_CLASS): """ manage the various reports Usage: manage [report type] Available report types: bugs ideas players Initializes a menu for reviewing and changing the status of current reports. """ key = "manage reports" aliases = tuple(f"manage {report_type}" for report_type in _REPORT_TYPES) locks = "cmd:pperm(Admin)"
[docs] def get_help(self): """Returns a help string containing the configured available report types""" report_types = iter_to_str("\n ".join(_REPORT_TYPES)) helptext = f"""\ manage the various reports Usage: manage [report type] Available report types: {report_types} Initializes a menu for reviewing and changing the status of current reports. """ return helptext
[docs] def func(self): report_type = self.cmdstring.split()[-1] if report_type == "reports": report_type = "players" if report_type not in _REPORT_TYPES: self.msg(f"'{report_type}' is not a valid report category.") return # remove the trailing s, just so everything reads nicer report_type = report_type[:-1] hub = _get_report_hub(report_type) if not hub: self.msg("You cannot manage that.") evmenu.EvMenu( self.account, menu, startnode="menunode_list_reports", hub=hub, persistent=True )
[docs]class ReportCmdBase(_DEFAULT_COMMAND_CLASS): """ A parent class for creating report commands. This help text may be displayed if your command's help text is not properly configured. """ help_category = "reports" # defines what locks the reports generated by this command will have set report_locks = "read:pperm(Admin)" # determines if the report can be filed without a target require_target = False # the message sent to the reporter after the report has been created success_msg = "Your report has been filed." # the report type for this command, if different from the key report_type = None
[docs] def at_pre_cmd(self): """validate that the needed hub script exists - if not, cancel the command""" hub = _get_report_hub(self.report_type or self.key) if not hub: # a return value of True from `at_pre_cmd` cancels the command return True self.hub = hub return super().at_pre_cmd()
[docs] def parse(self): """ Parse the target and message out of the arguments. Override if you want different syntax, but make sure to assign `report_message` and `target_str`. """ # do the base MuxCommand parsing first super().parse() # split out the report message and target strings if self.rhs: self.report_message = self.rhs self.target_str = self.lhs else: self.report_message = self.lhs self.target_str = ""
[docs] def create_report(self, *args, **kwargs): """ Creates the report. By default, this creates a Msg with any provided args and kwargs. Returns: success (bool) - True if the report was created successfully, or False if there was an issue. """ return create.create_message(*args, **kwargs)
[docs] def func(self): hub = self.hub if not self.args: self.msg("You must provide a message.") return target = None if self.target_str: target = self.target_search(self.target_str) if not target: return elif self.require_target: self.msg("You must include a target.") return receivers = [hub] if target: receivers.append(target) if self.create_report( self.account, self.report_message, receivers=receivers, locks=self.report_locks, tags=["report"] ): # the report Msg was successfully created self.msg(self.success_msg) else: # something went wrong self.msg( "Something went wrong creating your report. Please try again later or contact staff directly." )
# The commands below are the usable reporting commands
[docs]class CmdBug(ReportCmdBase): """ file a bug Usage: bug [<target> =] <message> Note: If a specific object, location or character is bugged, please target it for the report. Examples: bug hammer = This doesn't work as a crafting tool but it should bug every time I go through a door I get the message twice """ key = "bug" report_locks = "read:pperm(Developer)"
[docs]class CmdReport(ReportCmdBase): """ report a player Usage: report <player> = <message> All player reports will be reviewed. """ key = "report" report_type = "player" require_target = True account_caller = True
[docs]class CmdIdea(ReportCmdBase): """ submit a suggestion Usage: ideas idea <message> Example: idea wouldn't it be cool if we had horses we could ride """ key = "idea" aliases = ("ideas",) report_locks = "read:pperm(Builder)" success_msg = "Thank you for your suggestion!"
[docs] def func(self): # we add an extra feature to this command, allowing you to see all your submitted ideas if self.cmdstring == "ideas": # list your ideas if ( ideas := Msg.objects.search_message(sender=self.account, receiver=self.hub) .order_by("-db_date_created") .exclude(db_tags__db_key="closed") ): # todo: use a paginated menu self.msg( "Ideas you've submitted:\n " + "\n ".join( f"|w{item.message}|n (submitted {datetime_format(item.date_created)})" for item in ideas ) ) else: self.msg("You have no open suggestions.") return # proceed to do the normal report-command functionality super().func()
[docs]class ReportsCmdSet(CmdSet): key = "Reports CmdSet"
[docs] def at_cmdset_creation(self): super().at_cmdset_creation() if "bugs" in _REPORT_TYPES: self.add(CmdBug) if "ideas" in _REPORT_TYPES: self.add(CmdIdea) if "players" in _REPORT_TYPES: self.add(CmdReport) self.add(CmdManageReports)