@amatiscorp/disguard 1.2.0 → 1.3.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.
- package/README.md +271 -23
- package/dist/AntiSpam.d.ts +10 -1
- package/dist/AntiSpam.d.ts.map +1 -1
- package/dist/AntiSpam.js +108 -9
- package/dist/AntiSpam.js.map +1 -1
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +48 -1
- package/dist/defaults.js.map +1 -1
- package/dist/detectors/accounts.d.ts.map +1 -1
- package/dist/detectors/accounts.js +13 -0
- package/dist/detectors/accounts.js.map +1 -1
- package/dist/detectors/files.d.ts.map +1 -1
- package/dist/detectors/files.js +10 -0
- package/dist/detectors/files.js.map +1 -1
- package/dist/detectors/ghost.d.ts +4 -0
- package/dist/detectors/ghost.d.ts.map +1 -0
- package/dist/detectors/ghost.js +34 -0
- package/dist/detectors/ghost.js.map +1 -0
- package/dist/detectors/hop.d.ts +3 -0
- package/dist/detectors/hop.d.ts.map +1 -0
- package/dist/detectors/hop.js +30 -0
- package/dist/detectors/hop.js.map +1 -0
- package/dist/detectors/links.d.ts.map +1 -1
- package/dist/detectors/links.js +11 -0
- package/dist/detectors/links.js.map +1 -1
- package/dist/detectors/punctuation.d.ts +3 -0
- package/dist/detectors/punctuation.d.ts.map +1 -0
- package/dist/detectors/punctuation.js +26 -0
- package/dist/detectors/punctuation.js.map +1 -0
- package/dist/detectors/spoilers.d.ts +3 -0
- package/dist/detectors/spoilers.d.ts.map +1 -0
- package/dist/detectors/spoilers.js +25 -0
- package/dist/detectors/spoilers.js.map +1 -0
- package/dist/detectors/words.d.ts +3 -0
- package/dist/detectors/words.d.ts.map +1 -0
- package/dist/detectors/words.js +56 -0
- package/dist/detectors/words.js.map +1 -0
- package/dist/enforcement.d.ts.map +1 -1
- package/dist/enforcement.js +27 -0
- package/dist/enforcement.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +59 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
# Disguard
|
|
2
2
|
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
**Configurable antispam for discord.js v14**
|
|
6
|
+
|
|
7
|
+
Flood · phishing · channel hop · blocked words · ghost pings · TypeScript
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@amatiscorp/disguard)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
|
|
13
|
+
[Install](#install) · [Quick start](#quick-start) · [API](#api) · [Español](#español)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
3
19
|
npm: [`@amatiscorp/disguard`](https://www.npmjs.com/package/@amatiscorp/disguard)
|
|
4
20
|
|
|
5
21
|
Configurable antispam for [discord.js](https://discord.js.org) v14 bots.
|
|
6
22
|
|
|
7
23
|
It is **not** a bot. You plug it into your existing `Client` and decide every threshold, allowlist, and punishment.
|
|
8
24
|
|
|
9
|
-
Detects flood,
|
|
25
|
+
Detects flood, channel hopping, blocked words, phishing / unwanted links, duplicate images, mention spam, ghost pings, excessive caps, and emoji spam.
|
|
10
26
|
|
|
11
27
|
> **Español:** librería antispam configurable para bots de discord.js. No es un bot. La documentación en español está más abajo: [Español](#español).
|
|
12
28
|
|
|
@@ -21,6 +37,58 @@ Detects flood, repeated text, phishing / unwanted links, duplicate images, menti
|
|
|
21
37
|
|
|
22
38
|
# English
|
|
23
39
|
|
|
40
|
+
## What's new in 1.3.0
|
|
41
|
+
|
|
42
|
+
More detectors, role overlays, mute-style role punishments, and extra ignore knobs. Everything is optional and falls back to the preset.
|
|
43
|
+
|
|
44
|
+
| Option | Default | What it does |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `words` | **off**, empty list | Block a word list and/or regex. `ignoreCase` and `matchWholeWord` are both `true` by default. |
|
|
47
|
+
| `hop` | **on**, 5 channels / 8s | Same user posting across too many unique channels in a window (raid / hop). |
|
|
48
|
+
| `punctuation` | **on**, 10 | `aaaaaa` / `!!!!!!` runs longer than `maxRepeated`. |
|
|
49
|
+
| `spoilers` | **on**, 8 pairs | Too many `\|\|spoiler\|\|` pairs in one message. |
|
|
50
|
+
| `ghostPing` | **off** (`strict`: on) | Punish deleting a message that mentioned people (`messageDelete`). |
|
|
51
|
+
| `checkDeletes` | `true` | Listen for `messageDelete`. Set `false` if you do not want ghost-ping checks. |
|
|
52
|
+
| `ignored.prefixes` | `[]` | Skip messages that start with `!`, `/`, `.`, etc. |
|
|
53
|
+
| `ignoreThreads` | `false` | Skip thread messages. |
|
|
54
|
+
| `ignoreSystem` | `true` | Skip Discord system messages. |
|
|
55
|
+
| `roleOverrides` / `setRoleConfig` | `{}` | Per-role config overlay (before channel overrides). |
|
|
56
|
+
| `links.blockOauth` | `true` | Flag non-allowlisted URLs whose path looks like `/oauth`, `/authorize`, `/login`. Official `discord.com` stays allowed. |
|
|
57
|
+
| `files.maxBytes` | `0` | Max attachment size in bytes. `0` = unlimited. |
|
|
58
|
+
| `accounts.minGuildAgeDays` | `0` | Flag members who joined the guild too recently. `0` = off. |
|
|
59
|
+
| `punishment.addRoleIds` / `removeRoleIds` | `[]` | Add/remove roles on punish (mute role). Needs **Manage Roles**. |
|
|
60
|
+
| `punishment.minStrikesForRoles` | `1` | Minimum strikes before those role actions run. |
|
|
61
|
+
| `onCooldown` | — | Callback when extra spam is deleted quietly during the punishment cooldown. |
|
|
62
|
+
| `pause()` / `resume()` | — | Freeze enforcement without removing listeners. |
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const antispam = new AntiSpam(client, {
|
|
66
|
+
preset: "balanced",
|
|
67
|
+
locale: "es",
|
|
68
|
+
ignored: { prefixes: ["!", "/", "."], roles: ["STAFF_ROLE_ID"] },
|
|
69
|
+
words: { enabled: true, list: ["raid-now"], regex: ["n[i1]tro\\s+free"] },
|
|
70
|
+
hop: { maxChannels: 4, windowMs: 6_000 },
|
|
71
|
+
ghostPing: { enabled: true, minMentions: 1, maxAgeMs: 15_000 },
|
|
72
|
+
links: { blockOauth: true },
|
|
73
|
+
files: { maxBytes: 8 * 1024 * 1024 },
|
|
74
|
+
roleOverrides: {
|
|
75
|
+
"VIP_ROLE_ID": { flood: { maxMessages: 10 } },
|
|
76
|
+
},
|
|
77
|
+
punishment: {
|
|
78
|
+
addRoleIds: ["MUTE_ROLE_ID"],
|
|
79
|
+
minStrikesForRoles: 2,
|
|
80
|
+
},
|
|
81
|
+
onCooldown(message) {
|
|
82
|
+
console.log("quiet delete", message.id);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
antispam.setRoleConfig("VIP_ROLE_ID", { emojis: { maxEmojis: 40 } });
|
|
87
|
+
antispam.pause(); // raid over? antispam.resume();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Ghost pings need the message in cache (or `Partials.Message`) so `messageDelete` still has mentions. Kick/ban stay **off**. Role add/remove is opt-in via empty arrays.
|
|
91
|
+
|
|
24
92
|
## What's new in 1.2.0
|
|
25
93
|
|
|
26
94
|
More knobs, per-channel rules, and bilingual user-facing text.
|
|
@@ -105,6 +173,7 @@ console.log(antispam.getStats());
|
|
|
105
173
|
|
|
106
174
|
## Table of contents
|
|
107
175
|
|
|
176
|
+
- [What's new in 1.3.0](#whats-new-in-130)
|
|
108
177
|
- [What's new in 1.2.0](#whats-new-in-120)
|
|
109
178
|
- [What's new in 1.1.0](#whats-new-in-110)
|
|
110
179
|
- [Features](#features)
|
|
@@ -138,17 +207,23 @@ console.log(antispam.getStats());
|
|
|
138
207
|
| **file** | Dangerous attachments (`.exe`, `.bat`, `.dll`, `.msi`, …) |
|
|
139
208
|
| **zalgo** | Obfuscated / zalgo text (stacked combining marks) |
|
|
140
209
|
| **newline** | Message walls made of empty lines |
|
|
141
|
-
| **account** | Brand-new Discord accounts / default avatar (off by default) |
|
|
210
|
+
| **account** | Brand-new Discord accounts / default avatar / new guild members (off by default) |
|
|
142
211
|
| **length** | Over-long messages (off by default) |
|
|
212
|
+
| **word** | Custom blocked words and regex (off until you fill `list` / `regex`) |
|
|
213
|
+
| **hop** | Same user jumping across too many channels |
|
|
214
|
+
| **punctuation** | Long runs of the same character (`!!!!`, `aaaa`) |
|
|
215
|
+
| **spoiler** | Spoiler-tag walls |
|
|
216
|
+
| **ghost** | Deleted messages that mentioned users (off by default) |
|
|
143
217
|
|
|
144
218
|
Also included:
|
|
145
219
|
|
|
146
220
|
- Three presets: `lenient`, `balanced`, `strict`
|
|
147
|
-
- Ignore lists for users, roles, channels, categories, and
|
|
221
|
+
- Ignore lists for users, roles, channels, categories, guilds, and command prefixes
|
|
222
|
+
- Per-guild, per-channel, and per-role overlays
|
|
148
223
|
- Strike system with decay
|
|
149
|
-
- Optional delete / warn / timeout / kick / ban (kick and ban are **off** by default)
|
|
224
|
+
- Optional delete / warn / timeout / kick / ban / addRole / removeRole (kick and ban are **off** by default)
|
|
150
225
|
- `dryRun` to tune rules without punishing anyone
|
|
151
|
-
- Edit scanning (links and
|
|
226
|
+
- Edit scanning (links, mentions, and words)
|
|
152
227
|
- Zero extra runtime dependencies (peer: `discord.js`)
|
|
153
228
|
- In-memory store only — no database required
|
|
154
229
|
- Full TypeScript types
|
|
@@ -263,6 +338,7 @@ npm run dev
|
|
|
263
338
|
| Warn in the channel | **Send Messages** |
|
|
264
339
|
| Log embeds | **Embed Links** |
|
|
265
340
|
| Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
|
|
341
|
+
| Mute / add / remove roles (opt-in) | **Manage Roles** |
|
|
266
342
|
|
|
267
343
|
The bot will not sanction the guild owner or anyone above it in the role hierarchy. Those actions are skipped and reported in `onAction`.
|
|
268
344
|
|
|
@@ -270,11 +346,12 @@ Enable **Message Content Intent** in the [Discord Developer Portal](https://disc
|
|
|
270
346
|
|
|
271
347
|
## How it works
|
|
272
348
|
|
|
273
|
-
1. Ignores bots, webhooks, the owner, administrators, and anything in your ignore lists.
|
|
349
|
+
1. Ignores bots, webhooks, system messages, the owner, administrators, command prefixes, and anything in your ignore lists.
|
|
274
350
|
2. Keeps a short **in-memory** history per user and guild (no database).
|
|
275
|
-
3. Runs detectors in this order: files → flood → duplicates → links → images → mentions → zalgo → newlines → caps → emojis. The first match wins.
|
|
351
|
+
3. Runs detectors in this order: files → words → flood → hop → duplicates → links → images → mentions → zalgo → newlines → punctuation → spoilers → accounts → length → caps → emojis. The first match wins.
|
|
276
352
|
4. Adds one strike (with optional decay) and applies the configured punishment.
|
|
277
|
-
5. Message edits
|
|
353
|
+
5. Message edits re-check **links**, **mentions**, and **words**, so editing `hello` into a phishing URL or a blocked word is still caught without counting as flood.
|
|
354
|
+
6. Deletes can trigger **ghost** pings if `ghostPing.enabled` is true.
|
|
278
355
|
|
|
279
356
|
Call `antispam.stop()` on shutdown, hot reload, or plugin unload.
|
|
280
357
|
|
|
@@ -304,6 +381,8 @@ new AntiSpam(client, {
|
|
|
304
381
|
| Mentions | 10 | 6 | 3 |
|
|
305
382
|
| Block invites | no | no | yes |
|
|
306
383
|
| Block shorteners | no | yes | yes |
|
|
384
|
+
| Channel hop | off | 5 / 8s | 3 / 6s |
|
|
385
|
+
| Ghost pings | off | off | on |
|
|
307
386
|
| Auto timeout | off | 60s from 2 strikes | 5 min from 1 strike |
|
|
308
387
|
|
|
309
388
|
## Full configuration
|
|
@@ -320,13 +399,17 @@ Every option is optional. Missing fields fall back to the preset (or `balanced`
|
|
|
320
399
|
| `ignoreWebhooks` | `boolean` | `true` | Skip webhooks. |
|
|
321
400
|
| `ignoreOwner` | `boolean` | `true` | Skip the guild owner. |
|
|
322
401
|
| `ignoreAdministrators` | `boolean` | `true` | Skip members with Administrator. |
|
|
323
|
-
| `checkEdits` | `boolean` | `true` | Re-scan edits for links and
|
|
402
|
+
| `checkEdits` | `boolean` | `true` | Re-scan edits for links, mentions, and words. |
|
|
403
|
+
| `checkDeletes` | `boolean` | `true` | Listen for `messageDelete` (ghost pings). |
|
|
404
|
+
| `ignoreThreads` | `boolean` | `false` | Skip messages in threads. |
|
|
405
|
+
| `ignoreSystem` | `boolean` | `true` | Skip Discord system messages. |
|
|
324
406
|
| `cleanupIntervalMs` | `number` | `60000` | How often the memory store is pruned. |
|
|
325
407
|
| `locale` | `"en"` \| `"es"` | `"en"` | Language for warnings and log embeds. |
|
|
326
408
|
| `ignorePermissions` | `string[]` | `[]` | Permission names that bypass checks. |
|
|
327
409
|
| `disabledDetectors` | `DetectorType[]` | `[]` | Detectors to skip. |
|
|
328
410
|
| `detectorOrder` | `DetectorType[]` | `[]` | Custom order. Empty = default. |
|
|
329
411
|
| `channelOverrides` | `object` | `{}` | Per-channel patches keyed by channel id. |
|
|
412
|
+
| `roleOverrides` | `object` | `{}` | Per-role patches keyed by role id. |
|
|
330
413
|
|
|
331
414
|
### Ignore lists
|
|
332
415
|
|
|
@@ -337,10 +420,11 @@ ignored: {
|
|
|
337
420
|
channels: ["789"],
|
|
338
421
|
categories: ["101"],
|
|
339
422
|
guilds: ["202"],
|
|
423
|
+
prefixes: ["!", "/", "."],
|
|
340
424
|
}
|
|
341
425
|
```
|
|
342
426
|
|
|
343
|
-
IDs are snowflakes as strings. A staff role in `ignored.roles` bypasses every detector.
|
|
427
|
+
IDs are snowflakes as strings. A staff role in `ignored.roles` bypasses every detector. `prefixes` skip command messages (`!help` is ignored if `"!"` is listed).
|
|
344
428
|
|
|
345
429
|
### Flood
|
|
346
430
|
|
|
@@ -387,6 +471,9 @@ links: {
|
|
|
387
471
|
suspiciousTlds: [], // e.g. ["tk", "gq"]
|
|
388
472
|
customPatterns: [], // regex against the whole message
|
|
389
473
|
extraPhishingKeywords: ["fake giveaway"],
|
|
474
|
+
maxLinks: 0,
|
|
475
|
+
scanEmbeds: true,
|
|
476
|
+
blockOauth: true,
|
|
390
477
|
severity: "high",
|
|
391
478
|
}
|
|
392
479
|
```
|
|
@@ -402,6 +489,7 @@ Built-in checks (each can be turned off):
|
|
|
402
489
|
- Scam phrases plus any link (`free nitro`, `steam gift`, `verifica tu cuenta`, …)
|
|
403
490
|
- Discord invite links if `blockInvites` is `true`
|
|
404
491
|
- Markdown-masked links: `[click](https://evil.test)`
|
|
492
|
+
- Login / OAuth-looking paths on non-allowlisted hosts if `blockOauth` is `true`
|
|
405
493
|
|
|
406
494
|
```js
|
|
407
495
|
// Block foreign server invites
|
|
@@ -474,6 +562,7 @@ accounts: {
|
|
|
474
562
|
enabled: false, // turn on to flag new users
|
|
475
563
|
minAgeDays: 3,
|
|
476
564
|
blockDefaultAvatar: false,
|
|
565
|
+
minGuildAgeDays: 0, // 0 = off; e.g. 1 = joined this guild today
|
|
477
566
|
severity: "medium",
|
|
478
567
|
}
|
|
479
568
|
|
|
@@ -490,6 +579,7 @@ length: {
|
|
|
490
579
|
files: {
|
|
491
580
|
enabled: true,
|
|
492
581
|
blockedExtensions: ["exe", "bat", "cmd", "com", "scr", "dll", "msi", "vbs", "ps1", "jar", "apk"],
|
|
582
|
+
maxBytes: 0, // 0 = unlimited
|
|
493
583
|
severity: "critical",
|
|
494
584
|
}
|
|
495
585
|
|
|
@@ -506,6 +596,57 @@ newlines: {
|
|
|
506
596
|
}
|
|
507
597
|
```
|
|
508
598
|
|
|
599
|
+
### Words, channel hop, punctuation, spoilers
|
|
600
|
+
|
|
601
|
+
```js
|
|
602
|
+
words: {
|
|
603
|
+
enabled: false, // stays off until you add a list or regex
|
|
604
|
+
list: ["raid-now"],
|
|
605
|
+
regex: ["n[i1]tro\\s+free"],
|
|
606
|
+
ignoreCase: true,
|
|
607
|
+
matchWholeWord: true,
|
|
608
|
+
severity: "high",
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
hop: {
|
|
612
|
+
enabled: true,
|
|
613
|
+
maxChannels: 5, // 5th unique channel in the window triggers
|
|
614
|
+
windowMs: 8_000,
|
|
615
|
+
severity: "high",
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
punctuation: {
|
|
619
|
+
enabled: true,
|
|
620
|
+
maxRepeated: 10, // 11+ of the same character in a row
|
|
621
|
+
severity: "low",
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
spoilers: {
|
|
625
|
+
enabled: true,
|
|
626
|
+
maxSpoilers: 8,
|
|
627
|
+
severity: "low",
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
`lenient` turns hop and punctuation off. `strict` tightens hop to 3 channels / 6s.
|
|
632
|
+
|
|
633
|
+
### Ghost pings
|
|
634
|
+
|
|
635
|
+
Off in `balanced` / `lenient`. On in `strict`. Needs `checkDeletes: true` (default) and the deleted message in cache (`Partials.Message` in your Client).
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
ghostPing: {
|
|
639
|
+
enabled: false,
|
|
640
|
+
minMentions: 1,
|
|
641
|
+
maxAgeMs: 15_000, // 0 = any age
|
|
642
|
+
severity: "high",
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
checkDeletes: true
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Disable with `ghostPing: { enabled: false }` or `disabledDetectors: ["ghost"]`.
|
|
649
|
+
|
|
509
650
|
### Punishment
|
|
510
651
|
|
|
511
652
|
Kick and ban stay **disabled** on purpose. Turn them on only if you really want that.
|
|
@@ -525,6 +666,9 @@ punishment: {
|
|
|
525
666
|
cooldownMs: 8_000, // no second timeout/warn during this window
|
|
526
667
|
deleteDuringCooldown: true, // still delete extra spam quietly
|
|
527
668
|
warnAsEmbed: false,
|
|
669
|
+
addRoleIds: [], // e.g. ["MUTE_ROLE_ID"] — needs Manage Roles
|
|
670
|
+
removeRoleIds: [],
|
|
671
|
+
minStrikesForRoles: 1,
|
|
528
672
|
}
|
|
529
673
|
|
|
530
674
|
// timeout.scale: "none" | "linear" (base * strikes) | "exponential" (base * 2^n)
|
|
@@ -534,7 +678,7 @@ punishment: {
|
|
|
534
678
|
|
|
535
679
|
`warnMessage` placeholders: `{user}` `{reason}` `{type}` `{strikes}`.
|
|
536
680
|
|
|
537
|
-
With `escalate: true`, Disguard applies **one** hard action (timeout, or kick, or ban — whichever threshold you hit). Delete and warn can still run together.
|
|
681
|
+
With `escalate: true`, Disguard applies **one** hard action (timeout, or kick, or ban — whichever threshold you hit). Delete and warn can still run together. `addRole` / `removeRole` run if those ID lists are non-empty and strikes ≥ `minStrikesForRoles`.
|
|
538
682
|
|
|
539
683
|
If the bot lacks a permission, or the target is higher in the hierarchy, that action is skipped and listed in `result.skipped`.
|
|
540
684
|
|
|
@@ -552,6 +696,9 @@ new AntiSpam(client, {
|
|
|
552
696
|
onError(error, context) {
|
|
553
697
|
console.error("[disguard]", context, error);
|
|
554
698
|
},
|
|
699
|
+
onCooldown(message) {
|
|
700
|
+
// Extra spam during punishment.cooldownMs (already deleted if deleteDuringCooldown).
|
|
701
|
+
},
|
|
555
702
|
});
|
|
556
703
|
```
|
|
557
704
|
|
|
@@ -559,7 +706,9 @@ new AntiSpam(client, {
|
|
|
559
706
|
|
|
560
707
|
```ts
|
|
561
708
|
{
|
|
562
|
-
type: "flood" | "duplicate" | "link" | "image" | "mention" | "caps" | "emoji"
|
|
709
|
+
type: "flood" | "duplicate" | "link" | "image" | "mention" | "caps" | "emoji"
|
|
710
|
+
| "file" | "zalgo" | "newline" | "account" | "length"
|
|
711
|
+
| "word" | "hop" | "punctuation" | "spoiler" | "ghost",
|
|
563
712
|
severity: "low" | "medium" | "high" | "critical",
|
|
564
713
|
userId: string,
|
|
565
714
|
guildId: string,
|
|
@@ -567,7 +716,7 @@ new AntiSpam(client, {
|
|
|
567
716
|
messageId: string,
|
|
568
717
|
reason: string,
|
|
569
718
|
details: Record<string, unknown>,
|
|
570
|
-
recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban">,
|
|
719
|
+
recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban" | "addRole" | "removeRole">,
|
|
571
720
|
timestamp: number,
|
|
572
721
|
}
|
|
573
722
|
```
|
|
@@ -594,6 +743,9 @@ const antispam = new AntiSpam(client, options);
|
|
|
594
743
|
|
|
595
744
|
antispam.start();
|
|
596
745
|
antispam.stop();
|
|
746
|
+
antispam.pause();
|
|
747
|
+
antispam.resume();
|
|
748
|
+
antispam.isPaused();
|
|
597
749
|
|
|
598
750
|
const config = antispam.getConfig();
|
|
599
751
|
antispam.setConfig({ flood: { maxMessages: 8 } }); // deep merge, other keys stay
|
|
@@ -608,6 +760,8 @@ antispam.clearGuildConfig(guildId);
|
|
|
608
760
|
antispam.setChannelConfig(guildId, channelId, { images: { maxRepeats: 8 } });
|
|
609
761
|
antispam.getChannelConfig(guildId, channelId);
|
|
610
762
|
antispam.clearChannelConfig(guildId, channelId);
|
|
763
|
+
antispam.setRoleConfig(roleId, { flood: { maxMessages: 12 } });
|
|
764
|
+
antispam.clearRoleConfig(roleId);
|
|
611
765
|
|
|
612
766
|
antispam.use(customDetector);
|
|
613
767
|
antispam.getStats();
|
|
@@ -692,6 +846,18 @@ antispam.setConfig(resolveConfig("strict", { ignored: antispam.getConfig().ignor
|
|
|
692
846
|
images: { hashMode: "content", maxDownloadBytes: 1_000_000 }
|
|
693
847
|
```
|
|
694
848
|
|
|
849
|
+
**Mute role instead of timeout**
|
|
850
|
+
|
|
851
|
+
```js
|
|
852
|
+
punishment: {
|
|
853
|
+
timeout: { enabled: false, durationMs: 60_000, minStrikes: 99 },
|
|
854
|
+
addRoleIds: ["MUTE_ROLE_ID"],
|
|
855
|
+
minStrikesForRoles: 1,
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
The bot role must sit **above** the mute role. Needs **Manage Roles**.
|
|
860
|
+
|
|
695
861
|
**Reset a user after a false positive**
|
|
696
862
|
|
|
697
863
|
```js
|
|
@@ -746,9 +912,14 @@ Wait a few seconds between categories so flood does not eat the next test.
|
|
|
746
912
|
| File | Upload a dummy `.exe` or `.bat` |
|
|
747
913
|
| Zalgo | Text with many stacked combining marks |
|
|
748
914
|
| Newlines | A message with more than 15 line breaks |
|
|
915
|
+
| Channel hop | Same user posts in 5 different channels within 8 seconds |
|
|
916
|
+
| Punctuation | `aaaaaaaaaaa` or `!!!!!!!!!!!` |
|
|
917
|
+
| Spoilers | More than 8 `\|\|spoiler\|\|` pairs |
|
|
918
|
+
| Blocked word | Enable `words` first, then send a listed word |
|
|
919
|
+
| Ghost ping | Mention someone and delete the message within 15s (`ghostPing.enabled: true`) |
|
|
749
920
|
| Edit | Send `hello`, then edit it to `Free Nitro https://bit.ly/test` |
|
|
750
921
|
|
|
751
|
-
Watch the terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji`.
|
|
922
|
+
Watch the terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost`.
|
|
752
923
|
|
|
753
924
|
The second strike applies a 1 minute timeout with the default example config. Strikes decay after 15 minutes, or call `resetUser`.
|
|
754
925
|
|
|
@@ -789,6 +960,45 @@ MIT
|
|
|
789
960
|
|
|
790
961
|
# Español
|
|
791
962
|
|
|
963
|
+
## Novedades de 1.3.0
|
|
964
|
+
|
|
965
|
+
Más detectores, overlays por rol, mute por rol y más ignores. Todo es opcional.
|
|
966
|
+
|
|
967
|
+
| Opción | Default | Qué hace |
|
|
968
|
+
| --- | --- | --- |
|
|
969
|
+
| `words` | **apagado**, lista vacía | Palabras y/o regex bloqueados. `ignoreCase` y `matchWholeWord` en `true`. |
|
|
970
|
+
| `hop` | **on**, 5 canales / 8s | El mismo usuario escribe en demasiados canales (raid / hop). |
|
|
971
|
+
| `punctuation` | **on**, 10 | Rachas de `aaaaaa` / `!!!!!!` más largas que `maxRepeated`. |
|
|
972
|
+
| `spoilers` | **on**, 8 pares | Demasiados `\|\|spoiler\|\|` en un mensaje. |
|
|
973
|
+
| `ghostPing` | **off** (`strict`: on) | Castiga borrar un mensaje que mencionaba gente. |
|
|
974
|
+
| `checkDeletes` | `true` | Escucha `messageDelete`. |
|
|
975
|
+
| `ignored.prefixes` | `[]` | Ignora mensajes que empiezan por `!`, `/`, `.`. |
|
|
976
|
+
| `ignoreThreads` | `false` | Ignora hilos. |
|
|
977
|
+
| `ignoreSystem` | `true` | Ignora mensajes de sistema. |
|
|
978
|
+
| `roleOverrides` / `setRoleConfig` | `{}` | Config por rol (antes de la de canal). |
|
|
979
|
+
| `links.blockOauth` | `true` | URLs de login/OAuth fuera de la allowList. `discord.com` sigue permitido. |
|
|
980
|
+
| `files.maxBytes` | `0` | Tamaño máximo de adjunto. `0` = sin límite. |
|
|
981
|
+
| `accounts.minGuildAgeDays` | `0` | Miembros demasiado nuevos en **este** servidor. `0` = off. |
|
|
982
|
+
| `punishment.addRoleIds` / `removeRoleIds` | `[]` | Añadir/quitar roles al castigar (mute). Hace falta **Manage Roles**. |
|
|
983
|
+
| `onCooldown` | — | Callback cuando el cooldown borra spam extra en silencio. |
|
|
984
|
+
| `pause()` / `resume()` | — | Congela el antispam sin quitar listeners. |
|
|
985
|
+
|
|
986
|
+
```js
|
|
987
|
+
const antispam = new AntiSpam(client, {
|
|
988
|
+
locale: "es",
|
|
989
|
+
ignored: { prefixes: ["!", "/", "."], roles: ["ID_STAFF"] },
|
|
990
|
+
words: { enabled: true, list: ["raid-now"] },
|
|
991
|
+
hop: { maxChannels: 4, windowMs: 6_000 },
|
|
992
|
+
ghostPing: { enabled: true, maxAgeMs: 15_000 },
|
|
993
|
+
punishment: { addRoleIds: ["ID_MUTE"], minStrikesForRoles: 2 },
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
antispam.setRoleConfig("ID_VIP", { flood: { maxMessages: 10 } });
|
|
997
|
+
antispam.pause();
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Kick y ban siguen **apagados**. Ghost ping necesita el mensaje en caché (`Partials.Message`).
|
|
1001
|
+
|
|
792
1002
|
## Novedades de 1.2.0
|
|
793
1003
|
|
|
794
1004
|
Más opciones, reglas por canal y textos al usuario en inglés o español.
|
|
@@ -839,6 +1049,7 @@ new AntiSpam(client, {
|
|
|
839
1049
|
|
|
840
1050
|
## Tabla de contenidos
|
|
841
1051
|
|
|
1052
|
+
- [Novedades de 1.3.0](#novedades-de-130)
|
|
842
1053
|
- [Novedades de 1.2.0](#novedades-de-120)
|
|
843
1054
|
- [Novedades de 1.1.0](#novedades-de-110)
|
|
844
1055
|
- [Qué es](#qué-es)
|
|
@@ -862,7 +1073,7 @@ new AntiSpam(client, {
|
|
|
862
1073
|
|
|
863
1074
|
**Disguard** es una librería antispam para bots de discord.js v14. No es un bot: la enchufas a tu `Client` y tú decides umbrales, listas y castigos.
|
|
864
1075
|
|
|
865
|
-
Detecta flood,
|
|
1076
|
+
Detecta flood, salto de canales, palabras bloqueadas, phishing / enlaces no deseados (también en embeds), imágenes repetidas, spam de menciones, ghost pings, mayúsculas, emojis, archivos peligrosos, zalgo y paredes de líneas.
|
|
866
1077
|
|
|
867
1078
|
## Requisitos
|
|
868
1079
|
|
|
@@ -948,6 +1159,7 @@ npm run dev
|
|
|
948
1159
|
| Avisar en el canal | **Send Messages** |
|
|
949
1160
|
| Embed de logs | **Embed Links** |
|
|
950
1161
|
| Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
|
|
1162
|
+
| Mute / add / remove roles (opt-in) | **Manage Roles** |
|
|
951
1163
|
|
|
952
1164
|
No puede sancionar al dueño del servidor ni a quien esté por encima del bot en la jerarquía.
|
|
953
1165
|
|
|
@@ -955,11 +1167,12 @@ Activa **Message Content Intent** en el [Portal de Discord](https://discord.com/
|
|
|
955
1167
|
|
|
956
1168
|
## Cómo funciona
|
|
957
1169
|
|
|
958
|
-
1. Ignora bots, webhooks, el dueño, administradores y tus listas de ignore.
|
|
1170
|
+
1. Ignora bots, webhooks, mensajes de sistema, el dueño, administradores, prefijos de comando y tus listas de ignore.
|
|
959
1171
|
2. Guarda en **memoria** un historial corto por usuario y servidor. Sin base de datos.
|
|
960
|
-
3. Pasa el mensaje por: flood → duplicados → enlaces → imágenes → menciones → caps → emojis. El primero que dispare gana.
|
|
1172
|
+
3. Pasa el mensaje por: archivos → palabras → flood → hop → duplicados → enlaces → imágenes → menciones → zalgo → líneas → puntuación → spoilers → cuentas → longitud → caps → emojis. El primero que dispare gana.
|
|
961
1173
|
4. Suma un strike (con caducidad) y aplica el castigo configurado.
|
|
962
|
-
5. Las ediciones
|
|
1174
|
+
5. Las ediciones revisan **enlaces**, **menciones** y **palabras**.
|
|
1175
|
+
6. Los borrados pueden disparar **ghost** ping si `ghostPing.enabled` es true.
|
|
963
1176
|
|
|
964
1177
|
Llama `antispam.stop()` al apagar el proceso.
|
|
965
1178
|
|
|
@@ -992,8 +1205,12 @@ Todo es opcional. Lo que no pongas usa el preset.
|
|
|
992
1205
|
| `ignoreWebhooks` | `boolean` | `true` | Ignora webhooks. |
|
|
993
1206
|
| `ignoreOwner` | `boolean` | `true` | Ignora al dueño. |
|
|
994
1207
|
| `ignoreAdministrators` | `boolean` | `true` | Ignora quien tenga Administrator. |
|
|
995
|
-
| `checkEdits` | `boolean` | `true` | Revisa ediciones (enlaces y
|
|
1208
|
+
| `checkEdits` | `boolean` | `true` | Revisa ediciones (enlaces, menciones y palabras). |
|
|
1209
|
+
| `checkDeletes` | `boolean` | `true` | Escucha `messageDelete` (ghost pings). |
|
|
1210
|
+
| `ignoreThreads` | `boolean` | `false` | Ignora hilos. |
|
|
1211
|
+
| `ignoreSystem` | `boolean` | `true` | Ignora mensajes de sistema. |
|
|
996
1212
|
| `cleanupIntervalMs` | `number` | `60000` | Limpieza de memoria. |
|
|
1213
|
+
| `roleOverrides` | `object` | `{}` | Parches por rol. |
|
|
997
1214
|
|
|
998
1215
|
### Listas de ignore
|
|
999
1216
|
|
|
@@ -1004,6 +1221,7 @@ ignored: {
|
|
|
1004
1221
|
channels: ["id"],
|
|
1005
1222
|
categories: ["id"],
|
|
1006
1223
|
guilds: ["id"],
|
|
1224
|
+
prefixes: ["!", "/", "."],
|
|
1007
1225
|
}
|
|
1008
1226
|
```
|
|
1009
1227
|
|
|
@@ -1048,13 +1266,16 @@ links: {
|
|
|
1048
1266
|
suspiciousTlds: [],
|
|
1049
1267
|
customPatterns: ["steamcommunity\\.ru"],
|
|
1050
1268
|
extraPhishingKeywords: ["sorteo falso"],
|
|
1269
|
+
maxLinks: 0,
|
|
1270
|
+
scanEmbeds: true,
|
|
1271
|
+
blockOauth: true,
|
|
1051
1272
|
severity: "high",
|
|
1052
1273
|
}
|
|
1053
1274
|
```
|
|
1054
1275
|
|
|
1055
1276
|
La `allowList` gana a las heurísticas. `discord.com` y `youtube.com` ya vienen permitidos.
|
|
1056
1277
|
|
|
1057
|
-
Heurísticas incluidas (todas se pueden apagar): acortadores, IPs, punycode, clones de marcas, palabras de estafa + enlace, invitaciones si `blockInvites` es `true`,
|
|
1278
|
+
Heurísticas incluidas (todas se pueden apagar): acortadores, IPs, punycode, clones de marcas, palabras de estafa + enlace, invitaciones si `blockInvites` es `true`, enlaces enmascarados `[texto](url)`, y rutas de login/OAuth si `blockOauth` es `true`.
|
|
1058
1279
|
|
|
1059
1280
|
### Imágenes
|
|
1060
1281
|
|
|
@@ -1086,11 +1307,23 @@ emojis: { enabled: true, maxEmojis: 12, maxStickers: 3, severity: "low" },
|
|
|
1086
1307
|
### Archivos, zalgo, saltos de línea
|
|
1087
1308
|
|
|
1088
1309
|
```js
|
|
1089
|
-
files: { enabled: true, blockedExtensions: ["exe", "bat", "cmd", "dll", "msi"], severity: "critical" },
|
|
1310
|
+
files: { enabled: true, blockedExtensions: ["exe", "bat", "cmd", "dll", "msi"], maxBytes: 0, severity: "critical" },
|
|
1090
1311
|
zalgo: { enabled: true, maxCombining: 20, severity: "medium" },
|
|
1091
1312
|
newlines: { enabled: true, maxNewlines: 15, severity: "low" },
|
|
1092
1313
|
```
|
|
1093
1314
|
|
|
1315
|
+
### Palabras, hop, puntuación, spoilers, ghost ping
|
|
1316
|
+
|
|
1317
|
+
```js
|
|
1318
|
+
words: { enabled: false, list: ["raid-now"], regex: [], ignoreCase: true, matchWholeWord: true, severity: "high" },
|
|
1319
|
+
hop: { enabled: true, maxChannels: 5, windowMs: 8_000, severity: "high" },
|
|
1320
|
+
punctuation: { enabled: true, maxRepeated: 10, severity: "low" },
|
|
1321
|
+
spoilers: { enabled: true, maxSpoilers: 8, severity: "low" },
|
|
1322
|
+
ghostPing: { enabled: false, minMentions: 1, maxAgeMs: 15_000, severity: "high" },
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
`lenient` apaga hop y puntuación. `strict` enciende ghost ping y aprieta hop a 3 canales / 6s.
|
|
1326
|
+
|
|
1094
1327
|
### Castigos
|
|
1095
1328
|
|
|
1096
1329
|
Kick y ban van **apagados**. Actívalos solo si lo tienes claro.
|
|
@@ -1109,12 +1342,15 @@ punishment: {
|
|
|
1109
1342
|
strikeDecayMs: 15 * 60_000,
|
|
1110
1343
|
cooldownMs: 8_000,
|
|
1111
1344
|
deleteDuringCooldown: true,
|
|
1345
|
+
addRoleIds: [],
|
|
1346
|
+
removeRoleIds: [],
|
|
1347
|
+
minStrikesForRoles: 1,
|
|
1112
1348
|
}
|
|
1113
1349
|
```
|
|
1114
1350
|
|
|
1115
1351
|
Placeholders: `{user}` `{reason}` `{type}` `{strikes}`.
|
|
1116
1352
|
|
|
1117
|
-
Con `escalate: true` se aplica **un** castigo fuerte (timeout, kick o ban). Aviso y borrado se pueden sumar.
|
|
1353
|
+
Con `escalate: true` se aplica **un** castigo fuerte (timeout, kick o ban). Aviso y borrado se pueden sumar. `addRole` / `removeRole` corren si las listas no están vacías.
|
|
1118
1354
|
|
|
1119
1355
|
## Callbacks
|
|
1120
1356
|
|
|
@@ -1126,6 +1362,7 @@ new AntiSpam(client, {
|
|
|
1126
1362
|
onError(error, context) {
|
|
1127
1363
|
console.error(context, error);
|
|
1128
1364
|
},
|
|
1365
|
+
onCooldown(message) {},
|
|
1129
1366
|
});
|
|
1130
1367
|
```
|
|
1131
1368
|
|
|
@@ -1138,11 +1375,15 @@ const antispam = new AntiSpam(client, options);
|
|
|
1138
1375
|
|
|
1139
1376
|
antispam.start();
|
|
1140
1377
|
antispam.stop();
|
|
1378
|
+
antispam.pause();
|
|
1379
|
+
antispam.resume();
|
|
1141
1380
|
antispam.getConfig();
|
|
1142
1381
|
antispam.setConfig({ flood: { maxMessages: 8 } });
|
|
1143
1382
|
antispam.getStrikes(guildId, userId);
|
|
1144
1383
|
antispam.resetUser(guildId, userId);
|
|
1145
1384
|
antispam.setGuildConfig(guildId, { flood: { maxMessages: 3 } });
|
|
1385
|
+
antispam.setChannelConfig(guildId, channelId, { flood: { maxMessages: 12 } });
|
|
1386
|
+
antispam.setRoleConfig(roleId, { emojis: { maxEmojis: 40 } });
|
|
1146
1387
|
antispam.getStats();
|
|
1147
1388
|
|
|
1148
1389
|
const incident = await antispam.analyze(message);
|
|
@@ -1174,6 +1415,8 @@ new AntiSpam(client, {
|
|
|
1174
1415
|
|
|
1175
1416
|
**Quitar strikes:** `antispam.resetUser(guildId, userId)`.
|
|
1176
1417
|
|
|
1418
|
+
**Mute por rol:** `punishment: { addRoleIds: ["ID_MUTE"], timeout: { enabled: false, durationMs: 0, minStrikes: 99 } }`. El rol del bot debe estar por encima del mute.
|
|
1419
|
+
|
|
1177
1420
|
**Cambiar a strict en caliente**
|
|
1178
1421
|
|
|
1179
1422
|
```js
|
|
@@ -1211,9 +1454,14 @@ Espera unos segundos entre categorías.
|
|
|
1211
1454
|
| Archivo | Sube un `.exe` o `.bat` (aunque sea de mentira) |
|
|
1212
1455
|
| Zalgo | Texto con muchos diacríticos apilados |
|
|
1213
1456
|
| Líneas | Un mensaje con más de 15 enters |
|
|
1457
|
+
| Hop | El mismo usuario en 5 canales distintos en 8s |
|
|
1458
|
+
| Puntuación | `aaaaaaaaaaa` o `!!!!!!!!!!!` |
|
|
1459
|
+
| Spoilers | Más de 8 pares `\|\|spoiler\|\|` |
|
|
1460
|
+
| Palabra | Activa `words` y manda una de la lista |
|
|
1461
|
+
| Ghost ping | Menciona a alguien y borra el mensaje en menos de 15s (`ghostPing.enabled: true`) |
|
|
1214
1462
|
| Edición | Manda `hola` y edítalo a `Free Nitro https://bit.ly/test` |
|
|
1215
1463
|
|
|
1216
|
-
En la terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline`.
|
|
1464
|
+
En la terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost`.
|
|
1217
1465
|
|
|
1218
1466
|
Al segundo strike el ejemplo mete timeout de 1 minuto. Los strikes caducan a los 15 minutos, o usa `resetUser`.
|
|
1219
1467
|
|
package/dist/AntiSpam.d.ts
CHANGED
|
@@ -11,14 +11,20 @@ export declare class AntiSpam {
|
|
|
11
11
|
private readonly channelConfigs;
|
|
12
12
|
private stats;
|
|
13
13
|
private started;
|
|
14
|
+
private paused;
|
|
14
15
|
private cleanupTimer;
|
|
15
16
|
private readonly onMessage;
|
|
16
17
|
private readonly onEdit;
|
|
18
|
+
private readonly onDelete;
|
|
17
19
|
constructor(client: Client, options?: AntiSpamOptions);
|
|
18
|
-
/** Starts listening to `messageCreate` (and `messageUpdate` if enabled). */
|
|
20
|
+
/** Starts listening to `messageCreate` (and `messageUpdate` / `messageDelete` if enabled). */
|
|
19
21
|
start(): this;
|
|
20
22
|
/** Stops listening. Call this on shutdown. */
|
|
21
23
|
stop(): this;
|
|
24
|
+
/** Temporarily skip all enforcement without removing listeners. */
|
|
25
|
+
pause(): this;
|
|
26
|
+
resume(): this;
|
|
27
|
+
isPaused(): boolean;
|
|
22
28
|
/** Adds a custom detector. It runs after the built-in ones. */
|
|
23
29
|
use(detector: Detector): this;
|
|
24
30
|
getConfig(): ResolvedConfig;
|
|
@@ -30,6 +36,8 @@ export declare class AntiSpam {
|
|
|
30
36
|
getChannelConfig(guildId: string, channelId: string): ResolvedConfig;
|
|
31
37
|
setChannelConfig(guildId: string, channelId: string, patch: DeepPartial<ResolvedConfig>): ResolvedConfig;
|
|
32
38
|
clearChannelConfig(guildId: string, channelId: string): void;
|
|
39
|
+
setRoleConfig(roleId: string, patch: DeepPartial<ResolvedConfig>): void;
|
|
40
|
+
clearRoleConfig(roleId: string): void;
|
|
33
41
|
getStrikes(guildId: string, userId: string): number;
|
|
34
42
|
isCoolingDown(guildId: string, userId: string): boolean;
|
|
35
43
|
resetUser(guildId: string, userId: string): void;
|
|
@@ -43,6 +51,7 @@ export declare class AntiSpam {
|
|
|
43
51
|
isEdit?: boolean;
|
|
44
52
|
}): Promise<Incident | null>;
|
|
45
53
|
private handle;
|
|
54
|
+
private handleDelete;
|
|
46
55
|
shouldIgnore(message: Message): boolean;
|
|
47
56
|
private configFor;
|
|
48
57
|
private orderedDetectors;
|
package/dist/AntiSpam.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AntiSpam.d.ts","sourceRoot":"","sources":["../src/AntiSpam.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,OAAO,
|
|
1
|
+
{"version":3,"file":"AntiSpam.d.ts","sourceRoot":"","sources":["../src/AntiSpam.ts"],"names":[],"mappings":"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"}
|