Spaces:
Paused
Paused
| /** | |
| * Chat plugin to help manage hosts, proxies, and datacenters | |
| * Written by Annika | |
| * Original /adddatacenters command written by Zarel | |
| */ | |
| import { Utils } from "../../lib"; | |
| import type { AddressRange } from "../ip-tools"; | |
| import type { GlobalPermission } from "../user-groups"; | |
| const HOST_SUFFIXES = ['res', 'proxy', 'mobile']; | |
| const SUFFIX_ALIASES: { [k: string]: string } = { | |
| residential: 'res', | |
| }; | |
| const WHITELISTED_USERIDS: ID[] = []; | |
| function checkCanPerform( | |
| context: Chat.PageContext | Chat.CommandContext, user: User, permission: GlobalPermission = 'lockdown' | |
| ) { | |
| if (!WHITELISTED_USERIDS.includes(user.id)) context.checkCan(permission); | |
| } | |
| function getHostType(type: string) { | |
| type = toID(type); | |
| if (HOST_SUFFIXES.includes(type)) return type; | |
| if (SUFFIX_ALIASES[type]) return SUFFIX_ALIASES[type]; | |
| throw new Chat.ErrorMessage(`'${type}' is not a valid host type. Please specify one of ${HOST_SUFFIXES.join(', ')}.`); | |
| } | |
| export function visualizeRangeList(ranges: AddressRange[]) { | |
| let html = `<tr><th>Lowest IP address</th><th>Highest IP address</th><th>Host</th></tr>`; | |
| for (const range of ranges) { | |
| html += `<tr>`; | |
| html += `<td>${IPTools.numberToIP(range.minIP)}</td>`; | |
| html += `<td>${IPTools.numberToIP(range.maxIP)}</td>`; | |
| html += Utils.html`<td>${range.host}</td>`; | |
| html += `</tr>`; | |
| } | |
| return html; | |
| } | |
| function formatRange(range: AddressRange, includeModlogBrackets?: boolean) { | |
| const startBracket = includeModlogBrackets ? '[' : ''; | |
| const endBracket = includeModlogBrackets ? ']' : ''; | |
| let result = `${startBracket}${IPTools.numberToIP(range.minIP)}${endBracket}`; | |
| result += `-${startBracket}${IPTools.numberToIP(range.maxIP)}${endBracket}`; | |
| if (range.host) result += ` (${range.host})`; | |
| return result; | |
| } | |
| export const pages: Chat.PageTable = { | |
| proxies(query, user) { | |
| this.title = "[Proxies]"; | |
| checkCanPerform(this, user, 'globalban'); | |
| const openProxies = [...IPTools.singleIPOpenProxies]; | |
| const proxyHosts = [...IPTools.proxyHosts]; | |
| Utils.sortBy(openProxies, ip => { | |
| const number = IPTools.ipToNumber(ip); | |
| if (number === null) { | |
| Rooms.get('upperstaff')?.add(`|error|Invalid IP address in IPTools.singleIPOpenProxies: '${ip}'`); | |
| return -1; | |
| } | |
| return number; | |
| }); | |
| proxyHosts.sort(); | |
| IPTools.sortRanges(); | |
| let html = `<div class="ladder pad"><h2>Single IP proxies:</h2><table><tr><th>IP</th><th>Type</th></tr>`; | |
| for (const proxyIP of openProxies) { | |
| html += `<tr><td>${proxyIP}</td><td>Single IP open proxy</td></tr>`; | |
| } | |
| html += `</table></div>`; | |
| html += `<div class="ladder pad"><h2>Proxy hosts:</h2><table><tr><th>Host</th><th>Type</th></tr>`; | |
| for (const proxyHost of proxyHosts) { | |
| html += `<tr><td>${proxyHost}</td><td>Proxy host</td></tr>`; | |
| } | |
| html += `</table></div>`; | |
| html += `<div class="ladder pad"><h2>Proxy IP Ranges:</h2><table>`; | |
| html += visualizeRangeList(IPTools.ranges.filter(r => r.host?.endsWith('/proxy'))); | |
| html += `</table></div>`; | |
| return html; | |
| }, | |
| hosts(query, user) { | |
| this.title = "[Hosts]"; | |
| checkCanPerform(this, user, 'globalban'); | |
| const type = toID(query[0]) || 'all'; | |
| IPTools.sortRanges(); | |
| const mobileHosts = ['all', 'mobile'].includes(type) ? [...IPTools.mobileHosts] : []; | |
| const residentialHosts = ['all', 'residential', 'res'].includes(type) ? [...IPTools.residentialHosts] : []; | |
| const hostRanges = ['all', 'ranges', 'isps'].includes(type) ? | |
| IPTools.ranges.filter(r => r.host && !r.host.endsWith('/proxy')) : | |
| []; | |
| mobileHosts.sort(); | |
| residentialHosts.sort(); | |
| let html = `<div class="ladder pad"><h2>Mobile hosts:</h2><table><tr><th>Host</th><th>Type</th></tr>`; | |
| for (const mobileHost of mobileHosts) { | |
| html += `<tr><td>${mobileHost}</td><td>Mobile host</td></tr>`; | |
| } | |
| html += `</table></div>`; | |
| html += `<div class="ladder pad"><h2>Residential hosts:</h2><table><tr><th>Host</th><th>Type</th></tr>`; | |
| for (const resHost of residentialHosts) { | |
| html += `<tr><td>${resHost}</td><td>Residential host</td></tr>`; | |
| } | |
| html += `</table></div>`; | |
| html += `<div class="ladder pad"><h2>ISP IP Ranges:</h2><table>`; | |
| html += visualizeRangeList(hostRanges); | |
| html += `</table></div>`; | |
| return html; | |
| }, | |
| ranges(query, user) { | |
| this.title = "[IP Ranges]"; | |
| checkCanPerform(this, user, 'globalban'); | |
| const type = toID(query[0]) || 'all'; | |
| IPTools.sortRanges(); | |
| let html = ``; | |
| if (['all', 'mobile'].includes(type)) { | |
| html += `<div class="ladder pad"><h2>Mobile IP Ranges:</h2><table>`; | |
| html += visualizeRangeList(IPTools.ranges.filter(range => range.host?.endsWith('/mobile'))); | |
| html += `</table></div>`; | |
| } | |
| if (['all', 'res', 'residential'].includes(type)) { | |
| html += `<div class="ladder pad"><h2>Residential IP Ranges:</h2><table>`; | |
| html += visualizeRangeList(IPTools.ranges.filter(range => range.host?.endsWith('/res'))); | |
| html += `</table></div>`; | |
| } | |
| if (['all', 'proxy', 'proxies'].includes(type)) { | |
| html += `<div class="ladder pad"><h2>Proxy IP Ranges:</h2><table>`; | |
| html += visualizeRangeList(IPTools.ranges.filter(range => range.host?.endsWith('/proxy'))); | |
| html += `</table></div>`; | |
| } | |
| if (['all', 'openproxy'].includes(type)) { | |
| html += `<div class="ladder pad"><h2>Single-IP Open Proxies:</h2><table>`; | |
| for (const ip of IPTools.singleIPOpenProxies) { | |
| html += `<tr><td>${ip}</td></tr>`; | |
| } | |
| html += `</table></div>`; | |
| } | |
| return html; | |
| }, | |
| sharedipblacklist(args, user, connection) { | |
| this.title = `[Shared IP Blacklist]`; | |
| checkCanPerform(this, user, 'lock'); | |
| let buf = `<div class="pad"><h2>IPs blocked from being marked as shared</h2>`; | |
| if (!Punishments.sharedIpBlacklist.size) { | |
| buf += `<p>None currently.</p>`; | |
| } else { | |
| buf += `<div class="ladder"><table><tr><th>IP</th><th>Reason</th></tr>`; | |
| const sortedSharedIPBlacklist = [...Punishments.sharedIpBlacklist]; | |
| Utils.sortBy(sortedSharedIPBlacklist, ([ipOrRange]) => { | |
| if (IPTools.ipRegex.test(ipOrRange)) { | |
| const number = IPTools.ipToNumber(ipOrRange); | |
| if (number === null) { | |
| Monitor.error(`Invalid blacklisted-from-markshared IP: '${ipOrRange}`); | |
| return -1; | |
| } | |
| return number; | |
| } | |
| return IPTools.stringToRange(ipOrRange)!.minIP; | |
| }); | |
| for (const [ip, reason] of sortedSharedIPBlacklist) { | |
| buf += `<tr><td>${ip}</td><td>${reason}</td></tr>`; | |
| } | |
| buf += `</table></div>`; | |
| } | |
| buf += `</div>`; | |
| return buf; | |
| }, | |
| sharedips(args, user, connection) { | |
| this.title = `[Shared IPs]`; | |
| checkCanPerform(this, user, 'globalban'); | |
| let buf = `<div class="pad"><h2>IPs marked as shared</h2>`; | |
| if (!Punishments.sharedIps.size) { | |
| buf += `<p>None currently.</p>`; | |
| } else { | |
| buf += `<div class="ladder"><table><tr><th>IP</th><th>Location</th></tr>`; | |
| const sortedSharedIPs = [...Punishments.sharedIps]; | |
| Utils.sortBy(sortedSharedIPs, ([ip]) => { | |
| const number = IPTools.ipToNumber(ip); | |
| if (number === null) { | |
| Monitor.error(`Invalid shared IP address: '${ip}'`); | |
| return -1; | |
| } | |
| return number; | |
| }); | |
| for (const [ip, location] of sortedSharedIPs) { | |
| buf += `<tr><td>${ip}</td><td>${location}</td></tr>`; | |
| } | |
| buf += `</table></div>`; | |
| } | |
| buf += `</div>`; | |
| return buf; | |
| }, | |
| }; | |
| export const commands: Chat.ChatCommands = { | |
| dc: 'ipranges', | |
| datacenter: 'ipranges', | |
| datacenters: 'ipranges', | |
| iprange: 'ipranges', | |
| ipranges: { | |
| '': 'help', | |
| help() { | |
| return this.parse('/help ipranges'); | |
| }, | |
| show: 'view', | |
| view(target, room, user) { | |
| checkCanPerform(this, user, 'globalban'); | |
| const types = ['all', 'residential', 'res', 'mobile', 'proxy', 'openproxy']; | |
| const type = target ? toID(target) : 'all'; | |
| if (!types.includes(type)) { | |
| return this.errorReply(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`); | |
| } | |
| return this.parse(`/join view-ranges-${type}`); | |
| }, | |
| viewhelp: [ | |
| `/ipranges view - View the list of all IP ranges. Requires: hosts manager @ ~`, | |
| `/ipranges view [type] - View the list of a particular type of IP range ('residential', 'mobile', or 'proxy'). Requires: hosts manager @ ~`, | |
| ], | |
| // Originally by Zarel | |
| widen: 'add', | |
| add(target, room, user, connection, cmd) { | |
| checkCanPerform(this, user, 'globalban'); | |
| if (!target) return this.parse('/help ipranges add'); | |
| // should be in the format: IP, IP, name, URL | |
| const widen = cmd.includes('widen'); | |
| const [typeString, stringRange, host] = target.split(',').map(part => part.trim()); | |
| if (!host || !IPTools.hostRegex.test(host)) { | |
| return this.errorReply(`Invalid data: ${target}`); | |
| } | |
| const type = getHostType(typeString); | |
| const range = IPTools.stringToRange(stringRange); | |
| if (!range) return this.errorReply(`Couldn't parse IP range '${stringRange}'.`); | |
| range.host = `${IPTools.urlToHost(host)}?/${type}`; | |
| IPTools.sortRanges(); | |
| let result; | |
| try { | |
| result = IPTools.checkRangeConflicts(range, IPTools.ranges, widen); | |
| } catch (e: any) { | |
| return this.errorReply(e.message); | |
| } | |
| if (typeof result === 'number') { | |
| // Remove the range that is being widened | |
| IPTools.removeRange(IPTools.ranges[result].minIP, IPTools.ranges[result].maxIP); | |
| } | |
| IPTools.addRange(range as AddressRange & { host: string }); | |
| this.privateGlobalModAction(`${user.name} added the IP range ${formatRange(range)} to the list of ${type} ranges.`); | |
| this.globalModlog('IPRANGE ADD', null, formatRange(range, true)); | |
| }, | |
| addhelp: [ | |
| `/ipranges add [type], [low]-[high], [host] - Adds an IP range. Requires: hosts manager ~`, | |
| `/ipranges widen [type], [low]-[high], [host] - Adds an IP range, allowing a new range to completely cover an old range. Requires: hosts manager ~`, | |
| `For example: /ipranges add proxy, 5.152.192.0 - 5.152.223.255, redstation.com`, | |
| `Get datacenter info from whois; [low], [high] are the range in the last inetnum; [type] is one of res, proxy, or mobile.`, | |
| ], | |
| remove(target, room, user) { | |
| checkCanPerform(this, user); | |
| if (!target) return this.parse('/help ipranges remove'); | |
| const range = IPTools.stringToRange(target); | |
| if (!range) return this.errorReply(`Couldn't parse the IP range '${target}'.`); | |
| if (!IPTools.getRange(range.minIP, range.maxIP)) return this.errorReply(`No IP range found at '${target}'.`); | |
| void IPTools.removeRange(range.minIP, range.maxIP); | |
| this.privateGlobalModAction(`${user.name} removed the IP range ${formatRange(range)}.`); | |
| this.globalModlog('IPRANGE REMOVE', null, formatRange(range, true)); | |
| }, | |
| removehelp: [ | |
| `/ipranges remove [low IP]-[high IP] - Removes an IP range. Requires: hosts manager ~`, | |
| `Example: /ipranges remove 5.152.192.0-5.152.223.255`, | |
| ], | |
| rename(target, room, user) { | |
| checkCanPerform(this, user); | |
| if (!target) return this.parse('/help ipranges rename'); | |
| const [type, rangeString, url] = target.split(',').map(part => part.trim()); | |
| if (!url) { | |
| return this.parse('/help ipranges rename'); | |
| } | |
| const toRename = IPTools.stringToRange(rangeString); | |
| if (!toRename) return this.errorReply(`Couldn't parse IP range '${rangeString}'.`); | |
| const exists = IPTools.getRange(toRename.minIP, toRename.maxIP); | |
| if (!exists) return this.errorReply(`No IP range found at '${rangeString}'.`); | |
| const range = { | |
| minIP: toRename.minIP, | |
| maxIP: toRename.maxIP, | |
| host: `${IPTools.urlToHost(url)}?/${type}`, | |
| }; | |
| void IPTools.addRange(range); | |
| this.privateGlobalModAction(`${user.name} renamed the IP range ${formatRange(toRename)} to ${range.host}.`); | |
| this.globalModlog('IPRANGE RENAME', null, `IP range ${formatRange(toRename, true)} to ${range.host}`); | |
| }, | |
| renamehelp: [ | |
| `/ipranges rename [type], [low IP]-[high IP], [host] - Changes the host an IP range resolves to. Requires: hosts manager ~`, | |
| ], | |
| }, | |
| iprangeshelp() { | |
| const help = [ | |
| `<code>/ipranges view [type]</code>: view the list of a particular type of IP range (<code>residential</code>, <code>mobile</code>, or <code>proxy</code>). Requires: hosts manager @ ~`, | |
| `<code>/ipranges add [type], [low IP]-[high IP], [host]</code>: add IP ranges (can be multiline). Requires: hosts manager ~</summary><code>/ipranges view</code>: view the list of all IP ranges. Requires: hosts manager @ ~`, | |
| `<code>/ipranges widen [type], [low IP]-[high IP], [host]</code>: add IP ranges, allowing a new range to completely cover an old range. Requires: hosts manager ~`, | |
| `For example: <code>/ipranges add proxy, 5.152.192.0-5.152.223.255, redstation.com</code>.`, | |
| `Get datacenter info from <code>/whois</code>; <code>[low IP]</code>, <code>[high IP]</code> are the range in the last inetnum.`, | |
| `<code>/ipranges remove [low IP]-[high IP]</code>: remove IP range(s). Can be multiline. Requires: hosts manager ~`, | |
| `For example: <code>/ipranges remove 5.152.192.0, 5.152.223.255</code>.`, | |
| `<code>/ipranges rename [type], [low IP]-[high IP], [host]</code>: changes the host an IP range resolves to. Requires: hosts manager ~`, | |
| ]; | |
| return this.sendReply(`|html|<details class="readmore"><summary>${help.join('<br />')}`); | |
| }, | |
| viewhosts(target, room, user) { | |
| checkCanPerform(this, user, 'globalban'); | |
| const types = ['all', 'residential', 'mobile', 'ranges']; | |
| const type = target ? toID(target) : 'all'; | |
| if (!types.includes(type)) { | |
| return this.errorReply(`'${type}' isn't a valid host type. Specify one of ${types.join(', ')}.`); | |
| } | |
| return this.parse(`/join view-hosts-${type}`); | |
| }, | |
| viewhostshelp: [ | |
| `/viewhosts - View the list of hosts. Requires: hosts manager @ ~`, | |
| `/viewhosts [type] - View the list of a particular type of host. Requires: hosts manager @ ~`, | |
| `Host types are: 'all', 'residential', 'mobile', and 'ranges'.`, | |
| ], | |
| removehost: 'addhosts', | |
| removehosts: 'addhosts', | |
| addhost: 'addhosts', | |
| addhosts(target, room, user, connection, cmd) { | |
| checkCanPerform(this, user); | |
| const removing = cmd.includes('remove'); | |
| let [type, ...hosts] = target.split(','); | |
| type = toID(type); | |
| hosts = hosts.map(host => host.trim()); | |
| if (!hosts.length) return this.parse('/help addhosts'); | |
| switch (type) { | |
| case 'openproxy': | |
| for (const host of hosts) { | |
| if (!IPTools.ipRegex.test(host)) return this.errorReply(`'${host}' is not a valid IP address.`); | |
| if (removing !== IPTools.singleIPOpenProxies.has(host)) { | |
| return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy IPs.`); | |
| } | |
| } | |
| if (removing) { | |
| void IPTools.removeOpenProxies(hosts); | |
| } else { | |
| void IPTools.addOpenProxies(hosts); | |
| } | |
| break; | |
| case 'proxy': | |
| for (const host of hosts) { | |
| if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`); | |
| if (removing !== IPTools.proxyHosts.has(host)) { | |
| return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of proxy hosts.`); | |
| } | |
| } | |
| if (removing) { | |
| void IPTools.removeProxyHosts(hosts); | |
| } else { | |
| void IPTools.addProxyHosts(hosts); | |
| } | |
| break; | |
| case 'residential': | |
| for (const host of hosts) { | |
| if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`); | |
| if (removing !== IPTools.residentialHosts.has(host)) { | |
| return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of residential hosts.`); | |
| } | |
| } | |
| if (removing) { | |
| void IPTools.removeResidentialHosts(hosts); | |
| } else { | |
| void IPTools.addResidentialHosts(hosts); | |
| } | |
| break; | |
| case 'mobile': | |
| for (const host of hosts) { | |
| if (!IPTools.hostRegex.test(host)) return this.errorReply(`'${host}' is not a valid host.`); | |
| if (removing !== IPTools.mobileHosts.has(host)) { | |
| return this.errorReply(`'${host}' is ${removing ? 'not' : 'already'} in the list of mobile hosts.`); | |
| } | |
| } | |
| if (removing) { | |
| void IPTools.removeMobileHosts(hosts); | |
| } else { | |
| void IPTools.addMobileHosts(hosts); | |
| } | |
| break; | |
| default: | |
| return this.errorReply(`'${type}' isn't one of 'openproxy', 'proxy', 'residential', or 'mobile'.`); | |
| } | |
| this.privateGlobalModAction( | |
| `${user.name} ${removing ? 'removed' : 'added'} ${hosts.length} hosts (${hosts.join(', ')}) ${removing ? 'from' : 'to'} the ${type} category` | |
| ); | |
| this.globalModlog(removing ? 'REMOVEHOSTS' : 'ADDHOSTS', null, `${type}: ${hosts.join(', ')}`); | |
| }, | |
| addhostshelp: [ | |
| `/addhosts [category], host1, host2, ... - Adds hosts to the given category. Requires: hosts manager ~`, | |
| `/removehosts [category], host1, host2, ... - Removes hosts from the given category. Requires: hosts manager ~`, | |
| `Categories are: 'openproxy' (which takes IP addresses, not hosts), 'proxy', 'residential', and 'mobile'.`, | |
| ], | |
| viewproxies(target, room, user) { | |
| checkCanPerform(this, user, 'globalban'); | |
| return this.parse('/join view-proxies'); | |
| }, | |
| viewproxieshelp: [ | |
| `/viewproxies - View the list of proxies. Requires: hosts manager @ ~`, | |
| ], | |
| markshared(target, room, user) { | |
| if (!target) return this.parse('/help markshared'); | |
| checkCanPerform(this, user, 'globalban'); | |
| const [ip, note] = this.splitOne(target); | |
| if (!IPTools.ipRegex.test(ip)) { | |
| const pattern = IPTools.stringToRange(ip); | |
| if (!pattern) { | |
| return this.errorReply("Please enter a valid IP address."); | |
| } | |
| if (!user.can('rangeban')) { | |
| return this.errorReply('Only upper staff can markshare ranges.'); | |
| } | |
| for (const range of Punishments.sharedRanges.keys()) { | |
| if (IPTools.rangeIntersects(range, pattern)) { | |
| return this.errorReply( | |
| `Range ${IPTools.rangeToString(pattern)} intersects with shared range ${IPTools.rangeToString(range)}` | |
| ); | |
| } | |
| } | |
| } | |
| if (Punishments.isSharedIp(ip)) return this.errorReply("This IP is already marked as shared."); | |
| if (Punishments.isBlacklistedSharedIp(ip)) { | |
| return this.errorReply(`This IP is blacklisted from being marked as shared.`); | |
| } | |
| if (!note) { | |
| this.errorReply(`You must specify who owns this shared IP.`); | |
| this.parse(`/help markshared`); | |
| return; | |
| } | |
| Punishments.addSharedIp(ip, note); | |
| this.privateGlobalModAction(`The IP '${ip}' was marked as shared by ${user.name}. (${note})`); | |
| this.globalModlog('SHAREDIP', null, note, ip); | |
| }, | |
| marksharedhelp: [ | |
| `/markshared [IP], [owner/organization of IP] - Marks an IP address as shared.`, | |
| `Note: the owner/organization (i.e., University of Minnesota) of the shared IP is required. Requires @ ~`, | |
| ], | |
| unmarkshared(target, room, user) { | |
| if (!target) return this.parse('/help unmarkshared'); | |
| checkCanPerform(this, user, 'globalban'); | |
| target = target.trim(); | |
| const pattern = IPTools.stringToRange(target); | |
| if (!pattern) return this.errorReply("Please enter a valid IP address."); | |
| if (pattern.minIP !== pattern.maxIP && !user.can('rangeban')) { | |
| return this.errorReply(`Only administrators can unmarkshare ranges.`); | |
| } | |
| let shared = false; | |
| if (pattern.minIP !== pattern.maxIP) { | |
| for (const range of Punishments.sharedRanges.keys()) { | |
| shared = range.minIP === pattern.minIP && range.maxIP === pattern.maxIP; | |
| if (shared) break; | |
| } | |
| } else { | |
| shared = Punishments.sharedIps.has(target); | |
| } | |
| if (!shared) return this.errorReply(`That IP/range isn't marked as shared.`); | |
| Punishments.removeSharedIp(target); | |
| this.privateGlobalModAction(`The IP '${target}' was unmarked as shared by ${user.name}.`); | |
| this.globalModlog('UNSHAREDIP', null, null, target); | |
| }, | |
| unmarksharedhelp: [`/unmarkshared [IP] - Unmarks a shared IP address. Requires @ ~`], | |
| marksharedblacklist: 'nomarkshared', | |
| marksharedbl: 'nomarkshared', | |
| nomarkshared: { | |
| add(target, room, user) { | |
| if (!target) return this.parse(`/help nomarkshared`); | |
| checkCanPerform(this, user, 'globalban'); | |
| const [ip, ...reasonArr] = target.split(','); | |
| if (!IPTools.ipRangeRegex.test(ip)) return this.errorReply(`Please enter a valid IP address or range.`); | |
| if (!reasonArr?.length) { | |
| this.errorReply(`A reason is required.`); | |
| this.parse(`/help nomarkshared`); | |
| return; | |
| } | |
| if (Punishments.isBlacklistedSharedIp(ip)) { | |
| return this.errorReply(`This IP is already blacklisted from being marked as shared.`); | |
| } | |
| // this works because we test ipRangeRegex above, which works for both ranges AND single ips. | |
| // so we know here this is one of the two. | |
| // If it doesn't work as a single IP, it's a range. | |
| if (!IPTools.ipRegex.test(ip)) { | |
| // if it doesn't end with *, it doesn't function as a range in IPTools#stringToRange, only as a single IP. | |
| // that's valid behavior, but it's detrimental here. | |
| if (!ip.endsWith('*')) { | |
| this.errorReply(`That looks like a range, but it is invalid.`); | |
| this.errorReply(`Append * to the end of the range and try again.`); | |
| return; | |
| } | |
| if (!user.can('bypassall')) { | |
| return this.errorReply(`Only Administrators can add ranges.`); | |
| } | |
| const range = IPTools.stringToRange(ip); | |
| if (!range) return this.errorReply(`Invalid IP range.`); | |
| for (const sharedIp of Punishments.sharedIps.keys()) { | |
| const ipNum = IPTools.ipToNumber(sharedIp); | |
| if (IPTools.checkPattern([range], ipNum)) { | |
| this.parse(`/unmarkshared ${sharedIp}`); | |
| } | |
| } | |
| } else { | |
| if (Punishments.isSharedIp(ip)) this.parse(`/unmarkshared ${ip}`); | |
| } | |
| const reason = reasonArr.join(','); | |
| Punishments.addBlacklistedSharedIp(ip, reason); | |
| this.privateGlobalModAction(`The IP '${ip}' was blacklisted from being marked as shared by ${user.name}.`); | |
| this.globalModlog('SHAREDIP BLACKLIST', null, reason.trim(), ip); | |
| }, | |
| remove(target, room, user) { | |
| if (!target) return this.parse(`/help nomarkshared`); | |
| checkCanPerform(this, user); | |
| if (!IPTools.ipRangeRegex.test(target)) return this.errorReply(`Please enter a valid IP address or range.`); | |
| if (!Punishments.sharedIpBlacklist.has(target)) { | |
| return this.errorReply(`This IP is not blacklisted from being marked as shared.`); | |
| } | |
| Punishments.removeBlacklistedSharedIp(target); | |
| this.privateGlobalModAction(`The IP '${target}' was unblacklisted from being marked as shared by ${user.name}.`); | |
| this.globalModlog('SHAREDIP UNBLACKLIST', null, null, target); | |
| }, | |
| view() { | |
| return this.parse(`/join view-sharedipblacklist`); | |
| }, | |
| help: '', | |
| ''() { | |
| return this.parse(`/help nomarkshared`); | |
| }, | |
| }, | |
| nomarksharedhelp: [ | |
| `/nomarkshared add [IP], [reason] - Prevents an IP from being marked as shared until it's removed from this list. Requires ~`, | |
| `Note: Reasons are required.`, | |
| `/nomarkshared remove [IP] - Removes an IP from the nomarkshared list. Requires ~`, | |
| `/nomarkshared view - Lists all IPs prevented from being marked as shared. Requires @ ~`, | |
| ], | |
| sharedips: 'viewsharedips', | |
| viewsharedips() { | |
| return this.parse('/join view-sharedips'); | |
| }, | |
| viewsharedipshelp: [ | |
| `/viewsharedips — Lists IP addresses marked as shared. Requires: hosts manager @ ~`, | |
| ], | |
| }; | |