From 69862742223c75276766ad7af5d87c17ac533342 Mon Sep 17 00:00:00 2001 From: Cal Date: Sat, 4 May 2024 17:49:46 -0600 Subject: [PATCH 1/7] reports contrib --- .../base_systems/ingame_reports/README.md | 123 +++++++++ .../base_systems/ingame_reports/__init__.py | 1 + .../base_systems/ingame_reports/menu.py | 134 ++++++++++ .../base_systems/ingame_reports/reports.py | 251 ++++++++++++++++++ .../base_systems/ingame_reports/tests.py | 1 + 5 files changed, 510 insertions(+) create mode 100644 evennia/contrib/base_systems/ingame_reports/README.md create mode 100644 evennia/contrib/base_systems/ingame_reports/__init__.py create mode 100644 evennia/contrib/base_systems/ingame_reports/menu.py create mode 100644 evennia/contrib/base_systems/ingame_reports/reports.py create mode 100644 evennia/contrib/base_systems/ingame_reports/tests.py diff --git a/evennia/contrib/base_systems/ingame_reports/README.md b/evennia/contrib/base_systems/ingame_reports/README.md new file mode 100644 index 0000000000..70f876e582 --- /dev/null +++ b/evennia/contrib/base_systems/ingame_reports/README.md @@ -0,0 +1,123 @@ +# In-Game Reporting System + +This contrib provides an in-game reports system, handling bug reports, player reports, and idea submissions by default. It also supports adding your own types of reports, or removing any of the default report types. + +Each type of report has its own command for submitting new reports, and an admin command is also provided for managing the reports through a menu. + +## Installation + +To install the reports contrib, just add the provided cmdset to your default AccountCmdSet: + +```python +# 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) +``` + +The contrib also has two optional settings: `INGAME_REPORT_TYPES` and `INGAME_REPORT_STATUS_TAGS`. + +The `INGAME_REPORT_TYPES` setting is covered in detail in the section "Adding new types of reports". + +The `INGAME_REPORT_STATUS_TAGS` setting is covered in the section "Managing reports". + +## Usage + +By default, the following report types are available: + +* Bugs: Report bugs encountered during gameplay. +* Ideas: Submit suggestions for game improvement. +* Players: Report inappropriate player behavior. + +Players can submit new reports through the command for each report type, and staff are given access to a report-management command and menu. + +### Submitting reports + +Players can submit reports using the following commands: + +* `bug` - Files a bug report. An optional target can be included, making it easier for devs/builders to track down issues. +* `report` - Reports a player for inappropriate or rule-breaking behavior. *Requires* a target to be provided - it searches among accounts by default. +* `idea` - Submits a general suggestion, with no target. It also has an alias of `ideas` which allows you to view all of your submitted ideas. + +### Managing reports + +The `manage reports` command allows staff to review and manage the various types of reports. It dynamically aliases itself to include whatever types of reports you've configured for your game - by default, this means it makes `manage bugs`, `manage players`, and `manage ideas` available along with the default `manage reports`. + +Aside from reading over existing reports, the menu allows you to change the status of any given report. By default, the contrib includes two different status tags: `in progress` and `closed`. + +> Note: A report is created with no status tags, which is considered "open" + +If you want a different set of statuses for your reports, you can define the `INGAME_REPORT_STATUS_TAGS` to your list of statuses. + +**Example** + +```python +# in server/conf/settings.py + +# this will allow for the statuses of 'in progress', 'rejected', and 'completed', without the contrib-default of 'closed' +INGAME_REPORT_STATUS_TAGS = ('in progress', 'rejected', 'completed') +``` + +### Adding new types of reports + +The contrib is designed to make adding new types of reports to the system as simple as possible, requiring only two steps. + +#### Update your settings + +The contrib optionally references `INGAME_REPORT_TYPES` in your settings.py to see which types of reports can be managed. If you want to change the available report types, you'll need to define this setting. + +```python +# in server/conf/settings.py + +# this will include the contrib's report types as well as a custom 'complaint' report type +INGAME_REPORT_TYPES = ('bugs', 'ideas', 'players', 'complaints') +``` + +You can also use this setting to remove any of the contrib's report types - the contrib will respect this setting when building its cmdset with no additional steps. + +```python +# in server/conf/settings.py + +# this redefines the setting to not include 'ideas', so the ideas command and reports won't be available +INGAME_REPORT_TYPES = ('bugs', 'players') +``` + +#### Create a new ReportCmd + +`ReportCmdBase` is a parent command class which comes with the main functionality for submitting reports. Creating a new reporting command is as simple as inheriting from this class and defining a couple of class attributes. + +* `key` - This is the same as for any other command, setting the command's usable key. It also acts as the report type if that isn't explicitly set. +* `report_type` - The type of report this command is for (e.g. `player`). You only need to set it if you want a different string from the key. +* `report_locks` - The locks you want applied to the created reports. Defaults to `"read:pperm(Admin)"` +* `success_msg` - The string which is sent to players after submitting a report of this type. Defaults to `"Your report has been filed."` +* `require_target`: Set to `True` if your report requires a target (e.g. player reports). + +> Note: The contrib's own commands - `CmdBug`, `CmdIdea`, and `CmdReport` - are implemented the same way, so you can review them as examples. + +Example: + +```python +from evennia.contrib.base_systems.ingame_reports.reports import ReportCmdBase + +class CmdCustomReport(ReportCmdBase): + """ + file a custom report + + Usage: + customreport + + This is a custom report type. + """ + + key = "customreport" + report_type = "custom" + success_message = "You have successfully filed a custom report." +``` + +Add this new command to your default cmdset to enable filing your new report type. \ No newline at end of file diff --git a/evennia/contrib/base_systems/ingame_reports/__init__.py b/evennia/contrib/base_systems/ingame_reports/__init__.py new file mode 100644 index 0000000000..d4ad3e32aa --- /dev/null +++ b/evennia/contrib/base_systems/ingame_reports/__init__.py @@ -0,0 +1 @@ +from .reports import ReportsCmdSet diff --git a/evennia/contrib/base_systems/ingame_reports/menu.py b/evennia/contrib/base_systems/ingame_reports/menu.py new file mode 100644 index 0000000000..a7a4c98a2e --- /dev/null +++ b/evennia/contrib/base_systems/ingame_reports/menu.py @@ -0,0 +1,134 @@ +""" +The report-management menu module. +""" + +from django.conf import settings + +from evennia.comms.models import Msg +from evennia.utils import logger +from evennia.utils.utils import crop, datetime_format, is_iter, iter_to_str + +# the number of reports displayed on each page +_REPORTS_PER_PAGE = 10 + +_REPORT_STATUS_TAGS = ("closed", "in progress") +if hasattr(settings, "INGAME_REPORT_STATUS_TAGS"): + if is_iter(settings.INGAME_REPORT_STATUS_TAGS): + _REPORT_STATUS_TAGS = settings.INGAME_REPORT_STATUS_TAGS + else: + logger.log_warn( + "The 'INGAME_REPORT_STATUS_TAGS' setting must be an iterable of strings; falling back to defaults." + ) + + +def menunode_list_reports(caller, raw_string, **kwargs): + """Paginates and lists out reports for the provided hub""" + hub = caller.ndb._evmenu.hub + + page = kwargs.get("page", 0) + start = page * _REPORTS_PER_PAGE + end = start + _REPORTS_PER_PAGE + report_slice = report_list[start:end] + hub_name = " ".join(hub.key.split("_")).title() + text = f"Managing {hub_name}" + + if not (report_list := getattr(caller.ndb._evmenu, "report_list", None)): + report_list = Msg.objects.search_message(receiver=hub).order_by("db_date_created") + caller.ndb._evmenu.report_list = report_list + # allow the menu to filter print-outs by status + if kwargs.get("status"): + new_report_list = report_list.filter(db_tags__db_key=kwargs["status"]) + # we don't filter reports if there are no reports under that filter + if not new_report_list: + text = f"(No {kwargs['status']} reports)\n{text}" + else: + report_list = new_report_list + text = f"Managing {kwargs['status']} {hub_name}" + else: + report_list = report_list.exclude(db_tags__db_key="closed") + + # filter by lock access + report_list = [msg for msg in report_list if msg.access(caller, "read")] + + # this will catch both no reports filed and no permissions + if not report_list: + return "There is nothing there for you to manage.", {} + + options = [ + { + "desc": f"{datetime_format(report.date_created)} - {crop(report.message, 50)}", + "goto": ("menunode_manage_report", {"report": report}), + } + for report in report_slice + ] + options.append( + { + "key": ("|uF|nilter by status", "filter", "status", "f"), + "goto": "menunode_choose_filter", + } + ) + if start > 0: + options.append( + { + "key": (f"|uP|nrevious {_REPORTS_PER_PAGE}", "previous", "prev", "p"), + "goto": ( + "menunode_list_reports", + {"page": max(start - _REPORTS_PER_PAGE, 0) // _REPORTS_PER_PAGE}, + ), + } + ) + if end < len(report_list): + options.append( + { + "key": (f"|uN|next {_REPORTS_PER_PAGE}", "next", "n"), + "goto": ( + "menunode_list_reports", + {"page": (start + _REPORTS_PER_PAGE) // _REPORTS_PER_PAGE}, + ), + } + ) + return text, options + + +def menunode_choose_filter(caller, raw_string, **kwargs): + """apply or clear a status filter to the main report view""" + text = "View which reports?" + # options for all the possible statuses + options = [ + {"desc": status, "goto": ("menunode_list_reports", {"status": status})} + for status in _REPORT_STATUS_TAGS + ] + # no filter + options.append({"desc": "All open reports", "goto": "menunode_list_reports"}) + return text, options + + +def _report_toggle_tag(caller, raw_string, report, tag, **kwargs): + """goto callable to toggle a status tag on or off""" + if tag in report.tags.all(): + report.tags.remove(tag) + else: + report.tags.add(tag) + return ("menunode_manage_report", {"report": report}) + + +def menunode_manage_report(caller, raw_string, report, **kwargs): + """ + Read out the full report text and targets, and allow for changing the report's status. + """ + receivers = [r for r in report.receivers if r != caller.ndb._evmenu.hub] + text = f"""\ +{report.message} +{datetime_format(report.date_created)} by {iter_to_str(report.senders)}{' about '+iter_to_str(r.get_display_name(caller) for r in receivers) if receivers else ''} +{iter_to_str(report.tags.all())}""" + + options = [] + for tag in _REPORT_STATUS_TAGS: + options.append( + { + "desc": f"{'Unmark' if tag in report.tags.all() else 'Mark' } as {tag}", + "goto": (_report_toggle_tag, {"report": report, "tag": tag}), + } + ) + options.append({"desc": f"Manage another report", "goto": "menunode_list_reports"}) + return text, options diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py new file mode 100644 index 0000000000..2cbc0decfd --- /dev/null +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -0,0 +1,251 @@ +""" +In-Game Reports +""" + +# TODO: docstring + +from django.conf import settings + +from evennia import CmdSet +from evennia.utils import create, evmenu, logger, search +from evennia.utils.utils import datetime_format, is_iter, iter_to_str +from evennia.commands.default.muxcommand import MuxCommand +from evennia.comms.models import Msg + +from . import menu + +# TODO: use actual default command class +_DEFAULT_COMMAND_CLASS = MuxCommand + +# 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 + + +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)" + + 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 + + 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) + + +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." + report_type = None + + 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() + + def func(self): + hub = self.hub + if not self.args: + self.msg("You must provide a message.") + return + if self.rhs: + message = self.rhs + target = self.lhs + else: + message = self.lhs + target = None + + if target: + target = self.caller.search(target) + if not target: + return + elif self.require_target: + self.msg("You must include a target.") + return + + receivers = [hub] + if target: + receivers.append(target) + + if create.create_message( + self.account, 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 + + +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)" + + +class CmdReport(ReportCmdBase): + """ + report a player + + Usage: + report player = message + + All player reports will be reviewed. + """ + + key = "report" + report_type = "player" + require_target = True + + +class CmdIdea(ReportCmdBase): + """ + submit a suggestion + + Usage: + ideas + idea + + Example: + idea wouldn't it be cool if we had horses we could ride + """ + + key = "idea" + aliases = ("ideas",) + report_locks = "read:pperm(Builder)" + + 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() + + +class ReportsCmdSet(CmdSet): + key = "Reports CmdSet" + + 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) diff --git a/evennia/contrib/base_systems/ingame_reports/tests.py b/evennia/contrib/base_systems/ingame_reports/tests.py new file mode 100644 index 0000000000..777ab4dd18 --- /dev/null +++ b/evennia/contrib/base_systems/ingame_reports/tests.py @@ -0,0 +1 @@ +# TODO: write tests From ece8f4f1384a7f13436c137b048edf3d956fc9cc Mon Sep 17 00:00:00 2001 From: Cal Date: Sat, 4 May 2024 21:22:01 -0600 Subject: [PATCH 2/7] tests for reports contrib --- .../base_systems/ingame_reports/reports.py | 7 +- .../base_systems/ingame_reports/tests.py | 87 ++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index 2cbc0decfd..416546f24f 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -86,7 +86,7 @@ Initializes a menu for reviewing and changing the status of current reports. return helptext def func(self): - _, report_type = self.cmdstring.split()[-1] + report_type = self.cmdstring.split()[-1] if report_type == "reports": report_type = "players" if report_type not in _REPORT_TYPES: @@ -98,7 +98,9 @@ Initializes a menu for reviewing and changing the status of current reports. if not hub: self.msg("You cannot manage that.") - evmenu.EvMenu(self.account, menu, startnode="menunode_list_reports", hub=hub, persistent=True) + evmenu.EvMenu( + self.account, menu, startnode="menunode_list_reports", hub=hub, persistent=True + ) class ReportCmdBase(_DEFAULT_COMMAND_CLASS): @@ -212,6 +214,7 @@ class CmdIdea(ReportCmdBase): key = "idea" aliases = ("ideas",) report_locks = "read:pperm(Builder)" + success_msg = "Thank you for your suggestion!" def func(self): # we add an extra feature to this command, allowing you to see all your submitted ideas diff --git a/evennia/contrib/base_systems/ingame_reports/tests.py b/evennia/contrib/base_systems/ingame_reports/tests.py index 777ab4dd18..4556c250a4 100644 --- a/evennia/contrib/base_systems/ingame_reports/tests.py +++ b/evennia/contrib/base_systems/ingame_reports/tests.py @@ -1 +1,86 @@ -# TODO: write tests +from unittest.mock import Mock, patch, MagicMock +from evennia.utils import create +from evennia.comms.models import TempMsg +from evennia.utils.test_resources import EvenniaCommandTest + +from . import menu, reports + + +class _MockQuerySet(list): + def order_by(self, *args, **kwargs): + return self + + def exclude(self, *args, **kwargs): + return self + + def filter(self, *args, **kwargs): + return self + + +def _mock_pre(cmdobj): + """helper to mock at_pre_cmd""" + cmdobj.hub = Mock() + + +class TestReportCommands(EvenniaCommandTest): + @patch.object(create, "create_message", new=MagicMock()) + def test_report_cmd_base(self): + """verify that the base command functionality works""" + cmd = reports.ReportCmdBase + + # avoid test side-effects + with patch.object(cmd, "at_pre_cmd", new=_mock_pre) as _: + # no arguments + self.call(cmd(), "", "You must provide a message.") + # arguments, no target, no target required + self.call(cmd(), "test", "Your report has been filed.") + # arguments, custom success message + custom_success = "custom success message" + cmd.success_msg = custom_success + self.call(cmd(), "test", custom_success) + # arguments, no target, target required + cmd.require_target = True + self.call(cmd(), "test", "You must include a target.") + + @patch.object(create, "create_message", new=MagicMock()) + @patch.object(reports, "datetime_format", return_value="now") + def test_ideas_list(self, mock_datetime_format): + cmd = reports.CmdIdea + + fake_ideas = _MockQuerySet([TempMsg(message=f"idea {i+1}") for i in range(3)]) + expected = """\ +Ideas you've submitted: + idea 1 (submitted now) + idea 2 (submitted now) + idea 3 (submitted now) +""" + + with patch.object(cmd, "at_pre_cmd", new=_mock_pre) as _: + # submitting an idea + self.call(cmd(), "", "You must provide a message.") + # arguments, no target, no target required + self.call(cmd(), "test", "Thank you for your suggestion!") + + # viewing your submitted ideas + with patch.object(reports.Msg.objects, "search_message", return_value=fake_ideas): + self.call(cmd(), "", cmdstring="ideas", msg=expected) + + @patch.object(reports.evmenu, "EvMenu") + def test_cmd_manage_reports(self, evmenu_mock): + cmd = reports.CmdManageReports + hub = Mock() + + with patch.object(reports, "_get_report_hub", return_value=hub) as _: + # invalid report type fails + self.call( + cmd(), "", cmdstring="manage custom", msg="'custom' is not a valid report category." + ) + # verify valid type triggers evmenu + self.call(cmd(), "", cmdstring="manage bugs") + evmenu_mock.assert_called_once_with( + self.account, + menu, + startnode="menunode_list_reports", + hub=hub, + persistent=True, + ) From 793ae05ece5ac5d0ccdc51bf6ca9061502491fb5 Mon Sep 17 00:00:00 2001 From: Cal Date: Sat, 4 May 2024 22:04:03 -0600 Subject: [PATCH 3/7] reports contrib documentation --- .../base_systems/ingame_reports/README.md | 2 +- .../base_systems/ingame_reports/reports.py | 51 +++++++++++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/evennia/contrib/base_systems/ingame_reports/README.md b/evennia/contrib/base_systems/ingame_reports/README.md index 70f876e582..0e608e86bd 100644 --- a/evennia/contrib/base_systems/ingame_reports/README.md +++ b/evennia/contrib/base_systems/ingame_reports/README.md @@ -96,7 +96,7 @@ INGAME_REPORT_TYPES = ('bugs', 'players') * `report_type` - The type of report this command is for (e.g. `player`). You only need to set it if you want a different string from the key. * `report_locks` - The locks you want applied to the created reports. Defaults to `"read:pperm(Admin)"` * `success_msg` - The string which is sent to players after submitting a report of this type. Defaults to `"Your report has been filed."` -* `require_target`: Set to `True` if your report requires a target (e.g. player reports). +* `require_target`: Set to `True` if your report type requires a target (e.g. player reports). > Note: The contrib's own commands - `CmdBug`, `CmdIdea`, and `CmdReport` - are implemented the same way, so you can review them as examples. diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index 416546f24f..4cd38b3902 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -1,21 +1,48 @@ """ -In-Game Reports -""" +In-Game Reporting System -# TODO: docstring +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 datetime_format, is_iter, iter_to_str +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 # TODO: use actual default command class -_DEFAULT_COMMAND_CLASS = MuxCommand +_DEFAULT_COMMAND_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) # the default report types _REPORT_TYPES = ("bugs", "ideas", "players") @@ -116,6 +143,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): 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 def at_pre_cmd(self): @@ -134,13 +162,13 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): return if self.rhs: message = self.rhs - target = self.lhs + target_str = self.lhs else: message = self.lhs - target = None + target_str = "" - if target: - target = self.caller.search(target) + if target_str: + target = self.caller.search(target_str) if not target: return elif self.require_target: @@ -171,7 +199,7 @@ class CmdBug(ReportCmdBase): file a bug Usage: - bug [target] = message + bug [ =] Note: If a specific object, location or character is bugged, please target it for the report. @@ -189,7 +217,7 @@ class CmdReport(ReportCmdBase): report a player Usage: - report player = message + report = All player reports will be reviewed. """ @@ -197,6 +225,7 @@ class CmdReport(ReportCmdBase): key = "report" report_type = "player" require_target = True + account_caller = True class CmdIdea(ReportCmdBase): From a598a2b0cc1a0e31b54e904405094f9966bdea95 Mon Sep 17 00:00:00 2001 From: Cal Date: Mon, 6 May 2024 10:07:21 -0600 Subject: [PATCH 4/7] fix error --- evennia/contrib/base_systems/ingame_reports/reports.py | 1 + 1 file changed, 1 insertion(+) diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index 4cd38b3902..61e0e265b3 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -167,6 +167,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): message = self.lhs target_str = "" + target = None if target_str: target = self.caller.search(target_str) if not target: From 92ea5fe8ef3d9745c2ba3d0eedbc5e1adb62c7c1 Mon Sep 17 00:00:00 2001 From: Cal Date: Fri, 28 Jun 2024 17:13:42 -0600 Subject: [PATCH 5/7] update README, break up report cmd func --- .../base_systems/ingame_reports/README.md | 15 +++++++---- .../base_systems/ingame_reports/reports.py | 27 ++++++++++++++++--- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/base_systems/ingame_reports/README.md b/evennia/contrib/base_systems/ingame_reports/README.md index 0e608e86bd..0fdbfab737 100644 --- a/evennia/contrib/base_systems/ingame_reports/README.md +++ b/evennia/contrib/base_systems/ingame_reports/README.md @@ -41,13 +41,15 @@ Players can submit new reports through the command for each report type, and sta Players can submit reports using the following commands: -* `bug` - Files a bug report. An optional target can be included, making it easier for devs/builders to track down issues. -* `report` - Reports a player for inappropriate or rule-breaking behavior. *Requires* a target to be provided - it searches among accounts by default. -* `idea` - Submits a general suggestion, with no target. It also has an alias of `ideas` which allows you to view all of your submitted ideas. +* `bug ` - Files a bug report. An optional target can be included - `bug = ` - making it easier for devs/builders to track down issues. +* `report = ` - Reports a player for inappropriate or rule-breaking behavior. *Requires* a target to be provided - it searches among accounts by default. +* `idea ` - Submits a general suggestion, with no target. It also has an alias of `ideas` which allows you to view all of your submitted ideas. ### Managing reports -The `manage reports` command allows staff to review and manage the various types of reports. It dynamically aliases itself to include whatever types of reports you've configured for your game - by default, this means it makes `manage bugs`, `manage players`, and `manage ideas` available along with the default `manage reports`. +The `manage reports` command allows staff to review and manage the various types of reports by launching a management menu. + +This command will dynamically add aliases to itself based on the types of reports available, with each command string launching a menu for that particular report type. The aliases are built on the pattern `manage s` - by default, this means it makes `manage bugs`, `manage players`, and `manage ideas` available along with the default `manage reports`, and that e.g. `manage bugs` will launch the management menu for `bug`-type reports. Aside from reading over existing reports, the menu allows you to change the status of any given report. By default, the contrib includes two different status tags: `in progress` and `closed`. @@ -66,7 +68,10 @@ INGAME_REPORT_STATUS_TAGS = ('in progress', 'rejected', 'completed') ### Adding new types of reports -The contrib is designed to make adding new types of reports to the system as simple as possible, requiring only two steps. +The contrib is designed to make adding new types of reports to the system as simple as possible, requiring only two steps: + +1. Update your settings file to include an `INGAME_REPORT_TYPES` setting. +2. Create and add a new `ReportCmd` to your command set. #### Update your settings diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index 61e0e265b3..85c8259ae0 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -41,7 +41,6 @@ from evennia.comms.models import Msg from . import menu -# TODO: use actual default command class _DEFAULT_COMMAND_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) # the default report types @@ -155,6 +154,28 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): self.hub = hub return super().at_pre_cmd() + def target_search(self, searchterm, **kwargs): + """ + Search for a target that matches the given search term. By default, does a normal search via the + caller - a local object search for a Character, or an account search for an Account. + + Args: + searchterm (str) - The string to search for + + Returns: + result (Object, Account, or None) - the result of the search + """ + return self.caller.search(searchterm) + + 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) + def func(self): hub = self.hub if not self.args: @@ -169,7 +190,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): target = None if target_str: - target = self.caller.search(target_str) + target = self.target_search(target_str) if not target: return elif self.require_target: @@ -180,7 +201,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): if target: receivers.append(target) - if create.create_message( + if self.create_report( self.account, message, receivers=receivers, locks=self.report_locks, tags=["report"] ): # the report Msg was successfully created From 610a068c9291450778ed8e07bcd7b9f522bf1509 Mon Sep 17 00:00:00 2001 From: Cal Date: Fri, 28 Jun 2024 17:19:22 -0600 Subject: [PATCH 6/7] move report message/target parsing into .parse --- .../base_systems/ingame_reports/reports.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index 85c8259ae0..f5fd5f0ade 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -154,6 +154,22 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): self.hub = hub return super().at_pre_cmd() + 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 = "" + def target_search(self, searchterm, **kwargs): """ Search for a target that matches the given search term. By default, does a normal search via the @@ -181,16 +197,10 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): if not self.args: self.msg("You must provide a message.") return - if self.rhs: - message = self.rhs - target_str = self.lhs - else: - message = self.lhs - target_str = "" target = None - if target_str: - target = self.target_search(target_str) + if self.target_str: + target = self.target_search(self.target_str) if not target: return elif self.require_target: @@ -202,7 +212,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): receivers.append(target) if self.create_report( - self.account, message, receivers=receivers, locks=self.report_locks, tags=["report"] + self.account, self.message, receivers=receivers, locks=self.report_locks, tags=["report"] ): # the report Msg was successfully created self.msg(self.success_msg) From bbad81344844f38fe5165716f479a2f90c1bc0d5 Mon Sep 17 00:00:00 2001 From: Cal Date: Fri, 28 Jun 2024 17:34:15 -0600 Subject: [PATCH 7/7] fix typo --- evennia/contrib/base_systems/ingame_reports/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/base_systems/ingame_reports/reports.py b/evennia/contrib/base_systems/ingame_reports/reports.py index f5fd5f0ade..62d470676a 100644 --- a/evennia/contrib/base_systems/ingame_reports/reports.py +++ b/evennia/contrib/base_systems/ingame_reports/reports.py @@ -212,7 +212,7 @@ class ReportCmdBase(_DEFAULT_COMMAND_CLASS): receivers.append(target) if self.create_report( - self.account, self.message, receivers=receivers, locks=self.report_locks, tags=["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)