@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.
Files changed (64) hide show
  1. package/README.md +232 -14
  2. package/dist/AntiSpam.d.ts +7 -1
  3. package/dist/AntiSpam.d.ts.map +1 -1
  4. package/dist/AntiSpam.js +113 -3
  5. package/dist/AntiSpam.js.map +1 -1
  6. package/dist/defaults.d.ts.map +1 -1
  7. package/dist/defaults.js +62 -1
  8. package/dist/defaults.js.map +1 -1
  9. package/dist/detectors/files.d.ts.map +1 -1
  10. package/dist/detectors/files.js +14 -0
  11. package/dist/detectors/files.js.map +1 -1
  12. package/dist/detectors/forward.d.ts +3 -0
  13. package/dist/detectors/forward.d.ts.map +1 -0
  14. package/dist/detectors/forward.js +35 -0
  15. package/dist/detectors/forward.js.map +1 -0
  16. package/dist/detectors/ghost.d.ts.map +1 -1
  17. package/dist/detectors/ghost.js +4 -0
  18. package/dist/detectors/ghost.js.map +1 -1
  19. package/dist/detectors/impersonate.d.ts +3 -0
  20. package/dist/detectors/impersonate.d.ts.map +1 -0
  21. package/dist/detectors/impersonate.js +86 -0
  22. package/dist/detectors/impersonate.js.map +1 -0
  23. package/dist/detectors/joins.d.ts +4 -0
  24. package/dist/detectors/joins.d.ts.map +1 -0
  25. package/dist/detectors/joins.js +30 -0
  26. package/dist/detectors/joins.js.map +1 -0
  27. package/dist/detectors/polls.d.ts +3 -0
  28. package/dist/detectors/polls.d.ts.map +1 -0
  29. package/dist/detectors/polls.js +35 -0
  30. package/dist/detectors/polls.js.map +1 -0
  31. package/dist/detectors/stickers.d.ts +3 -0
  32. package/dist/detectors/stickers.d.ts.map +1 -0
  33. package/dist/detectors/stickers.js +60 -0
  34. package/dist/detectors/stickers.js.map +1 -0
  35. package/dist/detectors/voice.d.ts +3 -0
  36. package/dist/detectors/voice.d.ts.map +1 -0
  37. package/dist/detectors/voice.js +35 -0
  38. package/dist/detectors/voice.js.map +1 -0
  39. package/dist/detectors/words.d.ts.map +1 -1
  40. package/dist/detectors/words.js +27 -3
  41. package/dist/detectors/words.js.map +1 -1
  42. package/dist/enforcement.d.ts +2 -1
  43. package/dist/enforcement.d.ts.map +1 -1
  44. package/dist/enforcement.js +100 -5
  45. package/dist/enforcement.js.map +1 -1
  46. package/dist/index.d.ts +5 -3
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +9 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/store/MemoryStore.d.ts +3 -0
  51. package/dist/store/MemoryStore.d.ts.map +1 -1
  52. package/dist/store/MemoryStore.js +23 -0
  53. package/dist/store/MemoryStore.js.map +1 -1
  54. package/dist/types.d.ts +84 -2
  55. package/dist/types.d.ts.map +1 -1
  56. package/dist/utils/flags.d.ts +5 -0
  57. package/dist/utils/flags.d.ts.map +1 -0
  58. package/dist/utils/flags.js +41 -0
  59. package/dist/utils/flags.js.map +1 -0
  60. package/dist/utils/normalize.d.ts +2 -0
  61. package/dist/utils/normalize.d.ts.map +1 -1
  62. package/dist/utils/normalize.js +46 -0
  63. package/dist/utils/normalize.js.map +1 -1
  64. 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 · leaked secrets · channel hop · ghost pings · TypeScript
7
+ Flood · phishing · forwards · impersonation · join raids · leaked secrets · TypeScript
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/@amatiscorp/disguard.svg?style=flat-square)](https://www.npmjs.com/package/@amatiscorp/disguard)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](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 puntuación. `strict` enciende ghost ping y aprieta hop a 3 canales / 6s.
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
 
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"AntiSpam.d.ts","sourceRoot":"","sources":["../src/AntiSpam.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AntiSpam.d.ts","sourceRoot":"","sources":["../src/AntiSpam.ts"],"names":[],"mappings":"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"}