Spaces:
Running
Running
; | |
var __create = Object.create; | |
var __defProp = Object.defineProperty; | |
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
var __getOwnPropNames = Object.getOwnPropertyNames; | |
var __getProtoOf = Object.getPrototypeOf; | |
var __hasOwnProp = Object.prototype.hasOwnProperty; | |
var __export = (target, all) => { | |
for (var name in all) | |
__defProp(target, name, { get: all[name], enumerable: true }); | |
}; | |
var __copyProps = (to, from, except, desc) => { | |
if (from && typeof from === "object" || typeof from === "function") { | |
for (let key of __getOwnPropNames(from)) | |
if (!__hasOwnProp.call(to, key) && key !== except) | |
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | |
} | |
return to; | |
}; | |
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | |
// If the importer is in node compatibility mode or this is not an ESM | |
// file that has been converted to a CommonJS file using a Babel- | |
// compatible transform (i.e. "__esModule" has not been set), then set | |
// "default" to the CommonJS "module.exports" for node compatibility. | |
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | |
mod | |
)); | |
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
var helptickets_auto_exports = {}; | |
__export(helptickets_auto_exports, { | |
actionHandlers: () => actionHandlers, | |
addModAction: () => addModAction, | |
checkers: () => checkers, | |
classifier: () => classifier, | |
commands: () => commands, | |
determinePunishment: () => determinePunishment, | |
getMessageAverages: () => getMessageAverages, | |
getModlog: () => getModlog, | |
globalModlog: () => globalModlog, | |
pages: () => pages, | |
punishmentsFor: () => punishmentsFor, | |
runPunishments: () => runPunishments, | |
settings: () => settings | |
}); | |
module.exports = __toCommonJS(helptickets_auto_exports); | |
var import_lib = require("../../lib"); | |
var import_helptickets = require("./helptickets"); | |
var Artemis = __toESM(require("../artemis")); | |
const ORDERED_PUNISHMENTS = ["WARN", "FORCERENAME", "LOCK", "NAMELOCK", "WEEKLOCK", "WEEKNAMELOCK"]; | |
const PMLOG_IGNORE_TIME = 24 * 60 * 60 * 1e3; | |
const WHITELIST = ["mia"]; | |
const defaults = { | |
punishments: [{ | |
ticketType: "inapname", | |
punishment: "forcerename", | |
severity: { type: ["sexual_explicit", "severe_toxicity", "identity_attack"], certainty: 0.4 } | |
}, { | |
ticketType: "pmharassment", | |
punishment: "warn", | |
severity: { type: ["sexual_explicit", "severe_toxicity", "identity_attack"], certainty: 0.15 } | |
}], | |
applyPunishments: false | |
}; | |
const settings = (() => { | |
try { | |
return { ...defaults, ...JSON.parse((0, import_lib.FS)("config/chat-plugins/ht-auto.json").readSync()) }; | |
} catch { | |
return defaults; | |
} | |
})(); | |
function saveSettings() { | |
return (0, import_lib.FS)("config/chat-plugins/ht-auto.json").writeUpdate(() => JSON.stringify(settings)); | |
} | |
function visualizePunishment(punishment) { | |
const buf = [`punishment: ${punishment.punishment?.toUpperCase()}`]; | |
buf.push(`ticket type: ${punishment.ticketType}`); | |
if (punishment.severity) { | |
buf.push(`severity: ${punishment.severity.certainty} (for ${punishment.severity.type.join(", ")})`); | |
} | |
if (punishment.modlogCount) { | |
buf.push(`required modlog: ${punishment.modlogCount}`); | |
} | |
if (punishment.isSingleMessage) { | |
buf.push(`for single messages only`); | |
} | |
return buf.join(", "); | |
} | |
function checkAccess(context) { | |
if (!WHITELIST.includes(context.user.id)) | |
context.checkCan("bypassall"); | |
} | |
function punishmentsFor(type) { | |
return settings.punishments.filter((t) => t.ticketType === type); | |
} | |
function supersedes(p1, p2) { | |
return ORDERED_PUNISHMENTS.indexOf(p1) > ORDERED_PUNISHMENTS.indexOf(p2); | |
} | |
function determinePunishment(ticketType, results, modlog, isSingleMessage = false) { | |
const punishments = punishmentsFor(ticketType); | |
let action = null; | |
const types = []; | |
import_lib.Utils.sortBy(punishments, (p) => -ORDERED_PUNISHMENTS.indexOf(p.punishment)); | |
for (const punishment of punishments) { | |
if (isSingleMessage && !punishment.isSingleMessage) | |
continue; | |
if (punishment.modlogCount && modlog.length < punishment.modlogCount) | |
continue; | |
if (punishment.severity) { | |
let hit = false; | |
for (const type of punishment.severity.type) { | |
if (results[type] < punishment.severity.certainty) | |
continue; | |
hit = true; | |
types.push(type); | |
break; | |
} | |
if (!hit) | |
continue; | |
} | |
if (!action || supersedes(punishment.punishment, action)) { | |
action = punishment.punishment; | |
} | |
} | |
return { action, types }; | |
} | |
function globalModlog(action, user, note, roomid) { | |
user = Users.get(user) || user; | |
void Rooms.Modlog.write(roomid || "global", { | |
action, | |
ip: user && typeof user === "object" ? user.latestIp : void 0, | |
userid: toID(user) || void 0, | |
loggedBy: "artemis", | |
note | |
}); | |
} | |
function addModAction(message) { | |
Rooms.get("staff")?.add(`|c|~|/log ${message}`).update(); | |
} | |
async function getModlog(params) { | |
const search = { | |
note: [], | |
user: [], | |
ip: [], | |
action: [], | |
actionTaker: [] | |
}; | |
if (params.user) | |
search.user = [{ search: params.user, isExact: true }]; | |
if (params.ip) | |
search.ip = [{ search: params.ip }]; | |
if (params.actions) | |
search.action = params.actions.map((s) => ({ search: s })); | |
const res = await Rooms.Modlog.search("global", search); | |
return res?.results || []; | |
} | |
function closeTicket(ticket, msg) { | |
if (!ticket.open) | |
return; | |
ticket.open = false; | |
ticket.active = false; | |
ticket.resolved = { | |
time: Date.now(), | |
by: "the Artemis AI", | |
// we want it to be clear to end users that it was not a human | |
seen: false, | |
staffReason: "", | |
result: msg || "", | |
note: `Want to learn more about the AI? <a href="https://www.smogon.com/forums/threads/3570628/#post-9056769">Visit the information thread</a>.` | |
}; | |
(0, import_helptickets.writeTickets)(); | |
(0, import_helptickets.notifyStaff)(); | |
const tarUser = Users.get(ticket.userid); | |
if (tarUser) { | |
import_helptickets.HelpTicket.notifyResolved(tarUser, ticket, ticket.userid); | |
} | |
(0, import_helptickets.writeStats)(`${ticket.type} ${Date.now() - ticket.created} 0 0 resolved valid artemis`); | |
} | |
async function lock(user, result, ticket, isWeek, isName) { | |
const id = toID(user); | |
let desc, type; | |
const expireTime = isWeek ? Date.now() + 7 * 24 * 60 * 60 * 1e3 : null; | |
if (isName) { | |
if (typeof user === "object") | |
user.resetName(); | |
desc = "locked your username and prevented you from changing names"; | |
type = `locked from talking${isWeek ? ` for a week` : ""}`; | |
await Punishments.namelock(id, expireTime, null, false, result.reason || "Automatically locked due to a user report"); | |
} else { | |
type = isWeek ? "weeknamelocked" : "namelocked"; | |
desc = "locked you from talking in chats, battles, and PMing regular users"; | |
await Punishments.lock(id, expireTime, null, false, result.reason || "Automatically locked due to a user report"); | |
} | |
if (typeof user !== "string") { | |
let message = `|popup||html|${user.name} has ${desc} for ${isWeek ? "7" : "2"} days.`; | |
if (result.reason) | |
message += ` | |
Reason: ${result.reason}`; | |
let appeal = ""; | |
if (Chat.pages.help) { | |
appeal += `<a href="view-help-request--appeal"><button class="button"><strong>Appeal your punishment</strong></button></a>`; | |
} else if (Config.appealurl) { | |
appeal += `appeal: <a href="${Config.appealurl}">${Config.appealurl}</a>`; | |
} | |
if (appeal) | |
message += ` | |
If you feel that your lock was unjustified, you can ${appeal}.`; | |
message += ` | |
Your lock will expire in a few days.`; | |
user.send(message); | |
} | |
addModAction(`${id} was ${type} by Artemis. (${result.reason || `report from ${ticket.creator}`})`); | |
globalModlog( | |
`${isWeek ? "WEEK" : ""}${isName ? "NAME" : ""}LOCK`, | |
id, | |
(result.reason || `report from ${ticket.creator}`) + (result.proof ? ` PROOF: ${result.proof}` : "") | |
); | |
} | |
const actionHandlers = { | |
forcerename(user, result, ticket) { | |
if (typeof user === "string") | |
return; | |
const id = toID(user); | |
user.resetName(); | |
user.trackRename = id; | |
Monitor.forceRenames.set(id, true); | |
user.send( | |
`|nametaken|Your name was detected to be breaking our name rules. ${result.reason ? `Reason: ${result.reason}. ` : ""}Please change it, or submit a help ticket by typing /ht in chat to appeal this action.` | |
); | |
Rooms.get("staff")?.add( | |
`|html|<span class="username">${id}</span> was automatically forced to choose a new name by Artemis (report from ${ticket.userid}).` | |
).update(); | |
globalModlog( | |
"FORCERENAME", | |
id, | |
`username determined to be inappropriate due to a report by ${ticket.creator}`, | |
result.roomid | |
); | |
return `${id} was automatically forcerenamed. Thank you for reporting.`; | |
}, | |
async namelock(user, result, ticket) { | |
await lock(user, result, ticket, false, true); | |
return `${toID(user)} was automatically namelocked. Thank you for reporting.`; | |
}, | |
async weeknamelock(user, result, ticket) { | |
await lock(user, result, ticket, true, true); | |
return `${toID(user)} was automatically weeknamelocked. Thank you for reporting.`; | |
}, | |
async lock(user, result, ticket) { | |
await lock(user, result, ticket); | |
return `${toID(user)} was automatically locked. Thank you for reporting.`; | |
}, | |
async weeklock(user, result, ticket) { | |
await lock(user, result, ticket, true); | |
return `${toID(user)} was automatically weeklocked. Thank you for reporting.`; | |
}, | |
warn(user, result, ticket) { | |
user = toID(user); | |
user = Users.get(user) || user; | |
if (typeof user === "object") { | |
user.send(`|c|~|/warn ${result.reason || ""}`); | |
} else { | |
Punishments.offlineWarns.set(user, result.reason); | |
} | |
addModAction( | |
`${user} was warned by Artemis. ${typeof user === "string" ? "while offline " : ""}(${result.reason || `report from ${ticket.creator}`})` | |
); | |
globalModlog( | |
"WARN", | |
user, | |
result.reason || `report from ${ticket.creator}` | |
); | |
return `${user} was automatically warned. Thank you for reporting.`; | |
} | |
}; | |
function shouldNotProcess(message) { | |
return message.startsWith("/") && !message.startsWith("//") || // broadcasted chat command | |
message.startsWith("!"); | |
} | |
async function getMessageAverages(messages) { | |
const counts = {}; | |
const classified = []; | |
for (const message of messages) { | |
if (shouldNotProcess(message)) | |
continue; | |
const res = await classifier.classify(message); | |
if (!res) | |
continue; | |
classified.push(res); | |
for (const k in res) { | |
if (!counts[k]) | |
counts[k] = { count: 0, raw: 0 }; | |
counts[k].count++; | |
counts[k].raw += res[k]; | |
} | |
} | |
const averages = {}; | |
for (const k in counts) { | |
averages[k] = counts[k].raw / counts[k].count; | |
} | |
return { averages, classified }; | |
} | |
const checkers = { | |
async inapname(ticket) { | |
const id = toID(ticket.text[0]); | |
const user = Users.getExact(id); | |
if (user && !user.trusted) { | |
const result = await classifier.classify(user.name); | |
if (!result) | |
return; | |
const keys = ["identity_attack", "sexual_explicit", "severe_toxicity"]; | |
const matched = keys.some((k) => result[k] >= 0.4); | |
if (matched) { | |
const modlog = await getModlog({ | |
ip: user.latestIp, | |
actions: ["FORCERENAME", "NAMELOCK", "WEEKNAMELOCK"] | |
}); | |
let { action } = determinePunishment("inapname", result, modlog); | |
if (!action) | |
action = "forcerename"; | |
return /* @__PURE__ */ new Map([[user.id, { | |
action, | |
user, | |
result, | |
reason: "Username detected to be breaking username rules" | |
}]]); | |
} | |
} | |
}, | |
async inappokemon(ticket) { | |
const actions = /* @__PURE__ */ new Map(); | |
const links = [...(0, import_helptickets.getBattleLinks)(ticket.text[0]), ...(0, import_helptickets.getBattleLinks)(ticket.text[1])]; | |
for (const link of links) { | |
const log = await (0, import_helptickets.getBattleLog)(link); | |
if (!log) | |
continue; | |
for (const [user, pokemon] of Object.entries(log.pokemon)) { | |
const userid = toID(user); | |
let result = null; | |
for (const set of pokemon) { | |
if (!set.name) | |
continue; | |
const results = await classifier.classify(set.name); | |
if (!results) | |
continue; | |
const curAction = determinePunishment("inappokemon", results, []).action; | |
if (curAction && (!result || supersedes(curAction, result.action))) { | |
result = { action: curAction, name: set.name, result: results, replay: link }; | |
} | |
} | |
if (result) { | |
actions.set(user, { | |
action: result.action, | |
user: userid, | |
result: result.result, | |
reason: `Pokemon name detected to be breaking rules - '${result.name}'`, | |
roomid: link | |
}); | |
} | |
} | |
} | |
if (actions.size) | |
return actions; | |
}, | |
async battleharassment(ticket) { | |
const urls = (0, import_helptickets.getBattleLinks)(ticket.text[0]); | |
const actions = /* @__PURE__ */ new Map(); | |
for (const url of urls) { | |
const log = await (0, import_helptickets.getBattleLog)(url); | |
if (!log) | |
continue; | |
const messages = {}; | |
for (const message of log.log) { | |
const [username, text] = import_lib.Utils.splitFirst(message.slice(3), "|").map((f) => f.trim()); | |
const id = toID(username); | |
if (!id) | |
continue; | |
if (!messages[id]) | |
messages[id] = []; | |
messages[id].push(text); | |
} | |
for (const [id, messageList] of Object.entries(messages)) { | |
const { averages, classified } = await getMessageAverages(messageList); | |
const { action } = determinePunishment("battleharassment", averages, []); | |
if (action) { | |
const existingPunishment = actions.get(id); | |
if (!existingPunishment || supersedes(action, existingPunishment.action)) { | |
actions.set(id, { | |
action, | |
user: toID(id), | |
result: averages, | |
reason: `Not following rules in battles (https://${Config.routes.client}/${url})`, | |
proof: urls.join(", ") | |
}); | |
} | |
} | |
for (const result of classified) { | |
const curPunishment = determinePunishment("battleharassment", result, [], true).action; | |
if (!curPunishment) | |
continue; | |
const exists = actions.get(id); | |
if (!exists || supersedes(curPunishment, exists.action)) { | |
actions.set(id, { | |
action: curPunishment, | |
user: toID(id), | |
result: averages, | |
reason: `Not following rules in battles (https://${Config.routes.client}/${url})`, | |
proof: urls.join(", ") | |
}); | |
} | |
} | |
} | |
} | |
const creatorWasPunished = actions.get(ticket.userid); | |
if (creatorWasPunished) { | |
let displayReason = "You were punished for your behavior."; | |
if (actions.size !== 1) { | |
displayReason += ` ${actions.size - 1} other(s) were also punished.`; | |
} | |
creatorWasPunished.displayReason = displayReason; | |
} | |
if (actions.size) | |
return actions; | |
}, | |
async pmharassment(ticket) { | |
const actions = /* @__PURE__ */ new Map(); | |
const targetId = toID(ticket.text[0]); | |
const creator = ticket.userid; | |
if (!Config.getpmlog) | |
return; | |
const pmLog = await Config.getpmlog(targetId, creator); | |
const messages = {}; | |
const ids = /* @__PURE__ */ new Set(); | |
for (const { from, message, timestamp } of pmLog) { | |
if (Date.now() - new Date(timestamp).getTime() > PMLOG_IGNORE_TIME) | |
continue; | |
const id = toID(from); | |
ids.add(id); | |
if (!messages[id]) | |
messages[id] = []; | |
messages[id].push(message); | |
} | |
for (const id of ids) { | |
let punishment; | |
const { averages, classified } = await getMessageAverages(messages[id]); | |
const curPunishment = determinePunishment("pmharassment", averages, []).action; | |
if (curPunishment) { | |
if (!punishment || supersedes(curPunishment, punishment)) { | |
punishment = curPunishment; | |
} | |
if (punishment) { | |
actions.set(id, { | |
action: punishment, | |
user: id, | |
result: {}, | |
reason: `PM harassment (against ${ticket.userid === id ? targetId : ticket.userid})` | |
}); | |
} | |
} | |
for (const result of classified) { | |
const { action } = determinePunishment("pmharassment", result, [], true); | |
if (!action) | |
continue; | |
const exists = actions.get(id); | |
if (!exists || supersedes(action, exists.action)) { | |
actions.set(id, { | |
action, | |
user: id, | |
result: {}, | |
reason: `PM harassment (against ${ticket.userid === id ? targetId : ticket.userid})` | |
}); | |
} | |
} | |
} | |
const creatorWasPunished = actions.get(ticket.userid); | |
if (creatorWasPunished) { | |
let displayReason = `You were punished for your behavior. `; | |
if (actions.has(targetId) && targetId !== ticket.userid) { | |
displayReason += ` The person you reported was also punished.`; | |
} | |
creatorWasPunished.displayReason = displayReason; | |
} | |
if (actions.size) | |
return actions; | |
} | |
}; | |
const classifier = new Artemis.LocalClassifier(); | |
async function runPunishments(ticket, typeId) { | |
let result = null; | |
if (checkers[typeId]) { | |
result = await checkers[typeId](ticket) || null; | |
} | |
if (result) { | |
if (settings.applyPunishments) { | |
const responses = []; | |
for (const res of result.values()) { | |
const curResult = await actionHandlers[res.action.toLowerCase()](res.user, res, ticket); | |
if (curResult) | |
responses.push([res.action, res.displayReason || curResult]); | |
if (toID(res.user) === ticket.creator) { | |
closeTicket(ticket, res.displayReason); | |
} | |
} | |
if (responses.length) { | |
import_lib.Utils.sortBy(responses, (r) => -ORDERED_PUNISHMENTS.indexOf(r[0])); | |
closeTicket(ticket, responses[0][1]); | |
} else { | |
closeTicket(ticket); | |
} | |
} else { | |
ticket.recommended = []; | |
for (const res of result.values()) { | |
Rooms.get("abuselog")?.add( | |
`|c|~|/log [${ticket.type} Monitor] Recommended: ${res.action}: for ${res.user} (${res.reason})` | |
).update(); | |
ticket.recommended.push(`${res.action}: for ${res.user} (${res.reason})`); | |
} | |
} | |
} | |
} | |
const commands = { | |
aht: "autohelpticket", | |
autohelpticket: { | |
""() { | |
return this.parse(`/help autohelpticket`); | |
}, | |
async test(target) { | |
checkAccess(this); | |
target = target.trim(); | |
const response = await classifier.classify(target) || {}; | |
let buf = import_lib.Utils.html`<strong>Results for "${target}":</strong><br />`; | |
buf += `<strong>Score breakdown:</strong><br />`; | |
for (const k in response) { | |
buf += `• ${k}: ${response[k]}<br />`; | |
} | |
this.runBroadcast(); | |
this.sendReplyBox(buf); | |
}, | |
ap: "addpunishment", | |
add: "addpunishment", | |
addpunishment(target, room, user) { | |
checkAccess(this); | |
if (!toID(target)) | |
return this.parse(`/help autohelpticket`); | |
const args = Chat.parseArguments(target); | |
const punishment = {}; | |
for (const [k, list] of Object.entries(args)) { | |
if (k !== "type" && list.length > 1) | |
throw new Chat.ErrorMessage(`More than one ${k} param provided.`); | |
const val = list[0]; | |
switch (k) { | |
case "type": | |
case "t": | |
const types = list.map((f) => f.toLowerCase().replace(/\s/g, "_")); | |
for (const type2 of types) { | |
if (!Artemis.LocalClassifier.ATTRIBUTES[type2]) { | |
return this.errorReply( | |
`Invalid classifier type '${type2}'. Valid types are ` + Object.keys(Artemis.LocalClassifier.ATTRIBUTES).join(", ") | |
); | |
} | |
} | |
if (!punishment.severity) { | |
punishment.severity = { certainty: 0, type: [] }; | |
} | |
punishment.severity.type.push(...types); | |
break; | |
case "certainty": | |
case "c": | |
const num = parseFloat(val); | |
if (isNaN(num) || num < 0 || num > 1) { | |
return this.errorReply(`Certainty must be a number below 1 and above 0.`); | |
} | |
if (!punishment.severity) { | |
punishment.severity = { certainty: 0, type: [] }; | |
} | |
punishment.severity.certainty = num; | |
break; | |
case "modlog": | |
case "m": | |
const count = parseInt(val); | |
if (isNaN(count) || count < 0) { | |
return this.errorReply(`Modlog count must be a number above 0.`); | |
} | |
punishment.modlogCount = count; | |
break; | |
case "ticket": | |
case "tt": | |
case "tickettype": | |
const type = toID(val); | |
if (!(type in checkers)) { | |
return this.errorReply( | |
`The ticket type '${type}' does not exist or is not supported. Supported types are ${Object.keys(checkers).join(", ")}.` | |
); | |
} | |
punishment.ticketType = type; | |
break; | |
case "p": | |
case "punishment": | |
const name = toID(val).toUpperCase(); | |
if (!ORDERED_PUNISHMENTS.includes(name)) { | |
return this.errorReply( | |
`Punishment '${name}' not supported. Supported punishments: ${ORDERED_PUNISHMENTS.join(", ")}` | |
); | |
} | |
punishment.punishment = name; | |
break; | |
case "single": | |
case "s": | |
if (!this.meansYes(toID(val))) { | |
return this.errorReply( | |
`The 'single' value must always be 'on'. If you don't want it enabled, just do not use this argument type.` | |
); | |
} | |
punishment.isSingleMessage = true; | |
break; | |
} | |
} | |
if (!punishment.ticketType) { | |
return this.errorReply(`Must specify a ticket type to handle.`); | |
} | |
if (!punishment.punishment) { | |
return this.errorReply(`Must specify a punishment to apply.`); | |
} | |
if (!(punishment.severity?.certainty && punishment.severity?.type.length)) { | |
return this.errorReply(`A severity to monitor for must be specified (certainty).`); | |
} | |
for (const curP of settings.punishments) { | |
let matches = 0; | |
for (const k in curP) { | |
if (punishment[k] === curP[k]) { | |
matches++; | |
} | |
} | |
if (matches === Object.keys(punishment).length) { | |
return this.errorReply(`That punishment is already added.`); | |
} | |
} | |
settings.punishments.push(punishment); | |
saveSettings(); | |
this.privateGlobalModAction( | |
`${user.name} added a ${punishment.punishment} punishment to the Artemis helpticket handler.` | |
); | |
this.globalModlog(`AUTOHELPTICKET ADDPUNISHMENT`, null, visualizePunishment(punishment)); | |
}, | |
dp: "deletepunishment", | |
delete: "deletepunishment", | |
deletepunishment(target, room, user) { | |
checkAccess(this); | |
const num = parseInt(target) - 1; | |
if (isNaN(num)) | |
return this.parse(`/h autohelpticket`); | |
const punishment = settings.punishments[num]; | |
if (!punishment) | |
return this.errorReply(`There is no punishment at index ${num + 1}.`); | |
settings.punishments.splice(num, 1); | |
this.privateGlobalModAction( | |
`${user.name} removed the Artemis helpticket ${punishment.punishment} punishment indexed at ${num + 1}` | |
); | |
this.globalModlog(`AUTOHELPTICKET REMOVE`, null, visualizePunishment(punishment)); | |
}, | |
vp: "viewpunishments", | |
view: "viewpunishments", | |
viewpunishments() { | |
checkAccess(this); | |
let buf = `<strong>Artemis helpticket punishments</strong><hr />`; | |
if (!settings.punishments.length) { | |
buf += `None.`; | |
return this.sendReplyBox(buf); | |
} | |
buf += settings.punishments.map( | |
(curP, i) => `<strong>${i + 1}:</strong> ${visualizePunishment(curP)}` | |
).join("<br />"); | |
return this.sendReplyBox(buf); | |
}, | |
togglepunishments(target, room, user) { | |
checkAccess(this); | |
let message; | |
if (this.meansYes(target)) { | |
if (settings.applyPunishments) { | |
return this.errorReply(`Automatic punishments are already enabled.`); | |
} | |
settings.applyPunishments = true; | |
message = `${user.name} enabled automatic punishments for the Artemis ticket handler`; | |
} else if (this.meansNo(target)) { | |
if (!settings.applyPunishments) { | |
return this.errorReply(`Automatic punishments are already disabled.`); | |
} | |
settings.applyPunishments = false; | |
message = `${user.name} disabled automatic punishments for the Artemis ticket handler`; | |
} else { | |
return this.errorReply(`Invalid setting. Must be 'on' or 'off'.`); | |
} | |
this.privateGlobalModAction(message); | |
this.globalModlog(`AUTOHELPTICKET TOGGLE`, null, settings.applyPunishments ? "on" : "off"); | |
saveSettings(); | |
}, | |
stats(target) { | |
if (!target) | |
target = Chat.toTimestamp(new Date()).split(" ")[0]; | |
return this.parse(`/j view-autohelpticket-stats-${target}`); | |
}, | |
logs(target) { | |
if (!target) | |
target = Chat.toTimestamp(new Date()).split(" ")[0]; | |
return this.parse(`/j view-autohelpticket-logs-${target}`); | |
}, | |
resolve(target, room, user) { | |
this.checkCan("lock"); | |
const [ticketId, result] = import_lib.Utils.splitFirst(target, ",").map(toID); | |
const ticket = import_helptickets.tickets[ticketId]; | |
if (!ticket?.open) { | |
return this.popupReply(`The user '${ticketId}' does not have a ticket open at present.`); | |
} | |
if (!["success", "failure"].includes(result)) { | |
return this.popupReply(`The result must be 'success' or 'failure'.`); | |
} | |
(ticket.state || (ticket.state = {})).recommendResult = result; | |
(0, import_helptickets.writeTickets)(); | |
Chat.refreshPageFor(`help-text-${ticketId}`, "staff"); | |
} | |
}, | |
autohelptickethelp: [ | |
`/aht addpunishment [args] - Adds a punishment with the given [args]. Requires: whitelist ~`, | |
`/aht deletepunishment [index] - Deletes the automatic helpticket punishment at [index]. Requires: whitelist ~`, | |
`/aht viewpunishments - View automatic helpticket punishments. Requires: whitelist ~`, | |
`/aht togglepunishments [on | off] - Turn [on | off] automatic helpticket punishments. Requires: whitelist ~`, | |
`/aht stats - View success rates of the Artemis ticket handler. Requires: whitelist ~` | |
] | |
}; | |
const pages = { | |
autohelpticket: { | |
async stats(query, user) { | |
checkAccess(this); | |
let month; | |
if (query.length) { | |
month = /[0-9]{4}-[0-9]{2}/.exec(query.join("-"))?.[0]; | |
} else { | |
month = Chat.toTimestamp(new Date()).split(" ")[0].slice(0, -3); | |
} | |
if (!month) { | |
return this.errorReply(`Invalid month. Must be in YYYY-MM format.`); | |
} | |
this.title = `[Artemis Ticket Stats] ${month}`; | |
this.setHTML(`<div class="pad"><h3>Artemis ticket stats</h3><hr />Searching...`); | |
const found = await import_helptickets.HelpTicket.getTextLogs(["recommendResult"], month); | |
const percent = (numerator, denom) => Math.floor(numerator / denom * 100); | |
let buf = `<div class="pad">`; | |
buf += `<button style="float:right;" class="button" name="send" value="/join ${this.pageid}">`; | |
buf += `<i class="fa fa-refresh"></i> Refresh</button>`; | |
buf += `<h3>Artemis ticket stats</h3><hr />`; | |
const dayStats = {}; | |
const total = { successes: 0, failures: 0, total: 0 }; | |
const failed = []; | |
for (const ticket of found) { | |
const day = Chat.toTimestamp(new Date(ticket.created)).split(" ")[0]; | |
if (!dayStats[day]) | |
dayStats[day] = { successes: 0, failures: 0, total: 0 }; | |
dayStats[day].total++; | |
total.total++; | |
switch (ticket.state.recommendResult) { | |
case "success": | |
dayStats[day].successes++; | |
total.successes++; | |
break; | |
case "failure": | |
dayStats[day].failures++; | |
total.failures++; | |
failed.push([ticket.userid, ticket.type]); | |
break; | |
} | |
} | |
buf += `<strong>Total:</strong> ${total.total}<br />`; | |
buf += `<strong>Success rate:</strong> ${percent(total.successes, total.total)}% (${total.successes})<br />`; | |
buf += `<strong>Failure rate:</strong> ${percent(total.failures, total.total)}% (${total.failures})<br />`; | |
buf += `<strong>Day stats:</strong><br />`; | |
buf += `<div class="ladder pad"><table>`; | |
let header = ""; | |
let data = ""; | |
const sortedDays = import_lib.Utils.sortBy(Object.keys(dayStats), (d) => new Date(d).getTime()); | |
for (const [i, day] of sortedDays.entries()) { | |
const cur = dayStats[day]; | |
if (!cur.total) | |
continue; | |
header += `<th>${day.split("-")[2]} (${cur.total})</th>`; | |
data += `<td><small>${cur.successes} (${percent(cur.successes, cur.total)}%)`; | |
if (cur.failures) { | |
data += ` | ${cur.failures} (${percent(cur.failures, cur.total)}%)`; | |
} else { | |
data += " | 0 (0%)"; | |
} | |
data += "</small></td>"; | |
if ((i + 1) % 5 === 0 && sortedDays[i + 1]) { | |
buf += `<tr>${header}</tr><tr>${data}</tr>`; | |
buf += `</div></table>`; | |
buf += `<div class="ladder pad"><table>`; | |
header = ""; | |
data = ""; | |
} | |
} | |
buf += `<tr>${header}</tr><tr>${data}</tr>`; | |
buf += `</div></table>`; | |
buf += `<br />`; | |
if (failed.length) { | |
buf += `<details class="readmore"><summary>Marked as inaccurate</summary>`; | |
buf += failed.map(([userid, type]) => `<a href="/view-help-text-${userid}">${userid}</a> (${type})`).join("<br />"); | |
buf += `</details>`; | |
} | |
return buf; | |
}, | |
async logs(query, user) { | |
checkAccess(this); | |
let month; | |
if (query.length) { | |
month = /[0-9]{4}-[0-9]{2}/.exec(query.join("-"))?.[0]; | |
} else { | |
month = Chat.toTimestamp(new Date()).split(" ")[0].slice(0, -3); | |
} | |
if (!month) { | |
return this.errorReply(`Invalid month. Must be in YYYY-MM format.`); | |
} | |
this.title = `[Artemis Ticket Logs]`; | |
let buf = `<div class="pad"><h3>Artemis ticket logs</h3><hr />`; | |
const allHits = await import_helptickets.HelpTicket.getTextLogs(["recommended"], month); | |
import_lib.Utils.sortBy(allHits, (h) => -h.created); | |
if (allHits.length) { | |
buf += `<strong>All hits:</strong><hr />`; | |
for (const hit of allHits) { | |
if (!hit.recommended) | |
continue; | |
buf += `<a href="/view-help-text-${hit.userid}">${hit.userid}</a> (${hit.type}) `; | |
buf += `[${Chat.toTimestamp(new Date(hit.created))}]<br />`; | |
buf += import_lib.Utils.html`• <code><small>${hit.recommended.join(", ")}</small></code><hr />`; | |
} | |
} else { | |
buf += `<div class="message-error">No hits found.</div>`; | |
} | |
return buf; | |
} | |
} | |
}; | |
//# sourceMappingURL=helptickets-auto.js.map | |