holmes
This commit is contained in:
parent
83dea73e77
commit
c2b8b386ce
|
@ -14,7 +14,9 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.3.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
"lowdb": "^3.0.0",
|
"lowdb": "^3.0.0",
|
||||||
"revolt-api": "^0.5.16",
|
"revolt-api": "^0.5.16",
|
||||||
"revolt.js": "^6.0.20",
|
"revolt.js": "^6.0.20",
|
||||||
|
|
17
src/index.ts
17
src/index.ts
|
@ -3,6 +3,7 @@ import { config } from 'dotenv';
|
||||||
import { SendableEmbed } from 'revolt-api';
|
import { SendableEmbed } from 'revolt-api';
|
||||||
import { Low, JSONFile } from 'lowdb';
|
import { Low, JSONFile } from 'lowdb';
|
||||||
import { decodeTime } from 'ulid';
|
import { decodeTime } from 'ulid';
|
||||||
|
import sherlock from "./sherlock";
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
|
@ -27,7 +28,11 @@ const CommandFlags: CommandFlag[] = [
|
||||||
{
|
{
|
||||||
name: 'kick',
|
name: 'kick',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
name: 'nsfw',
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const PREFIX_WORD = '/kibby';
|
const PREFIX_WORD = '/kibby';
|
||||||
|
@ -38,7 +43,7 @@ const RE_FLAG = /^--\S+(=.*)?$/g;
|
||||||
const RE_FLAG_NAME = /^--[^=\s]+(=*?|$)/g;
|
const RE_FLAG_NAME = /^--[^=\s]+(=*?|$)/g;
|
||||||
const DB_FILE = process.env.DB_FILE || './db.json';
|
const DB_FILE = process.env.DB_FILE || './db.json';
|
||||||
const RE_USER_MENTION = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
const RE_USER_MENTION = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
||||||
const PUBLIC_COMMANDS = ['suicide', 'status', 'help', 'type'];
|
const PUBLIC_COMMANDS = ['suicide', 'status', 'help', 'type', 'sherlock'];
|
||||||
const COMMANDS = {
|
const COMMANDS = {
|
||||||
'help': 'List available commands',
|
'help': 'List available commands',
|
||||||
'status': 'Edit the bot\'s status',
|
'status': 'Edit the bot\'s status',
|
||||||
|
@ -49,6 +54,7 @@ const COMMANDS = {
|
||||||
'unapprove': 'Send users to probation',
|
'unapprove': 'Send users to probation',
|
||||||
'block': 'Troll a user',
|
'block': 'Troll a user',
|
||||||
'unblock': 'Untroll a user',
|
'unblock': 'Untroll a user',
|
||||||
|
'sherlock': 'Run a sherlock scan on a username',
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.TOKEN) throw "$TOKEN not set";
|
if (!process.env.TOKEN) throw "$TOKEN not set";
|
||||||
|
@ -489,6 +495,11 @@ client.on('message', async (message) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'sherlock': {
|
||||||
|
await sherlock(message, args, !!getFlag('nsfw'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'help': {
|
case 'help': {
|
||||||
const commands = Object.entries(COMMANDS).filter(c => privileged || PUBLIC_COMMANDS.includes(c[0]));
|
const commands = Object.entries(COMMANDS).filter(c => privileged || PUBLIC_COMMANDS.includes(c[0]));
|
||||||
|
|
||||||
|
@ -590,3 +601,5 @@ client.on('packet', async (packet) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { embed }
|
||||||
|
|
176
src/sherlock.ts
Normal file
176
src/sherlock.ts
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import { Message } from "revolt.js";
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import { embed } from ".";
|
||||||
|
import { SendableEmbed } from "revolt-api";
|
||||||
|
import FormData from 'form-data';
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
let RUNNING: string[] = [];
|
||||||
|
|
||||||
|
export default async function sherlock(message: Message, args: string[], nsfw: boolean) {
|
||||||
|
const SHERLOCK_COMMAND = (process.env.SHERLOCK_COMMAND || 'python3 sherlock').split(' ');
|
||||||
|
|
||||||
|
if (RUNNING.includes(message.channel_id)) {
|
||||||
|
return await message.reply({ embeds: [
|
||||||
|
embed('Please wait for the previous scan to finish before launching another one!', 'Sherlock', 'ERROR'),
|
||||||
|
] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!args.length) {
|
||||||
|
await message.reply({ embeds: [
|
||||||
|
embed('No username(s) to search for provided.', 'Sherlock', 'ERROR'),
|
||||||
|
] });
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length > 3) {
|
||||||
|
await message.reply({ embeds: [
|
||||||
|
embed('Please provide no more than 3 usernames.', 'Sherlock', 'ERROR'),
|
||||||
|
] });
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
RUNNING.push(message.channel_id);
|
||||||
|
|
||||||
|
const infoSearchingEmbed = embed(
|
||||||
|
`Looking up \`${args.join('`, `')}\`. This might take a while, please be patient!\n\nIncluding NSFW results: \`${nsfw}\``,
|
||||||
|
'Sherlock',
|
||||||
|
'INFO',
|
||||||
|
);
|
||||||
|
const msg = await message.reply({ embeds: [ infoSearchingEmbed ] });
|
||||||
|
const matches: { user: string, name: string, url: string }[] = [];
|
||||||
|
const searchedEmbeds: SendableEmbed[] = [];
|
||||||
|
const attachments: string[] = [];
|
||||||
|
let currentUser = '';
|
||||||
|
|
||||||
|
const addEmbed = async (name: string) => {
|
||||||
|
let text = `Found ${matches.filter(match => match.user == name).length} links!\n`;
|
||||||
|
for (const match of matches.filter(match => match.user == name)) {
|
||||||
|
const newLine = args.length > 1
|
||||||
|
? `\n- [${match.name}](${match.url.replaceAll(' ', '%20')})`
|
||||||
|
: `\n$\\textsf{\\color{#1abc9c}${match.name}}$: ${match.url.replaceAll(' ', '%20')}`;
|
||||||
|
|
||||||
|
if (text.length + newLine.length > 2000) break;
|
||||||
|
else if (text.length + newLine.length > 1800 / args.length) {
|
||||||
|
text = 'Message too long - Results will be sent as file';
|
||||||
|
attachments.push(await uploadFile(
|
||||||
|
matches.filter(match => match.user == name)
|
||||||
|
.map(m => `${m.name}: ${m.url}`)
|
||||||
|
.join('\n'),
|
||||||
|
name,
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else text += newLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedEmbeds.push(embed(
|
||||||
|
text,
|
||||||
|
`Sherlock - ${name}`,
|
||||||
|
'SUCCESS'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousLength = matches.length;
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
if (matches.length > previousLength) {
|
||||||
|
await msg?.edit({ embeds: [
|
||||||
|
infoSearchingEmbed,
|
||||||
|
...searchedEmbeds,
|
||||||
|
embed(
|
||||||
|
`Searching: \`${currentUser}\` - ${matches.filter(m => m.user == currentUser).length} found so far`,
|
||||||
|
`Sherlock - ${currentUser}`,
|
||||||
|
'INFO',
|
||||||
|
),
|
||||||
|
] });
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
// Would be super funny if the errors were actually fucking catched, wouldn't it?
|
||||||
|
clearInterval(interval);
|
||||||
|
proc.kill();
|
||||||
|
RUNNING = RUNNING.filter(r => r != message.channel_id);
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
const cmdArgs = nsfw
|
||||||
|
? [ ...SHERLOCK_COMMAND.slice(1), '--nsfw', '--output', '/dev/null', '--', ...args ]
|
||||||
|
: [ ...SHERLOCK_COMMAND.slice(1), '--output', '/dev/null', '--', ...args ];
|
||||||
|
console.log(`Invoking: ${SHERLOCK_COMMAND[0]} ${cmdArgs.join(' ')}`);
|
||||||
|
|
||||||
|
const proc = spawn(`${SHERLOCK_COMMAND[0]}`, cmdArgs, { shell: false });
|
||||||
|
|
||||||
|
proc.on('exit', async (res) => {
|
||||||
|
console.log(`[Sherlock] Exited with code ${res}`);
|
||||||
|
RUNNING = RUNNING.filter(r => r != message.channel_id);
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
if (res != 0) {
|
||||||
|
await msg?.edit({ embeds: [
|
||||||
|
embed(`Sherlock exited with code ${res}.\n\`\`\`\n${stderr}\n\`\`\``, 'Sherlock - Error', 'ERROR'),
|
||||||
|
] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await addEmbed(currentUser);
|
||||||
|
await msg?.edit({ embeds: searchedEmbeds });
|
||||||
|
if (attachments.length) {
|
||||||
|
await message.channel?.sendMessage({ attachments });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
let stderr = '';
|
||||||
|
proc.stderr.on('data', (data) => {
|
||||||
|
console.log('[Sherlock] [stderr] ' + data.toString('utf8'));
|
||||||
|
stderr += data.toString('utf8');
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdoutBuf = '';
|
||||||
|
proc.stdout.on('data', async (data) => {
|
||||||
|
stdoutBuf += data.toString('utf8');
|
||||||
|
|
||||||
|
if (stdoutBuf.includes('\n')) {
|
||||||
|
const buf = stdoutBuf.split('\n');
|
||||||
|
stdoutBuf = buf.pop() ?? '';
|
||||||
|
|
||||||
|
for (let line of buf) {
|
||||||
|
console.log('[Sherlock] [stdout] ' + line);
|
||||||
|
if (line.startsWith('[+]')) {
|
||||||
|
line = line.substring(3).trimStart();
|
||||||
|
const [name, url] = line.split(': ');
|
||||||
|
matches.push({ user: currentUser, name, url });
|
||||||
|
}
|
||||||
|
else if (line.startsWith('[*] Checking username')) {
|
||||||
|
line = line.substring('[*] Checking username'.length, line.length - 'on:'.length).trim();
|
||||||
|
|
||||||
|
if (currentUser) {
|
||||||
|
await addEmbed(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUser = line;
|
||||||
|
console.log('[Sherlock] Now scanning user:', currentUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
reject(e);
|
||||||
|
RUNNING = RUNNING.filter(r => r != message.channel_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadFile(file: any, filename: string): Promise<string> {
|
||||||
|
let data = new FormData();
|
||||||
|
data.append("file", file, { filename: filename });
|
||||||
|
|
||||||
|
let req = await axios.post("https://autumn.revolt.chat/attachments", data, {
|
||||||
|
headers: data.getHeaders(),
|
||||||
|
});
|
||||||
|
return (req.data as any)["id"] as string;
|
||||||
|
}
|
54
yarn.lock
54
yarn.lock
|
@ -32,6 +32,11 @@ argparse@^2.0.1:
|
||||||
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
|
||||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||||
|
|
||||||
axios@^0.21.4:
|
axios@^0.21.4:
|
||||||
version "0.21.4"
|
version "0.21.4"
|
||||||
resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz"
|
resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz"
|
||||||
|
@ -46,6 +51,15 @@ axios@^0.26.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.14.8"
|
follow-redirects "^1.14.8"
|
||||||
|
|
||||||
|
axios@^1.3.5:
|
||||||
|
version "1.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.5.tgz#e07209b39a0d11848e3e341fa087acd71dadc542"
|
||||||
|
integrity sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.0"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
busboy@^1.6.0:
|
busboy@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz"
|
resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz"
|
||||||
|
@ -53,6 +67,18 @@ busboy@^1.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
streamsearch "^1.1.0"
|
streamsearch "^1.1.0"
|
||||||
|
|
||||||
|
combined-stream@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||||
|
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||||
|
|
||||||
dotenv@^16.0.3:
|
dotenv@^16.0.3:
|
||||||
version "16.0.3"
|
version "16.0.3"
|
||||||
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz"
|
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz"
|
||||||
|
@ -63,11 +89,20 @@ eventemitter3@^4.0.7:
|
||||||
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
|
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
|
||||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||||
|
|
||||||
follow-redirects@^1.14.0, follow-redirects@^1.14.8:
|
follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0:
|
||||||
version "1.15.2"
|
version "1.15.2"
|
||||||
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
||||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||||
|
|
||||||
|
form-data@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||||
|
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.8"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
globalyzer@0.1.0:
|
globalyzer@0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz"
|
resolved "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz"
|
||||||
|
@ -112,6 +147,18 @@ lowdb@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
steno "^2.1.0"
|
steno "^2.1.0"
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
version "1.52.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
|
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||||
|
|
||||||
|
mime-types@^2.1.12:
|
||||||
|
version "2.1.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
|
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||||
|
dependencies:
|
||||||
|
mime-db "1.52.0"
|
||||||
|
|
||||||
mime@^3.0.0:
|
mime@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz"
|
||||||
|
@ -139,6 +186,11 @@ prettier@^2.6.2:
|
||||||
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz"
|
resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz"
|
||||||
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
|
integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
|
||||||
|
|
||||||
|
proxy-from-env@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
|
||||||
revolt-api@0.5.7:
|
revolt-api@0.5.7:
|
||||||
version "0.5.7"
|
version "0.5.7"
|
||||||
resolved "https://registry.npmjs.org/revolt-api/-/revolt-api-0.5.7.tgz"
|
resolved "https://registry.npmjs.org/revolt-api/-/revolt-api-0.5.7.tgz"
|
||||||
|
|
Loading…
Reference in a new issue