From cee8f5a12ccf0e6dccf9bbe5579bcbada49dca86 Mon Sep 17 00:00:00 2001
From: Mia <49593536+mia-pi-git@users.noreply.github.com>
Date: Thu, 23 Dec 2021 17:56:25 -0600
Subject: [PATCH] Abuse-monitor: Support tracking stats (#8575)
---
databases/migrations/chat-plugins/v5.sql | 17 +++++
server/chat-plugins/abuse-monitor.ts | 88 ++++++++++++++++++++++--
2 files changed, 99 insertions(+), 6 deletions(-)
create mode 100644 databases/migrations/chat-plugins/v5.sql
diff --git a/databases/migrations/chat-plugins/v5.sql b/databases/migrations/chat-plugins/v5.sql
new file mode 100644
index 0000000000..b1aa733ab5
--- /dev/null
+++ b/databases/migrations/chat-plugins/v5.sql
@@ -0,0 +1,17 @@
+-- Creates stats tables for the Perspective moderation tool.
+BEGIN TRANSACTION;
+
+CREATE TABLE perspective_stats (
+ staff TEXT NOT NULL,
+ roomid TEXT NOT NULL PRIMARY KEY,
+ result TINYINT(1) NOT NULL,
+ timestamp INTEGER NOT NULL
+);
+
+CREATE INDEX date_idx ON perspective_stats(date);
+
+-- update database version
+UPDATE db_info SET value = '5' WHERE key = 'version';
+COMMIT;
+
+
diff --git a/server/chat-plugins/abuse-monitor.ts b/server/chat-plugins/abuse-monitor.ts
index 776ae2255a..2cb5418e09 100644
--- a/server/chat-plugins/abuse-monitor.ts
+++ b/server/chat-plugins/abuse-monitor.ts
@@ -98,6 +98,12 @@ interface BattleInfo {
log: string[];
}
+// stolen from chatlog. necessary here, but importing chatlog sucks.
+function nextMonth(month: string) {
+ const next = new Date(new Date(`${month}-15`).getTime() + 30 * 24 * 60 * 60 * 1000);
+ return next.toISOString().slice(0, 7);
+}
+
// Mostly stolen from my code in helptickets.
// Necessary because we can't require this in without also requiring in a LOT of other
// modules, most of which crash the child process. Lot messier to fix that than it is to do this.
@@ -442,20 +448,39 @@ export const commands: Chat.ChatCommands = {
`|html|Remember to use /am respawn to deploy the settings to the child process.`
);
},
- resolve(target) {
+ async resolve(target) {
this.checkCan('lock');
target = target.toLowerCase().trim().replace(/ +/g, '');
- if (!target) return this.parse(`/help abusemonitor`);
- if (!cache[target]?.staffNotified) {
+ let [roomid, rawResult] = Utils.splitFirst(target, ',').map(f => f.trim());
+ if (!cache[roomid]?.staffNotified) {
return this.popupReply(`That room has not been flagged by the abuse monitor.`);
}
+ if (roomid.includes('-') && roomid.endsWith('pw')) {
+ // cut off passwords
+ roomid = roomid.split('-').slice(0, -1).join('-');
+ }
+ let result = toID(rawResult) === 'success' ? 1 : toID(rawResult) === 'failure' ? 0 : null;
+ if (!result) return this.popupReply(`Invalid result - must be 'success' or 'failure'.`);
+ const inserted = await Chat.database.get(`SELECT result FROM perspective_stats WHERE roomid = ?`, [roomid]);
+ if (inserted?.result) {
+ // has already been logged as accurate - ensure if one success is logged it's still a success if it's hit again
+ // (even if it's a failure now, it was a success before - that's what's relevant.)
+ result = inserted.result;
+ }
// we delete the cache because if more stuff happens in it
// post punishment, we want to know about it
- delete cache[target];
+ delete cache[roomid];
notifyStaff();
- this.closePage(`abusemonitor-view-${target}`);
+ this.closePage(`abusemonitor-view-${roomid}`);
// bring the listing page to the front - need to close and reopen
this.closePage(`abusemonitor-flagged`);
+ await Chat.database.run(
+ `INSERT INTO perspective_stats (staff, roomid, result, timestamp) VALUES ($staff, $roomid, $result, $timestamp) ` +
+ // on conflict in case it's re-triggered later.
+ // (we want it to be updated to success if it is now a success where it was previously inaccurate)
+ `ON CONFLICT (roomid) DO UPDATE SET result = $result, timestamp = $timestamp`,
+ {staff: this.user.id, roomid, result, timestamp: Date.now()}
+ );
return this.parse(`/j view-abusemonitor-flagged`);
},
async nojoinpunish(target, room, user) {
@@ -491,6 +516,10 @@ export const commands: Chat.ChatCommands = {
const [count, userid] = Utils.splitFirst(target, ',').map(toID);
this.parse(`/join view-abusemonitor-logs-${count || '200'}${userid ? `-${userid}` : ""}`);
},
+ stats(target) {
+ checkAccess(this);
+ return this.parse(`/join view-abusemonitor-stats${target ? `-${target}` : ''}`);
+ },
async respawn(target, room, user) {
checkAccess(this);
this.sendReply(`Respawning...`);
@@ -760,7 +789,9 @@ export const pages: Chat.PageTable = {
}
buf += `
`;
}
- buf += ``;
+ buf += `
${Chat.count(logs.length, 'logs')} found.
`; + let successes = 0; + let failures = 0; + const staffStats: RecordSuccess rate: ${(successes / logs.length) * 100}%
`; + buf += `Failure rate: ${(failures / logs.length) * 100}%
`; + buf += `Staff stats:
`; + buf += `| User | Total | Percent total |
|---|---|---|
| ${id} | ${staffStats[id]} | ${(staffStats[id] / logs.length) * 100}% |