added base
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
import {
|
||||
Client,
|
||||
GatewayIntentBits,
|
||||
Guild,
|
||||
GuildMember,
|
||||
} from "discord.js";
|
||||
|
||||
const tokenFile = Bun.file("tokens.txt");
|
||||
const messageFile = Bun.file("Message.txt");
|
||||
const optInFile = Bun.file("opted-in.txt");
|
||||
|
||||
const tokens = (await tokenFile.text())
|
||||
.split(/\r?\n/)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const message = await messageFile.text();
|
||||
|
||||
export const GUILD_ID = process.env.GUILD_ID;
|
||||
|
||||
if (!GUILD_ID) {
|
||||
throw new Error("GUILD_ID n'est pas défini.");
|
||||
}
|
||||
|
||||
const clients: Client[] = [];
|
||||
|
||||
/**
|
||||
* Membres ayant explicitement accepté de recevoir les DM.
|
||||
*
|
||||
* opted-in.txt :
|
||||
*
|
||||
* 123456789012345678
|
||||
* 987654321098765432
|
||||
*/
|
||||
async function getOptedInMembers(guild: Guild): Promise<GuildMember[]> {
|
||||
const content = await optInFile.text();
|
||||
|
||||
const optedInIds = new Set(
|
||||
content
|
||||
.split(/\r?\n/)
|
||||
.map((id) => id.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
const members = await guild.members.fetch();
|
||||
|
||||
return members
|
||||
.filter((member) => optedInIds.has(member.id))
|
||||
.toJSON();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connecte un bot et attend réellement son clientReady.
|
||||
*/
|
||||
function loginClient(token: string): Promise<Client> {
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
],
|
||||
});
|
||||
|
||||
clients.push(client);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const onReady = () => {
|
||||
cleanup();
|
||||
|
||||
console.log(
|
||||
`[${client.user?.username ?? client.user?.id}] ready`,
|
||||
);
|
||||
|
||||
resolve(client);
|
||||
};
|
||||
|
||||
const onError = (error: Error) => {
|
||||
cleanup();
|
||||
reject(error);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
client.off("clientReady", onReady);
|
||||
client.off("error", onError);
|
||||
};
|
||||
|
||||
client.once("clientReady", onReady);
|
||||
client.once("error", onError);
|
||||
|
||||
client.login(token).catch(onError);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Répartit les membres équitablement entre les bots.
|
||||
*/
|
||||
function batchMembers(
|
||||
members: GuildMember[],
|
||||
botCount: number,
|
||||
): GuildMember[][] {
|
||||
const batches: GuildMember[][] = Array.from(
|
||||
{ length: botCount },
|
||||
() => [],
|
||||
);
|
||||
|
||||
members.forEach((member, index) => {
|
||||
batches[index % botCount].push(member);
|
||||
});
|
||||
|
||||
return batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie le message uniquement aux membres opt-in
|
||||
* assignés à ce bot.
|
||||
*/
|
||||
async function sendBatch(
|
||||
client: Client,
|
||||
members: GuildMember[],
|
||||
): Promise<{
|
||||
sent: number;
|
||||
failed: number;
|
||||
}> {
|
||||
let sent = 0;
|
||||
let failed = 0;
|
||||
|
||||
console.log(
|
||||
`[${client.user?.username}] ${members.length} membre(s) dans son batch.`,
|
||||
);
|
||||
|
||||
for (const member of members) {
|
||||
const parsedMessage = message.replace(
|
||||
/\{USER\}/g,
|
||||
member.toString(),
|
||||
);
|
||||
|
||||
try {
|
||||
await member.send(parsedMessage);
|
||||
|
||||
sent++;
|
||||
|
||||
console.log(
|
||||
`[${client.user?.username}] DM envoyé à @${member.user.username}`,
|
||||
);
|
||||
} catch (error) {
|
||||
failed++;
|
||||
|
||||
console.error(
|
||||
`[${client.user?.username}] Impossible de DM @${member.user.username}`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
// Petit délai entre les envois.
|
||||
await Bun.sleep(1500);
|
||||
}
|
||||
|
||||
return {
|
||||
sent,
|
||||
failed,
|
||||
};
|
||||
}
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
console.error("uncaughtException:", error);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (error) => {
|
||||
console.error("unhandledRejection:", error);
|
||||
});
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 1. CONNECTER TOUS LES BOTS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
console.log(`Connexion de ${tokens.length} bot(s)...`);
|
||||
|
||||
const loginResults = await Promise.allSettled(
|
||||
tokens.map((token) => loginClient(token)),
|
||||
);
|
||||
|
||||
const readyClients: Client[] = [];
|
||||
|
||||
for (const [index, result] of loginResults.entries()) {
|
||||
if (result.status === "fulfilled") {
|
||||
readyClients.push(result.value);
|
||||
} else {
|
||||
console.error(
|
||||
`[Bot ${index + 1}] Échec de connexion :`,
|
||||
result.reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${readyClients.length}/${tokens.length} bot(s) sont prêts.`,
|
||||
);
|
||||
|
||||
if (readyClients.length === 0) {
|
||||
throw new Error("Aucun bot n'est connecté.");
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 2. RÉCUPÉRER LE SERVEUR
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
const guilds = await Promise.all(
|
||||
readyClients.map(async (client) => {
|
||||
const guild = await client.guilds
|
||||
.fetch(GUILD_ID)
|
||||
.catch(() => null);
|
||||
|
||||
return {
|
||||
client,
|
||||
guild,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const availableBots = guilds.filter(
|
||||
(
|
||||
item,
|
||||
): item is {
|
||||
client: Client;
|
||||
guild: Guild;
|
||||
} => item.guild !== null,
|
||||
);
|
||||
|
||||
for (const { client } of guilds) {
|
||||
const guild = await client.guilds
|
||||
.fetch(GUILD_ID)
|
||||
.catch(() => null);
|
||||
|
||||
if (!guild) {
|
||||
console.log(
|
||||
`[${client.user?.username}] Le bot n'est pas présent sur le serveur.`,
|
||||
);
|
||||
|
||||
console.log(
|
||||
`https://discord.com/oauth2/authorize?client_id=${client.user?.id}&scope=bot`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (availableBots.length === 0) {
|
||||
throw new Error(
|
||||
"Aucun des bots connectés n'est présent sur ce serveur.",
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 3. RÉCUPÉRER LES MEMBRES OPT-IN
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
const mainGuild = availableBots[0].guild;
|
||||
|
||||
console.log(
|
||||
`[${mainGuild.name}] Récupération des membres opt-in...`,
|
||||
);
|
||||
|
||||
const optedInMembers = await getOptedInMembers(mainGuild);
|
||||
|
||||
console.log(
|
||||
`[${mainGuild.name}] ${optedInMembers.length} membre(s) ont accepté les DM.`,
|
||||
);
|
||||
|
||||
if (optedInMembers.length === 0) {
|
||||
console.log("Aucun membre opt-in. Rien à envoyer.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 4. RÉPARTITION DES MEMBRES ENTRE LES BOTS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
const batches = batchMembers(
|
||||
optedInMembers,
|
||||
availableBots.length,
|
||||
);
|
||||
|
||||
console.log(
|
||||
`Répartition de ${optedInMembers.length} membre(s) entre ${availableBots.length} bot(s).`,
|
||||
);
|
||||
|
||||
for (let i = 0; i < availableBots.length; i++) {
|
||||
console.log(
|
||||
`[${availableBots[i].client.user?.username}] ${batches[i].length} membre(s)`,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 5. ENVOI DES BATCHS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
console.log("Début de l'envoi...");
|
||||
|
||||
const sendResults = await Promise.allSettled(
|
||||
availableBots.map(({ client }, index) =>
|
||||
sendBatch(client, batches[index]),
|
||||
),
|
||||
);
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* 6. RÉSULTATS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
let totalSent = 0;
|
||||
let totalFailed = 0;
|
||||
|
||||
for (const [index, result] of sendResults.entries()) {
|
||||
const username =
|
||||
availableBots[index].client.user?.username ??
|
||||
availableBots[index].client.user?.id ??
|
||||
`Bot ${index + 1}`;
|
||||
|
||||
if (result.status === "fulfilled") {
|
||||
totalSent += result.value.sent;
|
||||
totalFailed += result.value.failed;
|
||||
|
||||
console.log(
|
||||
`[${username}] terminé : ${result.value.sent} envoyé(s), ${result.value.failed} échec(s).`,
|
||||
);
|
||||
} else {
|
||||
console.error(
|
||||
`[${username}] le batch a échoué :`,
|
||||
result.reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log("========================================");
|
||||
console.log(" ENVOI TERMINÉ");
|
||||
console.log("========================================");
|
||||
console.log(`Membres opt-in : ${optedInMembers.length}`);
|
||||
console.log(`Messages envoyés : ${totalSent}`);
|
||||
console.log(`Échecs : ${totalFailed}`);
|
||||
console.log("========================================");
|
||||
Reference in New Issue
Block a user