Skip to content

Commit

Permalink
watchdog mode
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBoombastic committed Mar 20, 2023
1 parent 3d5d771 commit 817648c
Show file tree
Hide file tree
Showing 9 changed files with 510 additions and 80 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
MODE=MEMBERS
GUILD_ID=
CHANNEL_ID=
SPACING="\t"
TOKEN=
DELAY=1000
DICTIONARY=" !\"#$%&'()*+,-./0123456789:;<=>?@[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
DATE_FORMAT="L LTS UTCZ",
DATE_LOCALE=en
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Config
src/config*.json
src/data-*

# IDE
Expand Down
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
![Logo.png](img.png)
[![CodeFactor](https://www.codefactor.io/repository/github/mrboombastic/osintcord/badge)](https://www.codefactor.io/repository/github/mrboombastic/osintcord)

Just get data of (nearly) all of Discord guild members.
Just get data of (nearly) all of Discord guild members. Or track deleted and edited messages. Or why not both?

## Warning:

Expand All @@ -14,28 +14,34 @@ Using this on a user account is prohibited by the Discord TOS and can lead to th

- By using binaries:
- Download binary from [Releases](https://github.com/MrBoombastic/OSINTCord/releases) tab.
- Fill in the `config.json` file.
- Fill in the `.env` file.
- Run the binary.
- By using source code:
- Clone this repository.
- Make sure that you have Node.JS v18 installed.
- Install dependencies by using `yarn`.
- Fill in the `config.json` file and place it in `src` directory.
- Fill in the `.env` file and place it in `src` directory.
- Run `npm start`.

## Options:

You can use some default options from [config.example.json](config.example.json).

- `guildID`: The guild ID you want to get data from.
- `channelID`: The channel ID, which also will be used to get data from.
- `spacing`: Spacing between columns in output file.
- `token`: Your Discord account token.
- `delay`: Delay between *some* requests.
- `dictionary`: Characters used by the bruteforce method. Case-insensitive, duplicates are ignored.
- `dateFormat`: format of the parsed date (refer to the [Day.js manual](https://day.js.org/docs/en/display/format)).
- `dateLocale`: locale used in the parsed
date ([list of supported locales](https://github.com/iamkun/dayjs/tree/dev/src/locale)).
You can use some default options from [.env.example](.env.example).

- When using "MEMBERS" mode:
- `GUILD_ID`: The guild ID you want to get data from.
- `CHANNEL_ID`: The channel ID, which also will be used to get data from.
- `SPACING`: Spacing between columns in output file.
- `TOKEN`: Your Discord account token.
- `DELAY`: Delay between *some* requests.
- `DICTIONARY`: Characters used by the bruteforce method. Case-insensitive, duplicates are ignored.
- `DATE_FORMAT`: format of the parsed date (refer to
the [Day.js manual](https://day.js.org/docs/en/display/format)).
- `DATE_LOCALE`: locale used in the parsed
date ([list of supported locales](https://github.com/iamkun/dayjs/tree/dev/src/locale)).
- When using "WATCHDOG" mode:
- `GUILD_ID`: The guild ID you want to get data from. Set to `all`, if you want to receive data from all members'
guilds.
- `TOKEN`: Your Discord account token.

## FAQ:

Expand Down
10 changes: 0 additions & 10 deletions config.example.json

This file was deleted.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "osintcord",
"version": "1.4.0",
"version": "1.5.0",
"description": "Get data of (nearly) all of Discord guild members",
"main": "src/index.js",
"scripts": {
Expand Down Expand Up @@ -36,6 +36,9 @@
"dependencies": {
"dayjs": "^1.11.7",
"discord.js-selfbot-v13": "https://github.com/MrBoombastic/discord.js-selfbot-v13#old-deps",
"ora": "~5.4.1"
"dotenv": "^16.0.3",
"log4js": "^6.9.1",
"ora": "~5.4.1",
"request": "^2.88.2"
}
}
103 changes: 68 additions & 35 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
// Including libraries
const fs = require("fs");
const {Client} = require("discord.js-selfbot-v13");
global.dayjs = require("dayjs");
dayjs.extend(require("dayjs/plugin/localizedFormat"));
const {saveAndExit, checkConfig, art} = require("./utils.js");
const {saveAndExit, checkConfig, art, downloadFile} = require("./utils.js");
const {bruteforce, perms, overlap} = require("./steps.js");
const log4js = require("log4js");
require('dotenv').config();


// Setting up client
const client = new Client({
checkUpdate: false, partials: ["GUILD_MEMBER"]
});

// Config and guild are stored here
let config, guild;
let guild;

// Config file validation
try {
config = JSON.parse(fs.readFileSync("./config.json"));
} catch (e) {
console.error("ERROR: missing config file!");
process.exit(1);
}
const configStatus = checkConfig(config);
const configStatus = checkConfig();
if (!configStatus.ok) {
console.error(`ERROR: missing '${configStatus.prop}' in config file!`);
console.error(`ERROR: wrong config! Reason: ${configStatus.reason}`);
process.exit(1);
}

// Preparing date formatting
try {
require(`dayjs/locale/${config.dateLocale}`);
dayjs.locale(config.dateLocale);
require(`dayjs/locale/${process.env.DATE_LOCALE}`);
dayjs.locale(process.env.DATE_LOCALE);
} catch (e) {
console.warn(`WARNING: locale '${config.dateLocale}' not found. Using 'en' as fallback.`);
console.warn(`WARNING: locale '${process.env.DATE_LOCALE}' not found. Using 'en' as fallback.`);
dayjs.locale("en");
}

Expand All @@ -43,36 +39,73 @@ client.on("rateLimit", async (data) => {

// When bot is ready
client.on("ready", async () => {
console.log(art);
console.log(art.replace("$MODE", process.env.MODE));
console.log(`Logged in as ${client.user.tag} (${client.user?.emailAddress || "NO EMAIL"})`);
if (process.env.MODE === "WATCHDOG") {
// Getting target
const info = await client.guilds.cache.get(process.env.GUILD_ID);
console.log(`Target acquired: ${info.name}`);

// Getting target
guild = await client.guilds.cache.get(config.guildID);
if (!guild?.available) {
console.error("ERROR: selected guild is not available!\nAvailable guilds:", client.guilds.cache.map(x => `${x.name} (${x.id})`).join(", "));
process.exit(1);
}
const channel = await guild.channels.cache.get(config.channelID);
if (!channel) {
console.warn("WARNING: selected channel is missing! 'Member list' method will be skipped\nAvailable channels: ", guild.channels.cache.map(x => `${x.name} (${x.id})`).join(", "));
}
// Set up message logging
log4js.configure({
appenders: {watchdog: {type: "file", filename: `${process.env.GUILD_ID}.log`}},
categories: {default: {appenders: ["watchdog"], level: "info"}},
});

global.logger = log4js.getLogger("watchdog");
} else if (process.env.MODE === "MEMBERS") {
// Getting target
guild = await client.guilds.cache.get(process.env.GUILD_ID);
if (!guild?.available) {
console.error("ERROR: selected guild is not available!\nAvailable guilds:", client.guilds.cache.map(x => `${x.name} (${x.id})`).join(", "));
process.exit(1);
}
const channel = await guild.channels.cache.get(process.env.CHANNEL_ID);
if (!channel) {
console.warn("WARNING: selected channel is missing! 'Member list' method will be skipped\nAvailable channels: ", guild.channels.cache.map(x => `${x.name} (${x.id})`).join(", "));
}

console.log(`Target acquired: ${guild.name} (${channel?.name || "NO CHANNEL"})`);
console.log(`Target acquired: ${guild.name} (${channel?.name || "NO CHANNEL"})`);

// Fetching!
await perms(guild); // Method 1 - fetching with perms
if (channel) await overlap(guild, config, client); // Method 2 - overlap member list fetching
if ((guild.members.cache.size < guild.memberCount) && (guild.members.cache.size !== guild.memberCount)) await bruteforce(guild, config); // Method 3 - brute-force fetching
// Fetching!
await perms(guild); // Method 1 - fetching with perms
if (channel) await overlap(guild, client); // Method 2 - overlap member list fetching
if ((guild.members.cache.size < guild.memberCount) && (guild.members.cache.size !== guild.memberCount)) await bruteforce(guild); // Method 3 - brute-force fetching

// Done!
console.log(`Fetching done! Found ${guild.members.cache.size}/${guild.memberCount} => ${guild.members.cache.size / guild.memberCount * 100}% members.`);
// Done!
console.log(`Fetching done! Found ${guild.members.cache.size}/${guild.memberCount} => ${guild.members.cache.size / guild.memberCount * 100}% members.`);

await saveAndExit(client, config, guild);
await saveAndExit(client, guild);
}
});

client.on("messageDelete", message => {
if (message.guildId === process.env.GUILD_ID || process.env.GUILD_ID === "ALL") {
let info = `DELETED MESSAGE: Guild: ${message.guild.name} Channel: ${message.channel.name} Author: ${message.author.tag} Bot: ${message.author.bot}\nCONTENT: ${message.cleanContent}`;
if (message.attachments.size > 0) {
info += `\nMEDIA: ${message.attachments.map(x => x.proxyURL).join(", ")}`;
message.attachments.forEach(x => {
downloadFile(x.proxyURL);
});
}
logger.info(info);
}
});

client.on("messageUpdate", (oldMsg, newMsg) => {
if ((oldMsg.guildId === process.env.GUILD_ID || process.env.GUILD_ID === "ALL") && oldMsg.content !== newMsg.content) {
let info = `EDITED MESSAGE: Guild: ${oldMsg.guild.name} Channel: ${oldMsg.channel.name} Author: ${oldMsg.author?.tag} Bot: ${oldMsg.author?.bot}
OLD: ${oldMsg.content}
NEW: ${newMsg.content}`;
if (oldMsg.attachments.size > 0) info += `\nOLD MEDIA: ${newMsg.attachments.map(x => x.url).join(", ")}`;
if (newMsg.attachments.size > 0) info += `\nNEW MEDIA: ${newMsg.attachments.map(x => x.url).join(", ")}`;
logger.info(info);
}
});

process.on("SIGINT", async () => {
console.log("\nStopped upon user's request!");
await saveAndExit(client, config, guild);
await saveAndExit(client, guild);
});

client.login(config.token);
client.login(process.env.TOKEN);
14 changes: 7 additions & 7 deletions src/steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ module.exports = {
progressbar.stop();
},

bruteforce: async (guild, config) => {
bruteforce: async (guild) => {
// Dictionary info
const dictionary = (Array.from(new Set(config.dictionary.toLowerCase()))).sort(); //deduplication
const dictionary = (Array.from(new Set(process.env.DICTIONARY.toLowerCase()))).sort(); //deduplication
console.log("Using dictionary:", dictionary.join(''));

const progressbar = ora({text: "Starting 'brute-force' method!", prefixText: "[BRUTE-FORCE]"}).start();
Expand All @@ -29,14 +29,14 @@ module.exports = {
refreshLoading(progressbar, guild);
}, 500);
await guild.members.fetchBruteforce({
delay: config.delay, limit: 100, //max limit is 100 at once
delay: parseInt(process.env.DELAY), limit: 100, //max limit is 100 at once
dictionary: dictionary
});
clearInterval(stage);
progressbar.stop();
},

overlap: async (guild, config, client) => {
overlap: async (guild, client) => {
const progressbar = ora({
text: "Starting 'overlap member list' method!", prefixText: "[OVERLAP MEMBER LIST]"
}).start();
Expand All @@ -46,9 +46,9 @@ module.exports = {
refreshLoading(progressbar, guild);
}, 500);
for (let index = 0; index <= guild.memberCount; index += 100) {
await guild.members.fetchMemberList(config.channelID, index, index !== 100).catch(() => false);
if (guild.members.cache.size === guild.memberCount) break;
await client.sleep(config.delay);
await guild.members.fetchMemberList(process.env.CHANNEL_ID, index, index !== 100).catch(() => false);
if (guild.members.cache.size >= guild.memberCount) break;
await client.sleep(parseInt(process.env.DELAY));
}
clearInterval(stage);
progressbar.stop();
Expand Down
47 changes: 40 additions & 7 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const fs = require("fs");
const request = require("request");
const path = require("path");
const packagejson = require("../package.json");

module.exports = {
Expand All @@ -14,10 +16,20 @@ module.exports = {
return data.join(spacing);
},

checkConfig: function (config) {
const props = ["guildID", "channelID", "spacing", "token", "delay", "dictionary"];
checkConfig: function () {
let props = [];
switch (process.env.MODE) {
case "WATCHDOG":
props = ["GUILD_ID", "TOKEN"];
break;
case "MEMBERS":
props = ["GUILD_ID", "CHANNEL_ID", "SPACING", "TOKEN", "DELAY", "DICTIONARY", "DATE_FORMAT", "DATE_LOCALE"];
break;
default:
return {ok: false, reason: "MODE"};
}
for (let prop of props) {
if (!(prop in config)) return {ok: false, prop};
if (!process.env[prop]) return {ok: false, reason: prop};
}
return {ok: true};
},
Expand All @@ -26,13 +38,13 @@ module.exports = {
ora.text = `Fetching members... ${guild.members.cache.size}/${guild.memberCount} => ${Math.floor(guild.members.cache.size / guild.memberCount * 100)}%`;
},

saveAndExit: async function (client, config, guild) {
saveAndExit: async function (client, guild) {
if (guild) {
// Generating text output
const header = ["id", "username#discriminator", "nickname", "avatar", "roles", "created_at", "joined_at", "activity", "status", "flags\n"];
let data = header.join(config.spacing);
let data = header.join(process.env.SPACING);

data += guild.members.cache.map(member => module.exports.formatUserData(member, config.spacing, config.dateFormat)).join("\n");
data += guild.members.cache.map(member => module.exports.formatUserData(member, process.env.SPACING, process.env.DATE_FORMAT)).join("\n");

// Save to file
const filename = `data-${Date.now()}.txt`;
Expand All @@ -47,6 +59,27 @@ module.exports = {
client.destroy();
process.exit(0);
},
downloadFile: function (url) {
const mediaDir = './media';
if (!fs.existsSync(mediaDir)) {
fs.mkdirSync(mediaDir);
}
let fileName = url.replace("https://media.discordapp.net/attachments/", "");
fileName = fileName.replaceAll("/", "-");
const filePath = path.join(mediaDir, fileName);

request(url)
.on('error', (err) => {
console.error(`Error downloading file: ${err.message}`);
})
.pipe(fs.createWriteStream(filePath))
.on('error', (err) => {
console.error(`Error saving file: ${err.message}`);
})
.on('finish', () => {
console.log(`File saved to ${filePath}`);
});
},

art: `
██████╗ ███████╗██╗███╗ ██╗████████╗ ██████╗ ██████╗ ██████╗ ██████╗
Expand All @@ -55,5 +88,5 @@ module.exports = {
██║ ██║╚════██║██║██║╚██╗██║ ██║ ██║ ██║ ██║██╔══██╗██║ ██║
╚██████╔╝███████║██║██║ ╚████║ ██║ ╚██████╗╚██████╔╝██║ ██║██████╔╝
╚═════╝ ╚══════╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ v${packagejson.version}
`
=============\t\t\t$MODE MODE\t\t\t=============`
};
Loading

0 comments on commit 817648c

Please sign in to comment.