Spaces:
Running
Running
; | |
var __defProp = Object.defineProperty; | |
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
var __getOwnPropNames = Object.getOwnPropertyNames; | |
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
var permalocks_exports = {}; | |
__export(permalocks_exports, { | |
Nominations: () => Nominations, | |
Smogon: () => Smogon, | |
commands: () => commands, | |
getIPData: () => getIPData, | |
pages: () => pages | |
}); | |
module.exports = __toCommonJS(permalocks_exports); | |
var import_lib = require("../../lib"); | |
function getIPData(ip) { | |
try { | |
return (0, import_lib.Net)("https://miapi.dev/api/ip/" + ip).get().then(JSON.parse); | |
} catch { | |
return null; | |
} | |
} | |
const Smogon = new class { | |
async post(threadNum, postText) { | |
if (!Config.smogon) | |
return null; | |
try { | |
const raw = await (0, import_lib.Net)(`https://www.smogon.com/forums/api/posts`).get({ | |
method: "POST", | |
body: new URLSearchParams({ | |
thread_id: threadNum, | |
message: postText | |
}).toString(), | |
headers: { | |
"XF-Api-Key": Config.smogon, | |
"Content-Type": "application/x-www-form-urlencoded" | |
} | |
}); | |
const data = JSON.parse(raw); | |
if (data.errors?.length) { | |
const errData = data.errors.pop(); | |
throw new Error(errData.message); | |
} | |
return data; | |
} catch (e) { | |
if (e.message.includes("Not Found")) { | |
throw new Error("WHO DELETED THE PERMA THREAD"); | |
} | |
return { error: e.message }; | |
} | |
} | |
}(); | |
const Nominations = new class { | |
constructor() { | |
this.noms = []; | |
this.icons = {}; | |
this.load(); | |
} | |
load() { | |
try { | |
let data = JSON.parse((0, import_lib.FS)("config/chat-plugins/permas.json").readSync()); | |
if (Array.isArray(data)) { | |
data = { noms: data, icons: {} }; | |
(0, import_lib.FS)("config/chat-plugins/permas.json").writeSync(JSON.stringify(data)); | |
} | |
this.noms = data.noms; | |
this.icons = data.icons; | |
} catch { | |
} | |
} | |
fetchModlog(id) { | |
return Rooms.Modlog.search("global", { | |
user: [{ search: id, isExact: true }], | |
note: [], | |
ip: [], | |
action: [], | |
actionTaker: [] | |
}, void 0, true); | |
} | |
save() { | |
(0, import_lib.FS)("config/chat-plugins/permas.json").writeUpdate(() => JSON.stringify({ noms: this.noms, icons: this.icons })); | |
} | |
notifyStaff() { | |
const usRoom = Rooms.get("upperstaff"); | |
if (!usRoom) | |
return; | |
usRoom.send(`|uhtml|permanoms|${this.getDisplayButton()}`); | |
Chat.refreshPageFor("permalocks", usRoom); | |
} | |
async add(target, connection) { | |
const user = connection.user; | |
const [primary, rawAlts, rawIps, type, details] = import_lib.Utils.splitFirst(target, "|", 4).map((f) => f.trim()); | |
const primaryID = toID(primary); | |
const alts = rawAlts.split(",").map(toID).filter(Boolean); | |
const ips = rawIps.split(",").map((f) => f.trim()).filter(Boolean); | |
for (const ip of ips) { | |
if (!IPTools.ipRegex.test(ip)) | |
this.error(`Invalid IP: ${ip}`, connection); | |
} | |
const standings = this.getStandings(); | |
if (!standings[type]) { | |
this.error(`Invalid standing: ${type}.`, connection); | |
} | |
if (!details) { | |
this.error("Details must be provided. Explain why this user should be permalocked.", connection); | |
} | |
if (!primaryID) { | |
this.error("A primary username must be provided. Use one of their alts if necessary.", connection); | |
} | |
for (const nom of this.noms) { | |
if (nom.primaryID === primaryID) { | |
this.error(`'${primaryID}' was already nominated for permalock by ${nom.by}.`, connection); | |
} | |
} | |
const ipTable = new Set(ips); | |
const altTable = /* @__PURE__ */ new Set([...alts]); | |
for (const alt of [primaryID, ...alts]) { | |
const modlog = await this.fetchModlog(alt); | |
if (!modlog?.results.length) | |
continue; | |
for (const entry of modlog.results) { | |
if (entry.ip) | |
ipTable.add(entry.ip); | |
if (entry.autoconfirmedID) | |
altTable.add(entry.autoconfirmedID); | |
if (entry.alts) { | |
for (const id of entry.alts) | |
altTable.add(id); | |
} | |
} | |
} | |
altTable.delete(primaryID); | |
this.noms.push({ | |
by: user.id, | |
alts: [...altTable], | |
ips: import_lib.Utils.sortBy([...ipTable], (z) => -(IPTools.ipToNumber(z) || Infinity)), | |
info: details, | |
primaryID, | |
standing: type, | |
date: Date.now() | |
}); | |
import_lib.Utils.sortBy(this.noms, (nom) => -nom.date); | |
this.save(); | |
this.notifyStaff(); | |
Rooms.get("staff")?.addByUser(user, `${user.name} submitted a perma nomination for ${primaryID}`); | |
} | |
find(id) { | |
return this.noms.find((f) => f.primaryID === id); | |
} | |
error(message, conn) { | |
conn.popup(message); | |
throw new Chat.Interruption(); | |
} | |
close(target, context) { | |
const entry = this.find(target); | |
if (!entry) { | |
this.error(`There is no nomination pending for '${toID(target)}'.`, context.connection); | |
} | |
this.noms.splice(this.noms.findIndex((f) => f.primaryID === entry.primaryID), 1); | |
this.save(); | |
this.notifyStaff(); | |
return context.closePage(`permalocks-view-${entry.primaryID}`); | |
} | |
display(nom, canEdit) { | |
let buf = `<div class="infobox">`; | |
let title = nom.primaryID; | |
if (canEdit) { | |
title = `<a href="/view-permalocks-view-${nom.primaryID}" target="_replace">${nom.primaryID}</a>`; | |
} | |
buf += `<strong>${title}</strong> (submitted by ${nom.by})<br />`; | |
buf += `Submitted ${Chat.toTimestamp(new Date(nom.date), { human: true })}<br />`; | |
buf += `${Chat.count(nom.alts, "alts")}, ${Chat.count(nom.ips, "IPs")}`; | |
buf += `</div>`; | |
return buf; | |
} | |
displayModlog(results) { | |
if (!results) | |
return ""; | |
let curDate = ""; | |
return results.map((result) => { | |
const date = new Date(result.time || Date.now()); | |
const entryRoom = result.visualRoomID || result.roomID || "global"; | |
let [dateString, timestamp] = Chat.toTimestamp(date, { human: true }).split(" "); | |
let line = `<small>[${timestamp}] (${entryRoom})</small> ${result.action}`; | |
if (result.userid) { | |
line += `: [${result.userid}]`; | |
if (result.autoconfirmedID) | |
line += ` ac: [${result.autoconfirmedID}]`; | |
if (result.alts.length) | |
line += ` alts: [${result.alts.join("], [")}]`; | |
if (result.ip) | |
line += ` [<a href="https://whatismyipaddress.com/ip/${result.ip}" target="_blank">${result.ip}</a>]`; | |
} | |
if (result.loggedBy) | |
line += `: by ${result.loggedBy}`; | |
if (result.note) | |
line += import_lib.Utils.html`: ${result.note}`; | |
if (dateString !== curDate) { | |
curDate = dateString; | |
dateString = `</p><p>[${dateString}]<br />`; | |
} else { | |
dateString = ``; | |
} | |
const thisRoomID = entryRoom?.split(" ")[0]; | |
if (thisRoomID.startsWith("battle-")) { | |
timestamp = `<a href="/${thisRoomID}">${timestamp}</a>`; | |
} else { | |
const [day, time] = Chat.toTimestamp(date).split(" "); | |
timestamp = `<a href="/view-chatlog-${thisRoomID}--${day}--time-${toID(time)}">${timestamp}</a>`; | |
} | |
return `${dateString}${line}`; | |
}).join(`<br />`); | |
} | |
async displayActionPage(nom) { | |
let buf = `<div class="pad">`; | |
const standings = this.getStandings(); | |
buf += `<button class="button" name="send" value="/perma viewnom ${nom.primaryID}" style="float:right">`; | |
buf += `<i class="fa fa-refresh"></i> Refresh</button>`; | |
buf += `<h3>Nomination: ${nom.primaryID}</h3><hr />`; | |
buf += `<strong>By:</strong> ${nom.by} (on ${Chat.toTimestamp(new Date(nom.date))})<br />`; | |
buf += `<strong>Recommended punishment:</strong> ${standings[nom.standing]}<br />`; | |
buf += `<details class="readmore"><summary><strong>Modlog</strong></summary>`; | |
buf += `<div class="infobox limited">`; | |
const modlog = await this.fetchModlog(nom.primaryID); | |
if (!modlog) { | |
buf += `None found.`; | |
} else { | |
buf += this.displayModlog(modlog.results); | |
} | |
buf += `</div></details>`; | |
if (nom.alts.length) { | |
buf += `<details class="readmore"><summary><strong>Listed alts</strong></summary>`; | |
for (const [i, alt] of nom.alts.entries()) { | |
buf += `- ${alt}: `; | |
buf += `<form data-submitsend="/perma standing ${alt},{standing},{reason}">`; | |
buf += this.standingDropdown("standing"); | |
buf += ` <button class="button notifying" type="submit">Change standing</button>`; | |
buf += ` <input name="reason" placeholder="Reason" />`; | |
buf += `</form>`; | |
if (nom.alts[i + 1]) | |
buf += `<br />`; | |
} | |
buf += `</details>`; | |
} | |
if (nom.ips.length) { | |
buf += `<details class="readmore"><summary><strong>Listed IPs</strong></summary>`; | |
for (const [i, ip] of nom.ips.entries()) { | |
const ipData = await getIPData(ip); | |
buf += `- <a href="https://whatismyipaddress.com/ip/${ip}">${ip}</a>`; | |
if (ipData) { | |
buf += `(ISP: ${ipData.isp}, loc: ${ipData.city}, ${ipData.regionName} in ${ipData.country})`; | |
} | |
buf += `: `; | |
buf += `<form data-submitsend="/perma ipstanding ${ip},{standing},{reason}">`; | |
buf += this.standingDropdown("standing"); | |
buf += ` <button class="button notifying" type="submit">Change standing for all users on IP</button>`; | |
buf += ` <input name="reason" placeholder="Reason" />`; | |
buf += `</form>`; | |
if (nom.ips[i + 1]) | |
buf += `<br />`; | |
} | |
buf += `</details>`; | |
} | |
const [matches] = await LoginServer.request("ipmatches", { | |
id: nom.primaryID | |
}); | |
if (matches?.results?.length) { | |
buf += `<details class="readmore"><summary><strong>Registration IP matches</strong></summary>`; | |
for (const [i, { userid, banstate }] of matches.results.entries()) { | |
buf += `- ${userid}: `; | |
buf += `<form data-submitsend="/perma standing ${userid},{standing}">`; | |
buf += this.standingDropdown("standing", `${banstate}`); | |
buf += ` <button class="button notifying" type="submit">Change standing</button></form>`; | |
if (matches.results[i + 1]) | |
buf += `<br />`; | |
} | |
buf += `</details>`; | |
} | |
buf += `<p><strong>Staff notes:</strong></p>`; | |
buf += `<p><div class="infobox">${Chat.formatText(nom.info).replace(/\n/ig, "<br />")}</div></p>`; | |
buf += `<details class="readmore"><summary><strong>Act on primary:</strong></summary>`; | |
buf += `<form data-submitsend="/perma actmain ${nom.primaryID},{standing},{note}">`; | |
buf += `Standing: ${this.standingDropdown("standing")}`; | |
buf += `<br />Notes:<br />`; | |
buf += `<textarea name="note" style="width: 100%" cols="50" rows="10"></textarea><br />`; | |
buf += `<button class="button notifying" type="submit">Change standing and make post</button>`; | |
buf += `</form></details><br />`; | |
buf += `<button class="button notifying" name="send" value="/perma resolve ${nom.primaryID}">Mark resolved</button>`; | |
return buf; | |
} | |
standingDropdown(elemName, curStanding = null) { | |
let buf = `<select name="${elemName}">`; | |
const standings = this.getStandings(); | |
for (const k in standings) { | |
buf += `<option ${curStanding === k ? "disabled" : ""} value="${k}">${standings[k]}</option>`; | |
} | |
buf += `</select>`; | |
return buf; | |
} | |
getStandings() { | |
if (Config.standings) | |
return Config.standings; | |
Config.standings = { | |
"-20": "Confirmed", | |
"-10": "Autoconfirmed", | |
"0": "New", | |
"20": "Permalock", | |
"30": "Permaban", | |
"100": "Disabled" | |
}; | |
return Config.standings; | |
} | |
displayAll(canEdit) { | |
let buf = `<div class="pad">`; | |
buf += `<button class="button" name="send" value="/perma noms" style="float:right"><i class="fa fa-refresh"></i> Refresh</button>`; | |
buf += `<h3>Pending perma nominations</h3><hr />`; | |
if (!this.noms.length) { | |
buf += `None found.`; | |
return buf; | |
} | |
for (const nom of this.noms) { | |
buf += this.display(nom, canEdit); | |
buf += `<br />`; | |
} | |
return buf; | |
} | |
displayNomPage() { | |
let buf = `<div class="pad"><h3>Make a nomination for a permanent punishment.</h3><hr />`; | |
buf += `<form data-submitsend="/perma submit {primary}|{alts}|{ips}|{type}|{details}">`; | |
buf += `<div class="infobox">`; | |
buf += `<strong>Primary userid:</strong> <input name="primary" /><br />`; | |
buf += `<strong>Alts:</strong><br /><textarea name="alts"></textarea><br /><small>(Separated by commas)</small><br />`; | |
buf += `<strong>Static IPs:</strong><br /><textarea name="ips"></textarea><br /><small>(Separated by commas)</small></div><br />`; | |
buf += `<strong>Punishment:</strong> `; | |
buf += `<select name="type"><option value="20">Permalock</option><option value="30">Permaban</option></select>`; | |
buf += `<div class="infobox">`; | |
buf += `<strong>Please explain why this user deserves a permanent punishment</strong><br />`; | |
buf += `<small>Note: Modlogs are automatically included in review and do not need to be added here.</small><br />`; | |
buf += `<textarea style="width: 100%" name="details" cols="50" rows="10"></textarea></div>`; | |
buf += `<button class="button notifying" type="submit">Submit nomination</button>`; | |
return buf; | |
} | |
getDisplayButton() { | |
const unclaimed = this.noms.filter((f) => !f.claimed); | |
let buf = `<div class="infobox">`; | |
if (!this.noms.length) { | |
buf += `No permalock nominations active.`; | |
} else { | |
let className = "button"; | |
if (unclaimed.length) | |
className += " notifying"; | |
buf += `<button class="${className}" name="send" value="/j view-permalocks-list">`; | |
buf += `${Chat.count(this.noms.length, "nominations")}`; | |
if (unclaimed.length !== this.noms.length) { | |
buf += ` (${unclaimed.length} unclaimed)`; | |
} | |
buf += `</button>`; | |
} | |
buf += `</div>`; | |
return buf; | |
} | |
}(); | |
const commands = { | |
perma: { | |
""(target, room, user) { | |
this.checkCan("lock"); | |
if (!user.can("rangeban")) { | |
return this.parse(`/j view-permalocks-submit`); | |
} else { | |
return this.parse(`/j view-permalocks-list`); | |
} | |
}, | |
viewnom(target) { | |
this.checkCan("rangeban"); | |
return this.parse(`/j view-permalocks-view-${toID(target)}`); | |
}, | |
submit(target, room, user) { | |
this.checkCan("lock"); | |
return Nominations.add(target, this.connection); | |
}, | |
list() { | |
this.checkCan("lock"); | |
return this.parse(`/j view-permalocks-list`); | |
}, | |
nom() { | |
this.checkCan("lock"); | |
return this.parse(`/j view-permalocks-submit`); | |
}, | |
async actmain(target, room, user) { | |
this.checkCan("rangeban"); | |
const [primaryName, standingName, postReason] = import_lib.Utils.splitFirst(target, ",", 2).map((f) => f.trim()); | |
const primary = toID(primaryName); | |
if (!primary) | |
return this.popupReply(`Invalid primary username.`); | |
const nom = Nominations.find(primary); | |
if (!nom) | |
return this.popupReply(`No permalock nomination found for ${primary}.`); | |
const standing = parseInt(standingName); | |
const standings = Nominations.getStandings(); | |
if (!standings[standing]) | |
return this.popupReply(`Invalid standing.`); | |
if (!toID(postReason)) | |
return this.popupReply(`A reason must be given.`); | |
const threadNum = Config.permathread; | |
if (!threadNum) { | |
throw new Chat.ErrorMessage("The link to the perma has not been set - the post could not be made."); | |
} | |
let postBuf = `[b][url="https://${Config.routes.root}/users/${primary}"]${primary}[/url][/b]`; | |
const icon = Nominations.icons[user.id] ? `:${Nominations.icons[user.id]}: - ` : ``; | |
postBuf += ` was added to ${standings[standing]} by ${user.name} (${icon}${postReason}). | |
`; | |
postBuf += `Nominated by ${nom.by}. | |
[spoiler=Nomination notes]${nom.info}[/spoiler] | |
`; | |
postBuf += `${nom.alts.length ? `[spoiler=Alts]${nom.alts.join(", ")}[/spoiler]` : ""} | |
`; | |
if (nom.ips.length) { | |
postBuf += `[spoiler=IPs]`; | |
for (const ip of nom.ips) { | |
const ipData = await getIPData(ip); | |
postBuf += `- [url=https://whatismyipaddress.com/ip/${ip}]${ip}[/url]`; | |
if (ipData) { | |
postBuf += ` (ISP: ${ipData.isp}, loc: ${ipData.city}, ${ipData.regionName} in ${ipData.country})`; | |
} | |
postBuf += "\n"; | |
} | |
postBuf += `[/spoiler]`; | |
} | |
const modlog = await Nominations.fetchModlog(nom.primaryID); | |
if (modlog?.results.length) { | |
let rawHTML = Nominations.displayModlog(modlog.results); | |
rawHTML = rawHTML.replace(/<br \/>/g, "\n"); | |
rawHTML = import_lib.Utils.stripHTML(rawHTML); | |
rawHTML = rawHTML.replace(///g, "/"); | |
postBuf += ` | |
[spoiler=Modlog]${rawHTML}[/spoiler]`; | |
} | |
const res = await Smogon.post( | |
threadNum, | |
postBuf | |
); | |
if (!res || res.error) { | |
return this.popupReply(`Error making post: ${res?.error}`); | |
} | |
const url = `https://smogon.com/forums/threads/${threadNum}/post-${res.post.post_id}`; | |
const result = await LoginServer.request("setstanding", { | |
user: primary, | |
standing, | |
reason: url, | |
actor: user.id | |
}); | |
if (result[1]) { | |
return this.popupReply(`Error changing standing: ${result[1].message}`); | |
} | |
nom.post = url; | |
this.popupReply(`|html|Standing successfully changed. Smogon post can be found <a href="${url}">at this link</a>.`); | |
}, | |
async standing(target) { | |
this.checkCan("rangeban"); | |
const [name, rawStanding, reason] = import_lib.Utils.splitFirst(target, ",", 2).map((f) => f.trim()); | |
const id = toID(name); | |
if (!id || id.length > 18) { | |
return this.popupReply("Invalid username: " + name); | |
} | |
const standingNum = parseInt(rawStanding); | |
if (!standingNum) { | |
return this.popupReply(`Invalid standing: ` + rawStanding); | |
} | |
if (!reason.length) { | |
return this.popupReply(`A reason must be given.`); | |
} | |
const res = await LoginServer.request("setstanding", { | |
user: id, | |
standing: standingNum, | |
reason, | |
actor: this.user.id | |
}); | |
if (res[1]) { | |
return this.popupReply(`Error in standing change: ` + res[1].message); | |
} | |
this.popupReply(`Standing successfully changed to ${standingNum} for ${id}.`); | |
}, | |
async ipstanding(target) { | |
this.checkCan("rangeban"); | |
const [ip, standingName, reason] = import_lib.Utils.splitFirst(target, ",", 2).map((f) => f.trim()); | |
if (!IPTools.ipToNumber(ip)) { | |
return this.popupReply(`Invalid IP: ${ip}`); | |
} | |
const standingNum = parseInt(standingName); | |
if (!Config.standings[`${standingNum}`]) { | |
return this.popupReply(`Invalid standing: ${standingName}.`); | |
} | |
if (!reason.length) { | |
return this.popupReply("Specify a reason."); | |
} | |
const res = await LoginServer.request("ipstanding", { | |
reason, | |
standing: standingNum, | |
ip, | |
actor: this.user.id | |
}); | |
if (res[1]) { | |
return this.popupReply(`Error changing standing: ${res[1].message}`); | |
} | |
this.popupReply(`All standings on the IP ${ip} changed successfully to ${standingNum}.`); | |
this.globalModlog(`IPSTANDING`, null, `${standingNum}${reason ? ` (${reason})` : ""}`, ip); | |
}, | |
resolve(target) { | |
this.checkCan("rangeban"); | |
Nominations.close(target, this); | |
}, | |
seticon(target, room, user) { | |
this.checkCan("rangeban"); | |
let [monName, targetId] = target.split(","); | |
if (!targetId) | |
targetId = user.id; | |
const mon = Dex.species.get(monName); | |
if (!mon.exists) { | |
return this.errorReply(`Species ${monName} does not exist.`); | |
} | |
Nominations.icons[targetId] = mon.name.toLowerCase(); | |
Nominations.save(); | |
this.sendReply( | |
`|html|Updated ${targetId === user.id ? "your" : `${targetId}'s`} permalock post icon to <psicon pokemon='${mon.name.toLowerCase()}' />` | |
); | |
}, | |
deleteicon(target, room, user) { | |
this.checkCan("rangeban"); | |
const targetID = toID(target); | |
if (!Nominations.icons[targetID]) { | |
return this.errorReply(`${targetID} does not have an icon set.`); | |
} | |
delete Nominations.icons[targetID]; | |
Nominations.save(); | |
this.sendReply(`Removed ${targetID}'s permalock post icon.`); | |
}, | |
help: [ | |
"/perma nom OR /perma - Open the page to make a nomination for a permanent punishment. Requires: % @ ~", | |
"/perma list - View open nominations. Requires: % @ ~", | |
"/perma viewnom [userid] - View a nomination for the given [userid]. Requires: ~" | |
] | |
} | |
}; | |
const pages = { | |
permalocks: { | |
list(query, user, conn) { | |
this.checkCan("lock"); | |
this.title = "[Permalock Nominations]"; | |
return Nominations.displayAll(user.can("rangeban")); | |
}, | |
view(query, user) { | |
this.checkCan("rangeban"); | |
const id = toID(query.shift()); | |
if (!id) | |
return this.errorReply(`Invalid userid.`); | |
const nom = Nominations.find(id); | |
if (!nom) | |
return this.errorReply(`No nomination found for '${id}'.`); | |
this.title = `[Perma Nom] ${nom.primaryID}`; | |
return Nominations.displayActionPage(nom); | |
}, | |
submit() { | |
this.checkCan("lock"); | |
this.title = "[Perma Nom] Create"; | |
return Nominations.displayNomPage(); | |
} | |
} | |
}; | |
process.nextTick(() => { | |
Chat.multiLinePattern.register("/perma(noms?)? "); | |
}); | |
//# sourceMappingURL=permalocks.js.map | |