204 lines
6.8 KiB
JavaScript
204 lines
6.8 KiB
JavaScript
const {extend: createGotClient} = require('got');
|
|
const {ConnectionError, SayError, TimeoutError, ChatClient} = require('dank-twitch-irc');
|
|
const chalk = require('chalk');
|
|
const WS = require('ws');
|
|
const Config = require('./config.json');
|
|
|
|
const clients = [];
|
|
const banChunks = [];
|
|
const pingInterval = 120e3;
|
|
let lastIndex = 0;
|
|
const uuidRegex = /[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/i;
|
|
const msgRegex = /[\u034f\u2800\u{E0000}\u180e\ufeff\u2000-\u200d\u206D]/gu;
|
|
|
|
const pnslClient = createGotClient({
|
|
prefixUrl: 'https://bot.tetyys.com/api/v1',
|
|
throwHttpErrors: false,
|
|
headers: {
|
|
'Authorization': `Bearer ${Config.PNSLToken}`,
|
|
},
|
|
});
|
|
|
|
const fetchList = async (list) => {
|
|
const listID = uuidRegex.exec(list)?.[0];
|
|
if (!listID) {
|
|
throw new Error('Invalid List ID!');
|
|
}
|
|
|
|
const {body: listMetaBody, statusCode} = await pnslClient('BotLists/Properties', {searchParams: {Guid: listID}});
|
|
if (statusCode === 404) {
|
|
throw new Error('List was not found.');
|
|
}
|
|
|
|
if (statusCode === 401) {
|
|
throw new Error('Authorization token is invalid');
|
|
}
|
|
|
|
if (statusCode !== 200) {
|
|
throw new Error('Unexpected error occurred!');
|
|
}
|
|
|
|
const {body: listData} = await pnslClient(`BotLists/${listID}`);
|
|
|
|
const listMeta = JSON.parse(listMetaBody);
|
|
return {listMeta, listData};
|
|
};
|
|
|
|
const runList = async (listData, listID, channel) => {
|
|
const banArr = listData.split('\n');
|
|
|
|
if (banArr.length > Config.MaxChunkSize) {
|
|
while (banArr.length > 0) {
|
|
banChunks.push(banArr.splice(0, Config.MaxChunkSize));
|
|
}
|
|
|
|
for (const [i, chunk] of banChunks.entries()) {
|
|
const promises = [];
|
|
|
|
for (const entry of chunk) {
|
|
if (entry === '') continue;
|
|
[, user, ...reason] = entry.split(' ');
|
|
promises.push(getConnection().ban(channel, user, reason.join(' ')));
|
|
}
|
|
|
|
await Promise.allSettled(promises);
|
|
|
|
banChunks.shift();
|
|
|
|
if (banChunks.length === 0) {
|
|
break;
|
|
}
|
|
|
|
say(Config.Username, `Chunk ${Number(i) + 1} of ${listID} executed successfully on channel ${channel}`);
|
|
await sleep(Config.DelayPerChunk);
|
|
}
|
|
} else {
|
|
const promises = [];
|
|
|
|
for (const entry of banArr) {
|
|
if (entry === '') continue;
|
|
[, user, ...reason] = entry.split(' ');
|
|
promises.push(getConnection().ban(channel, user, reason.join(' ')));
|
|
}
|
|
|
|
await Promise.allSettled(promises);
|
|
}
|
|
};
|
|
|
|
const sleep = (ms) => {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
};
|
|
|
|
const createTwitchClient = () => {
|
|
return new ChatClient({
|
|
username: Config.Username,
|
|
password: Config.Password,
|
|
rateLimits: 'verifiedBot',
|
|
});
|
|
};
|
|
|
|
const getConnection = () => {
|
|
const readyClients = clients.filter((i) => i.ready);
|
|
return readyClients[++lastIndex % readyClients.length];
|
|
};
|
|
|
|
const say = (channel, message) => {
|
|
getConnection().say(channel, message);
|
|
};
|
|
|
|
const pnslWebSocket = new WS('wss://bot.tetyys.com/api/wss', [], {headers: {Authorization: `Bearer ${Config.PNSLToken}`}});
|
|
|
|
pnslWebSocket.on('open', () => {
|
|
console.log(`${chalk.green('[P&SL]')} || Connected to P&SL Websocket server`);
|
|
});
|
|
|
|
pnslWebSocket.on('message', async (data) => {
|
|
const {o: type, p: payload} = JSON.parse(data);
|
|
switch (type) {
|
|
case 0: {
|
|
const {listMeta} = await fetchList(payload.LatestBotList);
|
|
console.log(`${chalk.green('[P&SL]')} || Latest Botlist: ${payload.LatestBotList} [${listMeta.name}] (${listMeta.count})`);
|
|
break;
|
|
}
|
|
case 1: {
|
|
console.log(`${chalk.green('[P&SL]')} || New Botlist: ${payload.Guid} [${payload.Name}] (${payload.Count}) (${payload.Tags.join(' ')})`);
|
|
break;
|
|
}
|
|
case 2: {
|
|
console.log(`${chalk.green('[P&SL]')} || New False Positive: ${payload.UserTwitchId} [${payload.BotListGuid}]`);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
|
|
(async () => {
|
|
await Promise.all([...Array(Config.MaxConnections)].map(async (_, i) => {
|
|
clients[i] = createTwitchClient();
|
|
clients[i].connect();
|
|
clients[i].on('ready', async () => {
|
|
await clients[i].join(Config.Username);
|
|
});
|
|
clients[i].on('error', (error) => {
|
|
if (error instanceof SayError) return;
|
|
if (error instanceof ConnectionError) {
|
|
if (clients[i].ready) {
|
|
return;
|
|
}
|
|
clients[i].connect();
|
|
return console.error(`Error in client ${i} -> ${error.name} || ${error.message}`);
|
|
}
|
|
if (error instanceof TimeoutError) {
|
|
clients[i].connect();
|
|
return console.error(`Timeout in client ${i} -> ${error.name} || ${error.message} | Trying to reconnect`);
|
|
}
|
|
console.error(error);
|
|
});
|
|
return await new Promise((resolve) => clients[i].on('ready', () => resolve()));
|
|
}));
|
|
|
|
console.log(`${chalk.green('[TWITCH]')} || ${clients.filter((i) => i.ready).length} Clients connected`);
|
|
|
|
clients[0].on('PRIVMSG', async ({messageText, senderUserID, channelName}) => {
|
|
if (!Config.Users.includes(senderUserID)) return;
|
|
|
|
const message = messageText.replace(msgRegex, '').trimRight();
|
|
if (!message.startsWith(Config.Prefix)) return;
|
|
const content = message.split(/\s+/g);
|
|
const command = content[0].slice(Config.Prefix.length);
|
|
const args = content.slice(1);
|
|
|
|
if (command === 'ping') {
|
|
const channel = args[0];
|
|
return say(channel || channelName, `${clients.filter((i) => i.ready).length} Clients from total of ${clients.length} are operational`);
|
|
}
|
|
|
|
if (command === 'runlist') {
|
|
const listID = args[0];
|
|
const channel = args[1];
|
|
|
|
if (!listID) return say(Config.Username, 'No list ID provided!');
|
|
|
|
try {
|
|
if (channel) {
|
|
const mods = await getConnection().getMods(channel);
|
|
if (!mods.includes(Config.Username)) return say(Config.Username, `I am not a moderator in channel ${channel}!`);
|
|
}
|
|
const data = await fetchList(listID);
|
|
await runList(data.listData, listID, channel || channelName);
|
|
say(Config.Username, `List ${listID} executed successfully on channel ${channel || channelName}`);
|
|
} catch (error) {
|
|
console.error(error);
|
|
return say(Config.Username, `Error Occurred! ${error.message}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
setInterval(() => {
|
|
for (const client of clients) {
|
|
client.ping();
|
|
}
|
|
}, pingInterval);
|
|
})();
|