glassframe-protocol 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ # Performance
2
+
3
+ GlassFrame Protocol is built to stay fast under exactly the conditions that
4
+ make speed hardest: a raid, a nuke attempt, or a wave of borderline messages
5
+ all create a burst of work at the same moment. Two small pieces exist
6
+ specifically for that.
7
+
8
+ ## EventQueue - bounded concurrency
9
+
10
+ `src/core/EventQueue.js` is a plain async queue with a concurrency cap.
11
+ Every actual Discord mutation `PunishmentEngine` performs (ban/kick/timeout/
12
+ role removal) and every outbound Groq request `GroqClient` makes goes
13
+ through one of two queues instead of firing directly:
14
+
15
+ - `frame.actionQueue` - Discord mutations, default concurrency 4
16
+ (`config.performance.actionConcurrency`).
17
+ - `frame.aiQueue` - Groq requests, default concurrency 3
18
+ (`config.performance.aiConcurrency`).
19
+
20
+ Nothing is ever dropped - tasks queue instead of running unbounded. This
21
+ matters most during a burst: twenty raid removals firing at once is exactly
22
+ what trips Discord's per-route rate limit and makes every request slower,
23
+ including the ones that matter. Capping concurrency keeps steady throughput
24
+ instead of a stall-then-flood pattern. `GroqClient`'s per-request timeout
25
+ clock only starts once a request actually leaves the queue, so a busy
26
+ moment never produces a false "the AI didn't respond in time" the way it
27
+ would if the timer started at the moment of the API call being requested.
28
+
29
+ Both queues expose `getStats()` (`active`, `pending`, `completed`, `failed`,
30
+ `concurrency`), surfaced through `frame.getMetrics()` and `!gf metrics`.
31
+
32
+ ## PerformanceMonitor - lightweight internal metrics
33
+
34
+ `src/core/PerformanceMonitor.js` tracks two things, both in memory, neither
35
+ requiring a dependency:
36
+
37
+ - **Event counts** - `recordEvent(name)` increments a counter. Every layer's
38
+ main handler calls this once per event it processes
39
+ (`basicSecurity.messageCreate`, `antiRaid.guildMemberAdd`,
40
+ `antiNuke.auditEntry.<type>`, `groqClient.classify`, ...).
41
+ - **Latency samples** - `time(name)` returns a function; call it when the
42
+ unit of work finishes. The last 50 samples per name are kept and averaged.
43
+ Used around `punishmentEngine.execute` and `groqClient.classify`, the two
44
+ places most likely to be slow (Discord API round trips, network calls).
45
+
46
+ `frame.getMetrics()` returns all of this plus every cache's stats in one
47
+ snapshot:
48
+
49
+ ```js
50
+ {
51
+ performance: { uptimeMs, events: { "basicSecurity.messageCreate": 412, ... }, avgLatencyMs: { "groqClient.classify": 340.2, ... } },
52
+ queues: { actions: { active, pending, completed, failed, concurrency }, ai: { ... } },
53
+ caches: { threatScores: { size, hits, misses, evictions }, openCases: { ... }, groqVerdicts: { ... } }
54
+ }
55
+ ```
56
+
57
+ `!gf metrics` renders the same data as a Components V2 message. Neither
58
+ `EventQueue` nor `PerformanceMonitor` phones out anywhere - everything here
59
+ stays in the process.
@@ -0,0 +1,89 @@
1
+ # Protocol Layers
2
+
3
+ GlassFrame Protocol splits every reaction into two kinds, and keeping them
4
+ separate is the core design decision of the whole library.
5
+
6
+ ## Containment vs. punishment
7
+
8
+ **Containment** is a guild-wide, reversible, time-critical measure a layer
9
+ takes on its own the moment it detects trouble - raising verification level
10
+ during a join spike, reverting a role that just gained Administrator. These
11
+ can't wait for a shared decision pipeline; a raid in progress needs an
12
+ answer in milliseconds, not after a round trip through scoring.
13
+
14
+ **Punishment** is anything aimed at one member - timeout, kick, ban,
15
+ quarantine. Every layer funnels this through the same two pieces instead of
16
+ deciding on its own:
17
+
18
+ 1. **`ThreatEngine`** - turns a signal (`{ layer, weight, reason }`) into a
19
+ decaying score per member, and a tier (`none/low/medium/high/critical`).
20
+ 2. **`PunishmentEngine`** - turns a tier into an action, after checking
21
+ `RoleAnalyzer` for hierarchy and trust, and checking for an already-open
22
+ case on that member so a second signal a moment later merges into the
23
+ first incident instead of firing again.
24
+
25
+ This is what makes the protocol "smart" rather than reflexive: a moderator
26
+ account that trips one soft signal doesn't get banned, a member the bot has
27
+ no hierarchy over doesn't get a failed API call retried on every message,
28
+ and twenty raid joins in ten seconds produce one aggregated log line
29
+ instead of twenty separate ones.
30
+
31
+ ## The four layers
32
+
33
+ | Layer | Listens to | Containment it can do alone | Signals it reports |
34
+ |---|---|---|---|
35
+ | `basicSecurity` | `messageCreate` | deletes the offending message | spam rate, duplicate flood, mention spam, scam/phishing language, suspicious links (via `PhishingDatabase`) |
36
+ | `antiRaid` | `guildMemberAdd` | raises verification level, ends lockdown automatically | fast join-rate spikes, alt-account scoring, and creation-time cluster correlation (see below) |
37
+ | `antiNuke` | `guildAuditLogEntryCreate`, plus `messageCreate` only when webhook-abuse watching is on | reverts a dangerous permission grant, deletes an abusive webhook | destructive-action bursts per executor across channels/roles/bans/kicks/webhooks/emoji/stickers |
38
+ | `aiModeration` | `messageCreate` (independently of `basicSecurity`) | - | AI-confirmed category + confidence for gray-zone messages |
39
+
40
+ Each layer extends `src/core/Layer.js`, which handles enabling/disabling
41
+ cleanly: `enable()` attaches the layer's listeners, `disable()` detaches
42
+ every one of them (not just an early-return check inside the handler), so a
43
+ disabled layer truly does no work rather than quietly skipping its own
44
+ logic on every event.
45
+
46
+ `basicSecurity` and `aiModeration` both listen to `messageCreate`
47
+ independently rather than one calling into the other - either can be
48
+ enabled without the other, and if both happen to flag the same message,
49
+ `PunishmentEngine`'s per-member case debounce (see above) merges the two
50
+ signals into one incident rather than double-punishing.
51
+
52
+ ## Evasion-resistant detections
53
+
54
+ Two additions specifically target attacks designed to slip past the
55
+ straightforward rate-counters above:
56
+
57
+ - **Join clustering** (`AntiRaidLayer._clusterScore`) - a bulk-registered
58
+ account farm can trickle members in slowly enough that the join-rate
59
+ counter never trips. Clustering instead asks whether several recent
60
+ joiners' accounts were all *created* within the same narrow window
61
+ (`antiRaid.cluster`), which doesn't depend on how fast they join at all.
62
+ - **Webhook message flooding** (`AntiNukeLayer._handleWebhookMessage`) - a
63
+ webhook message carries no guild member, so a "create a webhook, then
64
+ spam through it" nuke is invisible to every per-member spam check in the
65
+ library. AntiNuke remembers who created a webhook (`recentWebhooks`) for
66
+ a short window and, if that webhook then floods messages
67
+ (`antiNuke.webhookAbuse`), deletes the webhook and reports the original
68
+ creator to `PunishmentEngine` - the only path in the library that reaches
69
+ a member through something *other* than a direct per-member signal.
70
+
71
+ Both feed the same `ThreatEngine` / `PunishmentEngine` pipeline as
72
+ everything else; they're evasion-resistant in what they watch for, not in
73
+ how they respond.
74
+
75
+ ## Why AntiNuke also runs a role audit
76
+
77
+ `RoleAnalyzer.scanRoles()` (role name vs. permission mismatch detection)
78
+ runs automatically once, the moment AntiNuke is armed, and again on demand
79
+ via `!gf scan`. It reports flags, never actions - a human decides what, if
80
+ anything, to do about a role that looks like impersonation bait or quietly
81
+ holds dangerous permissions.
82
+
83
+ ## Adding a fifth layer
84
+
85
+ Extend `Layer`, implement `onEnable()` / `onDisable()`, report signals via
86
+ `this.frame.punishmentEngine.report({ guild, member, layer, weight, reason })`,
87
+ register the instance in `GlassFrame`'s `this.layers` map, and add a button
88
+ in `src/ui/ControlPanel.js` if you want it on the panel. Nothing else in the
89
+ pipeline needs to know the new layer exists.
@@ -0,0 +1,93 @@
1
+ # Publishing to npm
2
+
3
+ This covers turning this folder into a real `npm install glassframe-protocol`
4
+ package, and what actually happens to the code when you do.
5
+
6
+ ## What "publish to npm" actually means for the code
7
+
8
+ `npm publish` uploads whatever `package.json`'s `"files"` field points at
9
+ (here: `dist/`, `docs/`, `examples/`, and the root `README.md` / `LICENSE` /
10
+ `GETTING_STARTED.md` / `CHANGELOG.md`) to the public npm registry. Anyone
11
+ who runs `npm install glassframe-protocol` downloads that and it lands as
12
+ plain text in their `node_modules/glassframe-protocol/` folder - Node has to
13
+ be able to parse it to run it, so there's no way to publish something
14
+ installable that isn't also readable on their disk. `src/`, the root
15
+ `config.js`, and the root `index.js` are **not** in `files`, so they never
16
+ get uploaded - only `dist/` does, which the build script (below) produces.
17
+ That's the real, honest ceiling of what "protection" a package publish can
18
+ offer; the [LICENSE](../LICENSE) is what actually restricts copying and
19
+ modifying it - that's a legal boundary, not a technical one.
20
+
21
+ ## One-time setup
22
+
23
+ 1. **Create an npm account** at https://www.npmjs.com/signup if you don't
24
+ have one, and verify your email.
25
+ 2. **Fill in the placeholders** in `package.json`: `author`, `repository`,
26
+ `homepage`, `bugs`. If you don't have a public git repo for this, you can
27
+ delete those three fields entirely rather than leave `TODO` text in a
28
+ published package.
29
+ 3. **Check the name is still free**: open
30
+ `https://www.npmjs.com/package/glassframe-protocol` in a browser. If it's
31
+ taken, either pick a different `name` in `package.json` or use a scoped
32
+ name like `@yourusername/glassframe-protocol` (scoped names are
33
+ basically always available, and `publishConfig.access: "public"` is
34
+ already set so a scoped package still publishes publicly rather than
35
+ defaulting to private).
36
+ 4. **(Optional) Install terser** for real minification of what gets
37
+ published: `npm install --save-dev terser`. Without it, `npm run build`
38
+ still works, it just copies files through unminified - see
39
+ `scripts/build.js` for why a shortcut minifier isn't used instead.
40
+
41
+ ## Publishing (same steps on Termux, Windows, and Linux - npm doesn't care)
42
+
43
+ ```
44
+ npm login
45
+ ```
46
+ Enter your npm username, password, and the one-time code from your
47
+ authenticator app (npm requires 2FA for publishing now). On Termux this
48
+ works exactly the same as any other terminal - it opens a login flow in
49
+ whatever browser is available, or accepts credentials directly depending on
50
+ your npm version.
51
+
52
+ ```
53
+ npm run build
54
+ ```
55
+ Regenerates `dist/` from the current source. This also runs automatically
56
+ before publish (see `prepublishOnly` in `package.json`), but running it
57
+ yourself first lets you check the output in `dist/` before it goes out.
58
+
59
+ ```
60
+ npm publish
61
+ ```
62
+ That's it - this uploads the package. `npm view glassframe-protocol` right
63
+ after should show your new version.
64
+
65
+ ## Verifying it from a completely clean install
66
+
67
+ On any machine (Termux, Windows PowerShell/cmd, Linux terminal):
68
+
69
+ ```
70
+ mkdir test-install && cd test-install
71
+ npm init -y
72
+ npm install glassframe-protocol discord.js
73
+ node -e "console.log(require('glassframe-protocol'))"
74
+ ```
75
+
76
+ If that prints the `GlassFrame` class with no errors, the published package
77
+ resolves and loads correctly end to end.
78
+
79
+ ## Publishing an update later
80
+
81
+ 1. Make your changes in `src/` / `config.js` / `index.js` (never in `dist/`
82
+ directly - it gets overwritten).
83
+ 2. Add an entry to `CHANGELOG.md` and bump `VERSION` in
84
+ `src/core/VersionInfo.js` to match.
85
+ 3. Bump `"version"` in `package.json` to the same number
86
+ (`npm version patch` / `minor` / `major` does both the bump and creates a
87
+ git tag for you, if you're using git).
88
+ 4. `npm publish` again - `prepublishOnly` rebuilds `dist/` automatically.
89
+
90
+ npm permanently reserves every version number you publish - you can
91
+ `npm unpublish` within a short window after publishing, but you can never
92
+ reuse the same version number afterward, so double-check `dist/` before
93
+ publishing rather than after.
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ const { Client, GatewayIntentBits } = require("discord.js");
4
+ const GlassFrame = require("../index");
5
+
6
+ const client = new Client({
7
+ intents: [
8
+ GatewayIntentBits.Guilds,
9
+ GatewayIntentBits.GuildMembers,
10
+ GatewayIntentBits.GuildMessages,
11
+ GatewayIntentBits.MessageContent,
12
+ GatewayIntentBits.GuildModeration
13
+ ]
14
+ });
15
+
16
+ client.once("ready", () => {
17
+ const frame = new GlassFrame(client, {
18
+ getLogChannel: async (guild) => {
19
+ return guild.channels.cache.find((c) => c.name === "security-logs") ?? null;
20
+ },
21
+ whitelist: [],
22
+ // Layers not listed here stay off until someone presses the panel button
23
+ // or you call frame.enableLayer(name) yourself.
24
+ autoStart: ["basicSecurity", "antiRaid", "antiNuke"],
25
+ debug: false,
26
+ config: {
27
+ aiModeration: {
28
+ // Supply one key or several - GlassFrame rotates across the pool and
29
+ // benches any key that comes back rate-limited.
30
+ apiKeys: (process.env.GROQ_API_KEYS || "").split(",").filter(Boolean)
31
+ }
32
+ }
33
+ });
34
+
35
+ frame.on("warning", (w) => console.warn(`[GlassFrame:${w.layer}]`, w.message));
36
+ frame.punishmentEngine.on("case", (record) => {
37
+ console.log(`[GlassFrame] ${record.action} on ${record.userId} (tier ${record.tier})`);
38
+ });
39
+
40
+ console.log(`GlassFrame Protocol ready in ${client.guilds.cache.size} guild(s).`);
41
+ console.log('Send "!gf panel" in a channel (Manage Server permission) to open the control panel.');
42
+ });
43
+
44
+ client.login(process.env.BOT_TOKEN);
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "glassframe-protocol",
3
+ "version": "1.2.0",
4
+ "description": "A layered, self-contained Discord security engine - AntiRaid, AntiNuke, Basic Security, and optional Groq-powered AI Moderation sharing one threat-scoring and punishment pipeline. Prefix commands only, Components V2 output, no slash commands.",
5
+ "main": "dist/index.js",
6
+ "files": [
7
+ "dist",
8
+ "docs",
9
+ "examples",
10
+ "README.md",
11
+ "LICENSE",
12
+ "GETTING_STARTED.md",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "node scripts/build.js",
17
+ "prepublishOnly": "node scripts/build.js",
18
+ "test": "echo \"See GETTING_STARTED.md - this package has no bundled test runner\" && exit 0"
19
+ },
20
+ "license": "SEE LICENSE IN LICENSE",
21
+ "author": "Sarthak & Sashreek, suvrodey9@gmail.com",
22
+ "keywords": [
23
+ "discord",
24
+ "discord.js",
25
+ "security",
26
+ "anti-raid",
27
+ "anti-nuke",
28
+ "moderation",
29
+ "groq",
30
+ "ai-moderation"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "discord.js": "^14.16.0"
40
+ },
41
+ "devDependencies": {
42
+ "terser": "^5.31.0"
43
+ }
44
+ }