Skip to content

Commit faff9b7

Browse files
committed
Plugin about section
1 parent c3f5410 commit faff9b7

12 files changed

Lines changed: 481 additions & 338 deletions

File tree

bot.sh

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
BOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PID_FILE="$BOT_DIR/data/bot.pid"
6+
LOG_FILE="$BOT_DIR/logs/bot.log"
7+
CMD="${1:-help}"
8+
9+
_running() {
10+
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null
11+
}
12+
13+
_pid() {
14+
cat "$PID_FILE" 2>/dev/null || echo "?"
15+
}
16+
17+
start() {
18+
if _running; then
19+
echo "Already running (PID $(_pid)). Use 'restart' to restart."
20+
exit 1
21+
fi
22+
23+
mkdir -p "$BOT_DIR/logs" "$BOT_DIR/data"
24+
printf '\n[%s] ──── Starting bot ────\n' "$(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE"
25+
26+
nohup node "$BOT_DIR/index.js" >> "$LOG_FILE" 2>&1 &
27+
local pid=$!
28+
echo "$pid" > "$PID_FILE"
29+
30+
# give it a moment to confirm it didn't immediately crash
31+
sleep 1
32+
if kill -0 "$pid" 2>/dev/null; then
33+
echo "Bot started │ PID $pid │ log: $LOG_FILE"
34+
else
35+
rm -f "$PID_FILE"
36+
echo "Bot crashed on startup. Check logs:"
37+
tail -20 "$LOG_FILE"
38+
exit 1
39+
fi
40+
}
41+
42+
stop() {
43+
if ! _running; then
44+
echo "Bot not running."
45+
rm -f "$PID_FILE"
46+
return 0
47+
fi
48+
49+
local pid
50+
pid=$(_pid)
51+
echo "Stopping bot (PID $pid)..."
52+
53+
kill -SIGTERM "$pid"
54+
55+
local waited=0
56+
while kill -0 "$pid" 2>/dev/null && (( waited < 10 )); do
57+
sleep 1
58+
(( waited++ ))
59+
done
60+
61+
if kill -0 "$pid" 2>/dev/null; then
62+
kill -SIGKILL "$pid"
63+
echo "Force-killed (PID $pid)"
64+
else
65+
echo "Stopped (PID $pid)"
66+
fi
67+
68+
rm -f "$PID_FILE"
69+
}
70+
71+
restart() {
72+
stop
73+
sleep 1
74+
start
75+
}
76+
77+
status() {
78+
if _running; then
79+
local pid
80+
pid=$(_pid)
81+
echo "Bot: running │ PID $pid"
82+
echo "Log: $LOG_FILE"
83+
ps -p "$pid" -o pid=,etime=,pcpu=,pmem= --no-headers 2>/dev/null \
84+
| awk '{printf " uptime %-12s cpu %s%% mem %s%%\n", $2, $3, $4}' \
85+
|| true
86+
elif [ -f "$PID_FILE" ]; then
87+
echo "Bot: stopped │ stale PID $(_pid) (removed)"
88+
rm -f "$PID_FILE"
89+
else
90+
echo "Bot: stopped"
91+
fi
92+
}
93+
94+
logs() {
95+
if [ ! -f "$LOG_FILE" ]; then
96+
echo "No log file yet: $LOG_FILE"
97+
exit 1
98+
fi
99+
exec tail -f "$LOG_FILE"
100+
}
101+
102+
case "$CMD" in
103+
start) start ;;
104+
stop) stop ;;
105+
restart) restart ;;
106+
status) status ;;
107+
logs) logs ;;
108+
*)
109+
echo "Usage: $0 {start|stop|restart|status|logs}"
110+
echo ""
111+
echo " start Start bot detached (stays alive after terminal close)"
112+
echo " stop Gracefully stop the bot (SIGTERM, SIGKILL after 10s)"
113+
echo " restart stop + start"
114+
echo " status Show running state, PID, uptime, cpu/mem"
115+
echo " logs Tail live log output (Ctrl-C to exit)"
116+
;;
117+
esac

core/PluginManager.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ class PluginManager {
487487
getPluginList() {
488488
return Array.from(this.plugins.values()).map((plugin) => ({
489489
name: plugin.name,
490+
displayName: plugin.manifest?.displayName,
491+
author: plugin.manifest?.author,
490492
version: plugin.manifest?.version,
491493
description: plugin.manifest?.description,
492494
requiresRestart: !!plugin.manifest?.requiresRestart,
@@ -495,8 +497,17 @@ class PluginManager {
495497
lastError: plugin.lastError,
496498
overrides: Array.from(plugin.overrides.keys()),
497499
commands: Array.from(plugin.commandNames),
500+
hasBrochure: !!(plugin.path && fs.existsSync(path.join(plugin.path, "Brochure.md"))),
498501
}));
499502
}
503+
504+
getBrochure(pluginName) {
505+
const plugin = this.plugins.get(pluginName);
506+
if (!plugin?.path) return null;
507+
const brochurePath = path.join(plugin.path, "Brochure.md");
508+
if (!fs.existsSync(brochurePath)) return null;
509+
return fs.readFileSync(brochurePath, "utf8");
510+
}
500511
}
501512

502513
module.exports = { PluginManager };

core/api/restart-bot.js

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,70 @@
1+
/**
2+
* Spawned detached by the API restart endpoint.
3+
* Kills the old bot process, waits for cleanup, then starts a new detached instance.
4+
*/
15
const { spawn } = require("child_process");
26
const fs = require("fs");
37
const path = require("path");
48

5-
const PID_FILE = path.join(process.cwd(), "data", "bot.pid");
6-
const START_SCRIPT = path.join(process.cwd(), "index.js");
9+
const ROOT = process.cwd();
10+
const PID_FILE = path.join(ROOT, "data", "bot.pid");
11+
const LOG_FILE = path.join(ROOT, "logs", "bot.log");
12+
const ENTRY = path.join(ROOT, "index.js");
713

814
function sleep(ms) {
915
return new Promise((resolve) => setTimeout(resolve, ms));
1016
}
1117

18+
function log(msg) {
19+
const line = `[${new Date().toISOString()}] [restart-bot] ${msg}\n`;
20+
process.stderr.write(line);
21+
try {
22+
fs.appendFileSync(LOG_FILE, line);
23+
} catch (_) { /* best-effort */ }
24+
}
25+
1226
async function main() {
13-
console.log("[Restart] Bot restart initiated...");
27+
log("Restart triggered.");
1428

29+
// Stop old process
1530
if (fs.existsSync(PID_FILE)) {
16-
try {
17-
const oldPid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
18-
if (oldPid && !isNaN(oldPid)) {
19-
console.log(`[Restart] Attempting to stop old process (PID: ${oldPid})`);
20-
try {
21-
process.kill(oldPid, "SIGTERM");
22-
} catch (e) {
23-
console.log("[Restart] Old process already gone");
24-
}
31+
const raw = fs.readFileSync(PID_FILE, "utf8").trim();
32+
const oldPid = parseInt(raw, 10);
33+
if (oldPid && !Number.isNaN(oldPid)) {
34+
log(`Sending SIGTERM to old process (PID ${oldPid})`);
35+
try {
36+
process.kill(oldPid, "SIGTERM");
37+
} catch (_) {
38+
log("Old process already gone.");
2539
}
26-
} catch (e) {
27-
console.log("[Restart] Could not read old PID file");
2840
}
2941
}
3042

31-
console.log("[Restart] Waiting for cleanup...");
43+
log("Waiting for cleanup...");
3244
await sleep(2000);
3345

34-
const env = { ...process.env };
35-
console.log(`[Restart] Starting bot from ${START_SCRIPT}...`);
46+
// Start new detached process, output to log file
47+
fs.mkdirSync(path.join(ROOT, "logs"), { recursive: true });
48+
fs.mkdirSync(path.join(ROOT, "data"), { recursive: true });
3649

37-
const child = spawn("node", [START_SCRIPT], {
38-
env,
39-
cwd: process.cwd(),
40-
stdio: "inherit",
41-
detached: false,
42-
});
50+
fs.appendFileSync(LOG_FILE, `\n[${new Date().toISOString()}] ──── Restarted by API ────\n`);
4351

44-
fs.writeFileSync(PID_FILE, String(child.pid));
52+
const logFd = fs.openSync(LOG_FILE, "a");
53+
const child = spawn("node", [ENTRY], {
54+
cwd: ROOT,
55+
env: process.env,
56+
detached: true,
57+
stdio: ["ignore", logFd, logFd],
58+
});
4559

46-
console.log(`[Restart] New bot started with PID: ${child.pid}`);
60+
child.unref();
61+
fs.closeSync(logFd);
4762

48-
child.on("exit", (code) => {
49-
console.log(`[Restart] Bot process exited with code ${code}`);
50-
process.exit(code);
51-
});
63+
fs.writeFileSync(PID_FILE, String(child.pid));
64+
log(`New bot started (PID ${child.pid})`);
5265
}
5366

5467
main().catch((err) => {
55-
console.error("[Restart] Failed to restart:", err);
68+
log(`Fatal: ${err.message}`);
5669
process.exit(1);
57-
});
70+
});

core/api/server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,14 @@ async function startApiServer({ client, db, pluginManager, hooks, startListening
513513
return { categories: registry.getCategories() };
514514
});
515515

516+
fastify.get("/api/plugins/:name/brochure", async (request, reply) => {
517+
const content = pluginManager.getBrochure(request.params.name);
518+
if (content === null) {
519+
return reply.code(404).send({ error: "No brochure found" });
520+
}
521+
return { content };
522+
});
523+
516524
fastify.get("/api/plugins/registry/:packageName", async (request, reply) => {
517525
const plugin = await registry.getPluginDetails(request.params.packageName);
518526
if (!plugin) {

data/plugin-registry.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@
5959
}
6060
}
6161
],
62-
"lastFetch": 1783095359267
62+
"lastFetch": 1783396510311
6363
}

deploy-commands.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ function loadPluginCommands(pluginsDir) {
7474
}
7575

7676
// Load all commands
77-
loadCommandsFromDirectory(commandsPath);
77+
if (existsSync(commandsPath)) {
78+
loadCommandsFromDirectory(commandsPath);
79+
}
7880
loadPluginCommands(pluginsPath);
7981

8082
// 🌐 Initialize REST client

events/guildMemberAdd.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
11
const { Events, EmbedBuilder, AttachmentBuilder } = require("discord.js");
22
const Database = require("../utils/database");
3-
const { checkRaidDetection } = require("../commands/antimodules/antiraid");
43

54
module.exports = {
65
name: Events.GuildMemberAdd,
76
async execute(member, client) {
8-
// 🛡️ Check for anti-raid detection first
9-
try {
10-
const db = await Database.getInstance();
11-
const raidDetected = await checkRaidDetection(member.guild, member, db);
12-
13-
if (raidDetected) {
14-
console.log(
15-
`🚨 Raid detected in ${member.guild.name} - ${member.user.tag} was part of rapid joining`
16-
);
17-
return; // Don't send welcome message if user was kicked/banned for raiding
18-
}
19-
} catch (error) {
20-
console.error("❌ Error checking anti-raid:", error);
21-
}
22-
237
// 🎂 Check for birthday today
248
try {
259
const db = await Database.getInstance();

0 commit comments

Comments
 (0)