mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-24 17:02:46 -05:00
370 lines
15 KiB
TypeScript
370 lines
15 KiB
TypeScript
/**
|
|
* Chat plugin to help manage hosts, proxies, and datacenters
|
|
* Written by Annika
|
|
* Original /adddatacenters command written by Zarel
|
|
*/
|
|
|
|
import {Utils} from "../../lib/utils";
|
|
import {Datacenter} from "../ip-tools";
|
|
|
|
const IP_REGEX = /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/;
|
|
const HOST_REGEX = /^.+\..{2,}$/;
|
|
|
|
const WHITELISTED_USERS = ['anubis'];
|
|
|
|
function ipSort(a: string, b: string) {
|
|
let i = 0;
|
|
let diff = 0;
|
|
const aParts = a.split('.');
|
|
const bParts = b.split('.');
|
|
while (diff === 0) {
|
|
diff = (parseInt(aParts[i]) || 0) - (parseInt(bParts[i]) || 0);
|
|
i++;
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
export const pages: PageTable = {
|
|
datacenters(query, user) {
|
|
this.title = "Datacenters";
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('globalban'))) return 'Permission denied.';
|
|
let html = `<div class="ladder pad"><h2>Datacenters:</h2><table>`;
|
|
html += `<tr><th>Lowest IP address</th><th>Highest IP address</th><th>Name</th><th>Host</th></tr>`;
|
|
IPTools.sortDatacenters();
|
|
for (const datacenter of IPTools.datacenters) {
|
|
html += `<tr>`;
|
|
html += `<td>${IPTools.numberToIP(datacenter.minIP)}</td>`;
|
|
html += `<td>${IPTools.numberToIP(datacenter.maxIP)}</td>`;
|
|
html += Utils.html`<td>${datacenter.name}</td>`;
|
|
html += Utils.html`<td>${datacenter.host}</td>`;
|
|
html += `</tr>`;
|
|
}
|
|
html += `</table></div>`;
|
|
return html;
|
|
},
|
|
|
|
hosts(query, user) {
|
|
this.title = "Hosts";
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('globalban'))) return 'Permission denied.';
|
|
const type = toID(query[0]) || 'all';
|
|
|
|
const openProxies = ['all', 'proxyips', 'proxies'].includes(type) ? [...IPTools.singleIPOpenProxies] : [];
|
|
const proxyHosts = ['all', 'proxyhosts', 'proxies'].includes(type) ? [...IPTools.proxyHosts] : [];
|
|
const mobileHosts = ['all', 'mobile'].includes(type) ? [...IPTools.mobileHosts] : [];
|
|
const residentialHosts = ['all', 'residential', 'res'].includes(type) ? [...IPTools.residentialHosts] : [];
|
|
|
|
openProxies.sort(ipSort);
|
|
proxyHosts.sort();
|
|
mobileHosts.sort();
|
|
residentialHosts.sort();
|
|
|
|
let html = `<div class="ladder pad"><h2>Hosts:</h2><table>`;
|
|
html += `<tr><th>Host</th><th>Type</th></tr>`;
|
|
|
|
for (const proxyIP of openProxies) {
|
|
html += `<tr><td>${proxyIP}</td><td>Single IP open proxy</td></tr>`;
|
|
}
|
|
for (const proxyHost of proxyHosts) {
|
|
html += `<tr><td>${proxyHost}</td><td>Proxy host</td></tr>`;
|
|
}
|
|
for (const mobileHost of mobileHosts) {
|
|
html += `<tr><td>${mobileHost}</td><td>Mobile host</td></tr>`;
|
|
}
|
|
for (const resHost of residentialHosts) {
|
|
html += `<tr><td>${resHost}</td><td>Residential host</td></tr>`;
|
|
}
|
|
html += `</table></div>`;
|
|
return html;
|
|
},
|
|
};
|
|
|
|
export const commands: ChatCommands = {
|
|
dc: 'datacenters',
|
|
datacenter: 'datacenters',
|
|
datacenters: {
|
|
'': 'help',
|
|
help() {
|
|
return this.parse('/help datacenters');
|
|
},
|
|
|
|
show: 'view',
|
|
view(target, room, user) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('globalban'))) return;
|
|
return this.parse('/join view-datacenters');
|
|
},
|
|
viewhelp: [
|
|
`/datacenters view - View the list of datacenters. Requires: hosts manager @ &`,
|
|
],
|
|
|
|
// Originally by Zarel
|
|
widen: 'add',
|
|
async add(target, room, user, connection, cmd) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('lockdown'))) return false;
|
|
if (!target) return this.parse('/help datacenters add');
|
|
// should be in the format: IP, IP, name, URL
|
|
const widen = cmd.includes('widen');
|
|
|
|
const datacentersToAdd: Datacenter[] = [];
|
|
for (const row of target.split('\n')) {
|
|
const [start, end, name, url] = row.split(',').map(part => part.trim());
|
|
if (!url || !IP_REGEX.test(start) || !IP_REGEX.test(end)) return this.errorReply(`Invalid data: ${row}`);
|
|
const datacenter = {
|
|
minIP: IPTools.ipToNumber(start),
|
|
maxIP: IPTools.ipToNumber(end),
|
|
name: name,
|
|
host: IPTools.urlToHost(url),
|
|
};
|
|
datacentersToAdd.push(datacenter);
|
|
}
|
|
|
|
let successes = 0;
|
|
let identicals = 0;
|
|
let widenSuccesses = 0;
|
|
for (const datacenter of datacentersToAdd) {
|
|
if (datacenter.maxIP < datacenter.minIP) {
|
|
this.errorReply(
|
|
`Invalid datacenter data for datacenter ${IPTools.numberToIP(datacenter.minIP)}-${IPTools.numberToIP(datacenter.maxIP)} (${datacenter.host})`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let iMin = 0;
|
|
let iMax = IPTools.datacenters.length;
|
|
while (iMin < iMax) {
|
|
const i = Math.floor((iMax + iMin) / 2);
|
|
if (datacenter.minIP > IPTools.datacenters[i].minIP) {
|
|
iMin = i + 1;
|
|
} else {
|
|
iMax = i;
|
|
}
|
|
}
|
|
if (iMin < IPTools.datacenters.length) {
|
|
const next = IPTools.datacenters[iMin];
|
|
if (datacenter.minIP === next.minIP && datacenter.maxIP === next.maxIP) {
|
|
identicals++;
|
|
continue;
|
|
}
|
|
if (datacenter.minIP <= next.minIP && datacenter.maxIP >= next.maxIP) {
|
|
if (widen === true) {
|
|
if (IPTools.datacenters[iMin + 1].minIP <= datacenter.maxIP) {
|
|
this.errorReply("You can only widen one datacenter at a time.");
|
|
continue;
|
|
}
|
|
widenSuccesses++;
|
|
await IPTools.removeDatacenter(next.minIP, next.maxIP);
|
|
void IPTools.addDatacenter(datacenter);
|
|
continue;
|
|
}
|
|
this.errorReply(`Too wide: ${IPTools.numberToIP(datacenter.minIP)}-${IPTools.numberToIP(datacenter.maxIP)} (${datacenter.host})`);
|
|
this.errorReply(
|
|
`Intersects with: ${IPTools.numberToIP(next.minIP)}-${IPTools.numberToIP(next.maxIP)} (${next.host})`
|
|
);
|
|
continue;
|
|
}
|
|
if (datacenter.maxIP >= next.minIP) {
|
|
this.errorReply(
|
|
`Could not insert: ${IPTools.numberToIP(datacenter.minIP)}-${IPTools.numberToIP(datacenter.maxIP)} ${datacenter.host}`
|
|
);
|
|
this.errorReply(
|
|
`Intersects with: ${IPTools.numberToIP(next.minIP)}-${IPTools.numberToIP(next.maxIP)} (${next.host})`
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
if (iMin > 0) {
|
|
const prev = IPTools.datacenters[iMin - 1];
|
|
if (datacenter.minIP >= prev.minIP && datacenter.maxIP <= prev.maxIP) {
|
|
this.errorReply(`Too narrow: ${IPTools.numberToIP(datacenter.minIP)}-${IPTools.numberToIP(datacenter.maxIP)} (${datacenter.host})`);
|
|
this.errorReply(`Intersects with: ${IPTools.numberToIP(prev.minIP)}-${IPTools.numberToIP(prev.maxIP)} (${prev.host})`);
|
|
continue;
|
|
}
|
|
if (datacenter.minIP <= prev.maxIP) {
|
|
this.errorReply(`Could not insert: ${IPTools.numberToIP(datacenter.minIP)}-${IPTools.numberToIP(datacenter.maxIP)} (${datacenter.host})`);
|
|
this.errorReply(`Intersects with: ${IPTools.numberToIP(prev.minIP)}-${IPTools.numberToIP(prev.maxIP)} (${prev.host})`);
|
|
continue;
|
|
}
|
|
}
|
|
successes++;
|
|
void IPTools.addDatacenter(datacenter);
|
|
}
|
|
|
|
const results = [];
|
|
if (successes) results.push(`added ${successes} datacenters`);
|
|
if (widenSuccesses) results.push(`widened ${widenSuccesses} datacenters`);
|
|
if (results.length) {
|
|
this.globalModlog('DATACENTER ADD', null, `by ${user.id}: ${results.join(', ')}`);
|
|
if (identicals) results.push(`${identicals} datacenters were already on the datacenter list`);
|
|
return this.sendReply(`Successfully ${results.join(' and ')}!`);
|
|
}
|
|
},
|
|
addhelp: [
|
|
`/datacenters add [low], [high], [name], [url] - Add datacenters (can be multiline). Requires: hosts manager &`,
|
|
`/datacenters widen [low], [high], [name], [url] - Add datacenters, allowing a new range to completely cover an old range. Requires: hosts manager &`,
|
|
`For example: /datacenters add 5.152.192.0, 5.152.223.255, Redstation Limited, http://redstation.com/`,
|
|
`Get datacenter info from whois; [low], [high] are the range in the last inetnum.`,
|
|
],
|
|
|
|
remove(target, room, user) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('lockdown'))) return false;
|
|
if (!target) return this.parse('/help datacenters remove');
|
|
let removed = 0;
|
|
for (const row of target.split('\n')) {
|
|
const [start, end] = row.split(',').map(ip => ip.trim());
|
|
if (!end || !IP_REGEX.test(start) || !IP_REGEX.test(end)) return this.errorReply(`Invalid data: ${row}`);
|
|
|
|
const minIP = IPTools.ipToNumber(start);
|
|
const maxIP = IPTools.ipToNumber(end);
|
|
if (!IPTools.getDatacenter(minIP, maxIP)) return this.errorReply(`No datacenter found at ${start}-${end}.`);
|
|
|
|
void IPTools.removeDatacenter(minIP, maxIP);
|
|
removed++;
|
|
}
|
|
this.globalModlog('DATACENTER REMOVE', null, `by ${user.id}: ${removed} datacenters`);
|
|
return this.sendReply(`Removed ${removed} datacenters!`);
|
|
},
|
|
removehelp: [
|
|
`/datacenters remove [low IP], [high IP] - Remove datacenter(s). Can be multiline. Requires: hosts manager &`,
|
|
`Example: /datacenters remove 5.152.192.0, 5.152.223.255`,
|
|
],
|
|
|
|
rename(target, room, user) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('lockdown'))) return false;
|
|
if (!target) return this.parse('/help datacenters rename');
|
|
const [start, end, name, url] = target.split(',').map(part => part.trim());
|
|
if (!(name || url) || !IP_REGEX.test(start) || !IP_REGEX.test(end)) return this.parse('/help renamedatacenter');
|
|
const minIP = IPTools.ipToNumber(start);
|
|
const maxIP = IPTools.ipToNumber(end);
|
|
const toRename = IPTools.getDatacenter(minIP, maxIP);
|
|
if (!toRename) return this.errorReply(`No datacenter found at ${start}-${end}`);
|
|
|
|
const datacenter = {
|
|
minIP: minIP,
|
|
maxIP: maxIP,
|
|
name: name || toRename.name,
|
|
host: url ? IPTools.urlToHost(url) : toRename.host,
|
|
};
|
|
void IPTools.addDatacenter(datacenter);
|
|
const renameInfo = `datacenter at ${datacenter.minIP}-${datacenter.maxIP} to ${datacenter.name} (${datacenter.host})`;
|
|
this.globalModlog('DATACENTER RENAME', null, `by ${user.id}: ${renameInfo}`);
|
|
return this.sendReply(`Renamed the ${renameInfo}.`);
|
|
},
|
|
renamehelp: [
|
|
`/datacenters rename [low IP], [high IP], [name], [url] - Renames a datacenter. You may leave one of [name] or [url] blank. Requires: hosts manager &`,
|
|
],
|
|
},
|
|
|
|
datacentershelp() {
|
|
const help = [
|
|
`<code>/datacenters view</code>: view the list of datacenters. Requires: hosts manager @ &`,
|
|
`<code>/datacenters add [low IP], [high IP], [name], [url]</code>: add datacenters (can be multiline). Requires: hosts manager &`,
|
|
`<code>/datacenters widen [low IP], [high IP], [name], [url]</code>: add datacenters, allowing a new range to completely cover an old range. Requires: hosts manager &`,
|
|
`For example: <code>/datacenters add 5.152.192.0, 5.152.223.255, Redstation Limited, http://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>/datacenters remove [low IP], [high IP]</code>: remove datacenter(s). Can be multiline. Requires: hosts manager &`,
|
|
`For example: <code>/datacenters remove 5.152.192.0, 5.152.223.255</code>.`,
|
|
`<code>/datacenters rename [low IP], [high IP], [name], [url]</code>: renames a datacenter. You may leave one of [name] or [url] blank. Requires: hosts manager &`,
|
|
];
|
|
return this.sendReply(`|html|<details class="readmore"><summary>Datacenter management commands:</summary>${help.join('<br />')}`);
|
|
},
|
|
|
|
viewhosts(target, room, user) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('globalban'))) return false;
|
|
const types = ['all', 'proxies', 'proxyips', 'proxyhosts', 'residential', 'mobile'];
|
|
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 all hosts and proxies. Requires: hosts manager @ &`,
|
|
`/viewhosts [type] - View the list of a particular type of proxy. Requires: hosts manager @ &`,
|
|
`Proxy types are: 'all', 'proxies', 'proxyips', 'proxyhosts', 'residential', and 'mobile'.`,
|
|
],
|
|
|
|
removehost: 'addhosts',
|
|
removehosts: 'addhosts',
|
|
addhost: 'addhosts',
|
|
addhosts(target, room, user, connection, cmd) {
|
|
if (!(WHITELISTED_USERS.includes(user.id) || this.can('lockdown'))) return false;
|
|
const removing = cmd.includes('remove');
|
|
let [type, toAdd] = target.split('|');
|
|
type = toID(type);
|
|
if (!toAdd) return this.parse('/help addhosts');
|
|
const hosts = toAdd.split(',').map(host => host.trim());
|
|
if (!hosts.length) return this.parse('/help addhosts');
|
|
|
|
switch (type) {
|
|
case 'openproxy':
|
|
for (const host of hosts) {
|
|
if (!IP_REGEX.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 (!HOST_REGEX.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 (!HOST_REGEX.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 (!HOST_REGEX.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.globalModlog(
|
|
removing ? 'REMOVEHOSTS' : 'ADDHOSTS',
|
|
null,
|
|
`by ${user.id}: ${hosts.length} hosts to category '${type}'`
|
|
);
|
|
return this.sendReply(`${removing ? 'Removed' : 'Added'} ${hosts.length} hosts!`);
|
|
},
|
|
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'.`,
|
|
],
|
|
};
|
|
|
|
process.nextTick(() => {
|
|
Chat.multiLinePattern.register('/(datacenters|datacenter|dc) (add|widen|remove)');
|
|
});
|