upgrade project and add deploy workflow
Some checks failed
Deploy Abondance Bot / deploy (push) Failing after 24s

This commit is contained in:
Shinuwa 2026-06-28 20:02:44 +02:00
parent 0dabb7c3c0
commit 3a50bd061a
10 changed files with 358 additions and 108 deletions

View file

@ -2,7 +2,7 @@ import { Client, GatewayIntentBits } from "discord.js";
import { config } from "../static/config.js";
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
intents: [GatewayIntentBits.Guilds],
});
async function initDiscordBot() {
@ -11,15 +11,16 @@ async function initDiscordBot() {
}
async function sendDiscordAlert(message) {
try {
const channel = await client.channels.fetch(config.textChannelId);
await channel.send(message);
} catch (err) {
console.error("❌ Erreur envoi Discord :", err);
const channel = await client.channels.fetch(config.textChannelId);
if (!channel?.isTextBased()) {
throw new Error(`Le canal Discord ${config.textChannelId} n'est pas textuel`);
}
await channel.send(message);
}
export {
initDiscordBot,
sendDiscordAlert
}
}

View file

@ -1,63 +1,164 @@
import cron from "node-cron";
import fetch from "node-fetch";
import fs from "fs";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import * as cheerio from "cheerio";
import { config } from "../static/config.js";
import { sendDiscordAlert } from "./discordBot.js"
import { sendDiscordAlert } from "./discordBot.js";
const membersJSON = "./src/static/data/members.json";
const membersJSON = new URL("../static/data/members.json", import.meta.url);
const scheduleTime = "30 9 * * *";
const scheduleTimezone = "Europe/Paris";
const requestTimeoutMs = 15_000;
const memberSelector = ".box_list_area .adventure_list_table li .guild_name .text a";
async function initMembersChecker() {
await checkGuildMembers();
cron.schedule(scheduleTime, checkGuildMembers, { timezone: scheduleTimezone });
}
async function getGuildMembers(guildUrl) {
const res = await fetch(guildUrl);
const html = await res.text();
function parseGuildMembers(html) {
const $ = cheerio.load(html);
const members = [];
const members = $(memberSelector)
.map((_, element) => $(element).text().trim())
.get()
.filter(Boolean);
$('.box_list_area .adventure_list_table li .guild_name .text a').each((i, el) => {
members.push($(el).text().trim());
});
if (members.length === 0) {
throw new Error(
"Aucun membre trouvé : la page est invalide ou sa structure HTML a changé",
);
}
if (new Set(members).size !== members.length) {
throw new Error("La liste extraite contient des membres en double");
}
return members;
}
async function checkGuildMembers() {
async function getGuildMembers(guildUrl, fetchImpl = fetch) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
try {
const newMembers = await getGuildMembers(config.guild_url);
const response = await fetchImpl(guildUrl, {
headers: {
Accept: "text/html,application/xhtml+xml",
"Accept-Language": "fr-FR,fr;q=0.9,en;q=0.8",
"User-Agent": "AbondanceGuildBot/1.0",
},
signal: controller.signal,
});
let oldMembers = [];
if (fs.existsSync(membersJSON)) {
oldMembers = JSON.parse(fs.readFileSync(membersJSON, "utf-8"));
if (!response.ok) {
throw new Error(`La page de guilde a répondu avec le statut HTTP ${response.status}`);
}
const added = newMembers.filter(m => !oldMembers.includes(m));
const removed = oldMembers.filter(m => !newMembers.includes(m));
if (added.length || removed.length) {
let message = "⚔️ **Changement dans la liste des membres :**\n";
if (added.length) message += ` Nouveaux membres : ${added.join(", ")}\n`;
if (removed.length) message += ` Membres partis : ${removed.join(", ")}`;
await sendDiscordAlert(message);
return parseGuildMembers(await response.text());
} catch (error) {
if (error.name === "AbortError") {
throw new Error(`La page de guilde n'a pas répondu sous ${requestTimeoutMs / 1000}s`);
}
fs.mkdirSync("./src/static/data/", { recursive: true });
fs.writeFileSync(membersJSON, JSON.stringify(newMembers, null, 2));
console.log("✅ Vérification terminée", new Date().toLocaleString("fr-FR", { timeZone: scheduleTimezone }));
} catch (err) {
console.error("❌ Erreur checkGuildMembers:", err);
try {
await sendDiscordAlert(`⚠️ Erreur lors du check des membres: \`${err.message}\``);
} catch {}
throw error;
} finally {
clearTimeout(timeout);
}
}
export {
initMembersChecker
function readMembers(fileUrl = membersJSON) {
if (!fs.existsSync(fileUrl)) return null;
const members = JSON.parse(fs.readFileSync(fileUrl, "utf8"));
if (
!Array.isArray(members) ||
members.length === 0 ||
members.some((member) => typeof member !== "string" || !member.trim())
) {
throw new Error("Le fichier members.json ne contient pas une liste valide");
}
return members;
}
function writeMembers(members, fileUrl = membersJSON) {
const filePath = fileUrl instanceof URL ? fileURLToPath(fileUrl) : fileUrl;
const temporaryFile = `${filePath}.${process.pid}.tmp`;
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(temporaryFile, `${JSON.stringify(members, null, 2)}\n`);
fs.renameSync(temporaryFile, filePath);
}
function assertPlausibleChange(oldMembers, newMembers) {
if (oldMembers.length >= 10 && newMembers.length < oldMembers.length / 2) {
throw new Error(
`Extraction suspecte : ${newMembers.length} membres trouvés contre ${oldMembers.length} auparavant`,
);
}
}
function getChanges(oldMembers, newMembers) {
const oldSet = new Set(oldMembers);
const newSet = new Set(newMembers);
return {
added: newMembers.filter((member) => !oldSet.has(member)),
removed: oldMembers.filter((member) => !newSet.has(member)),
};
}
function formatChanges({ added, removed }) {
let message = "⚔️ **Changement dans la liste des membres :**\n";
if (added.length) message += ` Nouveaux membres : ${added.join(", ")}\n`;
if (removed.length) message += ` Membres partis : ${removed.join(", ")}`;
return message.trimEnd();
}
async function checkGuildMembers({
guildUrl = config.guild_url,
fileUrl = membersJSON,
notify = sendDiscordAlert,
fetchImpl = fetch,
} = {}) {
try {
const newMembers = await getGuildMembers(guildUrl, fetchImpl);
const oldMembers = readMembers(fileUrl);
if (oldMembers) {
assertPlausibleChange(oldMembers, newMembers);
const changes = getChanges(oldMembers, newMembers);
if (changes.added.length || changes.removed.length) {
await notify(formatChanges(changes));
}
}
writeMembers(newMembers, fileUrl);
console.log(
`✅ Vérification terminée : ${newMembers.length} membres`,
new Date().toLocaleString("fr-FR", { timeZone: scheduleTimezone }),
);
} catch (error) {
console.error("❌ Erreur checkGuildMembers:", error);
try {
await notify(`⚠️ Erreur lors du check des membres : ${error.message}`);
} catch (notificationError) {
console.error("❌ Impossible d'envoyer l'erreur sur Discord :", notificationError);
}
}
}
async function initMembersChecker() {
await checkGuildMembers();
cron.schedule(scheduleTime, checkGuildMembers, {
timezone: scheduleTimezone,
noOverlap: true,
});
}
export {
assertPlausibleChange,
checkGuildMembers,
formatChanges,
getChanges,
getGuildMembers,
initMembersChecker,
parseGuildMembers,
};

View file

@ -1,10 +1,22 @@
const config = {
token: "MTQyODI5MDE1Mjk4MzE3MTE2Mw.GFwDOo.-RPnn6meXQL3xAh1S0vCQ9N0H3PeXcxKGSIWAM",
botOwner: "139829632764477440",
applicationId: "1428290152983171163",
guild_url: "https://www.naeu.playblackdesert.com/en-us/Adventure/Guild/GuildProfile?guildName=Abondance&region=EU",
discordGuildId: "1205611509305057410",
textChannelId: "1428068401825321094",
};
import "dotenv/config";
export { config };
function requiredEnv(name) {
const value = process.env[name]?.trim();
if (!value) {
throw new Error(`La variable d'environnement ${name} est obligatoire`);
}
return value;
}
const config = Object.freeze({
token: requiredEnv("DISCORD_TOKEN"),
botOwner: process.env.BOT_OWNER_ID?.trim(),
applicationId: process.env.APPLICATION_ID?.trim(),
guild_url: requiredEnv("GUILD_URL"),
discordGuildId: process.env.DISCORD_GUILD_ID?.trim(),
textChannelId: requiredEnv("DISCORD_CHANNEL_ID"),
});
export { config };