mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 15:54:52 +09:00
Players on the watch list are announced to every online admin on every instance when they join, and the join is posted to the Discord and JSON webhooks. The Reports page has a second table for the watch list with an Add button and a Delete button per row. Reports can be marked read or unread from the web interface. Each report row also has an Add to watch list button which opens the add form filled in with the player and reason. Setting exp_reports.watch_unread_reports treats players with unread reports as watched. Reports now store created_at_ms because marking them read changes updated_at_ms. Old reports.json files migrate on load with updated_at_ms as the creation time. The report webhook moved from the datastore update listener to the create handler so reading a report is not posted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
233 lines
8.4 KiB
Lua
233 lines
8.4 KiB
Lua
--[[-- ExpReports
|
|
Lets players report each other, with the reports kept on the controller.
|
|
|
|
Nothing is stored in the lua state. Each call sends a request to the
|
|
controller through the instance plugin, and the answer is printed to the
|
|
player who asked once it arrives, if they are still online.
|
|
]]
|
|
|
|
local clusterio_api = require("modules/clusterio/api")
|
|
local ExpUtil = require("modules/exp_util")
|
|
|
|
local format_player_name_locale = ExpUtil.format_player_name_locale
|
|
local format_rich_text_color_locale = ExpUtil.format_rich_text_color_locale
|
|
|
|
--- @class ExpReports
|
|
local ExpReports = {
|
|
--- Raised once the controller has accepted a report, the reported player is online
|
|
--- @type EventData.ExpReports.on_player_reported
|
|
on_player_reported = script.generate_event_name(),
|
|
--- Raised once the controller has deleted reports, the reported player is online
|
|
--- @type EventData.ExpReports.on_reports_deleted
|
|
on_reports_deleted = script.generate_event_name(),
|
|
}
|
|
|
|
--- @class EventData.ExpReports.on_player_reported : EventData
|
|
--- @field player_index uint
|
|
--- @field by_player_name string
|
|
--- @field reason string
|
|
--- @field report_count number Reports against the player including this one
|
|
--- @field by_player_names string[] Who made each of those reports
|
|
|
|
--- @class EventData.ExpReports.on_reports_deleted : EventData
|
|
--- @field player_index uint
|
|
--- @field by_player_name string Who deleted the reports
|
|
--- @field count number
|
|
|
|
--- @class ExpReports.Report
|
|
--- @field id number
|
|
--- @field player_name string
|
|
--- @field by_player_name string
|
|
--- @field reason string
|
|
--- @field instance_name string
|
|
--- @field created_at_ms number
|
|
--- @field is_read boolean?
|
|
--- @field updated_at_ms number
|
|
|
|
--- @class ExpReports.WatchListEntry
|
|
--- @field player_name string
|
|
--- @field reason string
|
|
--- @field by_player_name string
|
|
--- @field created_at_ms number
|
|
|
|
local error_settings = { color = ExpUtil.color.orange_red, sound_path = "utility/wire_pickup" }
|
|
|
|
--- Report a player, the outcome is printed once the controller answers
|
|
--- @param player LuaPlayer
|
|
--- @param reported_player LuaPlayer
|
|
--- @param reason string
|
|
function ExpReports.create_report(player, reported_player, reason)
|
|
clusterio_api.send_json("exp_reports:create", {
|
|
player_name = reported_player.name,
|
|
by_player_name = player.name,
|
|
reason = reason,
|
|
})
|
|
end
|
|
|
|
--- List the reports against a player, or against everyone, printed to the player once the controller answers
|
|
--- @param player LuaPlayer
|
|
--- @param reported_player LuaPlayer?
|
|
function ExpReports.list_reports(player, reported_player)
|
|
clusterio_api.send_json("exp_reports:list", {
|
|
caller = player.name,
|
|
player_name = reported_player and reported_player.name or nil,
|
|
})
|
|
end
|
|
|
|
--- Delete the reports against a player, or only those from one player
|
|
--- @param player LuaPlayer
|
|
--- @param reported_player LuaPlayer
|
|
--- @param by_player_name string?
|
|
function ExpReports.delete_reports(player, reported_player, by_player_name)
|
|
clusterio_api.send_json("exp_reports:delete", {
|
|
caller = player.name,
|
|
player_name = reported_player.name,
|
|
by_player_name = by_player_name,
|
|
})
|
|
end
|
|
|
|
--- A player who can still be printed to, nil once they have left
|
|
--- @param name string
|
|
--- @return LuaPlayer?
|
|
local function get_online_player(name)
|
|
local player = game.get_player(name)
|
|
if player and player.valid and player.connected then
|
|
return player
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- Names come from the controller, so the player may never have joined this server
|
|
--- @param name string
|
|
--- @return LocalisedString
|
|
local function format_player_name(name)
|
|
local player = game.get_player(name)
|
|
if player then
|
|
return format_player_name_locale(player)
|
|
end
|
|
return format_rich_text_color_locale(name, ExpUtil.color.white)
|
|
end
|
|
|
|
--- The controller accepted a report
|
|
--- @param payload { report: ExpReports.Report, reports: ExpReports.Report[] }
|
|
function ExpReports.receive_created(payload)
|
|
local report = payload.report
|
|
local player_name = format_player_name(report.player_name)
|
|
local by_player_name = format_player_name(report.by_player_name)
|
|
for _, player in pairs(game.connected_players) do
|
|
if player.admin then
|
|
player.print{ "exp-reports.created-admin", player_name, by_player_name, report.reason }
|
|
else
|
|
player.print{ "exp-reports.created", player_name, report.reason }
|
|
end
|
|
end
|
|
|
|
local player = get_online_player(report.player_name)
|
|
if not player then return end
|
|
|
|
local by_player_names = {}
|
|
for index, other in ipairs(payload.reports) do
|
|
by_player_names[index] = other.by_player_name
|
|
end
|
|
|
|
script.raise_event(ExpReports.on_player_reported, {
|
|
name = ExpReports.on_player_reported,
|
|
tick = game.tick,
|
|
player_index = player.index,
|
|
by_player_name = report.by_player_name,
|
|
reason = report.reason,
|
|
report_count = #payload.reports,
|
|
by_player_names = by_player_names,
|
|
})
|
|
end
|
|
|
|
--- The controller answered a list request
|
|
--- @param payload { caller: string, player_name: string?, reports: ExpReports.Report[] }
|
|
function ExpReports.receive_list(payload)
|
|
local caller = get_online_player(payload.caller)
|
|
if not caller then return end
|
|
|
|
if payload.player_name then
|
|
caller.print{ "exp-reports.list-title", format_player_name(payload.player_name), #payload.reports }
|
|
for _, report in ipairs(payload.reports) do
|
|
caller.print{ "exp-reports.list-entry", format_player_name(report.by_player_name), report.reason }
|
|
end
|
|
return
|
|
end
|
|
|
|
local counts = {} --- @type table<string, number>
|
|
local names = {} --- @type string[]
|
|
for _, report in ipairs(payload.reports) do
|
|
if not counts[report.player_name] then
|
|
names[#names + 1] = report.player_name
|
|
counts[report.player_name] = 0
|
|
end
|
|
counts[report.player_name] = counts[report.player_name] + 1
|
|
end
|
|
|
|
if #names == 0 then
|
|
caller.print{ "exp-reports.list-all-none" }
|
|
return
|
|
end
|
|
|
|
table.sort(names)
|
|
caller.print{ "exp-reports.list-all-title" }
|
|
for _, name in ipairs(names) do
|
|
caller.print{ "exp-reports.list-all-entry", format_player_name(name), counts[name] }
|
|
end
|
|
end
|
|
|
|
--- The controller deleted reports
|
|
--- @param payload { caller: string, player_name: string, by_player_name: string?, count: number }
|
|
function ExpReports.receive_deleted(payload)
|
|
if payload.count == 0 then
|
|
local caller = get_online_player(payload.caller)
|
|
if caller then
|
|
caller.print({ "exp-reports.deleted-none", format_player_name(payload.player_name) }, error_settings)
|
|
end
|
|
return
|
|
end
|
|
|
|
game.print{ "exp-reports.deleted", format_player_name(payload.player_name), payload.count, format_player_name(payload.caller) }
|
|
|
|
local player = get_online_player(payload.player_name)
|
|
if not player then return end
|
|
|
|
script.raise_event(ExpReports.on_reports_deleted, {
|
|
name = ExpReports.on_reports_deleted,
|
|
tick = game.tick,
|
|
player_index = player.index,
|
|
by_player_name = payload.caller,
|
|
count = payload.count,
|
|
})
|
|
end
|
|
|
|
--- A watched player joined an instance, which may be another one, admins are told why they are watched
|
|
--- @param payload { player_name: string, instance_name: string, watch_list_entry: ExpReports.WatchListEntry?, unread_reports: ExpReports.Report[] }
|
|
function ExpReports.receive_watched_joined(payload)
|
|
local player_name = format_player_name(payload.player_name)
|
|
local entry = payload.watch_list_entry
|
|
for _, player in pairs(game.connected_players) do
|
|
if player.admin then
|
|
player.print{ "exp-reports.watched-joined", player_name, payload.instance_name }
|
|
if entry then
|
|
player.print{ "exp-reports.watched-entry", format_player_name(entry.by_player_name), entry.reason }
|
|
end
|
|
for _, report in ipairs(payload.unread_reports) do
|
|
player.print{ "exp-reports.watched-report", format_player_name(report.by_player_name), report.reason }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- The controller refused a request, the message is printed to whoever asked
|
|
--- @param payload { caller: string, message: string }
|
|
function ExpReports.receive_error(payload)
|
|
local caller = get_online_player(payload.caller)
|
|
if caller then
|
|
caller.print(payload.message, error_settings)
|
|
end
|
|
end
|
|
|
|
return ExpReports
|