Skip to content

Add the exp_reports plugin and move the scenario onto it - #469

Open
bbassie wants to merge 1 commit into
explosivegaming:mainfrom
bbassie:feature/reports-plugin
Open

Add the exp_reports plugin and move the scenario onto it#469
bbassie wants to merge 1 commit into
explosivegaming:mainfrom
bbassie:feature/reports-plugin

Conversation

@bbassie

@bbassie bbassie commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Reports were the last legacy control module, kept in each instance's lua state. This replaces them with an exp_reports plugin along the lines you described: reports are immutable and hold the player, who reported them, the reason, the instance name and when it was made; the only operations are get, list, create and delete (ReportGetRequest, ReportListRequest with an optional player, ReportCreateRequest, ReportDeleteRequest), and ReportUpdatedEvent feeds the web ui. The controller keeps them in a SubscribableDatastore (database/exp_reports/reports.json), allows one report per reporter per player, and for web created reports takes the reporter from the connection's user rather than the request.

Nothing is stored in game. The lua module (modules/exp_reports) sends exp_reports:create|list|delete over ipc, the instance plugin asks the controller and hands the answer back with rcon (receive_created, receive_list, receive_deleted, receive_error), and the module prints it to whoever asked if they are still online. Answers are dropped when the instance is no longer running. Because the jail check needs the other reports, the create path also fetches the reports against the player and raises on_player_reported with report_count and by_player_names; report_jail, discord_alerts, the report commands and the player list button now go through the module, and the response strings moved into the module's own locale ([exp-reports], en/zh-CN/zh-TW).

The web ui gets a /reports page (permission exp_reports.report.list): a table with column search on player, reporter and reason (via useColumnSearch/useTableQueryState), an instance filter, time sort, delete per row behind exp_reports.report.delete, and a create form behind exp_reports.report.create which reports in the name of the web user.

Two optional controller config fields, exp_reports.discord_webhook_url and exp_reports.json_webhook_url, receive every new report: the Discord one as an embed with player, reporter, instance and reason, the JSON one as { "type": "report_created", "report": {...} }. Webhook failures are logged and never fail the report.

Tests (pnpm --filter @expcluster/reports test, 79 assertions): message round trips, the controller against a real Controller with an InstanceRecord and a fake control connection (instance vs web reporter, duplicate and empty reason refusals, list/get/delete, subscription replay, webhooks posted only for new reports, failed posts logged), the instance plugin against a real Instance (each ipc path, a controller refusal reaching the reporter, answers dropped after stop), and the lua module through the shared harness (payloads sent, every receiver's printing and events, offline players skipped). Lua lint is clean.

Smoke tested on my dev cluster: a report created from the web path came back with the connected user as reporter and no instance; one created from lua over rcon came back with instance_name: "EXP"; a local node http server on the JSON webhook received the record; the lua delete path removed both. The in game printing and the web page itself need a player and a browser, which I do not have here.

One thing to weigh: the reporter's own commands are still in exp_scenario with their existing permissions, only the storage and printing moved. If you would rather the commands live in the plugin too, that is a small follow up.

Reports were the last legacy control module, kept in the lua state of
each instance. They are now an exp_reports plugin which stores them on
the controller: a report is immutable and holds the reported player, who
made it, the reason, the instance name and when it was made, and the only
operations are get, list, create and delete.

In game nothing is stored. The lua module sends each request over ipc,
the instance plugin asks the controller and hands the answer back with
rcon, and the module prints it to whoever asked if they are still online.
Answers are dropped if the instance stopped meanwhile. The module raises
on_player_reported with the count and reporters of every report against
the player, which report_jail and discord_alerts use, and the commands
and player list button call into it.

The web ui gets a /reports page: a table with search on the player,
reporter and reason, a filter on the instance, delete per row and a
create form which reports in the name of the web user.

New reports are posted to two optional webhooks configured on the
controller, a Discord channel hook as an embed and a generic JSON hook.
Failures are logged rather than failing the report.

Tests cover the messages, the controller against a real Controller, the
instance plugin against a real Instance, and the lua module through the
shared harness.
@bbassie
bbassie force-pushed the feature/reports-plugin branch from a94293d to bad98c5 Compare September 6, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant