@amatiscorp/disguard 1.5.0 → 1.6.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 +232 -14
- package/dist/AntiSpam.d.ts +7 -1
- package/dist/AntiSpam.d.ts.map +1 -1
- package/dist/AntiSpam.js +113 -3
- package/dist/AntiSpam.js.map +1 -1
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +62 -1
- package/dist/defaults.js.map +1 -1
- package/dist/detectors/files.d.ts.map +1 -1
- package/dist/detectors/files.js +14 -0
- package/dist/detectors/files.js.map +1 -1
- package/dist/detectors/forward.d.ts +3 -0
- package/dist/detectors/forward.d.ts.map +1 -0
- package/dist/detectors/forward.js +35 -0
- package/dist/detectors/forward.js.map +1 -0
- package/dist/detectors/ghost.d.ts.map +1 -1
- package/dist/detectors/ghost.js +4 -0
- package/dist/detectors/ghost.js.map +1 -1
- package/dist/detectors/impersonate.d.ts +3 -0
- package/dist/detectors/impersonate.d.ts.map +1 -0
- package/dist/detectors/impersonate.js +86 -0
- package/dist/detectors/impersonate.js.map +1 -0
- package/dist/detectors/joins.d.ts +4 -0
- package/dist/detectors/joins.d.ts.map +1 -0
- package/dist/detectors/joins.js +30 -0
- package/dist/detectors/joins.js.map +1 -0
- package/dist/detectors/polls.d.ts +3 -0
- package/dist/detectors/polls.d.ts.map +1 -0
- package/dist/detectors/polls.js +35 -0
- package/dist/detectors/polls.js.map +1 -0
- package/dist/detectors/stickers.d.ts +3 -0
- package/dist/detectors/stickers.d.ts.map +1 -0
- package/dist/detectors/stickers.js +60 -0
- package/dist/detectors/stickers.js.map +1 -0
- package/dist/detectors/voice.d.ts +3 -0
- package/dist/detectors/voice.d.ts.map +1 -0
- package/dist/detectors/voice.js +35 -0
- package/dist/detectors/voice.js.map +1 -0
- package/dist/detectors/words.d.ts.map +1 -1
- package/dist/detectors/words.js +27 -3
- package/dist/detectors/words.js.map +1 -1
- package/dist/enforcement.d.ts +2 -1
- package/dist/enforcement.d.ts.map +1 -1
- package/dist/enforcement.js +100 -5
- package/dist/enforcement.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/store/MemoryStore.d.ts +3 -0
- package/dist/store/MemoryStore.d.ts.map +1 -1
- package/dist/store/MemoryStore.js +23 -0
- package/dist/store/MemoryStore.js.map +1 -1
- package/dist/types.d.ts +84 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/flags.d.ts +5 -0
- package/dist/utils/flags.d.ts.map +1 -0
- package/dist/utils/flags.js +41 -0
- package/dist/utils/flags.js.map +1 -0
- package/dist/utils/normalize.d.ts +2 -0
- package/dist/utils/normalize.d.ts.map +1 -1
- package/dist/utils/normalize.js +46 -0
- package/dist/utils/normalize.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Configurable antispam for discord.js v14**
|
|
6
6
|
|
|
7
|
-
Flood · phishing ·
|
|
7
|
+
Flood · phishing · forwards · impersonation · join raids · leaked secrets · TypeScript
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@amatiscorp/disguard)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -22,7 +22,7 @@ Configurable antispam for [discord.js](https://discord.js.org) v14 bots.
|
|
|
22
22
|
|
|
23
23
|
It is **not** a bot. You plug it into your existing `Client` and decide every threshold, allowlist, and punishment.
|
|
24
24
|
|
|
25
|
-
Detects flood, leaked tokens/webhooks, channel hopping, blocked words, phishing, duplicate images, mention spam, ghost pings, and more.
|
|
25
|
+
Detects flood, leaked tokens/webhooks, channel hopping, blocked words, phishing, duplicate images, mention spam, ghost pings, forwarded-message dumps, bot impersonation, join raids, and more.
|
|
26
26
|
|
|
27
27
|
**Languages**
|
|
28
28
|
|
|
@@ -33,6 +33,52 @@ Detects flood, leaked tokens/webhooks, channel hopping, blocked words, phishing,
|
|
|
33
33
|
|
|
34
34
|
# English
|
|
35
35
|
|
|
36
|
+
## What's new in 1.6.0
|
|
37
|
+
|
|
38
|
+
Forward dumps, sticker floods, bot/staff impersonation, join raids, voice-note spam, poll spam, and harder-to-dodge word/file filters. Kick/ban stay **off**. `joins` is **off** in `balanced` so a busy invite night does not false-positive.
|
|
39
|
+
|
|
40
|
+
| Option | Default | What it does |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `forwards` | **on**, 4 / 10s | Too many Discord **forwarded** messages (HasSnapshot) in a short window. |
|
|
43
|
+
| `stickers` | **on**, 3 per message, 8 / 10s | Sticker dumps. `blockOnly` (off) flags sticker-only messages. |
|
|
44
|
+
| `impersonate` | **on**, match bot at 0.84 | Nick / username / display name too close to the **bot** (or `names`). |
|
|
45
|
+
| `joins` | **off** (`strict`: on, 6 / 8s) | Too many **distinct accounts joining** the guild at once. Needs `guildMemberAdd`. |
|
|
46
|
+
| `voice` | **on**, 3 / 15s | Too many **voice messages** in a window. |
|
|
47
|
+
| `polls` | **on**, 2 / 20s | Too many **polls** in a window. |
|
|
48
|
+
| `files.blockDoubleExtensions` | `true` | Flags `virus.exe.png` (blocked ext hidden behind a safe one). |
|
|
49
|
+
| `words.scanEmbeds` | `true` | Word list also reads embed text. |
|
|
50
|
+
| `words.normalizeHomoglyphs` | `true` | Folds Cyrillic / Greek / fullwidth lookalikes (`rаid` → `raid`). |
|
|
51
|
+
| `ignoreBoosters` | `false` | Skip server boosters. |
|
|
52
|
+
| `punishment.slowmodeSeconds` | `0` | On `raid` / `join` hits, set channel slowmode (needs **Manage Channels**). Sticky until a mod changes it. |
|
|
53
|
+
| `getRecentIncidents()` | — | Last 50 live hits (not `analyze()`). |
|
|
54
|
+
| `setDetectorEnabled(type, bool)` | — | Toggle one detector without rewriting `disabledDetectors`. |
|
|
55
|
+
| `onJoin` | — | Callback for join-raid incidents (no message). |
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const antispam = new AntiSpam(client, {
|
|
59
|
+
preset: "balanced",
|
|
60
|
+
forwards: { maxForwards: 3, windowMs: 8_000 },
|
|
61
|
+
stickers: { maxStickers: 2, maxInWindow: 6 },
|
|
62
|
+
impersonate: { names: ["Admin", "Moderador", "Staff"] },
|
|
63
|
+
joins: { enabled: true, maxJoins: 8, windowMs: 10_000 },
|
|
64
|
+
voice: { maxVoice: 3 },
|
|
65
|
+
polls: { maxPolls: 2 },
|
|
66
|
+
ignoreBoosters: true,
|
|
67
|
+
words: { enabled: true, list: ["raid-now"], normalizeHomoglyphs: true },
|
|
68
|
+
punishment: { slowmodeSeconds: 10 },
|
|
69
|
+
onJoin(incident) {
|
|
70
|
+
console.log("join raid", incident.details);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
antispam.setDetectorEnabled("poll", false);
|
|
75
|
+
console.log(antispam.getRecentIncidents(10));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Turn `joins` on only if you want a burst of accounts joining at once to timeout the newest member. Pair it with `accounts.minAgeDays` if the raid is all-new alts. `impersonate.names` is for **staff/brand** clones; `matchBot` already covers the bot's own name.
|
|
79
|
+
|
|
80
|
+
`graceMessages` now also runs **impersonate** and **forward** during the hello window (still skips flood).
|
|
81
|
+
|
|
36
82
|
## What's new in 1.5.0
|
|
37
83
|
|
|
38
84
|
Channel raids, reply spam, empty messages, embed walls, file allowlists, and per-user overlays. Kick/ban stay **off**. `raid` is **off** in `balanced` so busy servers do not false-positive.
|
|
@@ -242,6 +288,7 @@ console.log(antispam.getStats());
|
|
|
242
288
|
|
|
243
289
|
## Table of contents
|
|
244
290
|
|
|
291
|
+
- [What's new in 1.6.0](#whats-new-in-160)
|
|
245
292
|
- [What's new in 1.5.0](#whats-new-in-150)
|
|
246
293
|
- [What's new in 1.4.0](#whats-new-in-140)
|
|
247
294
|
- [What's new in 1.3.0](#whats-new-in-130)
|
|
@@ -293,6 +340,12 @@ console.log(antispam.getStats());
|
|
|
293
340
|
| **blank** | Empty / whitespace-only messages |
|
|
294
341
|
| **embed** | Too many embeds in one message |
|
|
295
342
|
| **raid** | Many distinct users posting in one channel (off by default) |
|
|
343
|
+
| **forward** | Too many Discord forwarded messages in a window |
|
|
344
|
+
| **sticker** | Sticker dumps / optional sticker-only messages |
|
|
345
|
+
| **impersonate** | Nick/username too close to the bot or protected names |
|
|
346
|
+
| **join** | Mass joins in a short window (off by default) |
|
|
347
|
+
| **voice** | Too many voice messages in a window |
|
|
348
|
+
| **poll** | Too many polls in a window |
|
|
296
349
|
|
|
297
350
|
Also included:
|
|
298
351
|
|
|
@@ -300,7 +353,7 @@ Also included:
|
|
|
300
353
|
- Ignore lists for users, roles, channels, categories, guilds, and command prefixes
|
|
301
354
|
- Per-guild, per-channel, and per-role overlays
|
|
302
355
|
- Strike system with decay
|
|
303
|
-
- Optional delete / warn / timeout / kick / ban / addRole / removeRole (kick and ban are **off** by default)
|
|
356
|
+
- Optional delete / warn / timeout / kick / ban / addRole / removeRole / slowmode (kick and ban are **off** by default)
|
|
304
357
|
- `dryRun` to tune rules without punishing anyone
|
|
305
358
|
- Edit scanning (links, mentions, and words)
|
|
306
359
|
- Zero extra runtime dependencies (peer: `discord.js`)
|
|
@@ -418,6 +471,8 @@ npm run dev
|
|
|
418
471
|
| Log embeds | **Embed Links** |
|
|
419
472
|
| Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
|
|
420
473
|
| Mute / add / remove roles (opt-in) | **Manage Roles** |
|
|
474
|
+
| Slowmode on raid/join (opt-in) | **Manage Channels** |
|
|
475
|
+
| Join-raid detector | **Server Members Intent** (`guildMemberAdd`) |
|
|
421
476
|
|
|
422
477
|
The bot will not sanction the guild owner or anyone above it in the role hierarchy. Those actions are skipped and reported in `onAction`.
|
|
423
478
|
|
|
@@ -427,10 +482,11 @@ Enable **Message Content Intent** in the [Discord Developer Portal](https://disc
|
|
|
427
482
|
|
|
428
483
|
1. Ignores bots, webhooks, system messages, the owner, administrators, command prefixes, and anything in your ignore lists.
|
|
429
484
|
2. Keeps a short **in-memory** history per user and guild (no database).
|
|
430
|
-
3. Runs detectors in this order: files → secrets → words → flood → hop → echo → duplicates → attachments → raid → replies → blank → embeds → links → images → mentions → zalgo → newlines → punctuation → spoilers → invisible → accounts → length → caps → emojis. The first match wins.
|
|
485
|
+
3. Runs detectors in this order: files → secrets → words → impersonate → flood → hop → echo → duplicates → attachments → raid → replies → blank → embeds → forwards → voice → polls → stickers → links → images → mentions → zalgo → newlines → punctuation → spoilers → invisible → accounts → length → caps → emojis. The first match wins.
|
|
431
486
|
4. Adds one strike (with optional decay) and applies the configured punishment.
|
|
432
487
|
5. Message edits re-check **links**, **mentions**, **words**, **secrets**, and **invisible**.
|
|
433
488
|
6. Deletes can trigger **ghost** pings if `ghostPing.enabled` is true.
|
|
489
|
+
7. Joins can trigger **join** raids if `joins.enabled` is true (`guildMemberAdd`).
|
|
434
490
|
|
|
435
491
|
Call `antispam.stop()` on shutdown, hot reload, or plugin unload.
|
|
436
492
|
|
|
@@ -462,6 +518,7 @@ new AntiSpam(client, {
|
|
|
462
518
|
| Block shorteners | no | yes | yes |
|
|
463
519
|
| Channel hop | off | 5 / 8s | 3 / 6s |
|
|
464
520
|
| Ghost pings | off | off | on |
|
|
521
|
+
| Join raids | off | off | on |
|
|
465
522
|
| Auto timeout | off | 60s from 2 strikes | 5 min from 1 strike |
|
|
466
523
|
|
|
467
524
|
## Full configuration
|
|
@@ -480,8 +537,10 @@ Every option is optional. Missing fields fall back to the preset (or `balanced`
|
|
|
480
537
|
| `ignoreAdministrators` | `boolean` | `true` | Skip members with Administrator. |
|
|
481
538
|
| `checkEdits` | `boolean` | `true` | Re-scan edits for links, mentions, and words. |
|
|
482
539
|
| `checkDeletes` | `boolean` | `true` | Listen for `messageDelete` (ghost pings). |
|
|
540
|
+
| `checkJoins` | `boolean` | `true` | Listen for `guildMemberAdd` (join raids). |
|
|
483
541
|
| `ignoreThreads` | `boolean` | `false` | Skip messages in threads. |
|
|
484
542
|
| `ignoreSystem` | `boolean` | `true` | Skip Discord system messages. |
|
|
543
|
+
| `ignoreBoosters` | `boolean` | `false` | Skip server boosters. |
|
|
485
544
|
| `cleanupIntervalMs` | `number` | `60000` | How often the memory store is pruned. |
|
|
486
545
|
| `locale` | `"en"` \| `"es"` | `"en"` | Language for warnings and log embeds. |
|
|
487
546
|
| `ignorePermissions` | `string[]` | `[]` | Permission names that bypass checks. |
|
|
@@ -659,6 +718,7 @@ files: {
|
|
|
659
718
|
enabled: true,
|
|
660
719
|
blockedExtensions: ["exe", "bat", "cmd", "com", "scr", "dll", "msi", "vbs", "ps1", "jar", "apk"],
|
|
661
720
|
maxBytes: 0, // 0 = unlimited
|
|
721
|
+
blockDoubleExtensions: true, // virus.exe.png
|
|
662
722
|
severity: "critical",
|
|
663
723
|
}
|
|
664
724
|
|
|
@@ -684,6 +744,8 @@ words: {
|
|
|
684
744
|
regex: ["n[i1]tro\\s+free"],
|
|
685
745
|
ignoreCase: true,
|
|
686
746
|
matchWholeWord: true,
|
|
747
|
+
scanEmbeds: true,
|
|
748
|
+
normalizeHomoglyphs: true, // rаid-now (Cyrillic a) still matches
|
|
687
749
|
severity: "high",
|
|
688
750
|
}
|
|
689
751
|
|
|
@@ -726,6 +788,83 @@ checkDeletes: true
|
|
|
726
788
|
|
|
727
789
|
Disable with `ghostPing: { enabled: false }` or `disabledDetectors: ["ghost"]`.
|
|
728
790
|
|
|
791
|
+
### Forwards, stickers, voice, polls
|
|
792
|
+
|
|
793
|
+
```js
|
|
794
|
+
forwards: {
|
|
795
|
+
enabled: true,
|
|
796
|
+
maxForwards: 4, // 4th forward in the window triggers
|
|
797
|
+
windowMs: 10_000,
|
|
798
|
+
severity: "medium",
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
stickers: {
|
|
802
|
+
enabled: true,
|
|
803
|
+
maxStickers: 3, // per message
|
|
804
|
+
maxInWindow: 8, // 0 = off
|
|
805
|
+
windowMs: 10_000,
|
|
806
|
+
blockOnly: false, // true = sticker-only messages (no text)
|
|
807
|
+
severity: "low",
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
voice: {
|
|
811
|
+
enabled: true,
|
|
812
|
+
maxVoice: 3,
|
|
813
|
+
windowMs: 15_000,
|
|
814
|
+
severity: "medium",
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
polls: {
|
|
818
|
+
enabled: true,
|
|
819
|
+
maxPolls: 2,
|
|
820
|
+
windowMs: 20_000,
|
|
821
|
+
severity: "medium",
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
`lenient` turns forwards, voice, and polls off. `strict` tightens the windows and sets `stickers.blockOnly: true`.
|
|
826
|
+
|
|
827
|
+
### Impersonation
|
|
828
|
+
|
|
829
|
+
Compares nick / username / display name to the bot (`matchBot`) and any extra `names`. Text is folded (homoglyphs stripped, alphanumeric only) before similarity.
|
|
830
|
+
|
|
831
|
+
```js
|
|
832
|
+
impersonate: {
|
|
833
|
+
enabled: true,
|
|
834
|
+
matchBot: true,
|
|
835
|
+
names: ["Admin", "Moderador", "Staff"],
|
|
836
|
+
minSimilarity: 0.84,
|
|
837
|
+
checkNickname: true,
|
|
838
|
+
checkUsername: true,
|
|
839
|
+
checkDisplayName: true,
|
|
840
|
+
severity: "high",
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
Skip the bot itself. Short names under 3 letters after folding are ignored.
|
|
845
|
+
|
|
846
|
+
### Join raids
|
|
847
|
+
|
|
848
|
+
Off in `balanced` / `lenient`. On in `strict`. Needs `checkJoins: true` (default) and **Server Members Intent**. There is no message to delete: Disguard timeouts/kicks the newest joiner if those punishments are enabled.
|
|
849
|
+
|
|
850
|
+
```js
|
|
851
|
+
joins: {
|
|
852
|
+
enabled: false,
|
|
853
|
+
maxJoins: 8, // 8th distinct join in the window triggers
|
|
854
|
+
windowMs: 10_000,
|
|
855
|
+
minAccountAgeDays: 0, // e.g. 1 = only count accounts younger than 1 day
|
|
856
|
+
severity: "critical",
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
checkJoins: true
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
```js
|
|
863
|
+
onJoin(incident) {
|
|
864
|
+
// no Message — use incident.userId / guildId
|
|
865
|
+
}
|
|
866
|
+
```
|
|
867
|
+
|
|
729
868
|
### Punishment
|
|
730
869
|
|
|
731
870
|
Kick and ban stay **disabled** on purpose. Turn them on only if you really want that.
|
|
@@ -748,6 +887,7 @@ punishment: {
|
|
|
748
887
|
addRoleIds: [], // e.g. ["MUTE_ROLE_ID"] — needs Manage Roles
|
|
749
888
|
removeRoleIds: [],
|
|
750
889
|
minStrikesForRoles: 1,
|
|
890
|
+
slowmodeSeconds: 0, // e.g. 10 — on raid/join, needs Manage Channels
|
|
751
891
|
}
|
|
752
892
|
|
|
753
893
|
// timeout.scale: "none" | "linear" (base * strikes) | "exponential" (base * 2^n)
|
|
@@ -757,7 +897,7 @@ punishment: {
|
|
|
757
897
|
|
|
758
898
|
`warnMessage` placeholders: `{user}` `{reason}` `{type}` `{strikes}`.
|
|
759
899
|
|
|
760
|
-
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`.
|
|
900
|
+
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`. `slowmodeSeconds` only runs on **raid** and **join** hits; it stays until a moderator changes it.
|
|
761
901
|
|
|
762
902
|
If the bot lacks a permission, or the target is higher in the hierarchy, that action is skipped and listed in `result.skipped`.
|
|
763
903
|
|
|
@@ -778,6 +918,9 @@ new AntiSpam(client, {
|
|
|
778
918
|
onCooldown(message) {
|
|
779
919
|
// Extra spam during punishment.cooldownMs (already deleted if deleteDuringCooldown).
|
|
780
920
|
},
|
|
921
|
+
onJoin(incident) {
|
|
922
|
+
// Join raid. No message object.
|
|
923
|
+
},
|
|
781
924
|
});
|
|
782
925
|
```
|
|
783
926
|
|
|
@@ -788,7 +931,8 @@ new AntiSpam(client, {
|
|
|
788
931
|
type: "flood" | "duplicate" | "link" | "image" | "mention" | "caps" | "emoji"
|
|
789
932
|
| "file" | "zalgo" | "newline" | "account" | "length"
|
|
790
933
|
| "word" | "hop" | "punctuation" | "spoiler" | "ghost"
|
|
791
|
-
| "secret" | "echo" | "invisible" | "attach" | "reply" | "blank" | "embed" | "raid"
|
|
934
|
+
| "secret" | "echo" | "invisible" | "attach" | "reply" | "blank" | "embed" | "raid"
|
|
935
|
+
| "forward" | "sticker" | "impersonate" | "join" | "voice" | "poll",
|
|
792
936
|
severity: "low" | "medium" | "high" | "critical",
|
|
793
937
|
userId: string,
|
|
794
938
|
guildId: string,
|
|
@@ -796,7 +940,7 @@ new AntiSpam(client, {
|
|
|
796
940
|
messageId: string,
|
|
797
941
|
reason: string,
|
|
798
942
|
details: Record<string, unknown>,
|
|
799
|
-
recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban" | "addRole" | "removeRole" | "purge">,
|
|
943
|
+
recommendedActions: Array<"delete" | "warn" | "timeout" | "kick" | "ban" | "addRole" | "removeRole" | "purge" | "slowmode">,
|
|
800
944
|
timestamp: number,
|
|
801
945
|
}
|
|
802
946
|
```
|
|
@@ -850,6 +994,8 @@ antispam.clearRoleConfig(roleId);
|
|
|
850
994
|
antispam.use(customDetector);
|
|
851
995
|
antispam.getStats();
|
|
852
996
|
antispam.resetStats();
|
|
997
|
+
antispam.getRecentIncidents(20);
|
|
998
|
+
antispam.setDetectorEnabled("poll", false);
|
|
853
999
|
|
|
854
1000
|
// Analyze only — no punishment, no callbacks
|
|
855
1001
|
const incident = await antispam.analyze(message);
|
|
@@ -864,7 +1010,9 @@ Helpers you can import for your own tools:
|
|
|
864
1010
|
import {
|
|
865
1011
|
extractUrls,
|
|
866
1012
|
normalizeText,
|
|
1013
|
+
foldHomoglyphs,
|
|
867
1014
|
similarity,
|
|
1015
|
+
isForwardedMessage,
|
|
868
1016
|
DEFAULT_PHISHING_KEYWORDS,
|
|
869
1017
|
DEFAULT_SHORTENERS,
|
|
870
1018
|
OFFICIAL_BRANDS,
|
|
@@ -1009,9 +1157,17 @@ Wait a few seconds between categories so flood does not eat the next test.
|
|
|
1009
1157
|
| Blank | A message with only spaces |
|
|
1010
1158
|
| Embeds | A webhook-style dump with 7+ embeds |
|
|
1011
1159
|
| Raid | Enable `raid` and have 8 people post in the same channel in 6s |
|
|
1160
|
+
| Forwards | Forward the same / any messages 4 times in 10s |
|
|
1161
|
+
| Stickers | Send 4 stickers in one message |
|
|
1162
|
+
| Impersonate | Set your nick to the bot's username (or a name in `impersonate.names`) |
|
|
1163
|
+
| Voice | Send 3 voice messages in 15s |
|
|
1164
|
+
| Polls | Post 2 polls in 20s |
|
|
1165
|
+
| Double ext | Upload `foto.exe.png` |
|
|
1166
|
+
| Homoglyph word | Enable `words` with `raid-now`, send `rаid-now` (Cyrillic **а**) |
|
|
1167
|
+
| Joins | Enable `joins` and have 8 accounts join within 10s |
|
|
1012
1168
|
| Edit | Send `hello`, then edit it to `Free Nitro https://bit.ly/test` |
|
|
1013
1169
|
|
|
1014
|
-
Watch the terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost|secret|echo|invisible|attach`.
|
|
1170
|
+
Watch the terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost|secret|echo|invisible|attach|forward|sticker|impersonate|voice|poll|join`.
|
|
1015
1171
|
|
|
1016
1172
|
The second strike applies a 1 minute timeout with the default example config. Strikes decay after 15 minutes, or call `resetUser`.
|
|
1017
1173
|
|
|
@@ -1052,6 +1208,44 @@ MIT
|
|
|
1052
1208
|
|
|
1053
1209
|
# Español
|
|
1054
1210
|
|
|
1211
|
+
## Novedades de 1.6.0
|
|
1212
|
+
|
|
1213
|
+
Reenvíos, dumps de stickers, impersonación del bot/staff, raids de joins, mensajes de voz, encuestas y filtros de palabras/archivos más difíciles de esquivar. Kick/ban siguen **apagados**. `joins` va **apagado** en `balanced`.
|
|
1214
|
+
|
|
1215
|
+
| Opción | Default | Qué hace |
|
|
1216
|
+
| --- | --- | --- |
|
|
1217
|
+
| `forwards` | **on**, 4 / 10s | Demasiados mensajes **reenviados** de Discord. |
|
|
1218
|
+
| `stickers` | **on**, 3 por mensaje, 8 / 10s | Dump de stickers. `blockOnly` (off) caza mensajes solo-sticker. |
|
|
1219
|
+
| `impersonate` | **on**, bot a 0.84 | Nick / username / display name demasiado parecido al **bot** (o a `names`). |
|
|
1220
|
+
| `joins` | **off** (`strict`: on, 6 / 8s) | Demasiadas **cuentas distintas entrando** al servidor. Hace falta `guildMemberAdd`. |
|
|
1221
|
+
| `voice` | **on**, 3 / 15s | Demasiados **mensajes de voz**. |
|
|
1222
|
+
| `polls` | **on**, 2 / 20s | Demasiadas **encuestas**. |
|
|
1223
|
+
| `files.blockDoubleExtensions` | `true` | Caza `virus.exe.png`. |
|
|
1224
|
+
| `words.scanEmbeds` | `true` | La lista de palabras también lee embeds. |
|
|
1225
|
+
| `words.normalizeHomoglyphs` | `true` | Pliega lookalikes cirílicos / griegos / fullwidth. |
|
|
1226
|
+
| `ignoreBoosters` | `false` | Ignora boosters. |
|
|
1227
|
+
| `punishment.slowmodeSeconds` | `0` | En hits `raid` / `join`, pone slowmode (hace falta **Manage Channels**). |
|
|
1228
|
+
| `getRecentIncidents()` | — | Últimos 50 hits en vivo (no `analyze()`). |
|
|
1229
|
+
| `setDetectorEnabled(type, bool)` | — | Enciende/apaga un detector. |
|
|
1230
|
+
| `onJoin` | — | Callback de raid de joins (sin mensaje). |
|
|
1231
|
+
|
|
1232
|
+
```js
|
|
1233
|
+
new AntiSpam(client, {
|
|
1234
|
+
forwards: { maxForwards: 3 },
|
|
1235
|
+
impersonate: { names: ["Admin", "Moderador"] },
|
|
1236
|
+
joins: { enabled: true, maxJoins: 8 },
|
|
1237
|
+
ignoreBoosters: true,
|
|
1238
|
+
words: { enabled: true, list: ["raid-now"], normalizeHomoglyphs: true },
|
|
1239
|
+
punishment: { slowmodeSeconds: 10 },
|
|
1240
|
+
onJoin(incident) {
|
|
1241
|
+
console.log("join raid", incident.details);
|
|
1242
|
+
},
|
|
1243
|
+
});
|
|
1244
|
+
antispam.setDetectorEnabled("poll", false);
|
|
1245
|
+
```
|
|
1246
|
+
|
|
1247
|
+
Activa `joins` solo si quieres que un burst de cuentas nuevas dispare timeout al último en entrar.
|
|
1248
|
+
|
|
1055
1249
|
## Novedades de 1.5.0
|
|
1056
1250
|
|
|
1057
1251
|
Raids de canal, spam de replies, mensajes vacíos, paredes de embeds, allowlist de archivos y config por usuario. Kick/ban siguen **apagados**. `raid` va **apagado** en `balanced`.
|
|
@@ -1195,6 +1389,7 @@ new AntiSpam(client, {
|
|
|
1195
1389
|
|
|
1196
1390
|
## Tabla de contenidos
|
|
1197
1391
|
|
|
1392
|
+
- [Novedades de 1.6.0](#novedades-de-160)
|
|
1198
1393
|
- [Novedades de 1.5.0](#novedades-de-150)
|
|
1199
1394
|
- [Novedades de 1.4.0](#novedades-de-140)
|
|
1200
1395
|
- [Novedades de 1.3.0](#novedades-de-130)
|
|
@@ -1221,7 +1416,7 @@ new AntiSpam(client, {
|
|
|
1221
1416
|
|
|
1222
1417
|
**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.
|
|
1223
1418
|
|
|
1224
|
-
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.
|
|
1419
|
+
Detecta flood, salto de canales, palabras bloqueadas, phishing / enlaces no deseados (también en embeds), imágenes repetidas, spam de menciones, ghost pings, reenvíos, impersonación, raids de joins, mayúsculas, emojis, archivos peligrosos, zalgo y paredes de líneas.
|
|
1225
1420
|
|
|
1226
1421
|
## Requisitos
|
|
1227
1422
|
|
|
@@ -1308,6 +1503,8 @@ npm run dev
|
|
|
1308
1503
|
| Embed de logs | **Embed Links** |
|
|
1309
1504
|
| Kick / ban (opt-in) | **Kick Members** / **Ban Members** |
|
|
1310
1505
|
| Mute / add / remove roles (opt-in) | **Manage Roles** |
|
|
1506
|
+
| Slowmode en raid/join (opt-in) | **Manage Channels** |
|
|
1507
|
+
| Raid de joins | **Server Members Intent** (`guildMemberAdd`) |
|
|
1311
1508
|
|
|
1312
1509
|
No puede sancionar al dueño del servidor ni a quien esté por encima del bot en la jerarquía.
|
|
1313
1510
|
|
|
@@ -1317,10 +1514,11 @@ Activa **Message Content Intent** en el [Portal de Discord](https://discord.com/
|
|
|
1317
1514
|
|
|
1318
1515
|
1. Ignora bots, webhooks, mensajes de sistema, el dueño, administradores, prefijos de comando y tus listas de ignore.
|
|
1319
1516
|
2. Guarda en **memoria** un historial corto por usuario y servidor. Sin base de datos.
|
|
1320
|
-
3. Pasa el mensaje por: archivos → secretos → palabras → flood → hop → echo → duplicados → adjuntos → raid → replies → vacíos → embeds → enlaces → imágenes → menciones → zalgo → líneas → puntuación → spoilers → invisibles → cuentas → longitud → caps → emojis. El primero que dispare gana.
|
|
1517
|
+
3. Pasa el mensaje por: archivos → secretos → palabras → impersonate → flood → hop → echo → duplicados → adjuntos → raid → replies → vacíos → embeds → reenvíos → voz → encuestas → stickers → enlaces → imágenes → menciones → zalgo → líneas → puntuación → spoilers → invisibles → cuentas → longitud → caps → emojis. El primero que dispare gana.
|
|
1321
1518
|
4. Suma un strike (con caducidad) y aplica el castigo configurado.
|
|
1322
1519
|
5. Las ediciones revisan **enlaces**, **menciones** y **palabras**.
|
|
1323
1520
|
6. Los borrados pueden disparar **ghost** ping si `ghostPing.enabled` es true.
|
|
1521
|
+
7. Los joins pueden disparar **join** si `joins.enabled` es true.
|
|
1324
1522
|
|
|
1325
1523
|
Llama `antispam.stop()` al apagar el proceso.
|
|
1326
1524
|
|
|
@@ -1355,8 +1553,10 @@ Todo es opcional. Lo que no pongas usa el preset.
|
|
|
1355
1553
|
| `ignoreAdministrators` | `boolean` | `true` | Ignora quien tenga Administrator. |
|
|
1356
1554
|
| `checkEdits` | `boolean` | `true` | Revisa ediciones (enlaces, menciones y palabras). |
|
|
1357
1555
|
| `checkDeletes` | `boolean` | `true` | Escucha `messageDelete` (ghost pings). |
|
|
1556
|
+
| `checkJoins` | `boolean` | `true` | Escucha `guildMemberAdd` (raids de joins). |
|
|
1358
1557
|
| `ignoreThreads` | `boolean` | `false` | Ignora hilos. |
|
|
1359
1558
|
| `ignoreSystem` | `boolean` | `true` | Ignora mensajes de sistema. |
|
|
1559
|
+
| `ignoreBoosters` | `boolean` | `false` | Ignora boosters. |
|
|
1360
1560
|
| `cleanupIntervalMs` | `number` | `60000` | Limpieza de memoria. |
|
|
1361
1561
|
| `roleOverrides` | `object` | `{}` | Parches por rol. |
|
|
1362
1562
|
|
|
@@ -1455,7 +1655,7 @@ emojis: { enabled: true, maxEmojis: 12, maxStickers: 3, severity: "low" },
|
|
|
1455
1655
|
### Archivos, zalgo, saltos de línea
|
|
1456
1656
|
|
|
1457
1657
|
```js
|
|
1458
|
-
files: { enabled: true, blockedExtensions: ["exe", "bat", "cmd", "dll", "msi"], maxBytes: 0, severity: "critical" },
|
|
1658
|
+
files: { enabled: true, blockedExtensions: ["exe", "bat", "cmd", "dll", "msi"], maxBytes: 0, blockDoubleExtensions: true, severity: "critical" },
|
|
1459
1659
|
zalgo: { enabled: true, maxCombining: 20, severity: "medium" },
|
|
1460
1660
|
newlines: { enabled: true, maxNewlines: 15, severity: "low" },
|
|
1461
1661
|
```
|
|
@@ -1463,14 +1663,20 @@ newlines: { enabled: true, maxNewlines: 15, severity: "low" },
|
|
|
1463
1663
|
### Palabras, hop, puntuación, spoilers, ghost ping
|
|
1464
1664
|
|
|
1465
1665
|
```js
|
|
1466
|
-
words: { enabled: false, list: ["raid-now"], regex: [], ignoreCase: true, matchWholeWord: true, severity: "high" },
|
|
1666
|
+
words: { enabled: false, list: ["raid-now"], regex: [], ignoreCase: true, matchWholeWord: true, scanEmbeds: true, normalizeHomoglyphs: true, severity: "high" },
|
|
1467
1667
|
hop: { enabled: true, maxChannels: 5, windowMs: 8_000, severity: "high" },
|
|
1468
1668
|
punctuation: { enabled: true, maxRepeated: 10, severity: "low" },
|
|
1469
1669
|
spoilers: { enabled: true, maxSpoilers: 8, severity: "low" },
|
|
1470
1670
|
ghostPing: { enabled: false, minMentions: 1, maxAgeMs: 15_000, severity: "high" },
|
|
1671
|
+
forwards: { enabled: true, maxForwards: 4, windowMs: 10_000, severity: "medium" },
|
|
1672
|
+
stickers: { enabled: true, maxStickers: 3, maxInWindow: 8, blockOnly: false, severity: "low" },
|
|
1673
|
+
impersonate: { enabled: true, matchBot: true, names: ["Admin", "Moderador"], minSimilarity: 0.84, severity: "high" },
|
|
1674
|
+
joins: { enabled: false, maxJoins: 8, windowMs: 10_000, severity: "critical" },
|
|
1675
|
+
voice: { enabled: true, maxVoice: 3, windowMs: 15_000, severity: "medium" },
|
|
1676
|
+
polls: { enabled: true, maxPolls: 2, windowMs: 20_000, severity: "medium" },
|
|
1471
1677
|
```
|
|
1472
1678
|
|
|
1473
|
-
`lenient` apaga hop y
|
|
1679
|
+
`lenient` apaga hop, puntuación, reenvíos, voz y encuestas. `strict` enciende ghost ping, joins y aprieta umbrales.
|
|
1474
1680
|
|
|
1475
1681
|
### Castigos
|
|
1476
1682
|
|
|
@@ -1493,6 +1699,7 @@ punishment: {
|
|
|
1493
1699
|
addRoleIds: [],
|
|
1494
1700
|
removeRoleIds: [],
|
|
1495
1701
|
minStrikesForRoles: 1,
|
|
1702
|
+
slowmodeSeconds: 0,
|
|
1496
1703
|
}
|
|
1497
1704
|
```
|
|
1498
1705
|
|
|
@@ -1511,6 +1718,7 @@ new AntiSpam(client, {
|
|
|
1511
1718
|
console.error(context, error);
|
|
1512
1719
|
},
|
|
1513
1720
|
onCooldown(message) {},
|
|
1721
|
+
onJoin(incident) {},
|
|
1514
1722
|
});
|
|
1515
1723
|
```
|
|
1516
1724
|
|
|
@@ -1535,6 +1743,8 @@ antispam.setChannelConfig(guildId, channelId, { flood: { maxMessages: 12 } });
|
|
|
1535
1743
|
antispam.setRoleConfig(roleId, { emojis: { maxEmojis: 40 } });
|
|
1536
1744
|
antispam.setUserConfig(userId, { flood: { maxMessages: 2 } });
|
|
1537
1745
|
antispam.getStats();
|
|
1746
|
+
antispam.getRecentIncidents(10);
|
|
1747
|
+
antispam.setDetectorEnabled("poll", false);
|
|
1538
1748
|
|
|
1539
1749
|
const incident = await antispam.analyze(message);
|
|
1540
1750
|
const editado = await antispam.analyze(message, { isEdit: true });
|
|
@@ -1617,9 +1827,17 @@ Espera unos segundos entre categorías.
|
|
|
1617
1827
|
| Vacío | Un mensaje solo con espacios |
|
|
1618
1828
|
| Embeds | 7+ embeds en un mensaje |
|
|
1619
1829
|
| Raid | Activa `raid` y que 8 personas escriban en el mismo canal |
|
|
1830
|
+
| Reenvíos | Reenvía 4 mensajes en 10s |
|
|
1831
|
+
| Stickers | 4 stickers en un mensaje |
|
|
1832
|
+
| Impersonar | Pon tu nick igual al del bot (o un nombre de `impersonate.names`) |
|
|
1833
|
+
| Voz | 3 mensajes de voz en 15s |
|
|
1834
|
+
| Encuestas | 2 polls en 20s |
|
|
1835
|
+
| Doble ext | Sube `foto.exe.png` |
|
|
1836
|
+
| Homoglyph | Activa `words` con `raid-now` y manda `rаid-now` (а cirílica) |
|
|
1837
|
+
| Joins | Activa `joins` y que 8 cuentas entren en 10s |
|
|
1620
1838
|
| Edición | Manda `hola` y edítalo a `Free Nitro https://bit.ly/test` |
|
|
1621
1839
|
|
|
1622
|
-
En la terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost|secret|echo|invisible|attach`.
|
|
1840
|
+
En la terminal: `[detect] flood|duplicate|link|image|mention|caps|emoji|file|zalgo|newline|word|hop|punctuation|spoiler|ghost|secret|echo|invisible|attach|forward|sticker|impersonate|voice|poll|join`.
|
|
1623
1841
|
|
|
1624
1842
|
Al segundo strike el ejemplo mete timeout de 1 minuto. Los strikes caducan a los 15 minutos, o usa `resetUser`.
|
|
1625
1843
|
|
package/dist/AntiSpam.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Client, type Message } from "discord.js";
|
|
2
2
|
import { MemoryStore } from "./store/MemoryStore";
|
|
3
|
-
import type { AntiSpamOptions, AntiSpamStats, DeepPartial, Detector, Incident, ResolvedConfig } from "./types";
|
|
3
|
+
import type { AntiSpamOptions, AntiSpamStats, DeepPartial, Detector, DetectorType, Incident, ResolvedConfig } from "./types";
|
|
4
4
|
export declare class AntiSpam {
|
|
5
5
|
readonly client: Client;
|
|
6
6
|
readonly store: MemoryStore;
|
|
@@ -10,12 +10,14 @@ export declare class AntiSpam {
|
|
|
10
10
|
private readonly guildConfigs;
|
|
11
11
|
private readonly channelConfigs;
|
|
12
12
|
private stats;
|
|
13
|
+
private readonly recentIncidents;
|
|
13
14
|
private started;
|
|
14
15
|
private paused;
|
|
15
16
|
private cleanupTimer;
|
|
16
17
|
private readonly onMessage;
|
|
17
18
|
private readonly onEdit;
|
|
18
19
|
private readonly onDelete;
|
|
20
|
+
private readonly onJoin;
|
|
19
21
|
constructor(client: Client, options?: AntiSpamOptions);
|
|
20
22
|
/** Starts listening to `messageCreate` (and `messageUpdate` / `messageDelete` if enabled). */
|
|
21
23
|
start(): this;
|
|
@@ -46,6 +48,8 @@ export declare class AntiSpam {
|
|
|
46
48
|
isCoolingDown(guildId: string, userId: string): boolean;
|
|
47
49
|
resetUser(guildId: string, userId: string): void;
|
|
48
50
|
getStats(): AntiSpamStats;
|
|
51
|
+
getRecentIncidents(limit?: number): Incident[];
|
|
52
|
+
setDetectorEnabled(type: DetectorType, enabled: boolean): this;
|
|
49
53
|
resetStats(): void;
|
|
50
54
|
/**
|
|
51
55
|
* Analyzes a message without punishing.
|
|
@@ -56,10 +60,12 @@ export declare class AntiSpam {
|
|
|
56
60
|
}): Promise<Incident | null>;
|
|
57
61
|
private handle;
|
|
58
62
|
private handleDelete;
|
|
63
|
+
private handleJoin;
|
|
59
64
|
shouldIgnore(message: Message): boolean;
|
|
60
65
|
private configFor;
|
|
61
66
|
private orderedDetectors;
|
|
62
67
|
private toSnapshot;
|
|
68
|
+
private pushRecent;
|
|
63
69
|
private maxRetentionMs;
|
|
64
70
|
}
|
|
65
71
|
export declare function createAntiSpam(client: Client, options?: AntiSpamOptions): AntiSpam;
|
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,
|
|
1
|
+
{"version":3,"file":"AntiSpam.d.ts","sourceRoot":"","sources":["../src/AntiSpam.ts"],"names":[],"mappings":"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"}
|